summaryrefslogtreecommitdiff
path: root/alot/completion.py
diff options
context:
space:
mode:
Diffstat (limited to 'alot/completion.py')
-rw-r--r--alot/completion.py170
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 + '*')]