Commit f68fa1da authored by mathieui's avatar mathieui

Merge branch 'split-message-rendering' into 'master'

split message rendering

See merge request !48
parents d22b4b8c ca85411a
Pipeline #3064 pending with stages
...@@ -4,11 +4,12 @@ date has changed. ...@@ -4,11 +4,12 @@ date has changed.
""" """
import datetime
from gettext import gettext as _ from gettext import gettext as _
from poezio import timed_events, tabs
from poezio.plugin import BasePlugin from poezio.plugin import BasePlugin
import datetime from poezio.ui.types import InfoMessage
from poezio import tabs
from poezio import timed_events
class Plugin(BasePlugin): class Plugin(BasePlugin):
...@@ -30,7 +31,7 @@ class Plugin(BasePlugin): ...@@ -30,7 +31,7 @@ class Plugin(BasePlugin):
for tab in self.core.tabs: for tab in self.core.tabs:
if isinstance(tab, tabs.ChatTab): if isinstance(tab, tabs.ChatTab):
tab.add_message(msg) tab.add_message(InfoMessage(msg))
self.core.refresh_window() self.core.refresh_window()
self.schedule_event() self.schedule_event()
...@@ -16,6 +16,7 @@ Usage ...@@ -16,6 +16,7 @@ Usage
from poezio import tabs from poezio import tabs
from poezio.plugin import BasePlugin from poezio.plugin import BasePlugin
from poezio.theming import get_theme from poezio.theming import get_theme
from poezio.ui.types import Message
class Plugin(BasePlugin): class Plugin(BasePlugin):
...@@ -37,11 +38,13 @@ class Plugin(BasePlugin): ...@@ -37,11 +38,13 @@ class Plugin(BasePlugin):
if not isinstance(tab, tabs.MucTab): if not isinstance(tab, tabs.MucTab):
message['type'] = 'chat' message['type'] = 'chat'
tab.add_message( tab.add_message(
message['body'], Message(
nickname=tab.core.own_nick, message['body'],
nick_color=get_theme().COLOR_OWN_NICK, nickname=tab.core.own_nick,
identifier=message['id'], nick_color=get_theme().COLOR_OWN_NICK,
jid=tab.core.xmpp.boundjid, identifier=message['id'],
jid=tab.core.xmpp.boundjid,
),
typ=1, typ=1,
) )
message.send() message.send()
......
...@@ -17,7 +17,8 @@ from datetime import datetime ...@@ -17,7 +17,8 @@ from datetime import datetime
from poezio.plugin import BasePlugin from poezio.plugin import BasePlugin
from poezio import tabs from poezio import tabs
from poezio.text_buffer import Message, TextBuffer from poezio.text_buffer import TextBuffer
from poezio.ui.types import InfoMessage
def add_line( def add_line(
...@@ -26,18 +27,7 @@ def add_line( ...@@ -26,18 +27,7 @@ def add_line(
datetime: Optional[datetime] = None, datetime: Optional[datetime] = None,
) -> None: ) -> None:
"""Adds a textual entry in the TextBuffer""" """Adds a textual entry in the TextBuffer"""
text_buffer.add_message( text_buffer.add_message(InfoMessage(text, time=datetime))
text,
datetime, # Time
None, # Nickname
None, # Nick Color
False, # History
None, # User
False, # Highlight
None, # Identifier
None, # str_time
None, # Jid
)
class Plugin(BasePlugin): class Plugin(BasePlugin):
......
...@@ -205,6 +205,7 @@ from poezio.tabs import StaticConversationTab, PrivateTab ...@@ -205,6 +205,7 @@ from poezio.tabs import StaticConversationTab, PrivateTab
from poezio.theming import get_theme, dump_tuple from poezio.theming import get_theme, dump_tuple
from poezio.decorators import command_args_parser from poezio.decorators import command_args_parser
from poezio.core.structs import Completion from poezio.core.structs import Completion
from poezio.ui.types import InfoMessage, Message
POLICY_FLAGS = { POLICY_FLAGS = {
'ALLOW_V1': False, 'ALLOW_V1': False,
...@@ -385,25 +386,30 @@ class PoezioContext(Context): ...@@ -385,25 +386,30 @@ class PoezioContext(Context):
log.debug('OTR conversation with %s refreshed', self.peer) log.debug('OTR conversation with %s refreshed', self.peer)
if self.getCurrentTrust(): if self.getCurrentTrust():
msg = OTR_REFRESH_TRUSTED % format_dict msg = OTR_REFRESH_TRUSTED % format_dict
tab.add_message(msg, typ=self.log) tab.add_message(InfoMessage(msg), typ=self.log)
else: else:
msg = OTR_REFRESH_UNTRUSTED % format_dict msg = OTR_REFRESH_UNTRUSTED % format_dict
tab.add_message(msg, typ=self.log) tab.add_message(InfoMessage(msg), typ=self.log)
hl(tab) hl(tab)
elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT: elif newstate == STATE_FINISHED or newstate == STATE_PLAINTEXT:
log.debug('OTR conversation with %s finished', self.peer) log.debug('OTR conversation with %s finished', self.peer)
if tab: if tab:
tab.add_message(OTR_END % format_dict, typ=self.log) tab.add_message(InfoMessage(OTR_END % format_dict), typ=self.log)
hl(tab) hl(tab)
elif newstate == STATE_ENCRYPTED and tab: elif newstate == STATE_ENCRYPTED and tab:
if self.getCurrentTrust(): if self.getCurrentTrust():
tab.add_message(OTR_START_TRUSTED % format_dict, typ=self.log) tab.add_message(InfoMessage(OTR_START_TRUSTED % format_dict), typ=self.log)
else: else:
format_dict['our_fpr'] = self.user.getPrivkey() format_dict['our_fpr'] = self.user.getPrivkey()
format_dict['remote_fpr'] = self.getCurrentKey() format_dict['remote_fpr'] = self.getCurrentKey()
tab.add_message(OTR_TUTORIAL % format_dict, typ=0)
tab.add_message( tab.add_message(
OTR_START_UNTRUSTED % format_dict, typ=self.log) InfoMessage(OTR_TUTORIAL % format_dict),
typ=0
)
tab.add_message(
InfoMessage(OTR_START_UNTRUSTED % format_dict),
typ=self.log,
)
hl(tab) hl(tab)
log.debug('Set encryption state of %s to %s', self.peer, log.debug('Set encryption state of %s to %s', self.peer,
...@@ -639,7 +645,7 @@ class Plugin(BasePlugin): ...@@ -639,7 +645,7 @@ class Plugin(BasePlugin):
# Received an OTR error # Received an OTR error
proto_error = err.args[0].error # pylint: disable=no-member proto_error = err.args[0].error # pylint: disable=no-member
format_dict['err'] = proto_error.decode('utf-8', errors='replace') format_dict['err'] = proto_error.decode('utf-8', errors='replace')
tab.add_message(OTR_ERROR % format_dict, typ=0) tab.add_message(InfoMessage(OTR_ERROR % format_dict), typ=0)
del msg['body'] del msg['body']
del msg['html'] del msg['html']
hl(tab) hl(tab)
...@@ -649,7 +655,7 @@ class Plugin(BasePlugin): ...@@ -649,7 +655,7 @@ class Plugin(BasePlugin):
# Encrypted message received, but unreadable as we do not have # Encrypted message received, but unreadable as we do not have
# an OTR session in place. # an OTR session in place.
text = MESSAGE_UNREADABLE % format_dict text = MESSAGE_UNREADABLE % format_dict
tab.add_message(text, jid=msg['from'], typ=0) tab.add_message(InfoMessage(text), typ=0)
hl(tab) hl(tab)
del msg['body'] del msg['body']
del msg['html'] del msg['html']
...@@ -658,7 +664,7 @@ class Plugin(BasePlugin): ...@@ -658,7 +664,7 @@ class Plugin(BasePlugin):
except crypt.InvalidParameterError: except crypt.InvalidParameterError:
# Malformed OTR payload and stuff # Malformed OTR payload and stuff
text = MESSAGE_INVALID % format_dict text = MESSAGE_INVALID % format_dict
tab.add_message(text, jid=msg['from'], typ=0) tab.add_message(InfoMessage(text), typ=0)
hl(tab) hl(tab)
del msg['body'] del msg['body']
del msg['html'] del msg['html']
...@@ -669,7 +675,7 @@ class Plugin(BasePlugin): ...@@ -669,7 +675,7 @@ class Plugin(BasePlugin):
import traceback import traceback
exc = traceback.format_exc() exc = traceback.format_exc()
format_dict['exc'] = exc format_dict['exc'] = exc
tab.add_message(POTR_ERROR % format_dict, typ=0) tab.add_message(InfoMessage(POTR_ERROR % format_dict), typ=0)
log.error('Unspecified error in the OTR plugin', exc_info=True) log.error('Unspecified error in the OTR plugin', exc_info=True)
return return
# No error, proceed with the message # No error, proceed with the message
...@@ -688,10 +694,10 @@ class Plugin(BasePlugin): ...@@ -688,10 +694,10 @@ class Plugin(BasePlugin):
abort = get_tlv(tlvs, potr.proto.SMPABORTTLV) abort = get_tlv(tlvs, potr.proto.SMPABORTTLV)
if abort: if abort:
ctx.reset_smp() ctx.reset_smp()
tab.add_message(SMP_ABORTED_PEER % format_dict, typ=0) tab.add_message(InfoMessage(SMP_ABORTED_PEER % format_dict), typ=0)
elif ctx.in_smp and not ctx.smpIsValid(): elif ctx.in_smp and not ctx.smpIsValid():
ctx.reset_smp() ctx.reset_smp()
tab.add_message(SMP_ABORTED % format_dict, typ=0) tab.add_message(InfoMessage(SMP_ABORTED % format_dict), typ=0)
elif smp1 or smp1q: elif smp1 or smp1q:
# Received an SMP request (with a question or not) # Received an SMP request (with a question or not)
if smp1q: if smp1q:
...@@ -709,22 +715,22 @@ class Plugin(BasePlugin): ...@@ -709,22 +715,22 @@ class Plugin(BasePlugin):
# we did not initiate it # we did not initiate it
ctx.smp_own = False ctx.smp_own = False
format_dict['q'] = question format_dict['q'] = question
tab.add_message(SMP_REQUESTED % format_dict, typ=0) tab.add_message(InfoMessage(SMP_REQUESTED % format_dict), typ=0)
elif smp2: elif smp2:
# SMP reply received # SMP reply received
if not ctx.in_smp: if not ctx.in_smp:
ctx.reset_smp() ctx.reset_smp()
else: else:
tab.add_message(SMP_PROGRESS % format_dict, typ=0) tab.add_message(InfoMessage(SMP_PROGRESS % format_dict), typ=0)
elif smp3 or smp4: elif smp3 or smp4:
# Type 4 (SMP message 3) or 5 (SMP message 4) TLVs received # Type 4 (SMP message 3) or 5 (SMP message 4) TLVs received
# in both cases it is the final message of the SMP exchange # in both cases it is the final message of the SMP exchange
if ctx.smpIsSuccess(): if ctx.smpIsSuccess():
tab.add_message(SMP_SUCCESS % format_dict, typ=0) tab.add_message(InfoMessage(SMP_SUCCESS % format_dict), typ=0)
if not ctx.getCurrentTrust(): if not ctx.getCurrentTrust():
tab.add_message(SMP_RECIPROCATE % format_dict, typ=0) tab.add_message(InfoMessage(SMP_RECIPROCATE % format_dict), typ=0)
else: else:
tab.add_message(SMP_FAIL % format_dict, typ=0) tab.add_message(InfoMessage(SMP_FAIL % format_dict), typ=0)
ctx.reset_smp() ctx.reset_smp()
hl(tab) hl(tab)
self.core.refresh_window() self.core.refresh_window()
...@@ -780,12 +786,15 @@ class Plugin(BasePlugin): ...@@ -780,12 +786,15 @@ class Plugin(BasePlugin):
if decode_newlines: if decode_newlines:
body = body.replace('<br/>', '\n').replace('<br>', '\n') body = body.replace('<br/>', '\n').replace('<br>', '\n')
tab.add_message( tab.add_message(
body, Message(
nickname=tab.nick, body,
jid=msg['from'], nickname=tab.nick,
forced_user=user, jid=msg['from'],
user=user,
nick_color=nick_color
),
typ=ctx.log, typ=ctx.log,
nick_color=nick_color) )
hl(tab) hl(tab)
self.core.refresh_window() self.core.refresh_window()
del msg['body'] del msg['body']
...@@ -826,19 +835,22 @@ class Plugin(BasePlugin): ...@@ -826,19 +835,22 @@ class Plugin(BasePlugin):
tab.send_chat_state('inactive', always_send=True) tab.send_chat_state('inactive', always_send=True)
tab.add_message( tab.add_message(
msg['body'], Message(
nickname=self.core.own_nick or tab.own_nick, msg['body'],
nick_color=get_theme().COLOR_OWN_NICK, nickname=self.core.own_nick or tab.own_nick,
identifier=msg['id'], nick_color=get_theme().COLOR_OWN_NICK,
jid=self.core.xmpp.boundjid, identifier=msg['id'],
typ=ctx.log) jid=self.core.xmpp.boundjid,
),
typ=ctx.log
)
# remove everything from the message so that it doesn’t get sent # remove everything from the message so that it doesn’t get sent
del msg['body'] del msg['body']
del msg['replace'] del msg['replace']
del msg['html'] del msg['html']
elif is_relevant(tab) and ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'): elif is_relevant(tab) and ctx and ctx.getPolicy('REQUIRE_ENCRYPTION'):
warning_msg = MESSAGE_NOT_SENT % format_dict warning_msg = MESSAGE_NOT_SENT % format_dict
tab.add_message(warning_msg, typ=0) tab.add_message(InfoMessage(warning_msg), typ=0)
del msg['body'] del msg['body']
del msg['replace'] del msg['replace']
del msg['html'] del msg['html']
...@@ -856,7 +868,7 @@ class Plugin(BasePlugin): ...@@ -856,7 +868,7 @@ class Plugin(BasePlugin):
('\n - /message %s' % jid) for jid in res) ('\n - /message %s' % jid) for jid in res)
format_dict['help'] = help_msg format_dict['help'] = help_msg
warning_msg = INCOMPATIBLE_TAB % format_dict warning_msg = INCOMPATIBLE_TAB % format_dict
tab.add_message(warning_msg, typ=0) tab.add_message(InfoMessage(warning_msg), typ=0)
del msg['body'] del msg['body']
del msg['replace'] del msg['replace']
del msg['html'] del msg['html']
...@@ -900,22 +912,22 @@ class Plugin(BasePlugin): ...@@ -900,22 +912,22 @@ class Plugin(BasePlugin):
self.otr_start(tab, name, format_dict) self.otr_start(tab, name, format_dict)
elif action == 'ourfpr': elif action == 'ourfpr':
format_dict['fpr'] = self.account.getPrivkey() format_dict['fpr'] = self.account.getPrivkey()
tab.add_message(OTR_OWN_FPR % format_dict, typ=0) tab.add_message(InfoMessage(OTR_OWN_FPR % format_dict), typ=0)
elif action == 'fpr': elif action == 'fpr':
if name in self.contexts: if name in self.contexts:
ctx = self.contexts[name] ctx = self.contexts[name]
if ctx.getCurrentKey() is not None: if ctx.getCurrentKey() is not None:
format_dict['fpr'] = ctx.getCurrentKey() format_dict['fpr'] = ctx.getCurrentKey()
tab.add_message(OTR_REMOTE_FPR % format_dict, typ=0) tab.add_message(InfoMessage(OTR_REMOTE_FPR % format_dict), typ=0)
else: else:
tab.add_message(OTR_NO_FPR % format_dict, typ=0) tab.add_message(InfoMessage(OTR_NO_FPR % format_dict), typ=0)
elif action == 'drop': elif action == 'drop':
# drop the privkey (and obviously, end the current conversations before that) # drop the privkey (and obviously, end the current conversations before that)
for context in self.contexts.values(): for context in self.contexts.values():
if context.state not in (STATE_FINISHED, STATE_PLAINTEXT): if context.state not in (STATE_FINISHED, STATE_PLAINTEXT):
context.disconnect() context.disconnect()
self.account.drop_privkey() self.account.drop_privkey()
tab.add_message(KEY_DROPPED % format_dict, typ=0) tab.add_message(InfoMessage(KEY_DROPPED % format_dict), typ=0)
elif action == 'trust': elif action == 'trust':
ctx = self.get_context(name) ctx = self.get_context(name)
key = ctx.getCurrentKey() key = ctx.getCurrentKey()
...@@ -927,7 +939,7 @@ class Plugin(BasePlugin): ...@@ -927,7 +939,7 @@ class Plugin(BasePlugin):
format_dict['key'] = key format_dict['key'] = key
ctx.setTrust(fpr, 'verified') ctx.setTrust(fpr, 'verified')
self.account.saveTrusts() self.account.saveTrusts()
tab.add_message(TRUST_ADDED % format_dict, typ=0) tab.add_message(InfoMessage(TRUST_ADDED % format_dict), typ=0)
elif action == 'untrust': elif action == 'untrust':
ctx = self.get_context(name) ctx = self.get_context(name)
key = ctx.getCurrentKey() key = ctx.getCurrentKey()
...@@ -939,7 +951,7 @@ class Plugin(BasePlugin): ...@@ -939,7 +951,7 @@ class Plugin(BasePlugin):
format_dict['key'] = key format_dict['key'] = key
ctx.setTrust(fpr, '') ctx.setTrust(fpr, '')
self.account.saveTrusts() self.account.saveTrusts()
tab.add_message(TRUST_REMOVED % format_dict, typ=0) tab.add_message(InfoMessage(TRUST_REMOVED % format_dict), typ=0)
self.core.refresh_window() self.core.refresh_window()
def otr_start(self, tab, name, format_dict): def otr_start(self, tab, name, format_dict):
...@@ -954,7 +966,7 @@ class Plugin(BasePlugin): ...@@ -954,7 +966,7 @@ class Plugin(BasePlugin):
if otr.state != STATE_ENCRYPTED: if otr.state != STATE_ENCRYPTED:
format_dict['secs'] = secs format_dict['secs'] = secs
text = OTR_NOT_ENABLED % format_dict text = OTR_NOT_ENABLED % format_dict
tab.add_message(text, typ=0) tab.add_message(InfoMessage(text), typ=0)
self.core.refresh_window() self.core.refresh_window()
if secs > 0: if secs > 0:
...@@ -962,7 +974,7 @@ class Plugin(BasePlugin): ...@@ -962,7 +974,7 @@ class Plugin(BasePlugin):
self.api.add_timed_event(event) self.api.add_timed_event(event)
body = self.get_context(name).sendMessage(0, b'?OTRv?').decode() body = self.get_context(name).sendMessage(0, b'?OTRv?').decode()
self.core.xmpp.send_message(mto=name, mtype='chat', mbody=body) self.core.xmpp.send_message(mto=name, mtype='chat', mbody=body)
tab.add_message(OTR_REQUEST % format_dict, typ=0) tab.add_message(InfoMessage(OTR_REQUEST % format_dict), typ=0)
@staticmethod @staticmethod
def completion_otr(the_input): def completion_otr(the_input):
...@@ -1012,13 +1024,13 @@ class Plugin(BasePlugin): ...@@ -1012,13 +1024,13 @@ class Plugin(BasePlugin):
ctx.smpInit(secret, question) ctx.smpInit(secret, question)
else: else:
ctx.smpInit(secret) ctx.smpInit(secret)
tab.add_message(SMP_INITIATED % format_dict, typ=0) tab.add_message(InfoMessage(SMP_INITIATED % format_dict), typ=0)
elif action == 'answer': elif action == 'answer':
ctx.smpGotSecret(secret) ctx.smpGotSecret(secret)
elif action == 'abort': elif action == 'abort':
if ctx.in_smp: if ctx.in_smp:
ctx.smpAbort() ctx.smpAbort()
tab.add_message(SMP_ABORTED % format_dict, typ=0) tab.add_message(InfoMessage(SMP_ABORTED % format_dict), typ=0)
self.core.refresh_window() self.core.refresh_window()
@staticmethod @staticmethod
......
...@@ -53,8 +53,15 @@ from poezio.core.completions import CompletionCore ...@@ -53,8 +53,15 @@ from poezio.core.completions import CompletionCore
from poezio.core.tabs import Tabs from poezio.core.tabs import Tabs
from poezio.core.commands import CommandCore from poezio.core.commands import CommandCore
from poezio.core.handlers import HandlerCore from poezio.core.handlers import HandlerCore
from poezio.core.structs import POSSIBLE_SHOW, DEPRECATED_ERRORS, \ from poezio.core.structs import (
ERROR_AND_STATUS_CODES, Command, Status Command,
Status,
DEPRECATED_ERRORS,
ERROR_AND_STATUS_CODES,
POSSIBLE_SHOW,
)
from poezio.ui.types import Message, InfoMessage
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -1317,7 +1324,7 @@ class Core: ...@@ -1317,7 +1324,7 @@ class Core:
""" """
tab = self.tabs.by_name_and_class(jid, tabs.ConversationTab) tab = self.tabs.by_name_and_class(jid, tabs.ConversationTab)
if tab is not None: if tab is not None:
tab.add_message(msg, typ=2) tab.add_message(InfoMessage(msg), typ=2)
if self.tabs.current_tab is tab: if self.tabs.current_tab is tab:
self.refresh_window() self.refresh_window()
...@@ -1349,7 +1356,12 @@ class Core: ...@@ -1349,7 +1356,12 @@ class Core:
colors = get_theme().INFO_COLORS colors = get_theme().INFO_COLORS
color = colors.get(typ.lower(), colors.get('default', None)) color = colors.get(typ.lower(), colors.get('default', None))
nb_lines = self.information_buffer.add_message( nb_lines = self.information_buffer.add_message(
msg, nickname=typ, nick_color=color) Message(
txt=msg,
nickname=typ,
nick_color=color
)
)
popup_on = config.get('information_buffer_popup_on').split() popup_on = config.get('information_buffer_popup_on').split()
if isinstance(self.tabs.current_tab, tabs.RosterInfoTab): if isinstance(self.tabs.current_tab, tabs.RosterInfoTab):
self.refresh_window() self.refresh_window()
...@@ -1579,17 +1591,6 @@ class Core: ...@@ -1579,17 +1591,6 @@ class Core:
self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0) self.tab_win.resize(1, tabs.Tab.width, tabs.Tab.height - 2, 0)
self.left_tab_win = None self.left_tab_win = None
def add_message_to_text_buffer(self, buff, txt, nickname=None):
"""
Add the message to the room if possible, else, add it to the Info window
(in the Info tab of the info window in the RosterTab)
"""
if not buff:
self.information('Trying to add a message in no room: %s' % txt,
'Error')
return
buff.add_message(txt, nickname=nickname)
def full_screen_redraw(self): def full_screen_redraw(self):
""" """
Completely erase and redraw the screen Completely erase and redraw the screen
...@@ -2061,15 +2062,18 @@ class Core: ...@@ -2061,15 +2062,18 @@ class Core:
return return
error_message = self.get_error_message(error) error_message = self.get_error_message(error)
tab.add_message( tab.add_message(
error_message, Message(
highlight=True, error_message,
nickname='Error', highlight=True,
nick_color=get_theme().COLOR_ERROR_MSG, nickname='Error',
typ=2) nick_color=get_theme().COLOR_ERROR_MSG,
),
typ=2,
)
code = error['error']['code'] code = error['error']['code']
if code == '401': if code == '401':
msg = 'To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)' msg = 'To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)'
tab.add_message(msg, typ=2) tab.add_message(InfoMessage(msg), typ=2)
if code == '409': if code == '409':
if config.get('alternative_nickname') != '': if config.get('alternative_nickname') != '':
if not tab.joined: if not tab.joined:
...@@ -2078,8 +2082,12 @@ class Core: ...@@ -2078,8 +2082,12 @@ class Core:
else: else:
if not tab.joined: if not tab.joined:
tab.add_message( tab.add_message(
'You can join the room with an other nick, by typing "/join /other_nick"', InfoMessage(
typ=2) 'You can join the room with another nick, '
'by typing "/join /other_nick"'
),
typ=2,
)
self.refresh_window() self.refresh_window()
......
...@@ -39,6 +39,7 @@ from poezio.logger import logger ...@@ -39,6 +39,7 @@ from poezio.logger import logger
from poezio.roster import roster from poezio.roster import roster
from poezio.text_buffer import CorrectionError, AckError from poezio.text_buffer import CorrectionError, AckError
from poezio.theming import dump_tuple, get_theme from poezio.theming import dump_tuple, get_theme
from poezio.ui.types import XMLLog, Message as PMessage, BaseMessage, InfoMessage