render.py 7.62 KB
Newer Older
1 2 3 4 5 6
import logging
import curses

from datetime import datetime
from functools import singledispatch
from math import ceil, log10
mathieui's avatar
mathieui committed
7 8 9 10 11
from typing import (
    List,
    Tuple,
    TYPE_CHECKING,
)
12 13

from poezio import poopt
14 15 16
from poezio.theming import (
    get_theme,
)
17 18 19 20 21 22 23 24 25 26 27 28
from poezio.ui.consts import (
    FORMAT_CHAR,
    LONG_FORMAT,
    SHORT_FORMAT,
)
from poezio.ui.funcs import (
    truncate_nick,
    parse_attrs,
)
from poezio.ui.types import (
    BaseMessage,
    Message,
29
    StatusMessage,
30 31 32
    XMLLog,
)

mathieui's avatar
mathieui committed
33 34 35
if TYPE_CHECKING:
    from poezio.windows import Win

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
# 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)
97 98 99 100 101 102 103 104 105 106 107
    generated_lines = generate_lines(lines, msg, default_color='')
    if msg.top:
        generated_lines.reverse()
    return generated_lines


@build_lines.register(StatusMessage)
def build_status(msg: StatusMessage, width: int, timestamp: bool, nick_size: int = 10) -> List[Line]:
    msg.rebuild()
    offset = msg.compute_offset(timestamp, nick_size)
    lines = poopt.cut_text(msg.txt, width - offset - 1)
108 109 110 111 112 113 114 115 116 117 118
    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
mathieui's avatar
mathieui committed
119
def write_pre(msg: BaseMessage, win: 'Win', with_timestamps: bool, nick_size: int) -> int:
120 121
    """Write the part before text (only the timestamp)"""
    if with_timestamps:
122
        return PreMessageHelpers.write_time(win, False, msg.time)
123 124 125 126
    return 0


@write_pre.register(Message)
mathieui's avatar
mathieui committed
127
def write_pre_message(msg: Message, win: 'Win', with_timestamps: bool, nick_size: int) -> int:
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    """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)
mathieui's avatar
mathieui committed
171
def write_pre_xmllog(msg: XMLLog, win: 'Win', with_timestamps: bool, nick_size: int) -> int:
172 173 174
    """Write the part before the stanza (timestamp + IN/OUT)"""
    offset = 0
    if with_timestamps:
175
        offset += 1 + PreMessageHelpers.write_time(win, False, msg.time)
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
    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
mathieui's avatar
mathieui committed
192
    def write_revisions(buffer: 'Win', msg: Message) -> int:
193 194 195 196 197 198 199 200
        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
mathieui's avatar
mathieui committed
201
    def write_ack(buffer: 'Win') -> int:
202 203 204 205 206 207 208 209
        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
mathieui's avatar
mathieui committed
210
    def write_nack(buffer: 'Win') -> int:
211 212 213 214 215 216 217 218
        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
mathieui's avatar
mathieui committed
219
    def write_nickname(buffer: 'Win', nickname: str, color, highlight=False) -> None:
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        """
        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
mathieui's avatar
mathieui committed
237
    def write_time(buffer: 'Win', history: bool, time: datetime) -> int:
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
        """
        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