Rewrite part of the message handling/rendering
This commit is contained in:
parent
a5ef6ec910
commit
80ce8453f5
10 changed files with 374 additions and 310 deletions
|
@ -1349,7 +1349,10 @@ class Core:
|
|||
colors = get_theme().INFO_COLORS
|
||||
color = colors.get(typ.lower(), colors.get('default', None))
|
||||
nb_lines = self.information_buffer.add_message(
|
||||
msg, nickname=typ, nick_color=color)
|
||||
txt=msg,
|
||||
nickname=typ,
|
||||
nick_color=color
|
||||
)
|
||||
popup_on = config.get('information_buffer_popup_on').split()
|
||||
if isinstance(self.tabs.current_tab, tabs.RosterInfoTab):
|
||||
self.refresh_window()
|
||||
|
|
|
@ -72,7 +72,6 @@ def add_line(
|
|||
highlight=False,
|
||||
top=top,
|
||||
identifier=None,
|
||||
str_time=None,
|
||||
jid=None,
|
||||
)
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ from poezio.decorators import command_args_parser, refresh_wrapper
|
|||
from poezio.logger import logger
|
||||
from poezio.text_buffer import TextBuffer
|
||||
from poezio.theming import get_theme, dump_tuple
|
||||
from poezio.windows.funcs import truncate_nick
|
||||
from poezio.ui.funcs import truncate_nick
|
||||
from poezio.ui.consts import LONG_FORMAT_LENGTH
|
||||
|
||||
from slixmpp import JID, InvalidJID, Message
|
||||
|
||||
|
@ -839,12 +840,8 @@ class ChatTab(Tab):
|
|||
if message.me:
|
||||
offset += 1
|
||||
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
|
||||
if message.history:
|
||||
offset += 1 + LONG_FORMAT_LENGTH
|
||||
lines = poopt.cut_text(txt, self.text_win.width - offset - 1)
|
||||
for line in lines:
|
||||
built_lines.append(line)
|
||||
|
|
|
@ -262,7 +262,10 @@ class XMLTab(Tab):
|
|||
else:
|
||||
xml = self.core_buffer.messages[:]
|
||||
text = '\n'.join(
|
||||
('%s %s %s' % (msg.str_time, msg.nickname, clean_text(msg.txt))
|
||||
('%s %s %s' % (
|
||||
msg.time.strftime('%H:%M:%S'),
|
||||
msg.nickname,
|
||||
clean_text(msg.txt))
|
||||
for msg in xml))
|
||||
filename = os.path.expandvars(os.path.expanduser(args[0]))
|
||||
try:
|
||||
|
|
|
@ -63,7 +63,6 @@ class TextBuffer:
|
|||
highlight: bool = False,
|
||||
top: Optional[bool] = False,
|
||||
identifier: Optional[str] = None,
|
||||
str_time: Optional[str] = None,
|
||||
jid: Optional[str] = None,
|
||||
ack: int = 0) -> int:
|
||||
"""
|
||||
|
@ -71,14 +70,13 @@ class TextBuffer:
|
|||
"""
|
||||
msg = Message(
|
||||
txt,
|
||||
time,
|
||||
nickname,
|
||||
nick_color,
|
||||
history,
|
||||
user,
|
||||
identifier,
|
||||
top,
|
||||
str_time=str_time,
|
||||
time=time,
|
||||
nickname=nickname,
|
||||
nick_color=nick_color,
|
||||
history=history,
|
||||
user=user,
|
||||
identifier=identifier,
|
||||
top=top,
|
||||
highlight=highlight,
|
||||
jid=jid,
|
||||
ack=ack)
|
||||
|
@ -180,7 +178,7 @@ class TextBuffer:
|
|||
|
||||
if msg.user and msg.user is not user:
|
||||
raise CorrectionError("Different users")
|
||||
elif len(msg.str_time) > 8: # ugly
|
||||
elif msg.history:
|
||||
raise CorrectionError("Delayed message")
|
||||
elif not msg.user and (msg.jid is None or jid is None):
|
||||
raise CorrectionError('Could not check the '
|
||||
|
@ -195,13 +193,13 @@ class TextBuffer:
|
|||
|
||||
self.correction_ids[new_id] = orig_id
|
||||
message = Message(
|
||||
txt,
|
||||
time,
|
||||
msg.nickname,
|
||||
msg.nick_color,
|
||||
False,
|
||||
msg.user,
|
||||
orig_id,
|
||||
txt=txt,
|
||||
time=time,
|
||||
nickname=msg.nickname,
|
||||
nick_color=msg.nick_color,
|
||||
history=False,
|
||||
user=msg.user,
|
||||
identifier=orig_id,
|
||||
highlight=highlight,
|
||||
old_message=msg,
|
||||
revisions=msg.revisions + 1,
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
from datetime import datetime
|
||||
|
||||
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'
|
||||
|
||||
# Short date format (only show time)
|
||||
SHORT_FORMAT = '%H:%M:%S'
|
||||
SHORT_FORMAT_LENGTH = len(datetime.now().strftime(SHORT_FORMAT))
|
||||
|
||||
# Long date format (show date and time)
|
||||
LONG_FORMAT = '%Y-%m-%d %H:%M:%S'
|
||||
LONG_FORMAT_LENGTH = len(datetime.now().strftime(LONG_FORMAT))
|
||||
|
|
|
@ -22,12 +22,14 @@ def find_first_format_char(text: str,
|
|||
return pos
|
||||
|
||||
|
||||
def truncate_nick(nick: Optional[str], size=10) -> Optional[str]:
|
||||
def truncate_nick(nick: Optional[str], size=10) -> str:
|
||||
if size < 1:
|
||||
size = 1
|
||||
if nick and len(nick) > size:
|
||||
return nick[:size] + '…'
|
||||
return nick
|
||||
if nick:
|
||||
if len(nick) > size:
|
||||
return nick[:size] + '…'
|
||||
return nick
|
||||
return ''
|
||||
|
||||
|
||||
def parse_attrs(text: str, previous: Optional[List[str]] = None) -> List[str]:
|
||||
|
|
234
poezio/ui/render.py
Normal file
234
poezio/ui/render.py
Normal file
|
@ -0,0 +1,234 @@
|
|||
import logging
|
||||
import curses
|
||||
|
||||
from datetime import datetime
|
||||
from functools import singledispatch
|
||||
from typing import List, Optional, Tuple
|
||||
from math import ceil, log10
|
||||
|
||||
from poezio import poopt
|
||||
from poezio.ui.consts import (
|
||||
FORMAT_CHAR,
|
||||
LONG_FORMAT,
|
||||
SHORT_FORMAT,
|
||||
)
|
||||
from poezio.ui.funcs import (
|
||||
truncate_nick,
|
||||
parse_attrs,
|
||||
)
|
||||
from poezio.theming import (
|
||||
get_theme,
|
||||
)
|
||||
from poezio.ui.types import (
|
||||
BaseMessage,
|
||||
Message,
|
||||
XMLLog,
|
||||
)
|
||||
|
||||
# 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: BaseMessage, 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
|
||||
|
||||
def __repr__(self):
|
||||
return '(%s, %s)' % (self.start_pos, self.end_pos)
|
||||
|
||||
|
||||
LinePos = Tuple[int, int]
|
||||
|
||||
def generate_lines(lines: List[LinePos], msg: BaseMessage, default_color: str = '') -> List[Line]:
|
||||
line_objects = []
|
||||
attrs = [] # type: List[str]
|
||||
prepend = default_color if default_color else ''
|
||||
for line in lines:
|
||||
saved = Line(
|
||||
msg=msg,
|
||||
start_pos=line[0],
|
||||
end_pos=line[1],
|
||||
prepend=prepend)
|
||||
attrs = parse_attrs(msg.txt[line[0]:line[1]], attrs)
|
||||
if attrs:
|
||||
prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
|
||||
else:
|
||||
if default_color:
|
||||
prepend = default_color
|
||||
else:
|
||||
prepend = ''
|
||||
line_objects.append(saved)
|
||||
return line_objects
|
||||
|
||||
|
||||
@singledispatch
|
||||
def build_lines(msg: BaseMessage, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
|
||||
offset = msg.compute_offset(timestamp, nick_size)
|
||||
lines = poopt.cut_text(msg.txt, width - offset - 1)
|
||||
return generate_lines(lines, msg, default_color='')
|
||||
|
||||
|
||||
@build_lines.register(type(None))
|
||||
def build_separator(*args, **kwargs):
|
||||
return [None]
|
||||
|
||||
|
||||
@build_lines.register(Message)
|
||||
def build_message(msg: Message, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
|
||||
"""
|
||||
Build a list of lines from this message.
|
||||
"""
|
||||
txt = msg.txt
|
||||
if not txt:
|
||||
return []
|
||||
offset = msg.compute_offset(timestamp, nick_size)
|
||||
lines = poopt.cut_text(txt, width - offset - 1)
|
||||
return generate_lines(lines, msg, default_color='')
|
||||
|
||||
|
||||
@build_lines.register(XMLLog)
|
||||
def build_xmllog(msg: XMLLog, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
|
||||
offset = msg.compute_offset(timestamp, nick_size)
|
||||
lines = poopt.cut_text(msg.txt, width - offset - 1)
|
||||
return generate_lines(lines, msg, default_color='')
|
||||
|
||||
|
||||
@singledispatch
|
||||
def write_pre(msg: BaseMessage, win, with_timestamps: bool, nick_size: int) -> int:
|
||||
"""Write the part before text (only the timestamp)"""
|
||||
if with_timestamps:
|
||||
return 1 + PreMessageHelpers.write_time(win, False, msg.time)
|
||||
return 0
|
||||
|
||||
|
||||
@write_pre.register(Message)
|
||||
def write_pre_message(msg: Message, win, with_timestamps: bool, nick_size: int) -> int:
|
||||
"""Write the part before the body:
|
||||
- timestamp (short or long)
|
||||
- ack/nack
|
||||
- nick (with a "* " for /me)
|
||||
- LMC number if present
|
||||
"""
|
||||
offset = 0
|
||||
if with_timestamps:
|
||||
logging.debug(msg)
|
||||
offset += PreMessageHelpers.write_time(win, msg.history, msg.time)
|
||||
|
||||
if not msg.nickname: # not a message, nothing to do afterwards
|
||||
return offset
|
||||
|
||||
nick = truncate_nick(msg.nickname, nick_size)
|
||||
offset += poopt.wcswidth(nick)
|
||||
if msg.nick_color:
|
||||
color = msg.nick_color
|
||||
elif msg.user:
|
||||
color = msg.user.color
|
||||
else:
|
||||
color = None
|
||||
if msg.ack:
|
||||
if msg.ack > 0:
|
||||
offset += PreMessageHelpers.write_ack(win)
|
||||
else:
|
||||
offset += PreMessageHelpers.write_nack(win)
|
||||
if msg.me:
|
||||
with win.colored_text(color=get_theme().COLOR_ME_MESSAGE):
|
||||
win.addstr('* ')
|
||||
PreMessageHelpers.write_nickname(win, nick, color, msg.highlight)
|
||||
offset += PreMessageHelpers.write_revisions(win, msg)
|
||||
win.addstr(' ')
|
||||
offset += 3
|
||||
else:
|
||||
PreMessageHelpers.write_nickname(win, nick, color, msg.highlight)
|
||||
offset += PreMessageHelpers.write_revisions(win, msg)
|
||||
win.addstr('> ')
|
||||
offset += 2
|
||||
return offset
|
||||
|
||||
|
||||
@write_pre.register(XMLLog)
|
||||
def write_pre_xmllog(msg: XMLLog, win, with_timestamps: bool, nick_size: int) -> int:
|
||||
"""Write the part before the stanza (timestamp + IN/OUT)"""
|
||||
offset = 0
|
||||
if with_timestamps:
|
||||
offset += PreMessageHelpers.write_time(win, False, msg.time)
|
||||
theme = get_theme()
|
||||
if msg.incoming:
|
||||
char = theme.CHAR_XML_IN
|
||||
color = theme.COLOR_XML_IN
|
||||
else:
|
||||
char = theme.CHAR_XML_OUT
|
||||
color = theme.COLOR_XML_OUT
|
||||
nick = truncate_nick(char, nick_size)
|
||||
offset += poopt.wcswidth(nick)
|
||||
PreMessageHelpers.write_nickname(win, char, color)
|
||||
win.addstr(' ')
|
||||
return offset
|
||||
|
||||
class PreMessageHelpers:
|
||||
|
||||
@staticmethod
|
||||
def write_revisions(buffer, msg: Message) -> int:
|
||||
if msg.revisions:
|
||||
color = get_theme().COLOR_REVISIONS_MESSAGE
|
||||
with buffer.colored_text(color=color):
|
||||
buffer.addstr('%d' % msg.revisions)
|
||||
return ceil(log10(msg.revisions + 1))
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def write_ack(buffer) -> int:
|
||||
theme = get_theme()
|
||||
color = theme.COLOR_CHAR_ACK
|
||||
with buffer.colored_text(color=color):
|
||||
buffer.addstr(theme.CHAR_ACK_RECEIVED)
|
||||
buffer.addstr(' ')
|
||||
return poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
|
||||
|
||||
@staticmethod
|
||||
def write_nack(buffer) -> int:
|
||||
theme = get_theme()
|
||||
color = theme.COLOR_CHAR_NACK
|
||||
with buffer.colored_text(color=color):
|
||||
buffer.addstr(theme.CHAR_NACK)
|
||||
buffer.addstr(' ')
|
||||
return poopt.wcswidth(theme.CHAR_NACK) + 1
|
||||
|
||||
@staticmethod
|
||||
def write_nickname(buffer, nickname: str, color, highlight=False) -> None:
|
||||
"""
|
||||
Write the nickname, using the user's color
|
||||
and return the number of written characters
|
||||
"""
|
||||
if not nickname:
|
||||
return
|
||||
attr = None
|
||||
if highlight:
|
||||
hl_color = get_theme().COLOR_HIGHLIGHT_NICK
|
||||
if hl_color == "reverse":
|
||||
attr = curses.A_REVERSE
|
||||
else:
|
||||
color = hl_color
|
||||
with buffer.colored_text(color=color, attr=attr):
|
||||
buffer.addstr(nickname)
|
||||
|
||||
@staticmethod
|
||||
def write_time(buffer, history: bool, time: datetime) -> int:
|
||||
"""
|
||||
Write the date on the yth line of the window
|
||||
"""
|
||||
if time:
|
||||
if history:
|
||||
format = LONG_FORMAT
|
||||
else:
|
||||
format = SHORT_FORMAT
|
||||
logging.debug(time)
|
||||
time_str = time.strftime(format)
|
||||
color = get_theme().COLOR_TIME_STRING
|
||||
with buffer.colored_text(color=color):
|
||||
buffer.addstr(time_str)
|
||||
buffer.addstr(' ')
|
||||
return poopt.wcswidth(time_str) + 1
|
||||
return 0
|
|
@ -2,28 +2,78 @@
|
|||
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.ui.funcs import truncate_nick
|
||||
from poezio import poopt
|
||||
from poezio.ui.consts import FORMAT_CHAR
|
||||
from poezio.user import User
|
||||
from poezio.theming import dump_tuple, get_theme
|
||||
from poezio.ui.consts import (
|
||||
SHORT_FORMAT_LENGTH,
|
||||
LONG_FORMAT_LENGTH,
|
||||
)
|
||||
|
||||
|
||||
class Message:
|
||||
__slots__ = ('txt', 'nick_color', 'time', 'str_time', 'nickname', 'user',
|
||||
class BaseMessage:
|
||||
__slots__ = ('txt', 'time', 'identifier')
|
||||
|
||||
def __init__(self, txt: str, identifier: str = '', time: Optional[datetime] = None):
|
||||
self.txt = txt
|
||||
self.identifier = identifier
|
||||
if time is not None:
|
||||
self.time = time
|
||||
else:
|
||||
self.time = datetime.now()
|
||||
|
||||
def compute_offset(self, with_timestamps: bool, nick_size: int) -> int:
|
||||
return SHORT_FORMAT_LENGTH + 1
|
||||
|
||||
|
||||
class XMLLog(BaseMessage):
|
||||
"""XML Log message"""
|
||||
__slots__ = ('txt', 'time', 'identifier', 'incoming')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
txt: str,
|
||||
incoming: bool,
|
||||
):
|
||||
BaseMessage.__init__(
|
||||
self,
|
||||
txt=txt,
|
||||
identifier='',
|
||||
)
|
||||
self.txt = txt
|
||||
self.identifier = ''
|
||||
self.incoming = incoming
|
||||
|
||||
def compute_offset(self, with_timestamps: bool, nick_size: int) -> int:
|
||||
offset = 0
|
||||
theme = get_theme()
|
||||
IN, OUT = theme.CHAR_XML_IN, theme.CHAR_XML_OUT
|
||||
if with_timestamps:
|
||||
offset += 1 + SHORT_FORMAT_LENGTH
|
||||
if self.incoming:
|
||||
nick = IN
|
||||
else:
|
||||
nick = OUT
|
||||
nick = truncate_nick(nick, nick_size) or ''
|
||||
offset += 1 + len(nick)
|
||||
return offset
|
||||
|
||||
|
||||
class Message(BaseMessage):
|
||||
__slots__ = ('txt', 'nick_color', 'time', 'nickname', 'user', 'history',
|
||||
'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],
|
||||
time: Optional[datetime] = None,
|
||||
nick_color: Optional[Tuple] = None,
|
||||
history: bool = False,
|
||||
user: Optional[User] = None,
|
||||
identifier: Optional[str] = '',
|
||||
top: Optional[bool] = False,
|
||||
str_time: Optional[str] = None,
|
||||
highlight: bool = False,
|
||||
old_message: Optional['Message'] = None,
|
||||
revisions: int = 0,
|
||||
|
@ -33,27 +83,22 @@ class Message:
|
|||
Create a new Message object with parameters, check for /me messages,
|
||||
and delayed messages
|
||||
"""
|
||||
time = time if time is not None else datetime.now()
|
||||
BaseMessage.__init__(
|
||||
self,
|
||||
txt=txt.replace('\t', ' ') + '\x19o',
|
||||
identifier=identifier or '',
|
||||
time=time,
|
||||
)
|
||||
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.history = history
|
||||
self.nickname = nickname
|
||||
self.nick_color = nick_color
|
||||
self.user = user
|
||||
self.identifier = identifier
|
||||
self.top = top
|
||||
self.highlight = highlight
|
||||
self.me = me
|
||||
|
@ -91,68 +136,29 @@ class Message:
|
|||
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)
|
||||
def compute_offset(self, with_timestamps: bool, nick_size: int) -> int:
|
||||
offset = 0
|
||||
if with_timestamps:
|
||||
if self.history:
|
||||
offset += 1 + LONG_FORMAT_LENGTH
|
||||
else:
|
||||
offset += 1 + SHORT_FORMAT_LENGTH
|
||||
|
||||
if not self.nickname: # not a message, nothing to do afterwards
|
||||
return offset
|
||||
|
||||
nick = truncate_nick(self.nickname, nick_size) or ''
|
||||
offset += poopt.wcswidth(nick)
|
||||
if self.ack:
|
||||
theme = get_theme()
|
||||
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
|
||||
offset += 3
|
||||
else:
|
||||
offset += 2
|
||||
if self.revisions:
|
||||
offset += ceil(log10(self.revisions + 1))
|
||||
return offset
|
||||
|
|
|
@ -14,7 +14,8 @@ 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.ui.types import Line, Message
|
||||
from poezio.ui.types import Message
|
||||
from poezio.ui.render import Line, build_lines, write_pre
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -106,7 +107,7 @@ class BaseTextWin(Win):
|
|||
Build a list of lines from a message, without adding it
|
||||
to a list
|
||||
"""
|
||||
return []
|
||||
return build_lines(message, self.width, timestamp, nick_size)
|
||||
|
||||
def refresh(self) -> None:
|
||||
pass
|
||||
|
@ -117,20 +118,6 @@ class BaseTextWin(Win):
|
|||
"""
|
||||
self.addstr_colored(txt, y, x)
|
||||
|
||||
def write_time(self, time: str) -> int:
|
||||
"""
|
||||
Write the date on the yth line of the window
|
||||
"""
|
||||
if time:
|
||||
color = get_theme().COLOR_TIME_STRING
|
||||
curses_color = to_curses_attr(color)
|
||||
self._win.attron(curses_color)
|
||||
self.addstr(time)
|
||||
self._win.attroff(curses_color)
|
||||
self.addstr(' ')
|
||||
return poopt.wcswidth(time) + 1
|
||||
return 0
|
||||
|
||||
# TODO: figure out the type of room.
|
||||
def resize(self, height: int, width: int, y: int, x: int, room=None) -> None:
|
||||
if hasattr(self, 'width'):
|
||||
|
@ -341,15 +328,6 @@ class TextWin(BaseTextWin):
|
|||
self.built_lines.pop(0)
|
||||
return len(lines)
|
||||
|
||||
def build_message(self, message: Optional[Message], timestamp: bool = False, nick_size: int = 10) -> List[Union[None, Line]]:
|
||||
"""
|
||||
Build a list of lines from a message, without adding it
|
||||
to a list
|
||||
"""
|
||||
if message is None: # line separator
|
||||
return [None]
|
||||
return message.render(self.width, timestamp, nick_size)
|
||||
|
||||
def refresh(self) -> None:
|
||||
log.debug('Refresh: %s', self.__class__.__name__)
|
||||
if self.height <= 0:
|
||||
|
@ -367,11 +345,10 @@ class TextWin(BaseTextWin):
|
|||
if line:
|
||||
msg = line.msg
|
||||
if line.start_pos == 0:
|
||||
offset = self.write_pre_msg(msg, with_timestamps,
|
||||
nick_size)
|
||||
offset = write_pre(msg, self, with_timestamps, nick_size)
|
||||
elif y == 0:
|
||||
offset = self.compute_offset(msg, with_timestamps,
|
||||
nick_size)
|
||||
offset = msg.compute_offset(with_timestamps,
|
||||
nick_size)
|
||||
self.write_text(
|
||||
y, offset,
|
||||
line.prepend + line.msg.txt[line.start_pos:line.end_pos])
|
||||
|
@ -382,120 +359,11 @@ class TextWin(BaseTextWin):
|
|||
self._win.attrset(0)
|
||||
self._refresh()
|
||||
|
||||
def compute_offset(self, msg, with_timestamps, nick_size) -> int:
|
||||
offset = 0
|
||||
if with_timestamps and msg.str_time:
|
||||
offset += poopt.wcswidth(msg.str_time) + 1
|
||||
|
||||
if not msg.nickname: # not a message, nothing to do afterwards
|
||||
return offset
|
||||
|
||||
nick = truncate_nick(msg.nickname, nick_size)
|
||||
offset += poopt.wcswidth(nick)
|
||||
if msg.ack:
|
||||
theme = get_theme()
|
||||
if msg.ack > 0:
|
||||
offset += poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
|
||||
else:
|
||||
offset += poopt.wcswidth(theme.CHAR_NACK) + 1
|
||||
if msg.me:
|
||||
offset += 3
|
||||
else:
|
||||
offset += 2
|
||||
if msg.revisions:
|
||||
offset += ceil(log10(msg.revisions + 1))
|
||||
offset += self.write_revisions(msg)
|
||||
return offset
|
||||
|
||||
def write_pre_msg(self, msg, with_timestamps, nick_size) -> int:
|
||||
offset = 0
|
||||
if with_timestamps:
|
||||
offset += self.write_time(msg.str_time)
|
||||
|
||||
if not msg.nickname: # not a message, nothing to do afterwards
|
||||
return offset
|
||||
|
||||
nick = truncate_nick(msg.nickname, nick_size)
|
||||
offset += poopt.wcswidth(nick)
|
||||
if msg.nick_color:
|
||||
color = msg.nick_color
|
||||
elif msg.user:
|
||||
color = msg.user.color
|
||||
else:
|
||||
color = None
|
||||
if msg.ack:
|
||||
if msg.ack > 0:
|
||||
offset += self.write_ack()
|
||||
else:
|
||||
offset += self.write_nack()
|
||||
if msg.me:
|
||||
self._win.attron(to_curses_attr(get_theme().COLOR_ME_MESSAGE))
|
||||
self.addstr('* ')
|
||||
self.write_nickname(nick, color, msg.highlight)
|
||||
offset += self.write_revisions(msg)
|
||||
self.addstr(' ')
|
||||
offset += 3
|
||||
else:
|
||||
self.write_nickname(nick, color, msg.highlight)
|
||||
offset += self.write_revisions(msg)
|
||||
self.addstr('> ')
|
||||
offset += 2
|
||||
return offset
|
||||
|
||||
def write_revisions(self, msg) -> int:
|
||||
if msg.revisions:
|
||||
self._win.attron(
|
||||
to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
|
||||
self.addstr('%d' % msg.revisions)
|
||||
self._win.attrset(0)
|
||||
return ceil(log10(msg.revisions + 1))
|
||||
return 0
|
||||
|
||||
def write_line_separator(self, y) -> None:
|
||||
theme = get_theme()
|
||||
char = theme.CHAR_NEW_TEXT_SEPARATOR
|
||||
self.addnstr(y, 0, char * (self.width // len(char) - 1), self.width,
|
||||
to_curses_attr(theme.COLOR_NEW_TEXT_SEPARATOR))
|
||||
|
||||
def write_ack(self) -> int:
|
||||
theme = get_theme()
|
||||
color = theme.COLOR_CHAR_ACK
|
||||
self._win.attron(to_curses_attr(color))
|
||||
self.addstr(theme.CHAR_ACK_RECEIVED)
|
||||
self._win.attroff(to_curses_attr(color))
|
||||
self.addstr(' ')
|
||||
return poopt.wcswidth(theme.CHAR_ACK_RECEIVED) + 1
|
||||
|
||||
def write_nack(self) -> int:
|
||||
theme = get_theme()
|
||||
color = theme.COLOR_CHAR_NACK
|
||||
self._win.attron(to_curses_attr(color))
|
||||
self.addstr(theme.CHAR_NACK)
|
||||
self._win.attroff(to_curses_attr(color))
|
||||
self.addstr(' ')
|
||||
return poopt.wcswidth(theme.CHAR_NACK) + 1
|
||||
|
||||
def write_nickname(self, nickname, color, highlight=False) -> None:
|
||||
"""
|
||||
Write the nickname, using the user's color
|
||||
and return the number of written characters
|
||||
"""
|
||||
if not nickname:
|
||||
return
|
||||
if highlight:
|
||||
hl_color = get_theme().COLOR_HIGHLIGHT_NICK
|
||||
if hl_color == "reverse":
|
||||
self._win.attron(curses.A_REVERSE)
|
||||
else:
|
||||
color = hl_color
|
||||
if color:
|
||||
self._win.attron(to_curses_attr(color))
|
||||
self.addstr(nickname)
|
||||
if color:
|
||||
self._win.attroff(to_curses_attr(color))
|
||||
if highlight and hl_color == "reverse":
|
||||
self._win.attroff(curses.A_REVERSE)
|
||||
|
||||
def modify_message(self, old_id, message) -> None:
|
||||
"""
|
||||
Find a message, and replace it with a new one
|
||||
|
@ -544,28 +412,12 @@ class XMLTextWin(BaseTextWin):
|
|||
if line:
|
||||
msg = line.msg
|
||||
if line.start_pos == 0:
|
||||
if msg.nickname == theme.CHAR_XML_OUT:
|
||||
color = theme.COLOR_XML_OUT
|
||||
elif msg.nickname == theme.CHAR_XML_IN:
|
||||
color = theme.COLOR_XML_IN
|
||||
self.write_time(msg.str_time)
|
||||
self.write_prefix(msg.nickname, color)
|
||||
self.addstr(' ')
|
||||
offset = write_pre(msg, self, True, 10)
|
||||
if y != self.height - 1:
|
||||
self.addstr('\n')
|
||||
self._win.attrset(0)
|
||||
for y, line in enumerate(lines):
|
||||
offset = 0
|
||||
# Offset for the timestamp (if any) plus a space after it
|
||||
offset += len(line.msg.str_time)
|
||||
# space
|
||||
offset += 1
|
||||
|
||||
# Offset for the prefix
|
||||
offset += poopt.wcswidth(truncate_nick(line.msg.nickname))
|
||||
# space
|
||||
offset += 1
|
||||
|
||||
offset = msg.compute_offset(True, 10)
|
||||
self.write_text(
|
||||
y, offset,
|
||||
line.prepend + line.msg.txt[line.start_pos:line.end_pos])
|
||||
|
@ -573,43 +425,3 @@ class XMLTextWin(BaseTextWin):
|
|||
self.addstr('\n')
|
||||
self._win.attrset(0)
|
||||
self._refresh()
|
||||
|
||||
def build_message(self, message: Message, timestamp: bool = False, nick_size: int = 10) -> List[Line]:
|
||||
txt = message.txt
|
||||
ret = []
|
||||
default_color = None
|
||||
nick = truncate_nick(message.nickname, nick_size)
|
||||
offset = 0
|
||||
if nick:
|
||||
offset += poopt.wcswidth(nick) + 1 # + nick + ' ' length
|
||||
if message.str_time:
|
||||
offset += 1 + len(message.str_time)
|
||||
theme = get_theme()
|
||||
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
|
||||
|
||||
def write_prefix(self, nickname, color) -> None:
|
||||
self._win.attron(to_curses_attr(color))
|
||||
self.addstr(truncate_nick(nickname))
|
||||
self._win.attroff(to_curses_attr(color))
|
||||
|
|
Loading…
Reference in a new issue