summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/contact.py6
-rw-r--r--src/core.py6
-rw-r--r--src/data_forms.py4
-rw-r--r--src/multiuserchat.py31
-rw-r--r--src/poezio.py2
-rw-r--r--src/roster.py1
-rw-r--r--src/tabs.py221
-rw-r--r--src/theming.py2
-rw-r--r--src/windows.py57
-rw-r--r--src/xhtml.py21
10 files changed, 297 insertions, 54 deletions
diff --git a/src/contact.py b/src/contact.py
index 9d2885be..99c24a32 100644
--- a/src/contact.py
+++ b/src/contact.py
@@ -66,6 +66,12 @@ class Contact(object):
self._ask = None
self._groups = [] # a list of groups the contact is in
+ def get_groups(self):
+ """
+ Return the groups the contact is in
+ """
+ return self._groups
+
def get_bare_jid(self):
"""
Just get the bare_jid or the contact
diff --git a/src/core.py b/src/core.py
index b39bf904..1fb06b38 100644
--- a/src/core.py
+++ b/src/core.py
@@ -350,7 +350,7 @@ class Core(object):
"""
When a data form is received
"""
- self.information('%s' % messsage)
+ self.information('%s' % message)
def on_chatstate_active(self, message):
self.on_chatstate(message, "active")
@@ -1105,7 +1105,7 @@ class Core(object):
nick_from = message['mucnick']
room_from = message.getMucroom()
if message['type'] == 'error': # Check if it's an error
- return self.room_error(message, from_room)
+ return self.room_error(message, room_from)
room = self.get_room_by_name(room_from)
tab = self.get_tab_by_name(room_from, tabs.MucTab)
if tab and tab.get_room() and tab.get_room().get_user_by_name(nick_from) and\
@@ -1360,7 +1360,7 @@ class Core(object):
"""
/join [room][/nick] [password]
"""
- args = arg.split()
+ args = common.shell_split(arg)
password = None
if len(args) == 0:
t = self.current_tab()
diff --git a/src/data_forms.py b/src/data_forms.py
index 99d08caa..8445d3d2 100644
--- a/src/data_forms.py
+++ b/src/data_forms.py
@@ -428,7 +428,7 @@ class FormWin(object):
self._win = curses.newwin(height, width, y, x)
self.current_input = 0
self.inputs = [] # dict list
- for (name, field) in self._form.getFields():
+ for (name, field) in self._form.getFields().items():
if field['type'] == 'hidden':
continue
try:
@@ -508,7 +508,7 @@ class FormWin(object):
self._win.erase()
y = 0
i = 0
- for name, field in self._form.getFields():
+ for name, field in self._form.getFields().items():
if field['type'] == 'hidden':
continue
self.inputs[i]['label'].resize(1, self.width//3, y + 1, 0)
diff --git a/src/multiuserchat.py b/src/multiuserchat.py
index 10ea0daf..264f0e4a 100644
--- a/src/multiuserchat.py
+++ b/src/multiuserchat.py
@@ -67,13 +67,14 @@ def leave_groupchat(xmpp, jid, own_nick, msg):
"""
xmpp.plugin['xep_0045'].leaveMUC(jid, own_nick, msg)
-def eject_user(xmpp, jid, nick, reason):
+def set_user_role(xmpp, jid, nick, reason, role):
"""
- (try to) Eject an user from the room
+ (try to) Set the role of a MUC user
+ (role = 'none': eject user)
"""
iq = xmpp.makeIqSet()
query = ET.Element('{%s}query' % NS_MUC_ADMIN)
- item = ET.Element('{%s}item' % NS_MUC_ADMIN, {'nick':nick, 'role':'none'})
+ item = ET.Element('{%s}item' % NS_MUC_ADMIN, {'nick':nick, 'role':role})
if reason:
reason_el = ET.Element('{%s}reason' % NS_MUC_ADMIN)
reason_el.text = reason
@@ -81,4 +82,26 @@ def eject_user(xmpp, jid, nick, reason):
query.append(item)
iq.append(query)
iq['to'] = jid
- return iq.send()
+ try:
+ return iq.send()
+ except Exception as e:
+ return e.iq
+
+def set_user_affiliation(xmpp, jid, nick, reason, affiliation):
+ """
+ (try to) Set the affiliation of a MUC user
+ """
+ iq = xmpp.makeIqSet()
+ query = ET.Element('{%s}query' % NS_MUC_ADMIN)
+ item = ET.Element('{%s}item' % NS_MUC_ADMIN, {'nick':nick, 'affiliation':affiliation})
+ if reason:
+ reason_el = ET.Element('{%s}reason' % NS_MUC_ADMIN)
+ reason_el.text = reason
+ item.append(reason_el)
+ query.append(item)
+ iq.append(query)
+ iq['to'] = jid
+ try:
+ return iq.send()
+ except Exception as e:
+ return e.iq
diff --git a/src/poezio.py b/src/poezio.py
index dc877626..7b55ce96 100644
--- a/src/poezio.py
+++ b/src/poezio.py
@@ -27,6 +27,8 @@ def main():
signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore ctrl-c
if options.debug:
logging.basicConfig(filename=options.debug, level=logging.DEBUG)
+ else:
+ logging.basicConfig(level=logging.CRITICAL)
cocore = singleton.Singleton(core.Core)
cocore.start()
if not cocore.xmpp.start(): # Connect to remote server
diff --git a/src/roster.py b/src/roster.py
index fe68584b..4ef2da1b 100644
--- a/src/roster.py
+++ b/src/roster.py
@@ -90,6 +90,7 @@ class Roster(object):
for group in contact._groups:
if group not in groups:
# the contact is not in the group anymore
+ contact._groups.remove(group)
self.remove_contact_from_group(group, contact)
def remove_contact_from_group(self, group_name, contact):
diff --git a/src/tabs.py b/src/tabs.py
index 8606c450..a160a0a1 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -126,6 +126,12 @@ class Tab(object):
words = ['/%s'% (name) for name in self.core.commands] +\
['/%s' % (name) for name in self.commands]
the_input.auto_completion(words, '')
+ # Do not try to cycle command completion if there was only
+ # one possibily. The next tab will complete the argument.
+ # Otherwise we would need to add a useless space before being
+ # able to complete the arguments.
+ if len(the_input.hit_list) == 1:
+ the_input.do_command(' ')
return True
return False
@@ -283,7 +289,7 @@ class ChatTab(Tab):
for msg in self._room.messages[:-40:-1]:
if not msg:
continue
- txt = msg.txt
+ txt = xhtml.clean_text(msg.txt)
for char in char_we_dont_want:
txt = txt.replace(char, ' ')
for word in txt.split():
@@ -294,7 +300,7 @@ class ChatTab(Tab):
def on_enter(self):
txt = self.input.key_enter()
if txt:
- clean_text = xhtml.clean_text(txt)
+ clean_text = xhtml.clean_text_simple(txt)
if not self.execute_command(clean_text):
if txt.startswith('//'):
txt = txt[1:]
@@ -402,6 +408,8 @@ class MucTab(ChatTab):
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."), self.completion_unignore)
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['role'] = (self.command_role, _("Usage: /role <nick> <role> [reason]\nRole: Set the role of an user. Roles can be: none, visitor, participant, moderator. You also can give an optional reason."), None)
+ self.commands['affiliation'] = (self.command_affiliation, _("Usage: /affiliation <nick> <affiliation> [reason]\nAffiliation: Set the affiliation of an user. Affiliations can be: outcast, none, member, admin, owner. You also can give an optional reason."), None)
self.commands['topic'] = (self.command_topic, _("Usage: /topic <subject>\nTopic: Change the subject of the room"), self.completion_topic)
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]\nPart: disconnect from a room. You can specify an optional message."), None)
@@ -412,6 +420,8 @@ class MucTab(ChatTab):
self.commands['configure'] = (self.command_configure, _('Usage: /configure\nConfigure: Configure the current room, through a form.'), None)
self.commands['version'] = (self.command_version, _('Usage: /version <jid or nick>\nVersion: get the software version of the given JID or nick in room (usually its XMPP client and Operating System)'), None)
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.commands['clear'] = (self.command_clear,
+ _("""Usage: /clear\nClear: clears the current buffer'"""), None)
self.resize()
def scroll_user_list_up(self):
@@ -459,12 +469,21 @@ class MucTab(ChatTab):
self.core.xmpp.plugin['xep_0045'].configureRoom(self.get_name(), form)
self.core.close_tab()
+ def command_clear(self, args):
+ """
+ /clear
+ """
+ self._room.messages = []
+ self.text_win.rebuild_everything(self._room)
+ self.refresh()
+ self.core.doupdate()
+
def command_cycle(self, arg):
if self.get_room().joined:
muc.leave_groupchat(self.core.xmpp, self.get_name(), self.get_room().own_nick, arg)
self.get_room().disconnect()
self.core.disable_private_tabs(self.get_room().name)
- self.core.command_join('/%s' % self.core.get_bookmark_nickname(self.get_room().name), '0')
+ self.core.command_join('"/%s"' % self.core.get_bookmark_nickname(self.get_room().name), '0')
self.user_win.pos = 0
def command_recolor(self, arg):
@@ -604,24 +623,60 @@ class MucTab(ChatTab):
def completion_topic(self, the_input):
current_topic = self.get_room().topic
- return the_input.auto_completion([current_topic], ' ')
+ return the_input.auto_completion([current_topic], '')
def command_kick(self, arg):
"""
/kick <nick> [reason]
"""
args = common.shell_split(arg)
- if len(args) < 1:
+ if not len(args):
self.core.command_help('kick')
+ self._command_change_role('kick '+arg)
+
+ def command_role(self, arg):
+ """
+ /role <nick> <role> [reason]
+ Changes the role of an user
+ roles can be: none, visitor, participant, moderator
+ """
+ args = common.shell_split(arg)
+ if len(args) < 2:
+ self.core.command_help('role')
return
- nick = args[0]
- if len(args) >= 2:
- reason = ' '.join(args[1:])
+ nick, role = args[0],args[1]
+ if len(args) > 2:
+ reason = ' '.join(args[2:])
else:
reason = ''
- if not self.get_room().joined:
+ if not self.get_room().joined or \
+ not role in ('none', 'visitor', 'participant', 'moderator'):
return
- res = muc.eject_user(self.core.xmpp, self.get_name(), nick, reason)
+ res = muc.set_user_role(self.core.xmpp, self.get_name(), nick, reason, role)
+ if res['type'] == 'error':
+ self.core.room_error(res, self.get_name())
+
+ def command_affiliation(self, arg):
+ """
+ /affiliation <nick> <role> [reason]
+ Changes the affiliation of an user
+ roles can be: none, visitor, participant, moderator
+ """
+ args = common.shell_split(arg)
+ if len(args) < 2:
+ self.core.command_help('role')
+ return
+ nick, affiliation = args[0],args[1]
+ if len(args) > 2:
+ reason = ' '.join(args[2:])
+ else:
+ reason = ''
+ if not self.get_room().joined or \
+ not affiliation in ('none', 'member', 'admin', 'owner'):
+# replace this ↑ with this ↓ when the ban list support is done
+# not affiliation in ('outcast', 'none', 'member', 'admin', 'owner'):
+ return
+ res = muc.set_user_affiliation(self.core.xmpp, self.get_name(), nick, reason, affiliation)
if res['type'] == 'error':
self.core.room_error(res, self.get_name())
@@ -632,7 +687,7 @@ class MucTab(ChatTab):
if line.find('\x19') == -1:
msg['body'] = line
else:
- msg['body'] = xhtml.clean_text(line)
+ msg['body'] = xhtml.clean_text_simple(line)
msg['xhtml_im'] = xhtml.poezio_colors_to_html(line)
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates is not False:
msg['chat_state'] = needed
@@ -729,12 +784,12 @@ class MucTab(ChatTab):
word_list = [user.nick for user in sorted(self._room.users, key=compare_users, reverse=True)\
if user.nick != self._room.own_nick]
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):
+ input_pos = self.input.pos + self.input.line_pos
+ if ' ' not in self.input.get_text()[:input_pos] or (self.input.last_completion and\
+ self.input.get_text()[:input_pos] == self.input.last_completion + after):
add_after = after
else:
add_after = ' '
-
self.input.auto_completion(word_list, add_after)
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)
@@ -1024,7 +1079,7 @@ class PrivateTab(ChatTab):
if line.find('\x19') == -1:
msg['body'] = line
else:
- msg['body'] = xhtml.clean_text(line)
+ msg['body'] = xhtml.clean_text_simple(line)
msg['xhtml_im'] = xhtml.poezio_colors_to_html(line)
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates is not False:
needed = 'inactive' if self.core.status.show in ('xa', 'away') else 'active'
@@ -1215,6 +1270,9 @@ class RosterInfoTab(Tab):
self.commands['deny'] = (self.command_deny, _("Usage: /deny [jid]\nDeny: Use this command to remove and deny your presence to the provided JID (or the selected contact in your roster), who is asking you to be in his/here roster"), self.completion_deny)
self.commands['accept'] = (self.command_accept, _("Usage: /accept [jid]\nAccept: Use this command to authorize the provided JID (or the selected contact in your roster), to see your presence, and to ask to subscribe to it (mutual presence subscription)."), self.completion_deny)
self.commands['add'] = (self.command_add, _("Usage: /add <jid>\nAdd: Use this command to add the specified JID to your roster. The reverse authorization will automatically be accepted if the remote JID accepts your subscription, leading to a mutual presence subscription."), None)
+ self.commands['name'] = (self.command_name, _("Usage: /name <jid> <name>\nSet the given JID's name"), self.completion_name)
+ self.commands['groupadd'] = (self.command_groupadd, _("Usage: /groupadd <jid> <group>\nAdd the given JID to the given group"), self.completion_groupadd)
+ self.commands['groupremove'] = (self.command_groupremove, _("Usage: /groupremove <jid> <group>\nRemove the given JID from the given group"), self.completion_groupremove)
self.commands['remove'] = (self.command_remove, _("Usage: /remove [jid]\nRemove: Use this command to remove the specified JID from your roster. This wil unsubscribe you from its presence, cancel its subscription to yours, and remove the item from your roster"), self.completion_remove)
self.commands['export'] = (self.command_export, _("Usage: /export [/path/to/file]\nExport: Use this command to export your contacts into /path/to/file if specified, or $HOME/poezio_contacts if not."), None)
self.commands['import'] = (self.command_import, _("Usage: /import [/path/to/file]\nImport: Use this command to import your contacts from /path/to/file if specified, or $HOME/poezio_contacts if not."), None)
@@ -1267,6 +1325,87 @@ class RosterInfoTab(Tab):
return
self.core.xmpp.sendPresence(pto=jid, ptype='subscribe')
+ def command_name(self, args):
+ """
+ Set a name for the specified JID in your roster
+ """
+ args = args.split(None, 1)
+ if len(args) < 1:
+ return
+ jid = JID(args[0]).bare
+ name = args[1] if len(args) == 2 else ''
+
+ contact = roster.get_contact_by_jid(jid)
+ if not contact:
+ self.core.information(_('No such JID in roster'), 'Error')
+ return
+
+ groups = set(contact.get_groups())
+ subscription = contact.get_subscription()
+ if self.core.xmpp.update_roster(jid, name=name, groups=groups, subscription=subscription):
+ contact.set_name(name)
+
+ def command_groupadd(self, args):
+ """
+ Add the specified JID to the specified group
+ """
+ args = args.split(None, 1)
+ if len(args) != 2:
+ return
+ jid = JID(args[0]).bare
+ group = args[1]
+
+ contact = roster.get_contact_by_jid(jid)
+ if not contact:
+ self.core.information(_('No such JID in roster'), 'Error')
+ return
+
+ new_groups = set(contact.get_groups())
+ if group in new_groups:
+ self.core.information(_('JID already in group'), 'Error')
+ return
+
+ new_groups.add(group)
+ try:
+ new_groups.remove('none')
+ except KeyError:
+ pass
+
+ name = contact.get_name()
+ subscription = contact.get_subscription()
+ if self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription):
+ roster.edit_groups_of_contact(contact, new_groups)
+
+ def command_groupremove(self, args):
+ """
+ Remove the specified JID to the specified group
+ """
+ args = args.split(None, 1)
+ if len(args) != 2:
+ return
+ jid = JID(args[0]).bare
+ group = args[1]
+
+ contact = roster.get_contact_by_jid(jid)
+ if not contact:
+ self.core.information(_('No such JID in roster'), 'Error')
+ return
+
+ new_groups = set(contact.get_groups())
+ try:
+ new_groups.remove('none')
+ except KeyError:
+ pass
+ if group not in new_groups:
+ self.core.information(_('JID not in group'), 'Error')
+ return
+
+ new_groups.remove(group)
+ name = contact.get_name()
+ subscription = contact.get_subscription()
+ if self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription):
+ roster.edit_groups_of_contact(contact, new_groups)
+
def command_remove(self, args):
"""
Remove the specified JID from the roster. i.e. : unsubscribe
@@ -1342,6 +1481,53 @@ class RosterInfoTab(Tab):
jids = [contact.get_bare_jid() for contact in roster.get_contacts()]
return the_input.auto_completion(jids, '')
+ def completion_name(self, the_input):
+ text = the_input.get_text()
+ n = len(text.split())
+ if text.endswith(' '):
+ n += 1
+
+ if n == 2:
+ jids = [contact.get_bare_jid() for contact in roster.get_contacts()]
+ return the_input.auto_completion(jids, '')
+ return False
+
+ def completion_groupadd(self, the_input):
+ text = the_input.get_text()
+ n = len(text.split())
+ if text.endswith(' '):
+ n += 1
+
+ if n == 2:
+ jids = [contact.get_bare_jid() for contact in roster.get_contacts()]
+ return the_input.auto_completion(jids, '')
+ elif n == 3:
+ groups = [group.name for group in roster.get_groups() if group.name != 'none']
+ return the_input.auto_completion(groups, '')
+ return False
+
+ def completion_groupremove(self, the_input):
+ text = the_input.get_text()
+ args = text.split()
+ n = len(args)
+ if text.endswith(' '):
+ n += 1
+
+ if n == 2:
+ jids = [contact.get_bare_jid() for contact in roster.get_contacts()]
+ return the_input.auto_completion(jids, '')
+ elif n == 3:
+ contact = roster.get_contact_by_jid(args[1])
+ if not contact:
+ return False
+ groups = list(contact.get_groups())
+ try:
+ groups.remove('none')
+ except ValueError:
+ pass
+ return the_input.auto_completion(groups, '')
+ return False
+
def completion_deny(self, the_input):
"""
Complete the first argument from the list of the
@@ -1421,7 +1607,8 @@ class RosterInfoTab(Tab):
self.input.do_command("/") # we add the slash
def reset_help_message(self, _=None):
- curses.curs_set(0)
+ if self.core.current_tab() is self:
+ curses.curs_set(0)
self.input = self.default_help_message
self.input.refresh()
self.core.doupdate()
@@ -1563,7 +1750,7 @@ class ConversationTab(ChatTab):
if line.find('\x19') == -1:
msg['body'] = line
else:
- msg['body'] = xhtml.clean_text(line)
+ msg['body'] = xhtml.clean_text_simple(line)
msg['xhtml_im'] = xhtml.poezio_colors_to_html(line)
if config.get('send_chat_states', 'true') == 'true' and self.remote_wants_chatstates is not False:
needed = 'inactive' if self.core.status.show in ('xa', 'away') else 'active'
diff --git a/src/theming.py b/src/theming.py
index 382a3146..4bfdad42 100644
--- a/src/theming.py
+++ b/src/theming.py
@@ -118,7 +118,7 @@ class Theme(object):
# A list of colors randomly attributed to nicks in MUCs
# Setting more colors makes it harder to have two nicks with the same color,
# avoiding confusions.
- LIST_COLOR_NICKNAMES = [(1, -1), (2, -1), (3, -1), (4, -1), (5, -1), (6, -1), (7, -1), (8, -1), (9, -1), (10, -1), (11, -1), (12, -1), (13, -1), (14, -1), (23, -1), (23, -1), (88, -1), (99, -1), (100, -1), (154, -1), (213, -1), (216, -1), (227, -1)]
+ LIST_COLOR_NICKNAMES = [(1, -1), (2, -1), (3, -1), (4, -1), (5, -1), (6, -1), (8, -1), (9, -1), (10, -1), (11, -1), (12, -1), (13, -1), (14, -1), (23, -1), (23, -1), (88, -1), (99, -1), (100, -1), (154, -1), (213, -1), (216, -1), (227, -1)]
# This is your own nickname
COLOR_OWN_NICK = (254, -1)
diff --git a/src/windows.py b/src/windows.py
index 2352a82a..4f2c68c6 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -122,7 +122,6 @@ class Win(object):
self.move(y, x)
next_attr_char = text.find('\x19')
while next_attr_char != -1 and text:
- log.debug('Addstr_Colored: [%s]' % text.replace('\x19', '\\x19'))
if next_attr_char + 1 < len(text):
attr_char = text[next_attr_char+1].lower()
else:
@@ -883,26 +882,28 @@ class Input(Win):
self.rewrite_text()
return True
- def key_left(self, jump=True):
+ def key_left(self, jump=True, reset=True):
"""
Move the cursor one char to the left
"""
- self.reset_completion()
+ if reset:
+ self.reset_completion()
if self.pos == self.width-1 and self.line_pos > 0:
self.line_pos -= 1
elif self.pos >= 1:
self.pos -= 1
if jump and self.pos+self.line_pos >= 1 and self.text[self.pos+self.line_pos-1] == '\x19':
self.key_left()
- else:
+ elif reset:
self.rewrite_text()
return True
- def key_right(self, jump=True):
+ def key_right(self, jump=True, reset=True):
"""
Move the cursor one char to the right
"""
- self.reset_completion()
+ if reset:
+ self.reset_completion()
if self.pos == self.width-1:
if self.line_pos + self.width-1 < len(self.text):
self.line_pos += 1
@@ -910,7 +911,7 @@ class Input(Win):
self.pos += 1
if jump and self.pos+self.line_pos < len(self.text) and self.text[self.pos+self.line_pos-1] == '\x19':
self.key_right()
- else:
+ elif reset:
self.rewrite_text()
return True
@@ -936,8 +937,6 @@ class Input(Win):
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(word_list, add_after)
@@ -957,33 +956,42 @@ class Input(Win):
Normal completion
"""
(y, x) = self._win.getyx()
+ pos = self.pos + self.line_pos
+ if pos < len(self.text) and after.endswith(' ') and self.text[pos] == ' ':
+ after = after[:-1] # remove the last space if we are already on a space
if not self.last_completion:
- # begin is the begining of the word we want to complete
- if self.text.strip() and not self.text.endswith(' '):
- begin = self.text.split()[-1].lower()
+ space_before_cursor = self.text.rfind(' ', 0, pos)
+ if space_before_cursor != -1:
+ begin = self.text[space_before_cursor+1:pos]
else:
- begin = ''
+ begin = self.text[:pos]
hit_list = [] # list of matching nicks
for word in word_list:
- if word.lower().startswith(begin):
+ if word.lower().startswith(begin.lower()):
hit_list.append(word)
if len(hit_list) == 0:
return
self.hit_list = hit_list
end = len(begin)
else:
- 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
+ begin = self.last_completion
end = len(begin) + len(after)
- if end:
- self.text = self.text[:-end]
+ self.hit_list.append(self.hit_list.pop(0)) # rotate list
+
+ 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:]
+ for i in range(end):
+ try:
+ self.key_left(reset=False)
+ except:
+ pass
+ for i in range(len(nick + after)):
+ self.key_right(reset=False)
+
+ self.rewrite_text()
self.last_completion = nick
- self.text += nick +after
- self.key_end(False)
def shell_completion(self, word_list, after):
"""
@@ -1034,7 +1042,8 @@ class Input(Win):
return res
if not key or len(key) > 1:
return False # ignore non-handled keyboard shortcuts
- self.reset_completion()
+ if reset:
+ self.reset_completion()
self.text = self.text[:self.pos+self.line_pos]+key+self.text[self.pos+self.line_pos:]
(y, x) = self._win.getyx()
if x == self.width-1:
diff --git a/src/xhtml.py b/src/xhtml.py
index 9bb2705d..38239d18 100644
--- a/src/xhtml.py
+++ b/src/xhtml.py
@@ -176,6 +176,8 @@ log = logging.getLogger(__name__)
whitespace_re = re.compile(r'\s+')
+xhtml_attr_re = re.compile(r'\x19\d{0,3}\}|\x19[buaio]')
+
def get_body_from_message_stanza(message):
"""
Returns a string with xhtml markups converted to
@@ -249,7 +251,11 @@ def xhtml_to_poezio_colors(text):
log.debug(text)
xml = ET.fromstring(text)
message = ''
- for elem in xml.iter():
+ if version_info[1] == 2:
+ elems = xml.iter()
+ else:
+ elems = xml.getiterator()
+ for elem in elems:
if elem.tag == '{http://www.w3.org/1999/xhtml}a':
if 'href' in elem.attrib and elem.attrib['href'] != elem.text:
message += '\x19u%s\x19o (%s)' % (trim(elem.attrib['href']), trim(elem.text))
@@ -317,9 +323,18 @@ def xhtml_to_poezio_colors(text):
return message
-def clean_text(string):
+def clean_text(s):
+ """
+ Remove all xhtml-im attributes (\x19etc) from the string with the
+ complete color format, i.e \x19xxx}
+ """
+ s = re.sub(xhtml_attr_re, "", s)
+ return s
+
+def clean_text_simple(string):
"""
- Remove all \x19 from the string
+ Remove all \x19 from the string formatted with simple colors:
+ \x198
"""
pos = string.find('\x19')
while pos != -1: