diff options
Diffstat (limited to 'alot/completion.py')
-rw-r--r-- | alot/completion.py | 170 |
1 files changed, 113 insertions, 57 deletions
diff --git a/alot/completion.py b/alot/completion.py index 716554c3..38d1eade 100644 --- a/alot/completion.py +++ b/alot/completion.py @@ -19,40 +19,71 @@ 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""" +class Completer(object): + 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): + def __init__(self, dbman, accountman): self.dbman = dbman - self._contactscompleter = ContactsCompleter() + abooks = accountman.get_addressbooks() + self._contactscompleter = ContactsCompleter(abooks, addressesonly=True) self._tagscompleter = TagsCompleter(dbman) 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[plen:] + ':' 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): @@ -61,26 +92,47 @@ 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 [] + if single_tag: + prefix = original[:pos] + matching = [t for t in tags if t.startswith(prefix)] + return [(t, len(t)) for t in matching] else: - matching = [t[len(prefix):] for t in tags if t.startswith(prefix)] - if last: - return matching - else: - return [t + ',' for t in matching] + 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): """completes contacts""" + def __init__(self, abooks, addressesonly=False): + self.abooks = abooks + self.addressesonly = addressesonly - def complete(self, prefix): - # TODO - return [] + def complete(self, original, pos): + if not self.abooks: + return [] + prefix = original[:pos] + res = [] + for abook in self.abooks: + res = res + abook.lookup(prefix) + if self.addressesonly: + returnlist = [(email, len(email)) for (name, email) in res] + else: + returnlist = [] + for name, email in res: + newtext = "%s <%s>" % (name, email) + returnlist.append((newtext, len(newtext))) + return returnlist class AccountCompleter(Completer): @@ -89,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[len(prefix):] 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): @@ -101,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[olen:] + '' 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): @@ -117,41 +172,42 @@ class CommandLineCompleter(Completer): self.accountman = accountman self.mode = mode self._commandcompleter = CommandCompleter(dbman, mode) - self._querycompleter = QueryCompleter(dbman) + self._querycompleter = QueryCompleter(dbman, accountman) self._tagscompleter = TagsCompleter(dbman) - self._contactscompleter = ContactsCompleter() + abooks = accountman.get_addressbooks() + self._contactscompleter = ContactsCompleter(abooks, addressesonly=True) 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[len(fileprefix):]) - 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 + '*')] |