diff options
-rw-r--r-- | doc/en/plugins.txt | 43 | ||||
-rw-r--r-- | doc/en/plugins/gpg.txt | 2 | ||||
-rw-r--r-- | plugins/mpd_client.py | 2 | ||||
-rw-r--r-- | plugins/reminder.py | 56 | ||||
-rw-r--r-- | src/plugin.py | 29 | ||||
-rw-r--r-- | src/plugin_manager.py | 50 | ||||
-rw-r--r-- | src/tabs.py | 20 | ||||
-rw-r--r-- | src/windows.py | 18 |
8 files changed, 208 insertions, 12 deletions
diff --git a/doc/en/plugins.txt b/doc/en/plugins.txt index 87f09cb7..f31086dc 100644 --- a/doc/en/plugins.txt +++ b/doc/en/plugins.txt @@ -61,16 +61,47 @@ Everything else is handled by that _auto_completion()_ method (checking what special completion for that command, just pass None (the default value). *del_command*:: +self+, +name+ + -This command removes a tab command added by your plugin. +This method removes a command added by your plugin. * _name_: (string) the name of the command you want to remove. +*add_key*:: +self+, +key+, +handler+ + +This method adds a global keyboard shortcut on _key_ that will call _handler_. +You can get the keys with _python3 src/keyboard.py_. + +* _key_: String representing the key press in curses. +* _handler_: Method called whenever _key_ is pressed. + +*del_key*:: +self+, +key+ + +This method deletes a keyboard shortcut previously added by your plugin. + +* _key_: String representing the key press in curses. + +*add_tab_key*:: +self+, +tab_type+, +key+, +handler+ + +This method adds a tab-custom command to poezio. For example you can add _^G_ +keybind that the user could call in a specific tab when the plugin is loaded. + +* _tab_type_: You have to _import tabs_ in order to get tabs types. The + following are possible: +** _tabs.MucTab_: The MultiUserChat tabs +** _tabs.PrivateTab_: The Private tabs +** _tabs.ConversationTab_: The Roster tab +** _tabs.RosterInfoTab_: The MultiUserChat, Private, and Conversation tabs +** _tabs.ChatTab_: The MultiUserChat, Private, and Conversation tabs +** _tabs.MucListTab_: The MultiUserChat list tabs +* _key_: (string) the curses representation of the keypress (see above). +* _handler_: (function) the handler to be called when the keypress is found. + +*del_tab_command*:: +self+, +tab_type+, +key+ +This method removes a tab command added by your plugin. + +* _key_: (string) the name of the keybind you want to remove. +* _tab_type_: the type of tab (see help for _add_key_command_) *add_tab_command*:: +self+, +tab_type+, +name+, +handler+, +help+, +completion+ + -This method adds a tab-custom command to poezio. For example you can add /dou +This method adds a tab-custom command to poezio. For example you can add a /dou command that the user could call in a specific tab when the plugin is loaded. - * _tab_type_: You have to _import tabs_ in order to get tabs types. The following are possible: ** _tabs.MucTab_: The MultiUserChat tabs @@ -95,6 +126,12 @@ Everything else is handled by that _auto_completion()_ method (checking what strings match, how to cycle between matches, etc). If you don’t want any special completion for that command, just pass None (the default value). +*del_tab_command*:: +self+, +tab_type+, +name+ +This method removes a tab command added by your plugin. + +* _name_: (string) the name of the command you want to remove. +* _tab_type_: the type of tab (see help for _add_tab_command_) + *add_event_handler**: +self+, +event_name+, +handler+ +position+ This methods adds a callback that will be called whenever the given event occurs. <<example-2,ex 2>> diff --git a/doc/en/plugins/gpg.txt b/doc/en/plugins/gpg.txt index 9e87b6c7..70a6fd15 100644 --- a/doc/en/plugins/gpg.txt +++ b/doc/en/plugins/gpg.txt @@ -35,7 +35,7 @@ default), and fill it like this: [source,python] --------------------------------------------------------------------- -[Poezio] +[gpg] keyid = 091F9C78 passphrase = your OPTIONAL passphrase diff --git a/plugins/mpd_client.py b/plugins/mpd_client.py index b56e0d7f..833599ae 100644 --- a/plugins/mpd_client.py +++ b/plugins/mpd_client.py @@ -21,7 +21,7 @@ class Plugin(BasePlugin): s = '%(artist)s - %(title)s (%(album)s)' % current if 'full' in args: pourcentage = int(current_time / float(current['time']) * 10) - s += ' \x192[\x191' + '-'*(pourcentage-1) + '\x193+' + '\x191' + '-' * (10-pourcentage-1) + '\x192]\x19o' + s += ' \x192}[\x191}' + '-'*(pourcentage-1) + '\x193}+' + '\x191}' + '-' * (10-pourcentage-1) + '\x192}]\x19o' if not self.core.send_message('%s' % (s,)): self.core.information('Cannot send result (%s), this is not a conversation tab' % result) diff --git a/plugins/reminder.py b/plugins/reminder.py new file mode 100644 index 00000000..eb63efde --- /dev/null +++ b/plugins/reminder.py @@ -0,0 +1,56 @@ +from plugin import BasePlugin +import curses +import common +import timed_events + +class Plugin(BasePlugin): + + def init(self): + self.add_command('remind', self.command_remind, "Usage: /reminder <time in seconds> <todo>\nReminder: remind you of <todo> every <time> seconds..", None) + self.add_command('done', self.command_done, "Usage: /done <id>\nDone: Stop reminding you do the task identified by <id>", None) + self.add_command('tasks', self.command_tasks, "Usage: /tasks\nTasks: List all the current tasks and their ids.", None) + self.tasks = {} + self.count = 0 + + def command_remind(self, arg): + args = common.shell_split(arg) + if len(args) < 2: + return + try: + time = int(args[0]) + except: + return + + self.tasks[self.count] = (time, args[1]) + timed_event = timed_events.DelayedEvent(time, self.remind, self.count) + self.core.add_timed_event(timed_event) + self.count += 1 + + def command_done(self, arg="0"): + try: + id = int(arg) + except: + return + if not id in self.tasks: + return + + del self.tasks[id] + + def command_tasks(self, arg): + s = '' + for key in self.tasks: + s += '%s: %s\n' % key, self.tasks[key][1] + if s: + self.core.information(s, 'Info') + + def remind(self, id=0): + if not id in self.tasks: + return + self.core.information('Task %s: %s' % (id, self.tasks[id][1]), 'Info') + if self.config.get('beep', '') == 'true': + curses.beep() + timed_event = timed_events.DelayedEvent(self.tasks[id][0], self.remind, id) + self.core.add_timed_event(timed_event) + + + diff --git a/src/plugin.py b/src/plugin.py index 92adbc4b..137f704f 100644 --- a/src/plugin.py +++ b/src/plugin.py @@ -11,6 +11,11 @@ class PluginConfig(config.Config): RawConfigParser.__init__(self, None) self.read() + def get(self, option, default, section=None): + if not section: + section = self.module_name + return config.Config.get(self, option, default, section) + def read(self): """Read the config file""" RawConfigParser.read(self, self.file_name) @@ -96,6 +101,30 @@ class BasePlugin(object, metaclass=SafetyMetaclass): """ return self.plugin_manager.del_command(self.__module__, name) + def add_key(self, key, handler): + """ + Add a global keybind + """ + return self.plugin_manager.add_key(self.__module__, key, handler) + + def del_key(self, key): + """ + Remove a global keybind + """ + return self.plugin_manager.del_key(self.__module__, key) + + def add_tab_key(self, tab_type, key, handler): + """ + Add a keybind only for a type of tab. + """ + return self.plugin_manager.add_tab_key(self.__module__, tab_type, key, handler) + + def del_tab_key(self, tab_type, key): + """ + Remove a keybind added through add_tab_key. + """ + return self.plugin_manager.del_tab_key(self.__module__, tab_type, key) + def add_tab_command(self, tab_type, name, handler, help, completion=None): """ Add a command only for a type of tab. diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 437d8ee2..e3b786cb 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -36,6 +36,8 @@ class PluginManager(object): self.commands = {} # module name -> dict of commands loaded for the module self.event_handlers = {} # module name -> list of event_name/handler pairs loaded for the module self.tab_commands = {} #module name -> dict of tab types; tab type -> commands loaded by the module + self.keys = {} # module name → dict of keys/handlers loaded for the module + self.tab_keys = {} #module name → dict of tab types; tab type → list of keybinds (tuples) def load(self, name, notify=True): if name in self.plugins: @@ -61,6 +63,8 @@ class PluginManager(object): self.modules[name] = module self.commands[name] = {} + self.keys[name] = {} + self.tab_keys[name] = {} self.tab_commands[name] = {} self.event_handlers[name] = [] self.plugins[name] = module.Plugin(self, self.core, plugins_conf_dir) @@ -72,16 +76,23 @@ class PluginManager(object): try: for command in self.commands[name].keys(): del self.core.commands[command] + for key in self.keys[name].keys(): + del self.core.key_func[key] for tab in list(self.tab_commands[name].keys()): for command in self.tab_commands[name][tab]: self.del_tab_command(name, getattr(tabs, tab), command[0]) del self.tab_commands[name][tab] + for tab in list(self.tab_keys[name].keys()): + for key in self.tab_keys[name][tab]: + self.del_tab_key(name, getattr(tabs, tab), key[0]) + del self.tab_keys[name][tab] for event_name, handler in self.event_handlers[name]: self.del_event_handler(name, event_name, handler) self.plugins[name].unload() del self.plugins[name] del self.commands[name] + del self.keys[name] del self.tab_commands[name] del self.event_handlers[name] if notify: @@ -122,6 +133,45 @@ class PluginManager(object): if isinstance(tab, tab_type) and name in tab.commands: del tab.commands[name] + def add_tab_key(self, module_name, tab_type, key, handler): + keys = self.tab_keys[module_name] + t = tab_type.__name__ + if key in tab_type.plugin_keys: + return + if not t in keys: + keys[t] = [] + keys[t].append((key, handler)) + tab_type.plugin_keys[key] = handler + for tab in self.core.tabs: + if isinstance(tab, tab_type): + tab.update_keys() + + def del_tab_key(self, module_name, tab_type, key): + keys = self.tab_keys[module_name] + t = tab_type.__name__ + if not t in keys: + return + for _key in keys[t]: + if _key[0] == key: + keys[t].remove(_key) + del tab_type.plugin_keys[key] + for tab in self.core.tabs: + if isinstance(tab, tab_type) and key in tab.key_func: + del tab.key_func[key] + + def add_key(self, module_name, key, handler): + if key in self.core.key_func: + raise Exception(_("Key '%s' already exists") % (key,)) + keys = self.keys[module_name] + keys[key] = handler + self.core.key_func[key] = handler + + def del_key(self, module_name, key): + if key in self.keys[module_name]: + del self.keys[module_name][key] + if key in self.core.key_func: + del self.core.commands[key] + def add_command(self, module_name, name, handler, help, completion=None): if name in self.core.commands: raise Exception(_("Command '%s' already exists") % (name,)) diff --git a/src/tabs.py b/src/tabs.py index feb4be37..fd9755f6 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -228,6 +228,11 @@ class Tab(object): if not c in self.commands: self.commands[c] = self.plugin_commands[c] + def update_keys(self): + for k in self.plugin_keys: + if not k in self.key_func: + self.key_func[k] = self.plugin_keys[k] + def on_lose_focus(self): """ called when this tab loses the focus. @@ -284,6 +289,7 @@ class ChatTab(Tab): And also, add the /say command """ plugin_commands = {} + plugin_keys = {} def __init__(self): Tab.__init__(self) self._text_buffer = TextBuffer() @@ -308,6 +314,7 @@ class ChatTab(Tab): _('Usage: /clear\nClear: Clear the current buffer.'), None) self.chat_state = None self.update_commands() + self.update_keys() def last_words_completion(self): """ @@ -448,6 +455,7 @@ class MucTab(ChatTab): """ message_type = 'groupchat' plugin_commands = {} + plugin_keys = {} def __init__(self, jid, nick): ChatTab.__init__(self) self.own_nick = nick @@ -490,6 +498,7 @@ class MucTab(ChatTab): self.commands['names'] = (self.command_names, _('Usage: /names\nNames: Get the list of the users in the room, and the list of the people assuming the different roles.'), None) self.resize() self.update_commands() + self.update_keys() def completion_nick(self, the_input): """Completion for /nick""" @@ -905,7 +914,7 @@ class MucTab(ChatTab): add_after = after else: add_after = ' ' - self.input.auto_completion(word_list, add_after) + self.input.auto_completion(word_list, add_after, quotify=False) empty_after = self.input.get_text() == '' or (self.input.get_text().startswith('/') and not self.input.get_text().startswith('//')) self.send_composing_chat_state(empty_after) @@ -1264,6 +1273,7 @@ class PrivateTab(ChatTab): """ message_type = 'chat' plugin_commands = {} + plugin_keys = {} def __init__(self, name, nick): ChatTab.__init__(self) self.own_nick = nick @@ -1283,6 +1293,7 @@ class PrivateTab(ChatTab): self.parent_muc = self.core.get_tab_by_name(JID(name).bare, MucTab) self.on = True self.update_commands() + self.update_keys() def completion(self): self.complete_commands(self.input) @@ -1462,6 +1473,7 @@ class RosterInfoTab(Tab): A tab, splitted in two, containing the roster and infos """ plugin_commands = {} + plugin_keys = {} def __init__(self): Tab.__init__(self) self.name = "Roster" @@ -1497,6 +1509,7 @@ class RosterInfoTab(Tab): self.commands['clear_infos'] = (self.command_clear_infos, _("Usage: /clear_infos\nClear Infos: Use this command to clear the info buffer."), None) self.resize() self.update_commands() + self.update_keys() def resize(self): if not self.visible: @@ -1980,6 +1993,7 @@ class ConversationTab(ChatTab): The tab containg a normal conversation (not from a MUC) """ plugin_commands = {} + plugin_keys = {} additional_informations = {} message_type = 'chat' def __init__(self, jid): @@ -2000,6 +2014,7 @@ class ConversationTab(ChatTab): self.commands['info'] = (self.command_info, _('Usage: /info\nInfo: Get the status of the contact.'), None) self.resize() self.update_commands() + self.update_keys() @staticmethod def add_information_element(plugin_name, callback): @@ -2168,6 +2183,7 @@ class MucListTab(Tab): scrollable, and letting the user join them, etc """ plugin_commands = {} + plugin_keys = {} def __init__(self, server): Tab.__init__(self) self.state = 'normal' @@ -2188,6 +2204,8 @@ class MucListTab(Tab): self.key_func['^M'] = self.join_selected self.commands['close'] = (self.close, _("Usage: /close\nClose: Just close this tab."), None) self.resize() + self.update_keys() + self.update_commands() def refresh(self): if self.need_resize: diff --git a/src/windows.py b/src/windows.py index d7471d40..bbae1ab7 100644 --- a/src/windows.py +++ b/src/windows.py @@ -940,7 +940,7 @@ class Input(Win): self.rewrite_text() return True - def auto_completion(self, word_list, add_after): + def auto_completion(self, word_list, add_after, quotify=True): """ Complete the input, from a list of words if add_after is None, we use the value defined in completion @@ -948,6 +948,10 @@ class Input(Win): completion (with no additional space) """ completion_type = config.get('completion', 'normal') + if quotify: + for i, word in enumerate(word_list[:]): + if ' ' in word: + word_list[i] = '"' + word + '"' if completion_type == 'shell' and self.text != '': self.shell_completion(word_list, add_after) else: @@ -975,10 +979,12 @@ class Input(Win): begin = self.text[space_before_cursor+1:pos] else: begin = self.text[:pos] - hit_list = [] # list of matching nicks + hit_list = [] # list of matching hits for word in word_list: if word.lower().startswith(begin.lower()): hit_list.append(word) + elif word.startswith('"') and word.lower()[1:].startswith(begin.lower()): + hit_list.append(word) if len(hit_list) == 0: return self.hit_list = hit_list @@ -990,18 +996,18 @@ class Input(Win): self.text = self.text[:pos-end] + self.text[pos:] pos -= end - nick = self.hit_list[0] # take the first hit - self.text = self.text[:pos] + nick + after + self.text[pos:] + hit = self.hit_list[0] # take the first hit + self.text = self.text[:pos] + hit + after + self.text[pos:] for i in range(end): try: self.key_left(reset=False) except: pass - for i in range(len(nick + after)): + for i in range(len(hit + after)): self.key_right(reset=False) self.rewrite_text() - self.last_completion = nick + self.last_completion = hit def shell_completion(self, word_list, after): """ |