summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2011-08-28 17:26:30 +0100
committerPatrick Totzke <patricktotzke@gmail.com>2011-08-28 17:26:30 +0100
commitb76b71a8e8110494681b84aea758f018b1dc08c3 (patch)
tree47083986802ebf9c5134b131d84f5d1a07f7b75d /alot
parenta227b444c92610caebb05861da69e2222b39c6fc (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.py1
-rw-r--r--alot/command.py11
-rw-r--r--alot/completion.py147
-rw-r--r--alot/widgets.py33
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