Commit 41127e50 authored by mathieui's avatar mathieui

Move message rendering code to Message.render()

Also:

- rename format_chars to FORMAT_CHARS because it’s static constant
- move Line, Message, and a few funcs/consts to a new poezio.ui module
parent d22b4b8c
......@@ -31,7 +31,13 @@ from typing import (
TYPE_CHECKING,
)
from poezio import mam, poopt, timed_events, xhtml, windows
from poezio import (
mam,
poopt,
timed_events,
xhtml,
windows
)
from poezio.core.structs import Command, Completion, Status
from poezio.common import safeJID
from poezio.config import config
......
......@@ -14,90 +14,8 @@ log = logging.getLogger(__name__)
from typing import Dict, Union, Optional, List, Tuple
from datetime import datetime
from poezio.config import config
from poezio.theming import get_theme, dump_tuple
class Message:
__slots__ = ('txt', 'nick_color', 'time', 'str_time', 'nickname', 'user',
'identifier', 'top', 'highlight', 'me', 'old_message', 'revisions',
'jid', 'ack')
def __init__(self,
txt: str,
time: Optional[datetime],
nickname: Optional[str],
nick_color: Optional[Tuple],
history: bool,
user: Optional[str],
identifier: Optional[str],
top: Optional[bool] = False,
str_time: Optional[str] = None,
highlight: bool = False,
old_message: Optional['Message'] = None,
revisions: int = 0,
jid: Optional[str] = None,
ack: int = 0) -> None:
"""
Create a new Message object with parameters, check for /me messages,
and delayed messages
"""
time = time if time is not None else datetime.now()
if txt.startswith('/me '):
me = True
txt = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_ME_MESSAGE),
txt[4:])
else:
me = False
str_time = time.strftime("%H:%M:%S")
if history:
txt = txt.replace(
'\x19o',
'\x19o\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG))
str_time = time.strftime("%Y-%m-%d %H:%M:%S")
self.txt = txt.replace('\t', ' ') + '\x19o'
self.nick_color = nick_color
self.time = time
self.str_time = str_time
self.nickname = nickname
self.user = user
self.identifier = identifier
self.top = top
self.highlight = highlight
self.me = me
self.old_message = old_message
self.revisions = revisions
self.jid = jid
self.ack = ack
def _other_elems(self) -> str:
"Helper for the repr_message function"
acc = []
fields = list(self.__slots__)
fields.remove('old_message')
for field in fields:
acc.append('%s=%s' % (field, repr(getattr(self, field))))
return 'Message(%s, %s' % (', '.join(acc), 'old_message=')
def __repr__(self) -> str:
"""
repr() for the Message class, for debug purposes, since the default
repr() is recursive, so it can stack overflow given too many revisions
of a message
"""
init = self._other_elems()
acc = [init]
next_message = self.old_message
rev = 1
while next_message is not None:
acc.append(next_message._other_elems())
next_message = next_message.old_message
rev += 1
acc.append('None')
while rev:
acc.append(')')
rev -= 1
return ''.join(acc)
from poezio.ui.types import Message
class CorrectionError(Exception):
......
FORMAT_CHAR = '\x19'
# These are non-printable chars, so they should never appear in the input,
# I guess. But maybe we can find better chars that are even less risky.
FORMAT_CHARS = '\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x1A'
......@@ -4,14 +4,14 @@ Standalone functions used by the modules
import string
from typing import Optional, List
from poezio.windows.base_wins import FORMAT_CHAR, format_chars
from poezio.ui.consts import FORMAT_CHAR, FORMAT_CHARS
DIGITS = string.digits + '-'
def find_first_format_char(text: str,
chars: str = None) -> int:
to_find = chars or format_chars
to_find = chars or FORMAT_CHARS
pos = -1
for char in to_find:
p = text.find(char)
......
from datetime import datetime
from math import ceil, log10
from typing import Union, Optional, List, Tuple
from poezio.theming import get_theme, dump_tuple
from poezio.ui.funcs import truncate_nick, parse_attrs
from poezio import poopt
from poezio.ui.consts import FORMAT_CHAR
class Message:
__slots__ = ('txt', 'nick_color', 'time', 'str_time', 'nickname', 'user',
'identifier', 'top', 'highlight', 'me', 'old_message', 'revisions',
'jid', 'ack')
def __init__(self,
txt: str,
time: Optional[datetime],
nickname: Optional[str],
nick_color: Optional[Tuple],
history: bool,
user: Optional[str],
identifier: Optional[str],
top: Optional[bool] = False,
str_time: Optional[str] = None,
highlight: bool = False,
old_message: Optional['Message'] = None,
revisions: int = 0,
jid: Optional[str] = None,
ack: int = 0) -> None:
"""
Create a new Message object with parameters, check for /me messages,
and delayed messages
"""
time = time if time is not None else datetime.now()
if txt.startswith('/me '):
me = True
txt = '\x19%s}%s\x19o' % (dump_tuple(get_theme().COLOR_ME_MESSAGE),
txt[4:])
else:
me = False
str_time = time.strftime("%H:%M:%S")
if history:
txt = txt.replace(
'\x19o',
'\x19o\x19%s}' % dump_tuple(get_theme().COLOR_LOG_MSG))
str_time = time.strftime("%Y-%m-%d %H:%M:%S")
self.txt = txt.replace('\t', ' ') + '\x19o'
self.nick_color = nick_color
self.time = time
self.str_time = str_time
self.nickname = nickname
self.user = user
self.identifier = identifier
self.top = top
self.highlight = highlight
self.me = me
self.old_message = old_message
self.revisions = revisions
self.jid = jid
self.ack = ack
def _other_elems(self) -> str:
"Helper for the repr_message function"
acc = []
fields = list(self.__slots__)
fields.remove('old_message')
for field in fields:
acc.append('%s=%s' % (field, repr(getattr(self, field))))
return 'Message(%s, %s' % (', '.join(acc), 'old_message=')
def __repr__(self) -> str:
"""
repr() for the Message class, for debug purposes, since the default
repr() is recursive, so it can stack overflow given too many revisions
of a message
"""
init = self._other_elems()
acc = [init]
next_message = self.old_message
rev = 1
while next_message is not None:
acc.append(next_message._other_elems())
next_message = next_message.old_message
rev += 1
acc.append('None')
while rev:
acc.append(')')
rev -= 1
return ''.join(acc)
def render(self, width: int, timestamp: bool = False, nick_size: int = 10) -> List["Line"]:
"""
Build a list of lines from this message.
"""
txt = self.txt
if not txt:
return []
theme = get_theme()
if len(self.str_time) > 8:
default_color = (
FORMAT_CHAR + dump_tuple(theme.COLOR_LOG_MSG) + '}') # type: Optional[str]
else:
default_color = None
ret = [] # type: List[Union[None, Line]]
nick = truncate_nick(self.nickname, nick_size)
offset = 0
if self.ack:
if self.ack > 0:
offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
else:
offset += poopt.wcswidth(theme.CHAR_NACK) + 1
if nick:
offset += poopt.wcswidth(nick) + 2 # + nick + '> ' length
if self.revisions > 0:
offset += ceil(log10(self.revisions + 1))
if self.me:
offset += 1 # '* ' before and ' ' after
if timestamp:
if self.str_time:
offset += 1 + len(self.str_time)
if theme.CHAR_TIME_LEFT and self.str_time:
offset += 1
if theme.CHAR_TIME_RIGHT and self.str_time:
offset += 1
lines = poopt.cut_text(txt, width - offset - 1)
prepend = default_color if default_color else ''
attrs = [] # type: List[str]
for line in lines:
saved = Line(
msg=self,
start_pos=line[0],
end_pos=line[1],
prepend=prepend)
attrs = parse_attrs(self.txt[line[0]:line[1]], attrs)
if attrs:
prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
else:
if default_color:
prepend = default_color
else:
prepend = ''
ret.append(saved)
return ret
# msg is a reference to the corresponding Message object. text_start and
# text_end are the position delimiting the text in this line.
class Line:
__slots__ = ('msg', 'start_pos', 'end_pos', 'prepend')
def __init__(self, msg: Message, start_pos: int, end_pos: int, prepend: str) -> None:
self.msg = msg
self.start_pos = start_pos
self.end_pos = end_pos
self.prepend = prepend
......@@ -20,10 +20,7 @@ from typing import Optional, Tuple, TYPE_CHECKING
from poezio.theming import to_curses_attr, read_tuple
FORMAT_CHAR = '\x19'
# These are non-printable chars, so they should never appear in the input,
# I guess. But maybe we can find better chars that are even less risky.
format_chars = '\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x1A'
from poezio.ui.consts import FORMAT_CHAR
if TYPE_CHECKING:
from _curses import _CursesWindow # pylint: disable=E0611
......
......@@ -10,7 +10,7 @@ from poezio.common import safeJID
from poezio.config import config
from poezio.windows.base_wins import Win
from poezio.windows.funcs import truncate_nick
from poezio.ui.funcs import truncate_nick
from poezio.theming import get_theme, to_curses_attr
......
......@@ -10,8 +10,9 @@ from typing import List, Dict, Callable, Optional
from poezio import keyboard
from poezio import common
from poezio import poopt
from poezio.windows.base_wins import Win, format_chars
from poezio.windows.funcs import find_first_format_char
from poezio.windows.base_wins import Win
from poezio.ui.consts import FORMAT_CHARS
from poezio.ui.funcs import find_first_format_char
from poezio.config import config
from poezio.theming import to_curses_attr
......@@ -487,7 +488,7 @@ class Input(Win):
(\x0E to \x19 instead of \x19 + attr). We do not use any }
char in this version
"""
chars = format_chars + '\n'
chars = FORMAT_CHARS + '\n'
if y is not None and x is not None:
self.move(y, x)
format_char = find_first_format_char(text, chars)
......@@ -497,7 +498,7 @@ class Input(Win):
if text[format_char] == '\n':
attr_char = '|'
else:
attr_char = self.text_attributes[format_chars.index(
attr_char = self.text_attributes[FORMAT_CHARS.index(
text[format_char])]
self.addstr(text[:format_char])
self.addstr(attr_char, curses.A_REVERSE)
......@@ -696,7 +697,7 @@ class MessageInput(HistoryInput):
def cb(attr_char):
if attr_char in self.text_attributes:
char = format_chars[self.text_attributes.index(attr_char)]
char = FORMAT_CHARS[self.text_attributes.index(attr_char)]
self.do_command(char, False)
self.rewrite_text()
......
......@@ -9,28 +9,16 @@ from math import ceil, log10
from typing import Optional, List, Union
from poezio.windows.base_wins import Win, FORMAT_CHAR
from poezio.windows.funcs import truncate_nick, parse_attrs
from poezio.ui.funcs import truncate_nick, parse_attrs
from poezio import poopt
from poezio.config import config
from poezio.theming import to_curses_attr, get_theme, dump_tuple
from poezio.text_buffer import Message
from poezio.ui.types import Line, Message
log = logging.getLogger(__name__)
# msg is a reference to the corresponding Message object. text_start and
# text_end are the position delimiting the text in this line.
class Line:
__slots__ = ('msg', 'start_pos', 'end_pos', 'prepend')
def __init__(self, msg: Message, start_pos: int, end_pos: int, prepend: str) -> None:
self.msg = msg
self.start_pos = start_pos
self.end_pos = end_pos
self.prepend = prepend
class BaseTextWin(Win):
__slots__ = ('lines_nb_limit', 'pos', 'built_lines', 'lock', 'lock_buffer',
'separator_after')
......@@ -360,55 +348,7 @@ class TextWin(BaseTextWin):
"""
if message is None: # line separator
return [None]
txt = message.txt
if not txt:
return []
theme = get_theme()
if len(message.str_time) > 8:
default_color = (
FORMAT_CHAR + dump_tuple(theme.COLOR_LOG_MSG) + '}') # type: Optional[str]
else:
default_color = None
ret = [] # type: List[Union[None, Line]]
nick = truncate_nick(message.nickname, nick_size)
offset = 0
if message.ack:
if message.ack > 0:
offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
else:
offset += poopt.wcswidth(theme.CHAR_NACK) + 1
if nick:
offset += poopt.wcswidth(nick) + 2 # + nick + '> ' length
if message.revisions > 0:
offset += ceil(log10(message.revisions + 1))
if message.me:
offset += 1 # '* ' before and ' ' after
if timestamp:
if message.str_time:
offset += 1 + len(message.str_time)
if theme.CHAR_TIME_LEFT and message.str_time:
offset += 1
if theme.CHAR_TIME_RIGHT and message.str_time:
offset += 1
lines = poopt.cut_text(txt, self.width - offset - 1)
prepend = default_color if default_color else ''
attrs = [] # type: List[str]
for line in lines:
saved = Line(
msg=message,
start_pos=line[0],
end_pos=line[1],
prepend=prepend)
attrs = parse_attrs(message.txt[line[0]:line[1]], attrs)
if attrs:
prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
else:
if default_color:
prepend = default_color
else:
prepend = ''
ret.append(saved)
return ret
return message.render(self.width, timestamp, nick_size)
def refresh(self) -> None:
log.debug('Refresh: %s', self.__class__.__name__)
......
......@@ -488,7 +488,7 @@ def convert_simple_to_full_colors(text: str) -> str:
a \x19n} formatted one.
"""
# TODO, have a single list of this. This is some sort of
# duplicate from windows.format_chars
# duplicate from ui.consts.FORMAT_CHARS
mapping = str.maketrans({
'\x0E': '\x19b',
'\x0F': '\x19o',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment