diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2011-08-28 17:26:30 +0100 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2011-08-28 17:26:30 +0100 |
commit | b76b71a8e8110494681b84aea758f018b1dc08c3 (patch) | |
tree | 47083986802ebf9c5134b131d84f5d1a07f7b75d /alot | |
parent | a227b444c92610caebb05861da69e2222b39c6fc (diff) |
new completion semantics
this time it respects suffixes and can complete
recipients from your addressbook
Diffstat (limited to 'alot')
-rw-r--r-- | alot/account.py | 1 | ||||
-rw-r--r-- | alot/command.py | 11 | ||||
-rw-r--r-- | alot/completion.py | 147 | ||||
-rw-r--r-- | alot/widgets.py | 33 |
4 files changed, 115 insertions, 77 deletions
diff --git a/alot/account.py b/alot/account.py index d4bc0673..f0e68fc2 100644 --- a/alot/account.py +++ b/alot/account.py @@ -192,7 +192,6 @@ class AccountManager: for s in accountsections: options = filter(lambda x: x in self.allowed, config.options(s)) - args = {} if 'abook_command' in options: cmd = config.get(s, 'abook_command').encode('ascii', diff --git a/alot/command.py b/alot/command.py index 1c58e370..41604eb6 100644 --- a/alot/command.py +++ b/alot/command.py @@ -703,7 +703,6 @@ class PrintCommand(Command): ui.notify(ok_msg) - class SaveAttachmentCommand(Command): def __init__(self, all=False, path=None, **kwargs): Command.__init__(self, **kwargs) @@ -806,9 +805,9 @@ class EnvelopeEditCommand(Command): def openEnvelopeFromTmpfile(): # This parses the input from the tempfile. - # we do this ourselves here because we want to be able to - # just type utf-8 encoded stuff into the tempfile and let alot worry - # about encodings. + # we do this ourselves here because we want to be able to + # just type utf-8 encoded stuff into the tempfile and let alot + # worry about encodings. # get input f = open(tf.name) @@ -819,12 +818,12 @@ class EnvelopeEditCommand(Command): # go through multiline, utf-8 encoded headers key = value = None for line in headertext.splitlines(): - if re.match('\w+:', line): #new k/v pair + if re.match('\w+:', line): # new k/v pair if key and value: # save old one from stack del self.mail[key] # ensure unique values in mails self.mail[key] = encode_header(key, value) # save key, value = line.strip().split(':', 1) # parse new pair - elif key and value: # append new line without key prefix to value + elif key and value: # append new line without key prefix value += line if key and value: # save last one if present del self.mail[key] diff --git a/alot/completion.py b/alot/completion.py index 27e81bdc..3cc673aa 100644 --- a/alot/completion.py +++ b/alot/completion.py @@ -19,21 +19,37 @@ Copyright (C) 2011 Patrick Totzke <patricktotzke@gmail.com> import re import os +import glob import logging import command class Completer: - def complete(self, original): - """takes a string that's the prefix of a word, - returns a list of suffix-strings that complete the original""" + def complete(self, original, pos): + """returns a list of completions and cursor positions for the + string original from position pos on. + + :param original: the complete string to complete + :type original: str + :param pos: starting position to complete from + :returns: a list of tuples (ctext, cpos), where ctext is the completed + string and cpos the cursor position in the new string + """ return list() + def relevant_part(self, original, pos, sep=' '): + """calculates the subword in a sep-splitted list of original + that pos is in""" + start = original.rfind(sep, 0, pos) + 1 + end = original.find(sep, pos - 1) + if end == -1: + end = len(original) + return original[start:end], start, end, pos - start + class QueryCompleter(Completer): """completion for a notmuch query string""" - # TODO: boolean connectors and braces? def __init__(self, dbman, accountman): self.dbman = dbman self._contactscompleter = ContactsCompleter(accountman, @@ -42,19 +58,32 @@ class QueryCompleter(Completer): self.keywords = ['tag', 'from', 'to', 'subject', 'attachment', 'is', 'id', 'thread', 'folder'] - def complete(self, original): - prefix = original.split(' ')[-1] - m = re.search('(tag|is|to):(\w*)', prefix) + def complete(self, original, pos): + mypart, start, end, mypos = self.relevant_part(original, pos) + myprefix = mypart[:mypos] + m = re.search('(tag|is|to):(\w*)', myprefix) if m: cmd, params = m.groups() + cmdlen = len(cmd) + 1 # length of the keyword part incld colon if cmd == 'to': - return self._contactscompleter.complete(params) + localres = self._contactscompleter.complete(mypart[cmdlen:], + mypos - cmdlen) else: - return self._tagscompleter.complete(params, last=True) + localres = self._tagscompleter.complete(mypart[cmdlen:], + mypos - cmdlen) + resultlist = [] + for ltxt, lpos in localres: + newtext = original[:start] + cmd + ':' + ltxt + original[end:] + newpos = start + len(cmd) + 1 + lpos + resultlist.append((newtext, newpos)) + return resultlist else: - plen = len(prefix) - matched = filter(lambda t: t.startswith(prefix), self.keywords) - return [t + ':' for t in matched] + matched = filter(lambda t: t.startswith(myprefix), self.keywords) + resultlist = [] + for keyword in matched: + newprefix = original[:start] + keyword + ':' + resultlist.append((newprefix + original[end:], len(newprefix))) + return resultlist class TagsCompleter(Completer): @@ -63,18 +92,24 @@ class TagsCompleter(Completer): def __init__(self, dbman): self.dbman = dbman - def complete(self, original, last=False): - otags = original.split(',') - prefix = otags[-1] + def complete(self, original, pos, single_tag=True): tags = self.dbman.get_all_tags() - if len(otags) > 1 and last: - return [] - else: + if single_tag: + prefix = original[:pos] matching = [t for t in tags if t.startswith(prefix)] - if last: - return matching - else: - return [t + ',' for t in matching] + return [(t, len(t)) for t in matching] + else: + mypart, start, end, mypos = self.relevant_part(original, pos, + sep=',') + prefix = mypart[:mypos] + res = [] + for tag in tags: + if tag.startswith(prefix): + newprefix = original[:start] + tag + if not original[end:].startswith(','): + newprefix += ',' + res.append((newprefix + original[end:], len(newprefix))) + return res class ContactsCompleter(Completer): @@ -83,17 +118,20 @@ class ContactsCompleter(Completer): self.abooks = accountman.get_addressbooks() self.addressesonly = addressesonly - def complete(self, prefix): + def complete(self, original, pos): if not self.abooks: return [] + prefix = original[:pos] res = [] for abook in self.abooks: res = res + abook.lookup(prefix) - logging.debug(res) if self.addressesonly: - returnlist = [e for n,e in res] + returnlist = [(email, len(email)) for (name, email) in res] else: - returnlist = ["%s <%s>" % x for x in res] + returnlist = [] + for name, email in res: + newtext = "%s <%s>" % (name, email) + returnlist.append((newtext, len(newtext))) return returnlist @@ -103,9 +141,10 @@ class AccountCompleter(Completer): def __init__(self, accountman): self.accountman = accountman - def complete(self, prefix): + def complete(self, original, pos): valids = self.accountman.get_main_addresses() - return [a for a in valids if a.startswith(prefix)] + prefix = original[:pos] + return [(a, len(a)) for a in valids if a.startswith(prefix)] class CommandCompleter(Completer): @@ -115,12 +154,14 @@ class CommandCompleter(Completer): self.dbman = dbman self.mode = mode - def complete(self, original): + def complete(self, original, pos): #TODO refine <tab> should get current querystring + commandprefix = original[:pos] + logging.debug('original="%s" prefix="%s"' % (original, commandprefix)) cmdlist = command.COMMANDS['global'] cmdlist.update(command.COMMANDS[self.mode]) - olen = len(original) - return [t + '' for t in cmdlist if t.startswith(original)] + matching = [t for t in cmdlist if t.startswith(commandprefix)] + return [(t, len(t)) for t in matching] class CommandLineCompleter(Completer): @@ -136,36 +177,36 @@ class CommandLineCompleter(Completer): self._contactscompleter = ContactsCompleter(accountman) self._pathcompleter = PathCompleter() - def complete(self, prefix): - words = prefix.split(' ', 1) - if len(words) <= 1: # we complete commands - return self._commandcompleter.complete(prefix) + def complete(self, line, pos): + words = line.split(' ', 1) + + res = [] + if pos <= len(words[0]): # we complete commands + for cmd, cpos in self._commandcompleter.complete(line, pos): + newtext = ('%s %s' % (cmd, ' '.join(words[1:]))) + res.append((newtext, cpos + 1)) else: cmd, params = words + localpos = pos - (len(cmd) + 1) if cmd in ['search', 'refine']: - return self._querycompleter.complete(params) + res = self._querycompleter.complete(params, localpos) if cmd == 'retag': - return self._tagscompleter.complete(params) + res = self._tagscompleter.complete(params, localpos, + single_tag=False) if cmd == 'toggletag': - return self._tagscompleter.complete(params, last=True) + res = self._tagscompleter.complete(params, localpos) if cmd in ['to', 'compose']: - return self._contactscompleter.complete(params) + res = self._contactscompleter.complete(params, localpos) if cmd in ['attach', 'edit', 'save']: - return self._pathcompleter.complete(params) - else: - return [] + res = self._pathcompleter.complete(params, localpos) + res = [('%s %s' % (cmd, t), p + len(cmd) + 1) for (t, p) in res] + return res class PathCompleter(Completer): """completion for paths""" - def complete(self, prefix): - if not prefix: - return ['~/'] - dir = os.path.expanduser(os.path.dirname(prefix)) - fileprefix = os.path.basename(prefix) - res = [] - if os.path.isdir(dir): - for f in os.listdir(dir): - if f.startswith(fileprefix): - res.append(f) - return res + def complete(self, original, pos): + if not original: + return [('~/', 2)] + prefix = os.path.expanduser(original[:pos]) + return [(f, len(f)) for f in glob.glob(prefix + '*')] diff --git a/alot/widgets.py b/alot/widgets.py index 02cf6acb..4fe7439b 100644 --- a/alot/widgets.py +++ b/alot/widgets.py @@ -172,37 +172,36 @@ class CompleteEdit(urwid.Edit): if not isinstance(edit_text, unicode): edit_text = unicode(edit_text, errors='replace') self.start_completion_pos = len(edit_text) - self.completion_results = None + self.completions = None urwid.Edit.__init__(self, edit_text=edit_text, **kwargs) def keypress(self, size, key): cmd = command_map[key] + # if we tabcomplete if cmd in ['next selectable', 'prev selectable']: - pos = self.start_completion_pos - original = self.edit_text[:pos] - if not self.completion_results: # not in completion mode - self.completion_results = [''] + \ - self.completer.complete(original) + # if not already in completion mode + if not self.completions: + self.completions = [(self.edit_text, self.edit_pos)] + \ + self.completer.complete(self.edit_text, self.edit_pos) self.focus_in_clist = 1 - else: + else: # otherwise tab through results if cmd == 'next selectable': self.focus_in_clist += 1 else: self.focus_in_clist -= 1 - if len(self.completion_results) > 1: - completed = self.completion_results[self.focus_in_clist % - len(self.completion_results)] - self.set_edit_text(completed) - self.edit_pos += len(completed) + if len(self.completions) > 1: + ctext, cpos = self.completions[self.focus_in_clist % + len(self.completions)] + self.set_edit_text(ctext) + self.set_edit_pos(cpos) else: - self.set_edit_text(original + ' ') self.edit_pos += 1 - self.start_completion_pos = self.edit_pos - self.completion_results = None + if self.edit_pos >= len(self.edit_text): + self.edit_text += ' ' + self.completions = None else: result = urwid.Edit.keypress(self, size, key) - self.start_completion_pos = self.edit_pos - self.completion_results = None + self.completions = None return result |