summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Totzke <patricktotzke@gmail.com>2013-07-07 18:06:49 +0100
committerPatrick Totzke <patricktotzke@gmail.com>2013-07-07 18:06:49 +0100
commit949c5b17b48ed8e45d315ba9c55d3ace1b511cb3 (patch)
tree6a767759cee801d43cc0d0c3e9b86f2d380982da
parentb3126fd75a4e9ad0949f54afbf4240dd5e0297df (diff)
parent330e0542b6d1e32574b4c7561982f059a0b49de8 (diff)
Merge branch '0.3.4-fix-624'
-rw-r--r--alot/addressbooks.py11
-rw-r--r--alot/completion.py9
-rw-r--r--alot/errors.py4
-rw-r--r--alot/helper.py15
-rw-r--r--alot/ui.py9
-rw-r--r--alot/widgets/globals.py55
-rw-r--r--docs/source/api/interface.rst4
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)
diff --git a/alot/ui.py b/alot/ui.py
index 93379dad..c7dbb269 100644
--- a/alot/ui.py
+++ b/alot/ui.py
@@ -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: