summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core.py84
-rw-r--r--src/tab.py128
-rw-r--r--src/windows.py45
3 files changed, 170 insertions, 87 deletions
diff --git a/src/core.py b/src/core.py
index fdb77343..cd9cb32a 100644
--- a/src/core.py
+++ b/src/core.py
@@ -34,6 +34,8 @@ import common
import theme
import logging
+from sleekxmpp.xmlstream.stanzabase import JID
+
log = logging.getLogger(__name__)
import multiuserchat as muc
@@ -93,28 +95,33 @@ class Core(object):
self.resize_timer = None
self.previous_tab_nb = 0
self.own_nick = config.get('own_nick', self.xmpp.boundjid.bare)
- # global commands, available from all tabs (having an input, of course)
+ # global commands, available from all tabs
+ # a command is tuple of the form:
+ # (the function executing the command. Takes a string as argument,
+ # a string representing the help message,
+ # a completion function, taking a Input as argument. Can be None)
+ # The completion function should return True if a completion was
+ # made ; False otherwise
self.commands = {
- 'help': (self.command_help, '\_o< KOIN KOIN KOIN'),
- 'join': (self.command_join, _("Usage: /join [room_name][@server][/nick] [password]\nJoin: Join the specified room. You can specify a nickname after a slash (/). If no nickname is specified, you will use the default_nick in the configuration file. You can omit the room name: you will then join the room you\'re looking at (useful if you were kicked). You can also provide a room_name without specifying a server, the server of the room you're currently in will be used. You can also provide a password to join the room.\nExamples:\n/join room@server.tld\n/join room@server.tld/John\n/join room2\n/join /me_again\n/join\n/join room@server.tld/my_nick password\n/join / password")),
- 'quit': (self.command_quit, _("Usage: /quit\nQuit: Just disconnect from the server and exit poezio.")),
- 'exit': (self.command_quit, _("Usage: /exit\nExit: Just disconnect from the server and exit poezio.")),
- 'next': (self.rotate_rooms_right, _("Usage: /next\nNext: Go to the next room.")),
- 'n': (self.rotate_rooms_right, _("Usage: /n\nN: Go to the next room.")),
- 'prev': (self.rotate_rooms_left, _("Usage: /prev\nPrev: Go to the previous room.")),
- 'p': (self.rotate_rooms_left, _("Usage: /p\nP: Go to the previous room.")),
- 'win': (self.command_win, _("Usage: /win <number>\nWin: Go to the specified room.")),
- 'w': (self.command_win, _("Usage: /w <number>\nW: Go to the specified room.")),
- 'show': (self.command_show, _("Usage: /show <availability> [status]\nShow: Change your availability and (optionaly) your status. The <availability> argument is one of \"avail, available, ok, here, chat, away, afk, dnd, busy, xa\" and the optional [status] argument will be your status message")),
- 'away': (self.command_away, _("Usage: /away [message]\nAway: Sets your availability to away and (optional) sets your status message. This is equivalent to '/show away [message]'")),
- 'busy': (self.command_busy, _("Usage: /busy [message]\nBusy: Sets your availability to busy and (optional) sets your status message. This is equivalent to '/show busy [message]'")),
- 'avail': (self.command_avail, _("Usage: /avail [message]\nAvail: Sets your availability to available and (optional) sets your status message. This is equivalent to '/show available [message]'")),
- 'available': (self.command_avail, _("Usage: /available [message]\nAvailable: Sets your availability to available and (optional) sets your status message. This is equivalent to '/show available [message]'")),
- 'bookmark': (self.command_bookmark, _("Usage: /bookmark [roomname][/nick]\nBookmark: Bookmark the specified room (you will then auto-join it on each poezio start). This commands uses the same syntaxe as /join. Type /help join for syntaxe examples. Note that when typing \"/bookmark\" on its own, the room will be bookmarked with the nickname you\'re currently using in this room (instead of default_nick)")),
- 'set': (self.command_set, _("Usage: /set <option> [value]\nSet: Sets the value to the option in your configuration file. You can, for example, change your default nickname by doing `/set default_nick toto` or your resource with `/set resource blabla`. You can also set an empty value (nothing) by providing no [value] after <option>.")),
- 'link': (self.command_link, _("Usage: /link [option] [number]\nLink: Interact with a link in the conversation. Available options are 'open', 'copy'. Open just opens the link in the browser if it's http://, Copy just copy the link in the clipboard. An optional number can be provided, it indicates which link to interact with.")),
- 'whois': (self.command_whois, _('Usage: /whois <nickname>\nWhois: Request many informations about the user.')),
- 'theme': (self.command_theme, _('Usage: /theme\nTheme: Reload the theme defined in the config file.')),
+ 'help': (self.command_help, '\_o< KOIN KOIN KOIN', None),
+ 'join': (self.command_join, _("Usage: /join [room_name][@server][/nick] [password]\nJoin: Join the specified room. You can specify a nickname after a slash (/). If no nickname is specified, you will use the default_nick in the configuration file. You can omit the room name: you will then join the room you\'re looking at (useful if you were kicked). You can also provide a room_name without specifying a server, the server of the room you're currently in will be used. You can also provide a password to join the room.\nExamples:\n/join room@server.tld\n/join room@server.tld/John\n/join room2\n/join /me_again\n/join\n/join room@server.tld/my_nick password\n/join / password"), self.completion_join),
+ 'exit': (self.command_quit, _("Usage: /exit\nExit: Just disconnect from the server and exit poezio."), None),
+ 'next': (self.rotate_rooms_right, _("Usage: /next\nNext: Go to the next room."), None),
+ 'n': (self.rotate_rooms_right, _("Usage: /n\nN: Go to the next room."), None),
+ 'prev': (self.rotate_rooms_left, _("Usage: /prev\nPrev: Go to the previous room."), None),
+ 'p': (self.rotate_rooms_left, _("Usage: /p\nP: Go to the previous room."), None),
+ 'win': (self.command_win, _("Usage: /win <number>\nWin: Go to the specified room."), None),
+ 'w': (self.command_win, _("Usage: /w <number>\nW: Go to the specified room."), None),
+ 'show': (self.command_show, _("Usage: /show <availability> [status]\nShow: Change your availability and (optionaly) your status. The <availability> argument is one of \"avail, available, ok, here, chat, away, afk, dnd, busy, xa\" and the optional [status] argument will be your status message"), None),
+ 'away': (self.command_away, _("Usage: /away [message]\nAway: Sets your availability to away and (optional) sets your status message. This is equivalent to '/show away [message]'"), None),
+ 'busy': (self.command_busy, _("Usage: /busy [message]\nBusy: Sets your availability to busy and (optional) sets your status message. This is equivalent to '/show busy [message]'"), None),
+ 'avail': (self.command_avail, _("Usage: /avail [message]\nAvail: Sets your availability to available and (optional) sets your status message. This is equivalent to '/show available [message]'"), None),
+ 'available': (self.command_avail, _("Usage: /available [message]\nAvailable: Sets your availability to available and (optional) sets your status message. This is equivalent to '/show available [message]'"), None),
+ 'bookmark': (self.command_bookmark, _("Usage: /bookmark [roomname][/nick]\nBookmark: Bookmark the specified room (you will then auto-join it on each poezio start). This commands uses the same syntaxe as /join. Type /help join for syntaxe examples. Note that when typing \"/bookmark\" on its own, the room will be bookmarked with the nickname you\'re currently using in this room (instead of default_nick)"), None),
+ 'set': (self.command_set, _("Usage: /set <option> [value]\nSet: Sets the value to the option in your configuration file. You can, for example, change your default nickname by doing `/set default_nick toto` or your resource with `/set resource blabla`. You can also set an empty value (nothing) by providing no [value] after <option>."), None),
+ 'link': (self.command_link, _("Usage: /link [option] [number]\nLink: Interact with a link in the conversation. Available options are 'open', 'copy'. Open just opens the link in the browser if it's http://, Copy just copy the link in the clipboard. An optional number can be provided, it indicates which link to interact with."), None),
+ 'whois': (self.command_whois, _('Usage: /whois <nickname>\nWhois: Request many informations about the user.'), None),
+ 'theme': (self.command_theme, _('Usage: /theme\nTheme: Reload the theme defined in the config file.'), None),
}
self.key_func = {
@@ -146,7 +153,6 @@ class Core(object):
self.xmpp.add_event_handler("roster_update", self.on_roster_update)
self.xmpp.add_event_handler("changed_status", self.on_presence)
-
def grow_information_win(self):
"""
"""
@@ -908,6 +914,28 @@ class Core(object):
self.current_tab().on_gain_focus()
self.refresh_window()
+ def completion_join(self, the_input):
+ """
+ Try to complete the server of the MUC's jid (for now only from the currently
+ open ones)
+ TODO: have a history of recently joined MUCs, and use that too
+ """
+ txt = the_input.get_text()
+ if len(txt.split()) != 2:
+ # we are not on the 1st argument of the command line
+ return False
+ jid = JID(txt.split()[1])
+ if not jid.user or not jid.server or jid.resource != '':
+ # we are not writing the server part of the jid
+ return True
+ serv = jid.server
+ serv_list = []
+ for tab in self.tabs:
+ if isinstance(tab, MucTab):
+ serv_list.append('%s@%s'% (jid.user, JID(tab.get_name()).host))
+ the_input.auto_completion(serv_list, '')
+ return True
+
def command_join(self, arg):
"""
/join [room][/nick] [password]
@@ -1211,21 +1239,9 @@ class Core(object):
else:
self.command_say(line)
- # def command_say(self, line):
- # if isinstance(self.current_tab(), PrivateTab):
- # muc.send_private_message(self.xmpp, self.current_tab().get_name(), line)
- # elif isinstance(self.current_tab(), ConversationTab): # todo, special case # hu, I can't remember what special case was needed when I wrote that…
- # if isinstance(self.current_tab(), PrivateTab) or\
- # isinstance(self.current_tab(), ConversationTab):
- # self.add_message_to_text_buffer(self.current_tab().get_room(), line, None, self.own_nick)
- # elif isinstance(self.current_tab(), MucTab):
- # muc.send_groupchat_message(self.xmpp, self.current_tab().get_name(), line)
- # self.doupdate()
-
def doupdate(self):
self.current_tab().just_before_refresh()
curses.doupdate()
# # global core object
core = Core(connection)
-
diff --git a/src/tab.py b/src/tab.py
index 24c83072..b126d930 100644
--- a/src/tab.py
+++ b/src/tab.py
@@ -59,11 +59,39 @@ class Tab(object):
# and use them in on_input
self.commands = {} # and their own commands
- def refresh(self, tabs, informations, roster):
- """
- Called on each screen refresh (when something has changed)
- """
- raise NotImplementedError
+ def complete_commands(self, the_input):
+ """
+ Does command completion on the specified input for both global and tab-specific
+ commands.
+ This should be called from the completion method (on tab, for example), passing
+ the input where completion is to be made.
+ It can completion the command name itself or an argument of the command.
+ Returns True if a completion was made, False else.
+ """
+ txt = the_input.get_text()
+ # check if this is a command
+ if txt.startswith('/') and not txt.startswith('//'):
+ # check if we are in the middle of the command name
+ if len(txt.split()) > 1 or\
+ (txt.endswith(' ') and not the_input.last_completion):
+ command_name = txt.split()[0][1:]
+ if command_name in self.core.commands:
+ command = self.core.commands[command_name]
+ elif command_name in self.commands:
+ command = self.commands[command_name]
+ else: # Unknown command, cannot complete
+ return False
+ if command[2] is None:
+ return False # There's no completion functio
+ else:
+ return command[2](the_input)
+ else:
+ # complete the command's name
+ words = ['/%s'%(name) for name in list(self.core.commands.keys())] +\
+ ['/%s'% (name) for name in list(self.commands.keys())]
+ the_input.auto_completion(words, '')
+ return True
+ return False
def resize(self):
self.size = (self.height, self.width) = self.core.stdscr.getmaxyx()
@@ -72,6 +100,12 @@ class Tab(object):
else:
self.visible = True
+ def refresh(self, tabs, informations, roster):
+ """
+ Called on each screen refresh (when something has changed)
+ """
+ raise NotImplementedError
+
def get_color_state(self):
"""
returns the color that should be used in the GlobalInfoBar
@@ -224,7 +258,7 @@ class ChatTab(Tab):
self.key_func['\n'] = self.on_enter
self.commands['say'] = (self.command_say,
_("""Usage: /say <message>\nSay: Just send the message.
- Useful if you want your message to begin with a '/'"""))
+ Useful if you want your message to begin with a '/'"""), None)
def last_words_completion(self):
"""
@@ -242,7 +276,7 @@ class ChatTab(Tab):
for word in msg.txt.split():
if len(word) >= 4 and word not in words:
words.append(word)
- self.input.auto_completion(words, False)
+ self.input.auto_completion(words, ' ')
def on_enter(self):
txt = self.input.key_enter()
@@ -282,14 +316,14 @@ class MucTab(ChatTab):
self.key_func['^I'] = self.completion
self.key_func['M-i'] = self.completion
# commands
- self.commands['ignore'] = (self.command_ignore, _("Usage: /ignore <nickname> \nIgnore: Ignore a specified nickname."))
- self.commands['unignore'] = (self.command_unignore, _("Usage: /unignore <nickname>\nUnignore: Remove the specified nickname from the ignore list."))
- self.commands['kick'] = (self.command_kick, _("Usage: /kick <nick> [reason]\nKick: Kick the user with the specified nickname. You also can give an optional reason."))
- self.commands['topic'] = (self.command_topic, _("Usage: /topic <subject>\nTopic: Change the subject of the room"))
- self.commands['query'] = (self.command_query, _('Usage: /query <nick> [message]\nQuery: Open a private conversation with <nick>. This nick has to be present in the room you\'re currently in. If you specified a message after the nickname, it will immediately be sent to this user'))
- self.commands['part'] = (self.command_part, _("Usage: /part [message]\n Part: disconnect from a room. You can specify an optional message."))
- self.commands['nick'] = (self.command_nick, _("Usage: /nick <nickname>\nNick: Change your nickname in the current room"))
- self.commands['recolor'] = (self.command_recolor, _('Usage: /recolor\nRecolor: Re-assign a color to all participants of the current room, based on the last time they talked. Use this if the participants currently talking have too many identical colors.'))
+ self.commands['ignore'] = (self.command_ignore, _("Usage: /ignore <nickname> \nIgnore: Ignore a specified nickname."), None)
+ self.commands['unignore'] = (self.command_unignore, _("Usage: /unignore <nickname>\nUnignore: Remove the specified nickname from the ignore list."), None)
+ self.commands['kick'] = (self.command_kick, _("Usage: /kick <nick> [reason]\nKick: Kick the user with the specified nickname. You also can give an optional reason."), None)
+ self.commands['topic'] = (self.command_topic, _("Usage: /topic <subject>\nTopic: Change the subject of the room"), None)
+ self.commands['query'] = (self.command_query, _('Usage: /query <nick> [message]\nQuery: Open a private conversation with <nick>. This nick has to be present in the room you\'re currently in. If you specified a message after the nickname, it will immediately be sent to this user'), None)
+ self.commands['part'] = (self.command_part, _("Usage: /part [message]\n Part: disconnect from a room. You can specify an optional message."), None)
+ self.commands['nick'] = (self.command_nick, _("Usage: /nick <nickname>\nNick: Change your nickname in the current room"), None)
+ self.commands['recolor'] = (self.command_recolor, _('Usage: /recolor\nRecolor: Re-assign a color to all participants of the current room, based on the last time they talked. Use this if the participants currently talking have too many identical colors.'), None)
self.resize()
def command_recolor(self, arg):
@@ -478,8 +512,17 @@ class MucTab(ChatTab):
"""
Called when Tab is pressed, complete the nickname in the input
"""
+ if self.complete_commands(self.input):
+ return
compare_users = lambda x: x.last_talked
- self.input.auto_completion([user.nick for user in sorted(self._room.users, key=compare_users, reverse=True)])
+ word_list = [user.nick for user in sorted(self._room.users, key=compare_users, reverse=True)]
+ after = config.get('after_completion', ',')+" "
+ if ' ' not in self.input.get_text() or (self.input.last_completion and\
+ self.input.get_text()[:-len(after)] == self.input.last_completion):
+ add_after = after
+ else:
+ add_after = ' '
+ self.input.auto_completion(word_list, add_after)
def get_color_state(self):
return self._room.color_state
@@ -531,10 +574,17 @@ class PrivateTab(ChatTab):
self.info_win = windows.TextWin()
self.tab_win = windows.GlobalInfoBar()
self.input = windows.MessageInput()
- self.commands['unquery'] = (self.command_unquery, _("Usage: /unquery\nUnquery: close the tab"))
- self.commands['part'] = (self.command_unquery, _("Usage: /part\Part: close the tab"))
+ # keys
+ self.key_func['^I'] = self.completion
+ self.key_func['M-i'] = self.completion
+ # commands
+ self.commands['unquery'] = (self.command_unquery, _("Usage: /unquery\nUnquery: close the tab"), None)
+ self.commands['part'] = (self.command_unquery, _("Usage: /part\Part: close the tab"), None)
self.resize()
+ def completion(self):
+ self.complete_commands(self.input)
+
def command_say(self, line):
muc.send_private_message(self.core.xmpp, self.get_name(), line)
self.core.add_message_to_text_buffer(self.get_room(), line, None, self.get_room().own_nick)
@@ -631,6 +681,16 @@ class RosterInfoTab(Tab):
self.default_help_message = windows.HelpText("Enter commands with “/”. “o”: toggle offline show")
self.input = self.default_help_message
self.set_color_state(theme.COLOR_TAB_NORMAL)
+ self.key_func['^I'] = self.completion
+ self.key_func['M-i'] = self.completion
+ self.key_func["^J"] = self.on_enter
+ self.key_func["^M"] = self.on_enter
+ self.key_func[' '] = self.on_space
+ self.key_func["/"] = self.on_slash
+ self.key_func["KEY_UP"] = self.move_cursor_up
+ self.key_func["KEY_DOWN"] = self.move_cursor_down
+ self.key_func["o"] = self.toggle_offline_show
+ self.key_func["^F"] = self.start_search
self.resize()
def resize(self):
@@ -644,6 +704,12 @@ class RosterInfoTab(Tab):
self.contact_info_win.resize(3, roster_width, self.height-2-3, 0, self.core.stdscr)
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
+ def completion(self):
+ # Check if we are entering a command (with the '/' key)
+ if isinstance(self.input, windows.CommandInput) and\
+ not self.input.help_message:
+ self.complete_commands(self.input)
+
def refresh(self, tabs, informations, roster):
if not self.visible:
return
@@ -664,22 +730,11 @@ class RosterInfoTab(Tab):
self._color_state = color
def on_input(self, key):
- key_commands = {
- "^J": self.on_enter,
- "^M": self.on_enter,
- "\n": self.on_enter,
- ' ': self.on_space,
- "/": self.on_slash,
- "KEY_UP": self.move_cursor_up,
- "KEY_DOWN": self.move_cursor_down,
- "o": self.toggle_offline_show,
- "^F": self.start_search,
- }
res = self.input.do_command(key)
if res:
return True
- if key in key_commands:
- return key_commands[key]()
+ if key in self.key_func:
+ return self.key_func[key]()
def toggle_offline_show(self):
"""
@@ -793,10 +848,17 @@ class ConversationTab(ChatTab):
self.info_win = windows.TextWin()
self.tab_win = windows.GlobalInfoBar()
self.input = windows.MessageInput()
- self.commands['unquery'] = (self.command_unquery, _("Usage: /unquery\nUnquery: close the tab"))
- self.commands['part'] = (self.command_unquery, _("Usage: /part\Part: close the tab"))
+ # keys
+ self.key_func['^I'] = self.completion
+ self.key_func['M-i'] = self.completion
+ # commands
+ self.commands['unquery'] = (self.command_unquery, _("Usage: /unquery\nUnquery: close the tab"), None)
+ self.commands['part'] = (self.command_unquery, _("Usage: /part\Part: close the tab"), None)
self.resize()
+ def completion(self):
+ self.complete_commands(self.input)
+
def command_say(self, line):
muc.send_private_message(self.core.xmpp, self.get_name(), line)
self.core.add_message_to_text_buffer(self.get_room(), line, None, self.core.own_nick)
diff --git a/src/windows.py b/src/windows.py
index e6d77f1c..ff303ad8 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -803,17 +803,20 @@ class Input(Win):
self.rewrite_text()
return True
- def auto_completion(self, user_list, add_after=True):
+ def auto_completion(self, word_list, add_after):
"""
- Complete the nickname
+ Complete the input, from a list of words
+ if add_after is None, we use the value defined in completion
+ plus a space, after the completion. If it's a string, we use it after the
+ completion (with no additional space)
"""
if self.pos+self.line_pos != len(self.text): # or len(self.text) == 0
return # we don't complete if cursor is not at the end of line
completion_type = config.get('completion', 'normal')
if completion_type == 'shell' and self.text != '':
- self.shell_completion(user_list, add_after)
+ self.shell_completion(word_list, add_after)
else:
- self.normal_completion(user_list, add_after)
+ self.normal_completion(word_list, add_after)
return True
def reset_completion(self):
@@ -823,46 +826,48 @@ class Input(Win):
self.hit_list = []
self.last_completion = None
- def normal_completion(self, user_list, add_after):
+ def normal_completion(self, word_list, after):
"""
Normal completion
"""
- if add_after and (" " not in self.text.strip() or\
- self.last_completion and self.text == self.last_completion+config.get('after_completion', ',')+" "):
- after = config.get('after_completion', ',')+" "
- #if " " in self.text.strip() and (not self.last_completion or ' ' in self.last_completion):
- else:
- after = " " # don't put the "," if it's not the begining of the sentence
(y, x) = self._win.getyx()
if not self.last_completion:
# begin is the begining of the nick we want to complete
- if self.text.strip() != '':
+ # if self.text.strip() != '' and\
+ # not self.text.endswith(after):
+ if self.text.strip():
begin = self.text.split()[-1].lower()
else:
begin = ''
+ # else:
+ # begin = ''
hit_list = [] # list of matching nicks
- for user in user_list:
- if user.lower().startswith(begin):
- hit_list.append(user)
+ for word in word_list:
+ if word.lower().startswith(begin):
+ hit_list.append(word)
if len(hit_list) == 0:
return
self.hit_list = hit_list
end = len(begin)
else:
- begin = self.text[-len(after)-len(self.last_completion):-len(after)]
+ if after:
+ begin = self.text[-len(after)-len(self.last_completion):-len(after)]
+ else:
+ begin = self.last_completion
self.hit_list.append(self.hit_list.pop(0)) # rotate list
end = len(begin) + len(after)
- self.text = self.text[:-end]
+ if end:
+ self.text = self.text[:-end]
nick = self.hit_list[0] # take the first hit
self.last_completion = nick
self.text += nick +after
self.key_end(False)
- def shell_completion(self, user_list, add_after):
+ def shell_completion(self, word_list, after):
"""
Shell-like completion
"""
- if " " in self.text.strip() or not add_after:
+ if " " in self.text.strip() or add_after is not None:
after = " " # don't put the "," if it's not the begining of the sentence
else:
after = config.get('after_completion', ',')+" "
@@ -872,7 +877,7 @@ class Input(Win):
else:
begin = ''
hit_list = [] # list of matching nicks
- for user in user_list:
+ for user in word_list:
if user.lower().startswith(begin):
hit_list.append(user)
if len(hit_list) == 0: