diff options
author | Patrick Totzke <patricktotzke@gmail.com> | 2013-07-07 18:06:49 +0100 |
---|---|---|
committer | Patrick Totzke <patricktotzke@gmail.com> | 2013-07-07 18:06:49 +0100 |
commit | 949c5b17b48ed8e45d315ba9c55d3ace1b511cb3 (patch) | |
tree | 6a767759cee801d43cc0d0c3e9b86f2d380982da | |
parent | b3126fd75a4e9ad0949f54afbf4240dd5e0297df (diff) | |
parent | 330e0542b6d1e32574b4c7561982f059a0b49de8 (diff) |
Merge branch '0.3.4-fix-624'
-rw-r--r-- | alot/addressbooks.py | 11 | ||||
-rw-r--r-- | alot/completion.py | 9 | ||||
-rw-r--r-- | alot/errors.py | 4 | ||||
-rw-r--r-- | alot/helper.py | 15 | ||||
-rw-r--r-- | alot/ui.py | 9 | ||||
-rw-r--r-- | alot/widgets/globals.py | 55 | ||||
-rw-r--r-- | docs/source/api/interface.rst | 4 |
7 files changed, 88 insertions, 19 deletions
diff --git a/alot/addressbooks.py b/alot/addressbooks.py index 59f093de..890eb0e3 100644 --- a/alot/addressbooks.py +++ b/alot/addressbooks.py @@ -9,6 +9,10 @@ from helper import call_cmd from alot.helper import split_commandstring +class AddressbookError(Exception): + pass + + class AddressBook(object): """can look up email addresses and realnames for contacts. @@ -89,6 +93,13 @@ class MatchSdtoutAddressbook(AddressBook): def lookup(self, prefix): cmdlist = split_commandstring(self.command) resultstring, errmsg, retval = call_cmd(cmdlist + [prefix]) + if retval != 0: + msg = 'abook command "%s" returned with ' % self.command + msg += 'return code %d' % retval + if errmsg: + msg += ':\n%s' % errmsg + raise AddressbookError(msg) + if not resultstring: return [] lines = resultstring.splitlines() diff --git a/alot/completion.py b/alot/completion.py index fd3bee52..65329d5b 100644 --- a/alot/completion.py +++ b/alot/completion.py @@ -13,7 +13,8 @@ from alot.buffers import EnvelopeBuffer from alot.settings import settings from alot.utils.booleanaction import BooleanAction from alot.helper import split_commandline - +from alot.addressbooks import AddressbookError +from errors import CompletionError class Completer(object): """base class for completers""" @@ -28,6 +29,7 @@ class Completer(object): :returns: pairs of completed string and cursor position in the new string :rtype: list of (str, int) + :raises: :exc:`CompletionError` """ return list() @@ -212,7 +214,10 @@ class AbooksCompleter(Completer): prefix = original[:pos] res = [] for abook in self.abooks: - res = res + abook.lookup(prefix) + try: + res = res + abook.lookup(prefix) + except AddressbookError as e: + raise CompletionError(e) if self.addressesonly: returnlist = [(email, len(email)) for (name, email) in res] else: diff --git a/alot/errors.py b/alot/errors.py index 435a4bf9..9f4bd519 100644 --- a/alot/errors.py +++ b/alot/errors.py @@ -20,3 +20,7 @@ class GPGProblem(Exception): def __init__(self, message, code): self.code = code super(GPGProblem, self).__init__(message) + + +class CompletionError(Exception): + pass diff --git a/alot/helper.py b/alot/helper.py index 8ce9c725..c5820b8c 100644 --- a/alot/helper.py +++ b/alot/helper.py @@ -295,7 +295,7 @@ def call_cmd(cmdlist, stdin=None): :type cmdlist: list of str :param stdin: string to pipe to the process :type stdin: str - :return: triple of stdout, error msg, return value of the shell command + :return: triple of stdout, stderr, return value of the shell command :rtype: str, str, int """ @@ -308,11 +308,14 @@ def call_cmd(cmdlist, stdin=None): out, err = proc.communicate(stdin) ret = proc.poll() else: - out = subprocess.check_output(cmdlist) - # todo: get error msg. rval - except (subprocess.CalledProcessError, OSError), e: - err = str(e) - ret = -1 + try: + out = subprocess.check_output(cmdlist) + except subprocess.CalledProcessError as e: + err = e.output + ret = e.returncode + except OSError as e: + err = e.strerror + ret = e.errno out = string_decode(out, urwid.util.detected_encoding) err = string_decode(err, urwid.util.detected_encoding) @@ -222,12 +222,19 @@ class UI(object): self._passall = False d.callback(text) + def cerror(e): + logging.error(e) + self.notify('completion error: %s' % e.message, + priority='error') + self.update() + prefix = prefix + settings.get('prompt_suffix') # set up widgets leftpart = urwid.Text(prefix, align='left') editpart = CompleteEdit(completer, on_exit=select_or_cancel, - edit_text=text, history=history) + edit_text=text, history=history, + on_error=cerror) for i in range(tab): # hit some tabs editpart.keypress((0,), 'tab') diff --git a/alot/widgets/globals.py b/alot/widgets/globals.py index d9e7fadb..62929303 100644 --- a/alot/widgets/globals.py +++ b/alot/widgets/globals.py @@ -10,6 +10,7 @@ import urwid from alot.helper import string_decode from alot.settings import settings from alot.db.attachment import Attachment +from alot.errors import CompletionError class AttachmentWidget(urwid.WidgetWrap): @@ -71,10 +72,43 @@ class ChoiceWidget(urwid.Text): class CompleteEdit(urwid.Edit): - def __init__(self, completer, on_exit, edit_text=u'', - history=None, **kwargs): + """ + This is a vamped-up :class:`urwid.Edit` widget that allows for + tab-completion using :class:`~alot.completion.Completer` objects + + These widgets are meant to be used as user input prompts and hence + react to 'return' key presses by calling a 'on_exit' callback + that processes the current text value. + + The interpretation of some keypresses is hard-wired: + :enter: calls 'on_exit' callback with current value + :esc: calls 'on_exit' with value `None`, which can be interpreted + as cancelation + :tab: calls the completer and tabs forward in the result list + :shift tab: tabs backward in the result list + :up/down: move in the local input history + :ctrl a/e: moves curser to the beginning/end of the input + """ + def __init__(self, completer, on_exit, + on_error=None, + edit_text=u'', + history=None, + **kwargs): + """ + :param completer: completer to use + :type completer: alot.completion.Completer + :param on_exit: "enter"-callback that interprets the input (str) + :type on_exit: callable + :param on_error: callback that handles :class:`completion errors <alot.errors.CompletionErrors>` + :type on_error: callback + :param edit_text: initial text + :type edit_text: str + :param history: initial command history + :type history: list or str + """ self.completer = completer self.on_exit = on_exit + self.on_error = on_error self.history = list(history) # we temporarily add stuff here self.historypos = None @@ -88,10 +122,16 @@ class CompleteEdit(urwid.Edit): # if we tabcomplete if key in ['tab', 'shift tab'] and self.completer: # 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 + if self.completions is None: + self.completions = [(self.edit_text, self.edit_pos)] + try: + self.completions += self.completer.complete(self.edit_text, + self.edit_pos) + self.focus_in_clist = 1 + except CompletionError, e: + if self.on_error is not None: + self.on_error(e) + else: # otherwise tab through results if key == 'tab': self.focus_in_clist += 1 @@ -103,9 +143,6 @@ class CompleteEdit(urwid.Edit): self.set_edit_text(ctext) self.set_edit_pos(cpos) else: - self.edit_pos += 1 - if self.edit_pos >= len(self.edit_text): - self.edit_text += ' ' self.completions = None elif key in ['up', 'down']: if self.history: diff --git a/docs/source/api/interface.rst b/docs/source/api/interface.rst index 0ee0cb29..27ed06d5 100644 --- a/docs/source/api/interface.rst +++ b/docs/source/api/interface.rst @@ -107,11 +107,13 @@ of each other; the :class:`~alot.completion.QueryCompleter` for example uses a :class:`~alot.completion.TagsCompleter` internally to allow tagstring completion after "is:" or "tag:" keywords when typing a notmuch querystring. -All these classes overide the method :class:`~alot.completion.Completer.complete`, which +All these classes overide the method :meth:`~alot.completion.Completer.complete`, which for a given string and cursor position in that string returns a list of tuples `(completed_string, new_cursor_position)` that are taken to be the completed values. Note that `completed_string` does not need to have the original string as prefix. +:meth:`~alot.completion.Completer.complete` may rise :class:`alot.errors.CompletionError` +exceptions. .. automodule:: alot.completion :members: |