summaryrefslogtreecommitdiff
path: root/alot/completion/command.py
diff options
context:
space:
mode:
Diffstat (limited to 'alot/completion/command.py')
-rw-r--r--alot/completion/command.py221
1 files changed, 221 insertions, 0 deletions
diff --git a/alot/completion/command.py b/alot/completion/command.py
new file mode 100644
index 00000000..305e445f
--- /dev/null
+++ b/alot/completion/command.py
@@ -0,0 +1,221 @@
+# Copyright (C) 2011-2019 Patrick Totzke <patricktotzke@gmail.com>
+# This file is released under the GNU GPL, version 3 or a later revision.
+# For further details see the COPYING file
+
+import logging
+
+from alot import commands
+from alot.buffers import EnvelopeBuffer
+from alot.settings.const import settings
+from alot.utils.cached_property import cached_property
+from .completer import Completer
+from .commandname import CommandNameCompleter
+from .tag import TagCompleter
+from .query import QueryCompleter
+from .contacts import ContactsCompleter
+from .accounts import AccountCompleter
+from .path import PathCompleter
+from .stringlist import StringlistCompleter
+from .multipleselection import MultipleSelectionCompleter
+from .cryptokey import CryptoKeyCompleter
+from .argparse import ArgparseOptionCompleter
+
+
+class CommandCompleter(Completer):
+ """completes one command consisting of command name and parameters"""
+
+ def __init__(self, dbman, mode, currentbuffer=None):
+ """
+ :param dbman: used to look up available 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.dbman = dbman
+ self.mode = mode
+ self.currentbuffer = currentbuffer
+ self._commandnamecompleter = CommandNameCompleter(mode)
+
+ @cached_property
+ def _querycompleter(self):
+ return QueryCompleter(self.dbman)
+
+ @cached_property
+ def _tagcompleter(self):
+ return TagCompleter(self.dbman)
+
+ @cached_property
+ def _contactscompleter(self):
+ abooks = settings.get_addressbooks()
+ return ContactsCompleter(abooks)
+
+ @cached_property
+ def _pathcompleter(self):
+ return PathCompleter()
+
+ @cached_property
+ def _accountscompleter(self):
+ return AccountCompleter()
+
+ @cached_property
+ def _secretkeyscompleter(self):
+ return CryptoKeyCompleter(private=True)
+
+ @cached_property
+ def _publickeyscompleter(self):
+ return CryptoKeyCompleter(private=False)
+
+ 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._commandnamecompleter.complete(line, pos):
+ newtext = ('%s %s' % (cmd, ' '.join(words[1:])))
+ res.append((newtext, cpos + 1))
+ else:
+ cmd, params = words
+ localpos = pos - (len(cmd) + 1)
+ parser = commands.lookup_parser(cmd, self.mode)
+ if parser is not None:
+ # set 'res' - the result set of matching completionstrings
+ # depending on the current mode and command
+
+ # detect if we are completing optional parameter
+ arguments_until_now = params[:localpos].split(' ')
+ all_optionals = True
+ logging.debug(str(arguments_until_now))
+ for a in arguments_until_now:
+ logging.debug(a)
+ if a and not a.startswith('-'):
+ all_optionals = False
+ # complete optional parameter if
+ # 1. all arguments prior to current position are optional
+ # 2. the parameter starts with '-' or we are at its beginning
+ if all_optionals:
+ myarg = arguments_until_now[-1]
+ start_myarg = params.rindex(myarg)
+ beforeme = params[:start_myarg]
+ # set up local stringlist completer
+ # and let it complete for given list of options
+ localcompleter = ArgparseOptionCompleter(parser)
+ localres = localcompleter.complete(myarg, len(myarg))
+ res = [(
+ beforeme + c, p + start_myarg) for (c, p) in localres]
+
+ # global
+ elif cmd == 'search':
+ res = self._querycompleter.complete(params, localpos)
+ elif cmd == 'help':
+ res = self._commandnamecompleter.complete(params, localpos)
+ elif cmd in ['compose']:
+ res = self._contactscompleter.complete(params, localpos)
+ # search
+ elif self.mode == 'search' and cmd == 'refine':
+ res = self._querycompleter.complete(params, localpos)
+ elif self.mode == 'search' and cmd in ['tag', 'retag', 'untag',
+ 'toggletags']:
+ localcomp = MultipleSelectionCompleter(self._tagcompleter,
+ separator=',')
+ res = localcomp.complete(params, localpos)
+ elif self.mode == 'search' and cmd == 'toggletag':
+ localcomp = MultipleSelectionCompleter(self._tagcompleter,
+ separator=' ')
+ res = localcomp.complete(params, localpos)
+ # envelope
+ elif self.mode == 'envelope' and cmd == 'set':
+ plist = params.split(' ', 1)
+ if len(plist) == 1: # complete from header keys
+ localprefix = params
+ headers = ['Subject', 'To', 'Cc', 'Bcc', 'In-Reply-To',
+ 'From']
+ localcompleter = StringlistCompleter(headers)
+ localres = localcompleter.complete(
+ localprefix, localpos)
+ res = [(c, p + 6) for (c, p) in localres]
+ else: # must have 2 elements
+ header, params = plist
+ localpos = localpos - (len(header) + 1)
+ if header.lower() in ['to', 'cc', 'bcc']:
+ res = self._contactscompleter.complete(params,
+ localpos)
+ elif header.lower() == 'from':
+ res = self._accountscompleter.complete(params,
+ localpos)
+
+ # prepend 'set ' + header and correct position
+ def f(completed, pos):
+ return ('%s %s' % (header, completed),
+ pos + len(header) + 1)
+ res = [f(c, p) for c, p in res]
+ logging.debug(res)
+
+ elif self.mode == 'envelope' and cmd == 'unset':
+ plist = params.split(' ', 1)
+ if len(plist) == 1: # complete from header keys
+ localprefix = params
+ buf = self.currentbuffer
+ if buf:
+ if isinstance(buf, EnvelopeBuffer):
+ available = buf.envelope.headers.keys()
+ localcompleter = StringlistCompleter(available)
+ localres = localcompleter.complete(localprefix,
+ localpos)
+ res = [(c, p + 6) for (c, p) in localres]
+
+ elif self.mode == 'envelope' and cmd == 'attach':
+ res = self._pathcompleter.complete(params, localpos)
+ elif self.mode == 'envelope' and cmd in ['sign', 'togglesign']:
+ res = self._secretkeyscompleter.complete(params, localpos)
+ elif self.mode == 'envelope' and cmd in ['encrypt',
+ 'rmencrypt',
+ 'toggleencrypt']:
+ res = self._publickeyscompleter.complete(params, localpos)
+ elif self.mode == 'envelope' and cmd in ['tag', 'toggletags',
+ 'untag', 'retag']:
+ localcomp = MultipleSelectionCompleter(self._tagcompleter,
+ separator=',')
+ res = localcomp.complete(params, localpos)
+ # thread
+ elif self.mode == 'thread' and cmd == 'save':
+ res = self._pathcompleter.complete(params, localpos)
+ elif self.mode == 'thread' and cmd in ['fold', 'unfold',
+ 'togglesource',
+ 'toggleheaders']:
+ res = self._querycompleter.complete(params, localpos)
+ elif self.mode == 'thread' and cmd in ['tag', 'retag', 'untag',
+ 'toggletags']:
+ localcomp = MultipleSelectionCompleter(self._tagcompleter,
+ separator=',')
+ res = localcomp.complete(params, localpos)
+ elif cmd == 'move':
+ directions = ['up', 'down', 'page up', 'page down',
+ 'halfpage up', 'halfpage down', 'first',
+ 'last']
+ if self.mode == 'thread':
+ directions += ['parent', 'first reply', 'last reply',
+ 'next sibling', 'previous sibling',
+ 'next', 'previous', 'next unfolded',
+ 'previous unfolded']
+ localcompleter = StringlistCompleter(directions)
+ res = localcompleter.complete(params, localpos)
+
+ # prepend cmd and correct position
+ res = [('%s %s' % (cmd, t), p + len(cmd) + 1)
+ for (t, p) in res]
+
+ # re-insert whitespaces and correct position
+ wso = whitespaceoffset
+ res = [(' ' * wso + cmdstr, p + wso) for cmdstr, p in res]
+ return res