summaryrefslogtreecommitdiff
path: root/alot
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2012-09-04 20:01:12 +0100
committerPatrick Totzke <patricktotzke@gmail.com>2012-09-04 20:01:12 +0100
commit3d66ca079370e9d948b3c4554de313db464c0e69 (patch)
tree9afd423b7d38abdd4e4268384f3da2b9f6abb96d /alot
parent9dfd5c7f3ff8b8929f81f212b677195006c47a1f (diff)
parente63d5c9f1275901f93e2ce7ad3bdb09a7703fcc0 (diff)
Merge branch '0.3.2-feature-metacommands-78'
Diffstat (limited to 'alot')
-rw-r--r--alot/commands/globals.py23
-rw-r--r--alot/completion.py69
-rw-r--r--alot/helper.py17
-rw-r--r--alot/ui.py24
4 files changed, 116 insertions, 17 deletions
diff --git a/alot/commands/globals.py b/alot/commands/globals.py
index 587cfa48..39262fe8 100644
--- a/alot/commands/globals.py
+++ b/alot/commands/globals.py
@@ -114,12 +114,7 @@ class PromptCommand(Command):
if cmdline:
# save into prompt history
ui.commandprompthistory.append(cmdline)
-
- try:
- cmd = commandfactory(cmdline, mode)
- ui.apply_command(cmd)
- except CommandParseError, e:
- ui.notify(e.message, priority='error')
+ ui.apply_commandline(cmdline)
@registerCommand(MODE, 'refresh')
@@ -749,3 +744,19 @@ class ComposeCommand(Command):
spawn=self.force_spawn,
refocus=False)
ui.apply_command(cmd)
+
+
+class CommandSequenceCommand(Command):
+ """Meta-Command that just applies a sequence of given Commands in order"""
+
+ def __init__(self, commandlist=[], **kwargs):
+ Command.__init__(self, **kwargs)
+ self.commandlist = commandlist
+
+ @inlineCallbacks
+ def apply(self, ui):
+ for cmdstring in self.commandlist:
+ logging.debug('CMDSEQ: apply %s' % str(cmdstring))
+ # translate cmdstring into :class:`Command`
+ cmd = commandfactory(cmdstring, ui.mode)
+ yield ui.apply_command(cmd)
diff --git a/alot/completion.py b/alot/completion.py
index d916b6b0..29fd368c 100644
--- a/alot/completion.py
+++ b/alot/completion.py
@@ -11,6 +11,7 @@ import alot.commands as commands
from alot.buffers import EnvelopeBuffer
from alot.settings import settings
from alot.utils.booleanaction import BooleanAction
+from alot.helper import split_commandline
class Completer(object):
@@ -255,8 +256,8 @@ class AccountCompleter(StringlistCompleter):
StringlistCompleter.__init__(self, resultlist)
-class CommandCompleter(Completer):
- """completes commands"""
+class CommandNameCompleter(Completer):
+ """completes command names"""
def __init__(self, mode):
"""
@@ -275,8 +276,8 @@ class CommandCompleter(Completer):
return [(t, len(t)) for t in matching]
-class CommandLineCompleter(Completer):
- """completion for commandline"""
+class CommandCompleter(Completer):
+ """completes one command consisting of command name and parameters"""
def __init__(self, dbman, mode, currentbuffer=None):
"""
@@ -292,7 +293,7 @@ class CommandLineCompleter(Completer):
self.dbman = dbman
self.mode = mode
self.currentbuffer = currentbuffer
- self._commandcompleter = CommandCompleter(mode)
+ self._commandnamecompleter = CommandNameCompleter(mode)
self._querycompleter = QueryCompleter(dbman)
self._tagcompleter = TagCompleter(dbman)
abooks = settings.get_addressbooks()
@@ -300,11 +301,18 @@ class CommandLineCompleter(Completer):
self._pathcompleter = PathCompleter()
def complete(self, line, pos):
+ # remember how many preceding space characters we see until the command
+ # string starts. We'll continue to complete from there on and will add
+ # these whitespaces again at the very end
+ whitespaceoffset = len(line) - len(line.lstrip())
+ line = line[whitespaceoffset:]
+ pos = pos - whitespaceoffset
+
words = line.split(' ', 1)
res = []
if pos <= len(words[0]): # we complete commands
- for cmd, cpos in self._commandcompleter.complete(line, pos):
+ for cmd, cpos in self._commandnamecompleter.complete(line, pos):
newtext = ('%s %s' % (cmd, ' '.join(words[1:])))
res.append((newtext, cpos + 1))
else:
@@ -339,7 +347,7 @@ class CommandLineCompleter(Completer):
elif cmd == 'search':
res = self._querycompleter.complete(params, localpos)
elif cmd == 'help':
- res = self._commandcompleter.complete(params, localpos)
+ res = self._commandnamecompleter.complete(params, localpos)
elif cmd in ['compose']:
res = self._contactscompleter.complete(params, localpos)
# search
@@ -401,6 +409,53 @@ class CommandLineCompleter(Completer):
# prepend cmd and correct position
res = [('%s %s' % (cmd, t), p + len(cmd) + 1) for (t, p) in res]
+ res = [(' ' * whitespaceoffset + cmd, p + whitespaceoffset) for cmd, p in res]
+ return res
+
+
+class CommandLineCompleter(Completer):
+ """completes command lines: semicolon separated command strings"""
+
+ def __init__(self, dbman, mode, currentbuffer=None):
+ """
+ :param dbman: used to look up avaliable tagstrings
+ :type dbman: :class:`~alot.db.DBManager`
+ :param mode: mode identifier
+ :type mode: str
+ :param currentbuffer: currently active buffer. If defined, this will be
+ used to dynamically extract possible completion
+ strings
+ :type currentbuffer: :class:`~alot.buffers.Buffer`
+ """
+ self._commandcompleter = CommandCompleter(dbman, mode, currentbuffer)
+
+ def get_context(self, line, pos):
+ """
+ computes start and end position of substring of line that is the
+ command string under given position
+ """
+ commands = split_commandline(line) + ['']
+ i = 0
+ start = 0
+ end = len(commands[i])
+ while pos > end:
+ i += 1
+ start = end + 1
+ end += 1 + len(commands[i])
+ return start, end
+
+ def complete(self, line, pos):
+ cstart, cend = self.get_context(line, pos)
+ before = line[:cstart]
+ after = line[cend:]
+ cmdstring = line[cstart:cend]
+ cpos = pos - cstart
+
+ res = []
+ for ccmd, ccpos in self._commandcompleter.complete(cmdstring, cpos):
+ newtext = before + ccmd + after
+ newpos = pos + (ccpos - cpos)
+ res.append((newtext, newpos))
return res
diff --git a/alot/helper.py b/alot/helper.py
index 3e5e9dce..a8327145 100644
--- a/alot/helper.py
+++ b/alot/helper.py
@@ -24,6 +24,23 @@ import StringIO
import logging
+def split_commandline(s, comments=False, posix=True):
+ """
+ splits semi-colon separated commandlines
+ """
+ # shlex seems to remove unescaped quotes
+ s = s.replace('\'','\\\'')
+ # encode s to utf-8 for shlex
+ if isinstance(s, unicode):
+ s = s.encode('utf-8')
+ lex = shlex.shlex(s, posix=posix)
+ lex.whitespace_split = True
+ lex.whitespace = ';'
+ if not comments:
+ lex.commenters = ''
+ return list(lex)
+
+
def split_commandstring(cmdstring):
"""
split command string into a list of strings to pass on to subprocess.Popen
diff --git a/alot/ui.py b/alot/ui.py
index cb9f16d2..5268752b 100644
--- a/alot/ui.py
+++ b/alot/ui.py
@@ -9,7 +9,9 @@ from settings import settings
from buffers import BufferlistBuffer
from commands import commandfactory
from alot.commands import CommandParseError
+from alot.commands.globals import CommandSequenceCommand
from alot.helper import string_decode
+from alot.helper import split_commandline
from alot.widgets.globals import CompleteEdit
from alot.widgets.globals import ChoiceWidget
@@ -118,19 +120,17 @@ class UI(object):
keyseq = ' '.join(self.input_queue)
cmdline = settings.get_keybinding(self.mode, keyseq)
if cmdline:
+ clear()
logging.debug("cmdline: '%s'" % cmdline)
# move keys are always passed
if cmdline.startswith('move '):
movecmd = cmdline[5:].rstrip()
logging.debug("GOT MOVE: '%s'" % movecmd)
if movecmd in ['up', 'down', 'page up', 'page down']:
- clear()
return [movecmd]
elif not self._locked:
try:
- clear()
- cmd = commandfactory(cmdline, self.mode)
- self.apply_command(cmd)
+ self.apply_commandline(cmdline)
except CommandParseError, e:
self.notify(e.message, priority='error')
@@ -141,6 +141,21 @@ class UI(object):
# update statusbar
self.update()
+ def apply_commandline(self, cmdline):
+ # split commandline if necessary
+ cmd = None
+ cmdlist = split_commandline(cmdline)
+ if len(cmdlist) == 1:
+ try:
+ # translate cmdstring into :class:`Command`
+ cmd = commandfactory(cmdlist[0], self.mode)
+ except CommandParseError, e:
+ self.notify(e.message, priority='error')
+ return
+ else:
+ cmd = CommandSequenceCommand(cmdlist)
+ self.apply_command(cmd)
+
def _unhandeled_input(self, key):
"""
Called by :class:`urwid.MainLoop` if a keypress was passed to the root
@@ -523,3 +538,4 @@ class UI(object):
d = defer.maybeDeferred(cmd.apply, self)
d.addErrback(errorHandler)
d.addCallback(call_posthook)
+ return d