diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2012-09-04 20:01:12 +0100 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2012-09-04 20:01:12 +0100 |
commit | 3d66ca079370e9d948b3c4554de313db464c0e69 (patch) | |
tree | 9afd423b7d38abdd4e4268384f3da2b9f6abb96d /alot | |
parent | 9dfd5c7f3ff8b8929f81f212b677195006c47a1f (diff) | |
parent | e63d5c9f1275901f93e2ce7ad3bdb09a7703fcc0 (diff) |
Merge branch '0.3.2-feature-metacommands-78'
Diffstat (limited to 'alot')
-rw-r--r-- | alot/commands/globals.py | 23 | ||||
-rw-r--r-- | alot/completion.py | 69 | ||||
-rw-r--r-- | alot/helper.py | 17 | ||||
-rw-r--r-- | alot/ui.py | 24 |
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 @@ -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 |