Light refactoring + typing
This commit is contained in:
parent
38f0cd1c32
commit
ec05954420
4 changed files with 197 additions and 204 deletions
|
@ -6,19 +6,19 @@ handlers but those are defined in submodules in order to avoir cluttering
|
||||||
this file.
|
this file.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import shutil
|
|
||||||
import curses
|
import curses
|
||||||
import os
|
import os
|
||||||
import pipes
|
import pipes
|
||||||
import sys
|
import sys
|
||||||
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
from collections import defaultdict
|
||||||
|
from typing import Callable, Dict, List, Optional, Tuple, Type
|
||||||
|
|
||||||
from slixmpp.xmlstream.handler import Callback
|
from slixmpp import JID
|
||||||
from slixmpp.util import FileSystemPerJidCache
|
from slixmpp.util import FileSystemPerJidCache
|
||||||
|
from slixmpp.xmlstream.handler import Callback
|
||||||
|
|
||||||
from poezio import connection
|
from poezio import connection
|
||||||
from poezio import decorators
|
from poezio import decorators
|
||||||
|
@ -39,6 +39,7 @@ from poezio.logger import logger
|
||||||
from poezio.plugin_manager import PluginManager
|
from poezio.plugin_manager import PluginManager
|
||||||
from poezio.roster import roster
|
from poezio.roster import roster
|
||||||
from poezio.size_manager import SizeManager
|
from poezio.size_manager import SizeManager
|
||||||
|
from poezio.user import User
|
||||||
from poezio.text_buffer import TextBuffer
|
from poezio.text_buffer import TextBuffer
|
||||||
from poezio.theming import get_theme
|
from poezio.theming import get_theme
|
||||||
from poezio import keyboard, xdg
|
from poezio import keyboard, xdg
|
||||||
|
@ -50,6 +51,8 @@ from poezio.core.handlers import HandlerCore
|
||||||
from poezio.core.structs import POSSIBLE_SHOW, DEPRECATED_ERRORS, \
|
from poezio.core.structs import POSSIBLE_SHOW, DEPRECATED_ERRORS, \
|
||||||
ERROR_AND_STATUS_CODES, Command, Status
|
ERROR_AND_STATUS_CODES, Command, Status
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Core:
|
class Core:
|
||||||
"""
|
"""
|
||||||
|
@ -98,8 +101,7 @@ class Core:
|
||||||
self.plugins_autoloaded = False
|
self.plugins_autoloaded = False
|
||||||
self.plugin_manager = PluginManager(self)
|
self.plugin_manager = PluginManager(self)
|
||||||
self.events = events.EventHandler()
|
self.events = events.EventHandler()
|
||||||
self.events.add_event_handler('tab_change',
|
self.events.add_event_handler('tab_change', self.on_tab_change)
|
||||||
self.on_tab_change)
|
|
||||||
|
|
||||||
self.tabs = Tabs(self.events)
|
self.tabs = Tabs(self.events)
|
||||||
self.previous_tab_nb = 0
|
self.previous_tab_nb = 0
|
||||||
|
@ -205,78 +207,55 @@ class Core:
|
||||||
self.key_func.update(key_func)
|
self.key_func.update(key_func)
|
||||||
|
|
||||||
# Add handlers
|
# Add handlers
|
||||||
self.xmpp.add_event_handler('connected', self.handler.on_connected)
|
xmpp_event_handlers = [
|
||||||
self.xmpp.add_event_handler('connection_failed',
|
('attention', self.handler.on_attention),
|
||||||
self.handler.on_failed_connection)
|
('carbon_received', self.handler.on_carbon_received),
|
||||||
self.xmpp.add_event_handler('disconnected',
|
('carbon_sent', self.handler.on_carbon_sent),
|
||||||
self.handler.on_disconnected)
|
('changed_status', self.handler.on_presence),
|
||||||
self.xmpp.add_event_handler('stream_error',
|
('chatstate_active', self.handler.on_chatstate_active),
|
||||||
self.handler.on_stream_error)
|
('chatstate_composing', self.handler.on_chatstate_composing),
|
||||||
self.xmpp.add_event_handler('failed_all_auth',
|
('chatstate_gone', self.handler.on_chatstate_gone),
|
||||||
self.handler.on_failed_all_auth)
|
('chatstate_inactive', self.handler.on_chatstate_inactive),
|
||||||
self.xmpp.add_event_handler('no_auth', self.handler.on_no_auth)
|
('chatstate_paused', self.handler.on_chatstate_paused),
|
||||||
self.xmpp.add_event_handler("session_start",
|
('connected', self.handler.on_connected),
|
||||||
self.handler.on_session_start)
|
('connection_failed', self.handler.on_failed_connection),
|
||||||
self.xmpp.add_event_handler("session_start",
|
('disconnected', self.handler.on_disconnected),
|
||||||
self.handler.on_session_start_features)
|
('failed_all_auth', self.handler.on_failed_all_auth),
|
||||||
self.xmpp.add_event_handler("groupchat_presence",
|
('got_offline', self.handler.on_got_offline),
|
||||||
self.handler.on_groupchat_presence)
|
('got_online', self.handler.on_got_online),
|
||||||
self.xmpp.add_event_handler("groupchat_message",
|
('groupchat_config_status', self.handler.on_status_codes),
|
||||||
self.handler.on_groupchat_message)
|
('groupchat_decline', self.handler.on_groupchat_decline),
|
||||||
self.xmpp.add_event_handler("groupchat_invite",
|
('groupchat_direct_invite',
|
||||||
self.handler.on_groupchat_invitation)
|
self.handler.on_groupchat_direct_invitation),
|
||||||
self.xmpp.add_event_handler(
|
('groupchat_invite', self.handler.on_groupchat_invitation),
|
||||||
"groupchat_direct_invite",
|
('groupchat_message', self.handler.on_groupchat_message),
|
||||||
self.handler.on_groupchat_direct_invitation)
|
('groupchat_presence', self.handler.on_groupchat_presence),
|
||||||
self.xmpp.add_event_handler("groupchat_decline",
|
('groupchat_subject', self.handler.on_groupchat_subject),
|
||||||
self.handler.on_groupchat_decline)
|
('http_confirm', self.handler.http_confirm),
|
||||||
self.xmpp.add_event_handler("groupchat_config_status",
|
('message', self.handler.on_message),
|
||||||
self.handler.on_status_codes)
|
('message_error', self.handler.on_error_message),
|
||||||
self.xmpp.add_event_handler("groupchat_subject",
|
('message_xform', self.handler.on_data_form),
|
||||||
self.handler.on_groupchat_subject)
|
('no_auth', self.handler.on_no_auth),
|
||||||
self.xmpp.add_event_handler("message", self.handler.on_message)
|
('presence_error', self.handler.on_presence_error),
|
||||||
self.xmpp.add_event_handler("message_error",
|
('receipt_received', self.handler.on_receipt),
|
||||||
self.handler.on_error_message)
|
('roster_subscription_authorized',
|
||||||
self.xmpp.add_event_handler("receipt_received",
|
self.handler.on_subscription_authorized),
|
||||||
self.handler.on_receipt)
|
('roster_subscription_remove',
|
||||||
self.xmpp.add_event_handler("got_online", self.handler.on_got_online)
|
self.handler.on_subscription_remove),
|
||||||
self.xmpp.add_event_handler("got_offline", self.handler.on_got_offline)
|
('roster_subscription_removed',
|
||||||
self.xmpp.add_event_handler("roster_update",
|
self.handler.on_subscription_removed),
|
||||||
self.handler.on_roster_update)
|
('roster_subscription_request',
|
||||||
self.xmpp.add_event_handler("changed_status", self.handler.on_presence)
|
self.handler.on_subscription_request),
|
||||||
self.xmpp.add_event_handler("presence_error",
|
('roster_update', self.handler.on_roster_update),
|
||||||
self.handler.on_presence_error)
|
('session_start', self.handler.on_session_start),
|
||||||
self.xmpp.add_event_handler("roster_subscription_request",
|
('session_start', self.handler.on_session_start_features),
|
||||||
self.handler.on_subscription_request)
|
('ssl_cert', self.handler.validate_ssl),
|
||||||
self.xmpp.add_event_handler("roster_subscription_authorized",
|
('ssl_invalid_chain', self.handler.ssl_invalid_chain),
|
||||||
self.handler.on_subscription_authorized)
|
('stream_error', self.handler.on_stream_error),
|
||||||
self.xmpp.add_event_handler("roster_subscription_remove",
|
]
|
||||||
self.handler.on_subscription_remove)
|
for name, handler in xmpp_event_handlers:
|
||||||
self.xmpp.add_event_handler("roster_subscription_removed",
|
self.xmpp.add_event_handler(name, handler)
|
||||||
self.handler.on_subscription_removed)
|
|
||||||
self.xmpp.add_event_handler("message_xform", self.handler.on_data_form)
|
|
||||||
self.xmpp.add_event_handler("chatstate_active",
|
|
||||||
self.handler.on_chatstate_active)
|
|
||||||
self.xmpp.add_event_handler("chatstate_composing",
|
|
||||||
self.handler.on_chatstate_composing)
|
|
||||||
self.xmpp.add_event_handler("chatstate_paused",
|
|
||||||
self.handler.on_chatstate_paused)
|
|
||||||
self.xmpp.add_event_handler("chatstate_gone",
|
|
||||||
self.handler.on_chatstate_gone)
|
|
||||||
self.xmpp.add_event_handler("chatstate_inactive",
|
|
||||||
self.handler.on_chatstate_inactive)
|
|
||||||
self.xmpp.add_event_handler("attention", self.handler.on_attention)
|
|
||||||
self.xmpp.add_event_handler("ssl_cert", self.handler.validate_ssl)
|
|
||||||
self.xmpp.add_event_handler("ssl_invalid_chain",
|
|
||||||
self.handler.ssl_invalid_chain)
|
|
||||||
self.xmpp.add_event_handler('carbon_received',
|
|
||||||
self.handler.on_carbon_received)
|
|
||||||
self.xmpp.add_event_handler('carbon_sent', self.handler.on_carbon_sent)
|
|
||||||
self.xmpp.add_event_handler('http_confirm', self.handler.http_confirm)
|
|
||||||
|
|
||||||
all_stanzas = Callback('custom matcher', connection.MatchAll(None),
|
|
||||||
self.handler.incoming_stanza)
|
|
||||||
self.xmpp.register_handler(all_stanzas)
|
|
||||||
if config.get('enable_avatars'):
|
if config.get('enable_avatars'):
|
||||||
self.xmpp.add_event_handler("vcard_avatar_update",
|
self.xmpp.add_event_handler("vcard_avatar_update",
|
||||||
self.handler.on_vcard_avatar)
|
self.handler.on_vcard_avatar)
|
||||||
|
@ -298,6 +277,10 @@ class Core:
|
||||||
self.xmpp.add_event_handler("user_gaming_publish",
|
self.xmpp.add_event_handler("user_gaming_publish",
|
||||||
self.handler.on_gaming_event)
|
self.handler.on_gaming_event)
|
||||||
|
|
||||||
|
all_stanzas = Callback('custom matcher', connection.MatchAll(None),
|
||||||
|
self.handler.incoming_stanza)
|
||||||
|
self.xmpp.register_handler(all_stanzas)
|
||||||
|
|
||||||
self.initial_joins = []
|
self.initial_joins = []
|
||||||
|
|
||||||
self.connected_events = {}
|
self.connected_events = {}
|
||||||
|
@ -316,40 +299,34 @@ class Core:
|
||||||
# string option (""), they will be called for every option change
|
# string option (""), they will be called for every option change
|
||||||
# The callback takes two argument: the config option, and the new
|
# The callback takes two argument: the config option, and the new
|
||||||
# value
|
# value
|
||||||
self.configuration_change_handlers = {"": []}
|
self.configuration_change_handlers = defaultdict(list)
|
||||||
self.add_configuration_handler("create_gaps",
|
config_handlers = [
|
||||||
self.on_gaps_config_change)
|
('', self.on_any_config_change),
|
||||||
self.add_configuration_handler("request_message_receipts",
|
('ack_message_receipts', self.on_ack_receipts_config_change),
|
||||||
self.on_request_receipts_config_change)
|
('connection_check_interval', self.xmpp.set_keepalive_values),
|
||||||
self.add_configuration_handler("ack_message_receipts",
|
('connection_timeout_delay', self.xmpp.set_keepalive_values),
|
||||||
self.on_ack_receipts_config_change)
|
('create_gaps', self.on_gaps_config_change),
|
||||||
self.add_configuration_handler(
|
('deterministic_nick_colors', self.on_nick_determinism_changed),
|
||||||
"plugins_dir", self.plugin_manager.on_plugins_dir_change)
|
('enable_carbons', self.on_carbons_switch),
|
||||||
self.add_configuration_handler(
|
('enable_vertical_tab_list',
|
||||||
"plugins_conf_dir", self.plugin_manager.on_plugins_conf_dir_change)
|
self.on_vertical_tab_list_config_change),
|
||||||
self.add_configuration_handler("connection_timeout_delay",
|
('hide_user_list', self.on_hide_user_list_change),
|
||||||
self.xmpp.set_keepalive_values)
|
('password', self.on_password_change),
|
||||||
self.add_configuration_handler("connection_check_interval",
|
('plugins_conf_dir',
|
||||||
self.xmpp.set_keepalive_values)
|
self.plugin_manager.on_plugins_conf_dir_change),
|
||||||
self.add_configuration_handler("themes_dir", theming.update_themes_dir)
|
('plugins_dir', self.plugin_manager.on_plugins_dir_change),
|
||||||
self.add_configuration_handler("theme", self.on_theme_config_change)
|
('request_message_receipts',
|
||||||
self.add_configuration_handler("use_bookmarks_method",
|
self.on_request_receipts_config_change),
|
||||||
self.on_bookmarks_method_config_change)
|
('theme', self.on_theme_config_change),
|
||||||
self.add_configuration_handler("password", self.on_password_change)
|
('themes_dir', theming.update_themes_dir),
|
||||||
self.add_configuration_handler("enable_vertical_tab_list",
|
('use_bookmarks_method', self.on_bookmarks_method_config_change),
|
||||||
self.on_vertical_tab_list_config_change)
|
('vertical_tab_list_size',
|
||||||
self.add_configuration_handler("vertical_tab_list_size",
|
self.on_vertical_tab_list_config_change),
|
||||||
self.on_vertical_tab_list_config_change)
|
]
|
||||||
self.add_configuration_handler("deterministic_nick_colors",
|
for option, handler in config_handlers:
|
||||||
self.on_nick_determinism_changed)
|
self.add_configuration_handler(option, handler)
|
||||||
self.add_configuration_handler("enable_carbons",
|
|
||||||
self.on_carbons_switch)
|
|
||||||
self.add_configuration_handler("hide_user_list",
|
|
||||||
self.on_hide_user_list_change)
|
|
||||||
|
|
||||||
self.add_configuration_handler("", self.on_any_config_change)
|
def on_tab_change(self, old_tab: tabs.Tab, new_tab: tabs.Tab):
|
||||||
|
|
||||||
def on_tab_change(self, old_tab, new_tab):
|
|
||||||
"""Whenever the current tab changes, change focus and refresh"""
|
"""Whenever the current tab changes, change focus and refresh"""
|
||||||
old_tab.on_lose_focus()
|
old_tab.on_lose_focus()
|
||||||
new_tab.on_gain_focus()
|
new_tab.on_gain_focus()
|
||||||
|
@ -361,14 +338,12 @@ class Core:
|
||||||
"""
|
"""
|
||||||
roster.modified()
|
roster.modified()
|
||||||
|
|
||||||
def add_configuration_handler(self, option, callback):
|
def add_configuration_handler(self, option: str, callback: Callable):
|
||||||
"""
|
"""
|
||||||
Add a callback, associated with the given option. It will be called
|
Add a callback, associated with the given option. It will be called
|
||||||
each time the configuration option is changed using /set or by
|
each time the configuration option is changed using /set or by
|
||||||
reloading the configuration with a signal
|
reloading the configuration with a signal
|
||||||
"""
|
"""
|
||||||
if option not in self.configuration_change_handlers:
|
|
||||||
self.configuration_change_handlers[option] = []
|
|
||||||
self.configuration_change_handlers[option].append(callback)
|
self.configuration_change_handlers[option].append(callback)
|
||||||
|
|
||||||
def trigger_configuration_change(self, option, value):
|
def trigger_configuration_change(self, option, value):
|
||||||
|
@ -599,50 +574,6 @@ class Core:
|
||||||
main loop waiting for the user to press a key
|
main loop waiting for the user to press a key
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def replace_line_breaks(key):
|
|
||||||
"replace ^J with \n"
|
|
||||||
if key == '^J':
|
|
||||||
return '\n'
|
|
||||||
return key
|
|
||||||
|
|
||||||
def separate_chars_from_bindings(char_list):
|
|
||||||
"""
|
|
||||||
returns a list of lists. For example if you give
|
|
||||||
['a', 'b', 'KEY_BACKSPACE', 'n', 'u'], this function returns
|
|
||||||
[['a', 'b'], ['KEY_BACKSPACE'], ['n', 'u']]
|
|
||||||
|
|
||||||
This way, in case of lag (for example), we handle the typed text
|
|
||||||
by “batch” as much as possible (instead of one char at a time,
|
|
||||||
which implies a refresh after each char, which is very slow),
|
|
||||||
but we still handle the special chars (backspaces, arrows,
|
|
||||||
ctrl+x ou alt+x, etc) one by one, which avoids the issue of
|
|
||||||
printing them OR ignoring them in that case. This should
|
|
||||||
resolve the “my ^W are ignored when I lag ;(”.
|
|
||||||
"""
|
|
||||||
res = []
|
|
||||||
current = []
|
|
||||||
for char in char_list:
|
|
||||||
assert char
|
|
||||||
# Transform that stupid char into what we actually meant
|
|
||||||
if char == '\x1f':
|
|
||||||
char = '^/'
|
|
||||||
if len(char) == 1:
|
|
||||||
current.append(char)
|
|
||||||
else:
|
|
||||||
# special case for the ^I key, it’s considered as \t
|
|
||||||
# only when pasting some text, otherwise that’s the ^I
|
|
||||||
# (or M-i) key, which stands for completion by default.
|
|
||||||
if char == '^I' and len(char_list) != 1:
|
|
||||||
current.append('\t')
|
|
||||||
continue
|
|
||||||
if current:
|
|
||||||
res.append(current)
|
|
||||||
current = []
|
|
||||||
res.append([char])
|
|
||||||
if current:
|
|
||||||
res.append(current)
|
|
||||||
return res
|
|
||||||
|
|
||||||
log.debug("Input is readable.")
|
log.debug("Input is readable.")
|
||||||
big_char_list = [replace_key_with_bound(key)\
|
big_char_list = [replace_key_with_bound(key)\
|
||||||
for key in self.read_keyboard()]
|
for key in self.read_keyboard()]
|
||||||
|
@ -705,7 +636,7 @@ class Core:
|
||||||
self.focus_tab_named(roster_row.jid)
|
self.focus_tab_named(roster_row.jid)
|
||||||
self.refresh_window()
|
self.refresh_window()
|
||||||
|
|
||||||
def get_conversation_messages(self):
|
def get_conversation_messages(self) -> Optional[List[Tuple]]:
|
||||||
"""
|
"""
|
||||||
Returns a list of all the messages in the current chat.
|
Returns a list of all the messages in the current chat.
|
||||||
If the current tab is not a ChatTab, returns None.
|
If the current tab is not a ChatTab, returns None.
|
||||||
|
@ -717,7 +648,7 @@ class Core:
|
||||||
return None
|
return None
|
||||||
return self.tabs.current_tab.get_conversation_messages()
|
return self.tabs.current_tab.get_conversation_messages()
|
||||||
|
|
||||||
def insert_input_text(self, text):
|
def insert_input_text(self, text: str):
|
||||||
"""
|
"""
|
||||||
Insert the given text into the current input
|
Insert the given text into the current input
|
||||||
"""
|
"""
|
||||||
|
@ -725,7 +656,7 @@ class Core:
|
||||||
|
|
||||||
##################### Anything related to command execution ###################
|
##################### Anything related to command execution ###################
|
||||||
|
|
||||||
def execute(self, line):
|
def execute(self, line: str):
|
||||||
"""
|
"""
|
||||||
Execute the /command or just send the line on the current room
|
Execute the /command or just send the line on the current room
|
||||||
"""
|
"""
|
||||||
|
@ -811,7 +742,7 @@ class Core:
|
||||||
exc_info=True)
|
exc_info=True)
|
||||||
self.information(str(exc), 'Error')
|
self.information(str(exc), 'Error')
|
||||||
|
|
||||||
def do_command(self, key, raw):
|
def do_command(self, key: str, raw: bool):
|
||||||
"""
|
"""
|
||||||
Execute the action associated with a key
|
Execute the action associated with a key
|
||||||
|
|
||||||
|
@ -830,7 +761,7 @@ class Core:
|
||||||
else:
|
else:
|
||||||
self.tabs.current_tab.on_input(key, raw)
|
self.tabs.current_tab.on_input(key, raw)
|
||||||
|
|
||||||
def try_execute(self, line):
|
def try_execute(self, line: str):
|
||||||
"""
|
"""
|
||||||
Try to execute a command in the current tab
|
Try to execute a command in the current tab
|
||||||
"""
|
"""
|
||||||
|
@ -859,7 +790,7 @@ class Core:
|
||||||
"""
|
"""
|
||||||
return self.status
|
return self.status
|
||||||
|
|
||||||
def set_status(self, pres, msg):
|
def set_status(self, pres: str, msg: str):
|
||||||
"""
|
"""
|
||||||
Set our current status so we can remember
|
Set our current status so we can remember
|
||||||
it and use it back when needed (for example to display it
|
it and use it back when needed (for example to display it
|
||||||
|
@ -875,7 +806,7 @@ class Core:
|
||||||
'Unable to save the status in '
|
'Unable to save the status in '
|
||||||
'the config file', 'Error')
|
'the config file', 'Error')
|
||||||
|
|
||||||
def get_bookmark_nickname(self, room_name):
|
def get_bookmark_nickname(self, room_name: str) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the nickname associated with a bookmark
|
Returns the nickname associated with a bookmark
|
||||||
or the default nickname
|
or the default nickname
|
||||||
|
@ -903,7 +834,7 @@ class Core:
|
||||||
lambda event: self.xmpp.connect(),
|
lambda event: self.xmpp.connect(),
|
||||||
disposable=True)
|
disposable=True)
|
||||||
|
|
||||||
def send_message(self, msg):
|
def send_message(self, msg: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Function to use in plugins to send a message in the current
|
Function to use in plugins to send a message in the current
|
||||||
conversation.
|
conversation.
|
||||||
|
@ -914,7 +845,7 @@ class Core:
|
||||||
self.tabs.current_tab.command_say(msg)
|
self.tabs.current_tab.command_say(msg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def invite(self, jid, room, reason=None):
|
def invite(self, jid: JID, room: JID, reason: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Checks if the sender supports XEP-0249, then send an invitation,
|
Checks if the sender supports XEP-0249, then send an invitation,
|
||||||
or a mediated one if it does not.
|
or a mediated one if it does not.
|
||||||
|
@ -974,13 +905,16 @@ class Core:
|
||||||
|
|
||||||
### Tab getters ###
|
### Tab getters ###
|
||||||
|
|
||||||
def get_tabs(self, cls=None):
|
def get_tabs(self, cls: Optional[Type[tabs.Tab]] = None):
|
||||||
"Get all the tabs of a type"
|
"Get all the tabs of a type"
|
||||||
if cls is None:
|
if cls is None:
|
||||||
return self.tabs.get_tabs()
|
return self.tabs.get_tabs()
|
||||||
return self.tabs.by_class(cls)
|
return self.tabs.by_class(cls)
|
||||||
|
|
||||||
def get_conversation_by_jid(self, jid, create=True, fallback_barejid=True):
|
def get_conversation_by_jid(self,
|
||||||
|
jid: JID,
|
||||||
|
create=True,
|
||||||
|
fallback_barejid=True) -> tabs.ChatTab:
|
||||||
"""
|
"""
|
||||||
From a JID, get the tab containing the conversation with it.
|
From a JID, get the tab containing the conversation with it.
|
||||||
If none already exist, and create is "True", we create it
|
If none already exist, and create is "True", we create it
|
||||||
|
@ -1013,7 +947,7 @@ class Core:
|
||||||
conversation = None
|
conversation = None
|
||||||
return conversation
|
return conversation
|
||||||
|
|
||||||
def add_tab(self, new_tab, focus=False):
|
def add_tab(self, new_tab: tabs.Tab, focus=False):
|
||||||
"""
|
"""
|
||||||
Appends the new_tab in the tab list and
|
Appends the new_tab in the tab list and
|
||||||
focus it if focus==True
|
focus it if focus==True
|
||||||
|
@ -1022,7 +956,7 @@ class Core:
|
||||||
if focus:
|
if focus:
|
||||||
self.tabs.set_current_tab(new_tab)
|
self.tabs.set_current_tab(new_tab)
|
||||||
|
|
||||||
def insert_tab(self, old_pos, new_pos=99999):
|
def insert_tab(self, old_pos: int, new_pos=99999) -> bool:
|
||||||
"""
|
"""
|
||||||
Insert a tab at a position, changing the number of the following tabs
|
Insert a tab at a position, changing the number of the following tabs
|
||||||
returns False if it could not move the tab, True otherwise
|
returns False if it could not move the tab, True otherwise
|
||||||
|
@ -1108,7 +1042,9 @@ class Core:
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
def focus_tab_named(self, tab_name, type_=None):
|
def focus_tab_named(self,
|
||||||
|
tab_name: str,
|
||||||
|
type_: Optional[Type[tabs.Tab]] = None) -> bool:
|
||||||
"""Returns True if it found a tab to focus on"""
|
"""Returns True if it found a tab to focus on"""
|
||||||
if type_ is None:
|
if type_ is None:
|
||||||
tab = self.tabs.by_name(tab_name)
|
tab = self.tabs.by_name(tab_name)
|
||||||
|
@ -1121,7 +1057,8 @@ class Core:
|
||||||
|
|
||||||
### Opening actions ###
|
### Opening actions ###
|
||||||
|
|
||||||
def open_conversation_window(self, jid, focus=True):
|
def open_conversation_window(self, jid: JID,
|
||||||
|
focus=True) -> tabs.ConversationTab:
|
||||||
"""
|
"""
|
||||||
Open a new conversation tab and focus it if needed. If a resource is
|
Open a new conversation tab and focus it if needed. If a resource is
|
||||||
provided, we open a StaticConversationTab, else a
|
provided, we open a StaticConversationTab, else a
|
||||||
|
@ -1137,7 +1074,8 @@ class Core:
|
||||||
self.refresh_window()
|
self.refresh_window()
|
||||||
return new_tab
|
return new_tab
|
||||||
|
|
||||||
def open_private_window(self, room_name, user_nick, focus=True):
|
def open_private_window(self, room_name: str, user_nick: str,
|
||||||
|
focus=True) -> tabs.PrivateTab:
|
||||||
"""
|
"""
|
||||||
Open a Private conversation in a MUC and focus if needed.
|
Open a Private conversation in a MUC and focus if needed.
|
||||||
"""
|
"""
|
||||||
|
@ -1162,7 +1100,12 @@ class Core:
|
||||||
tab.privates.append(new_tab)
|
tab.privates.append(new_tab)
|
||||||
return new_tab
|
return new_tab
|
||||||
|
|
||||||
def open_new_room(self, room, nick, *, password=None, focus=True):
|
def open_new_room(self,
|
||||||
|
room: str,
|
||||||
|
nick: str,
|
||||||
|
*,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
focus=True) -> tabs.MucTab:
|
||||||
"""
|
"""
|
||||||
Open a new tab.MucTab containing a muc Room, using the specified nick
|
Open a new tab.MucTab containing a muc Room, using the specified nick
|
||||||
"""
|
"""
|
||||||
|
@ -1171,7 +1114,8 @@ class Core:
|
||||||
self.refresh_window()
|
self.refresh_window()
|
||||||
return new_tab
|
return new_tab
|
||||||
|
|
||||||
def open_new_form(self, form, on_cancel, on_send, **kwargs):
|
def open_new_form(self, form, on_cancel: Callable, on_send: Callable,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Open a new tab containing the form
|
Open a new tab containing the form
|
||||||
The callback are called with the completed form as parameter in
|
The callback are called with the completed form as parameter in
|
||||||
|
@ -1182,7 +1126,7 @@ class Core:
|
||||||
|
|
||||||
### Modifying actions ###
|
### Modifying actions ###
|
||||||
|
|
||||||
def rename_private_tabs(self, room_name, old_nick, user):
|
def rename_private_tabs(self, room_name: str, old_nick: str, user: User):
|
||||||
"""
|
"""
|
||||||
Call this method when someone changes his/her nick in a MUC,
|
Call this method when someone changes his/her nick in a MUC,
|
||||||
this updates the name of all the opened private conversations
|
this updates the name of all the opened private conversations
|
||||||
|
@ -1193,8 +1137,8 @@ class Core:
|
||||||
if tab:
|
if tab:
|
||||||
tab.rename_user(old_nick, user)
|
tab.rename_user(old_nick, user)
|
||||||
|
|
||||||
def on_user_left_private_conversation(self, room_name, user,
|
def on_user_left_private_conversation(self, room_name: str, user: User,
|
||||||
status_message):
|
status_message: str):
|
||||||
"""
|
"""
|
||||||
The user left the MUC: add a message in the associated
|
The user left the MUC: add a message in the associated
|
||||||
private conversation
|
private conversation
|
||||||
|
@ -1204,7 +1148,7 @@ class Core:
|
||||||
if tab:
|
if tab:
|
||||||
tab.user_left(status_message, user)
|
tab.user_left(status_message, user)
|
||||||
|
|
||||||
def on_user_rejoined_private_conversation(self, room_name, nick):
|
def on_user_rejoined_private_conversation(self, room_name: str, nick: str):
|
||||||
"""
|
"""
|
||||||
The user joined a MUC: add a message in the associated
|
The user joined a MUC: add a message in the associated
|
||||||
private conversation
|
private conversation
|
||||||
|
@ -1214,7 +1158,9 @@ class Core:
|
||||||
if tab:
|
if tab:
|
||||||
tab.user_rejoined(nick)
|
tab.user_rejoined(nick)
|
||||||
|
|
||||||
def disable_private_tabs(self, room_name, reason=None):
|
def disable_private_tabs(self,
|
||||||
|
room_name: str,
|
||||||
|
reason: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Disable private tabs when leaving a room
|
Disable private tabs when leaving a room
|
||||||
"""
|
"""
|
||||||
|
@ -1224,7 +1170,8 @@ class Core:
|
||||||
if tab.name.startswith(room_name):
|
if tab.name.startswith(room_name):
|
||||||
tab.deactivate(reason=reason)
|
tab.deactivate(reason=reason)
|
||||||
|
|
||||||
def enable_private_tabs(self, room_name, reason=None):
|
def enable_private_tabs(self, room_name: str,
|
||||||
|
reason: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
Enable private tabs when joining a room
|
Enable private tabs when joining a room
|
||||||
"""
|
"""
|
||||||
|
@ -1234,12 +1181,12 @@ class Core:
|
||||||
if tab.name.startswith(room_name):
|
if tab.name.startswith(room_name):
|
||||||
tab.activate(reason=reason)
|
tab.activate(reason=reason)
|
||||||
|
|
||||||
def on_user_changed_status_in_private(self, jid, status):
|
def on_user_changed_status_in_private(self, jid: JID, status: str):
|
||||||
tab = self.tabs.by_name_and_class(jid, tabs.ChatTab)
|
tab = self.tabs.by_name_and_class(jid, tabs.ChatTab)
|
||||||
if tab is not None: # display the message in private
|
if tab is not None: # display the message in private
|
||||||
tab.update_status(status)
|
tab.update_status(status)
|
||||||
|
|
||||||
def close_tab(self, tab=None):
|
def close_tab(self, tab: tabs.Tab = None):
|
||||||
"""
|
"""
|
||||||
Close the given tab. If None, close the current one
|
Close the given tab. If None, close the current one
|
||||||
"""
|
"""
|
||||||
|
@ -1262,7 +1209,7 @@ class Core:
|
||||||
gc.get_referrers(tab))
|
gc.get_referrers(tab))
|
||||||
del tab
|
del tab
|
||||||
|
|
||||||
def add_information_message_to_conversation_tab(self, jid, msg):
|
def add_information_message_to_conversation_tab(self, jid: JID, msg: str):
|
||||||
"""
|
"""
|
||||||
Search for a ConversationTab with the given jid (full or bare),
|
Search for a ConversationTab with the given jid (full or bare),
|
||||||
if yes, add the given message to it
|
if yes, add the given message to it
|
||||||
|
@ -1281,7 +1228,7 @@ class Core:
|
||||||
return
|
return
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
def information(self, msg, typ=''):
|
def information(self, msg: str, typ=''):
|
||||||
"""
|
"""
|
||||||
Displays an informational message in the "Info" buffer
|
Displays an informational message in the "Info" buffer
|
||||||
"""
|
"""
|
||||||
|
@ -1987,24 +1934,67 @@ class Core:
|
||||||
self.refresh_window()
|
self.refresh_window()
|
||||||
|
|
||||||
|
|
||||||
class KeyDict(dict):
|
class KeyDict(Dict[str, Callable]):
|
||||||
"""
|
"""
|
||||||
A dict, with a wrapper for get() that will return a custom value
|
A dict, with a wrapper for get() that will return a custom value
|
||||||
if the key starts with _exc_
|
if the key starts with _exc_
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self, k, d=None):
|
def get(self, key: str, default: Optional[Callable] = None) -> Callable:
|
||||||
if isinstance(k, str) and k.startswith('_exc_') and len(k) > 5:
|
if isinstance(key, str) and key.startswith('_exc_') and len(key) > 5:
|
||||||
return lambda: dict.get(self, '_exc_')(k[5:])
|
return lambda: dict.get(self, '_exc_')(key[5:])
|
||||||
return dict.get(self, k, d)
|
return dict.get(self, key, default)
|
||||||
|
|
||||||
|
|
||||||
def replace_key_with_bound(key):
|
def replace_key_with_bound(key: str) -> str:
|
||||||
"""
|
"""
|
||||||
Replace an inputted key with the one defined as its replacement
|
Replace an inputted key with the one defined as its replacement
|
||||||
in the config
|
in the config
|
||||||
"""
|
"""
|
||||||
bind = config.get(key, default=key, section='bindings')
|
return config.get(key, default=key, section='bindings') or key
|
||||||
if not bind:
|
|
||||||
bind = key
|
|
||||||
return bind
|
def replace_line_breaks(key: str) -> str:
|
||||||
|
"replace ^J with \n"
|
||||||
|
if key == '^J':
|
||||||
|
return '\n'
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def separate_chars_from_bindings(char_list: List[str]) -> List[List[str]]:
|
||||||
|
"""
|
||||||
|
returns a list of lists. For example if you give
|
||||||
|
['a', 'b', 'KEY_BACKSPACE', 'n', 'u'], this function returns
|
||||||
|
[['a', 'b'], ['KEY_BACKSPACE'], ['n', 'u']]
|
||||||
|
|
||||||
|
This way, in case of lag (for example), we handle the typed text
|
||||||
|
by “batch” as much as possible (instead of one char at a time,
|
||||||
|
which implies a refresh after each char, which is very slow),
|
||||||
|
but we still handle the special chars (backspaces, arrows,
|
||||||
|
ctrl+x ou alt+x, etc) one by one, which avoids the issue of
|
||||||
|
printing them OR ignoring them in that case. This should
|
||||||
|
resolve the “my ^W are ignored when I lag ;(”.
|
||||||
|
"""
|
||||||
|
res = []
|
||||||
|
current = []
|
||||||
|
for char in char_list:
|
||||||
|
assert char
|
||||||
|
# Transform that stupid char into what we actually meant
|
||||||
|
if char == '\x1f':
|
||||||
|
char = '^/'
|
||||||
|
if len(char) == 1:
|
||||||
|
current.append(char)
|
||||||
|
else:
|
||||||
|
# special case for the ^I key, it’s considered as \t
|
||||||
|
# only when pasting some text, otherwise that’s the ^I
|
||||||
|
# (or M-i) key, which stands for completion by default.
|
||||||
|
if char == '^I' and len(char_list) != 1:
|
||||||
|
current.append('\t')
|
||||||
|
continue
|
||||||
|
if current:
|
||||||
|
res.append(current)
|
||||||
|
current = []
|
||||||
|
res.append([char])
|
||||||
|
if current:
|
||||||
|
res.append(current)
|
||||||
|
return res
|
||||||
|
|
|
@ -15,6 +15,8 @@ shortcut, like ^A, M-a or KEY_RESIZE)
|
||||||
import curses
|
import curses
|
||||||
import curses.ascii
|
import curses.ascii
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Callable, List, Optional, Tuple
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# A callback that will handle the next key entered by the user. For
|
# A callback that will handle the next key entered by the user. For
|
||||||
|
@ -24,10 +26,10 @@ log = logging.getLogger(__name__)
|
||||||
# shortcuts or inserting text in the current output. The callback
|
# shortcuts or inserting text in the current output. The callback
|
||||||
# is always reset to None afterwards (to resume the normal
|
# is always reset to None afterwards (to resume the normal
|
||||||
# processing of keys)
|
# processing of keys)
|
||||||
continuation_keys_callback = None
|
continuation_keys_callback = None # type: Optional[Callable]
|
||||||
|
|
||||||
|
|
||||||
def get_next_byte(s):
|
def get_next_byte(s) -> Tuple[Optional[int], Optional[bytes]]:
|
||||||
"""
|
"""
|
||||||
Read the next byte of the utf-8 char
|
Read the next byte of the utf-8 char
|
||||||
ncurses seems to return a string of the byte
|
ncurses seems to return a string of the byte
|
||||||
|
@ -43,8 +45,8 @@ def get_next_byte(s):
|
||||||
return (ord(c), c.encode('latin-1')) # returns a number and a bytes object
|
return (ord(c), c.encode('latin-1')) # returns a number and a bytes object
|
||||||
|
|
||||||
|
|
||||||
def get_char_list(s):
|
def get_char_list(s) -> List[str]:
|
||||||
ret_list = []
|
ret_list = [] # type: List[str]
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
key = s.get_wch()
|
key = s.get_wch()
|
||||||
|
@ -109,7 +111,7 @@ class Keyboard:
|
||||||
"""
|
"""
|
||||||
self.escape = True
|
self.escape = True
|
||||||
|
|
||||||
def get_user_input(self, s):
|
def get_user_input(self, s) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Returns a list of all the available characters to read (for example it
|
Returns a list of all the available characters to read (for example it
|
||||||
may contain a whole text if there’s some lag, or the user pasted text,
|
may contain a whole text if there’s some lag, or the user pasted text,
|
||||||
|
|
|
@ -355,4 +355,4 @@ def create_logger() -> None:
|
||||||
logger = Logger()
|
logger = Logger()
|
||||||
|
|
||||||
|
|
||||||
logger = None
|
logger = None # type: Optional[Logger]
|
||||||
|
|
|
@ -13,7 +13,7 @@ Once created, they must be added to the list of checked events with
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Callable
|
from typing import Callable, Union
|
||||||
|
|
||||||
|
|
||||||
class DelayedEvent:
|
class DelayedEvent:
|
||||||
|
@ -22,7 +22,8 @@ class DelayedEvent:
|
||||||
Use it if you want an event to happen in, e.g. 6 seconds.
|
Use it if you want an event to happen in, e.g. 6 seconds.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, delay: int, callback: Callable, *args) -> None:
|
def __init__(self, delay: Union[int, float], callback: Callable,
|
||||||
|
*args) -> None:
|
||||||
"""
|
"""
|
||||||
Create a new DelayedEvent.
|
Create a new DelayedEvent.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue