diff options
-rw-r--r-- | alot/buffers/thread.py | 5 | ||||
-rw-r--r-- | alot/commands/bufferlist.py | 16 | ||||
-rw-r--r-- | alot/commands/common.py | 6 | ||||
-rw-r--r-- | alot/commands/envelope.py | 21 | ||||
-rw-r--r-- | alot/commands/globals.py | 23 | ||||
-rw-r--r-- | alot/commands/namedqueries.py | 4 | ||||
-rw-r--r-- | alot/commands/search.py | 10 | ||||
-rw-r--r-- | alot/commands/taglist.py | 5 | ||||
-rw-r--r-- | alot/commands/thread.py | 47 | ||||
-rw-r--r-- | alot/ui.py | 50 |
10 files changed, 101 insertions, 86 deletions
diff --git a/alot/buffers/thread.py b/alot/buffers/thread.py index c89d4688..15aa9354 100644 --- a/alot/buffers/thread.py +++ b/alot/buffers/thread.py @@ -1,6 +1,8 @@ # Copyright (C) 2011-2018 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file +import asyncio import urwid import logging from urwidtrees import ArrowTree, TreeBox, NestedTree @@ -112,7 +114,8 @@ class ThreadBuffer(Buffer): self._auto_unread_writing = True msg.remove_tags(['unread'], afterwards=clear) fcmd = commands.globals.FlushCommand(silent=True) - self.ui.apply_command(fcmd) + asyncio.get_event_loop().create_task( + self.ui.apply_command(fcmd)) else: logging.debug('Tbuffer: No, msg not unread') else: diff --git a/alot/commands/bufferlist.py b/alot/commands/bufferlist.py index 73bd12a4..d58fab91 100644 --- a/alot/commands/bufferlist.py +++ b/alot/commands/bufferlist.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file from ..commands import Command, registerCommand @@ -18,14 +19,11 @@ class BufferFocusCommand(Command): @registerCommand(MODE, 'close') class BufferCloseCommand(Command): """close focussed buffer""" - def apply(self, ui): + + async def apply(self, ui): bufferlist = ui.current_buffer selected = bufferlist.get_selected_buffer() - d = ui.apply_command(globals.BufferCloseCommand(buffer=selected)) - - def cb(_): - if bufferlist is not selected: - bufferlist.rebuild() - ui.update() - d.addCallback(cb) - return d + await ui.apply_command(globals.BufferCloseCommand(buffer=selected)) + if bufferlist is not selected: + bufferlist.rebuild() + ui.update() diff --git a/alot/commands/common.py b/alot/commands/common.py index 71280d38..8280a5c0 100644 --- a/alot/commands/common.py +++ b/alot/commands/common.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file @@ -10,7 +11,7 @@ from .globals import PromptCommand class RetagPromptCommand(Command): """prompt to retag selected thread's or message's tags""" - def apply(self, ui): + async def apply(self, ui): get_selected_item = getattr(ui.current_buffer, { 'search': 'get_selected_thread', 'thread': 'get_selected_message'}[ui.mode]) @@ -25,4 +26,5 @@ class RetagPromptCommand(Command): elif tag: tags.append(tag) initial_tagstring = ','.join(sorted(tags)) + ',' - return ui.apply_command(PromptCommand('retag ' + initial_tagstring)) + r = await ui.apply_command(PromptCommand('retag ' + initial_tagstring)) + return r diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py index b06bb92d..8198b1fd 100644 --- a/alot/commands/envelope.py +++ b/alot/commands/envelope.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import argparse @@ -100,16 +101,16 @@ class RefineCommand(Command): Command.__init__(self, **kwargs) self.key = key - def apply(self, ui): + async def apply(self, ui): value = ui.current_buffer.envelope.get(self.key, '') cmdstring = 'set %s %s' % (self.key, value) - ui.apply_command(globals.PromptCommand(cmdstring)) + await ui.apply_command(globals.PromptCommand(cmdstring)) @registerCommand(MODE, 'save') class SaveCommand(Command): """save draft""" - def apply(self, ui): + async def apply(self, ui): envelope = ui.current_buffer.envelope # determine account to use @@ -139,15 +140,15 @@ class SaveCommand(Command): logging.debug('adding new mail to index') try: ui.dbman.add_message(path, account.draft_tags + envelope.tags) - ui.apply_command(globals.FlushCommand()) - ui.apply_command(commands.globals.BufferCloseCommand()) + await ui.apply_command(globals.FlushCommand()) + await ui.apply_command(commands.globals.BufferCloseCommand()) except DatabaseError as e: logging.error(str(e)) ui.notify('could not index message:\n%s' % str(e), priority='error', block=True) else: - ui.apply_command(commands.globals.BufferCloseCommand()) + await ui.apply_command(commands.globals.BufferCloseCommand()) @registerCommand(MODE, 'send') @@ -259,7 +260,7 @@ class SendCommand(Command): ui.clear_notify([clearme]) if self.envelope_buffer is not None: cmd = commands.globals.BufferCloseCommand(self.envelope_buffer) - ui.apply_command(cmd) + await ui.apply_command(cmd) ui.notify('mail sent successfully') if self.envelope is not None: if self.envelope.replied: @@ -275,7 +276,7 @@ class SendCommand(Command): if path is not None: logging.debug('adding new mail to index') ui.dbman.add_message(path, account.sent_tags + initial_tags) - ui.apply_command(globals.FlushCommand()) + await ui.apply_command(globals.FlushCommand()) @registerCommand(MODE, 'edit', arguments=[ @@ -300,7 +301,7 @@ class EditCommand(Command): self.edit_only_body = False Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): ebuffer = ui.current_buffer if not self.envelope: self.envelope = ui.current_buffer.envelope @@ -386,7 +387,7 @@ class EditCommand(Command): spawn=self.force_spawn, thread=self.force_spawn, refocus=self.refocus) - ui.apply_command(cmd) + await ui.apply_command(cmd) @registerCommand(MODE, 'set', arguments=[ diff --git a/alot/commands/globals.py b/alot/commands/globals.py index 8d8932bd..f99e5f31 100644 --- a/alot/commands/globals.py +++ b/alot/commands/globals.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import argparse @@ -72,7 +73,7 @@ class ExitCommand(Command): for b in ui.buffers: b.cleanup() - ui.apply_command(FlushCommand(callback=ui.exit)) + await ui.apply_command(FlushCommand(callback=ui.exit)) ui.cleanup() if ui.db_was_locked: @@ -155,7 +156,7 @@ class PromptCommand(Command): if cmdline: # save into prompt history ui.commandprompthistory.append(cmdline) - ui.apply_commandline(cmdline) + await ui.apply_commandline(cmdline) else: raise CommandCanceled() @@ -348,11 +349,11 @@ class EditCommand(ExternalCommand): spawn=self.spawn, thread=self.thread, **kwargs) - def apply(self, ui): + async def apply(self, ui): if self.cmdlist is None: ui.notify('no editor set', priority='error') else: - return ExternalCommand.apply(self, ui) + return await ExternalCommand.apply(self, ui) @registerCommand(MODE, 'pyshell') @@ -373,9 +374,9 @@ class RepeatCommand(Command): def __init__(self, **kwargs): Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): if ui.last_commandline is not None: - ui.apply_commandline(ui.last_commandline) + await ui.apply_commandline(ui.last_commandline) else: ui.notify('no last command') @@ -435,7 +436,7 @@ class BufferCloseCommand(Command): Command.__init__(self, **kwargs) async def apply(self, ui): - def one_buffer(prompt=True): + async def one_buffer(prompt=True): """Helper to handle the case on only one buffer being opened. prompt is a boolean that is passed to ExitCommand() as the _prompt @@ -452,13 +453,13 @@ class BufferCloseCommand(Command): # 'close without sending' else: logging.info('closing the last buffer, exiting') - ui.apply_command(ExitCommand(_prompt=prompt)) + await ui.apply_command(ExitCommand(_prompt=prompt)) if self.buffer is None: self.buffer = ui.current_buffer if len(ui.buffers) == 1: - one_buffer() + await one_buffer() return if (isinstance(self.buffer, buffers.EnvelopeBuffer) and @@ -472,7 +473,7 @@ class BufferCloseCommand(Command): # Because we await above it is possible that the settings or the number # of buffers chould change, so retest. if len(ui.buffers) == 1: - one_buffer(prompt=False) + await one_buffer(prompt=False) else: ui.buffer_close(self.buffer, self.redraw) @@ -961,7 +962,7 @@ class ComposeCommand(Command): cmd = commands.envelope.EditCommand(envelope=self.envelope, spawn=self.force_spawn, refocus=False) - ui.apply_command(cmd) + await ui.apply_command(cmd) @registerCommand( diff --git a/alot/commands/namedqueries.py b/alot/commands/namedqueries.py index 362e2bb6..f10724a8 100644 --- a/alot/commands/namedqueries.py +++ b/alot/commands/namedqueries.py @@ -20,11 +20,11 @@ class NamedqueriesSelectCommand(Command): self._filt = filt Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): query_name = ui.current_buffer.get_selected_query() query = ['query:"%s"' % query_name] if self._filt: query.extend(['and'] + self._filt) cmd = SearchCommand(query=query) - ui.apply_command(cmd) + await ui.apply_command(cmd) diff --git a/alot/commands/search.py b/alot/commands/search.py index 2b8e4849..c6c5cab6 100644 --- a/alot/commands/search.py +++ b/alot/commands/search.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import argparse @@ -87,10 +88,10 @@ class RefinePromptCommand(Command): """prompt to change this buffers querystring""" repeatable = True - def apply(self, ui): + async def apply(self, ui): sbuffer = ui.current_buffer oldquery = sbuffer.querystring - return ui.apply_command(PromptCommand('refine ' + oldquery)) + return await ui.apply_command(PromptCommand('refine ' + oldquery)) RetagPromptCommand = registerCommand(MODE, 'retagprompt')(RetagPromptCommand) @@ -164,7 +165,7 @@ class TagCommand(Command): self.flush = flush Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): searchbuffer = ui.current_buffer threadline_widget = searchbuffer.get_selected_threadline() # pass if the current buffer has no selected threadline @@ -226,7 +227,8 @@ class TagCommand(Command): # flush index if self.flush: - ui.apply_command(commands.globals.FlushCommand(callback=refresh)) + await ui.apply_command( + commands.globals.FlushCommand(callback=refresh)) @registerCommand( diff --git a/alot/commands/taglist.py b/alot/commands/taglist.py index 1fa01369..f5e8af73 100644 --- a/alot/commands/taglist.py +++ b/alot/commands/taglist.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file from . import Command, registerCommand @@ -11,7 +12,7 @@ MODE = 'taglist' class TaglistSelectCommand(Command): """search for messages with selected tag""" - def apply(self, ui): + async def apply(self, ui): tagstring = ui.current_buffer.get_selected_tag() cmd = SearchCommand(query=['tag:"%s"' % tagstring]) - ui.apply_command(cmd) + await ui.apply_command(cmd) diff --git a/alot/commands/thread.py b/alot/commands/thread.py index 9c5c8c9a..801ce2f0 100644 --- a/alot/commands/thread.py +++ b/alot/commands/thread.py @@ -1,4 +1,5 @@ # Copyright (C) 2011-2012 Patrick Totzke <patricktotzke@gmail.com> +# Copyright © 2018 Dylan Baker # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import argparse @@ -140,7 +141,7 @@ class ReplyCommand(Command): self.force_spawn = spawn Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): # get message to reply to if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() @@ -286,9 +287,9 @@ class ReplyCommand(Command): # continue to compose encrypt = mail.get_content_subtype() == 'encrypted' - ui.apply_command(ComposeCommand(envelope=envelope, - spawn=self.force_spawn, - encrypt=encrypt)) + await ui.apply_command(ComposeCommand(envelope=envelope, + spawn=self.force_spawn, + encrypt=encrypt)) @staticmethod def clear_my_address(my_addresses, value): @@ -344,7 +345,7 @@ class ForwardCommand(Command): self.force_spawn = spawn Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): # get message to forward if not given in constructor if not self.message: self.message = ui.current_buffer.get_selected_message() @@ -405,8 +406,8 @@ class ForwardCommand(Command): envelope.add('From', from_header) # continue to compose - ui.apply_command(ComposeCommand(envelope=envelope, - spawn=self.force_spawn)) + await ui.apply_command(ComposeCommand(envelope=envelope, + spawn=self.force_spawn)) @registerCommand(MODE, 'bounce') @@ -470,7 +471,7 @@ class BounceMailCommand(Command): logging.debug("bouncing mail") logging.debug(mail.__class__) - ui.apply_command(SendCommand(mail=mail)) + await ui.apply_command(SendCommand(mail=mail)) @registerCommand(MODE, 'editnew', arguments=[ @@ -490,7 +491,7 @@ class EditNewCommand(Command): self.force_spawn = spawn Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() @@ -515,9 +516,9 @@ class EditNewCommand(Command): for b in self.message.get_attachments(): envelope.attach(b) - ui.apply_command(ComposeCommand(envelope=envelope, - spawn=self.force_spawn, - omit_signature=True)) + await ui.apply_command(ComposeCommand(envelope=envelope, + spawn=self.force_spawn, + omit_signature=True)) @registerCommand( @@ -833,7 +834,7 @@ class RemoveCommand(Command): for m in messages: ui.dbman.remove_message(m, afterwards=callback) - ui.apply_command(FlushCommand()) + await ui.apply_command(FlushCommand()) @registerCommand(MODE, 'print', arguments=[ @@ -958,7 +959,7 @@ class OpenAttachmentCommand(Command): Command.__init__(self, **kwargs) self.attachment = attachment - def apply(self, ui): + async def apply(self, ui): logging.info('open attachment') mimetype = self.attachment.get_content_type() @@ -1007,10 +1008,10 @@ class OpenAttachmentCommand(Command): # XXX: could this be repalced with "'needsterminal' not in entry"? overtakes = entry.get('needsterminal') is None - ui.apply_command(ExternalCommand(handler_cmdlist, - stdin=handler_stdin, - on_success=afterwards, - thread=overtakes)) + await ui.apply_command(ExternalCommand(handler_cmdlist, + stdin=handler_stdin, + on_success=afterwards, + thread=overtakes)) else: ui.notify('unknown mime type') @@ -1067,13 +1068,13 @@ class ThreadSelectCommand(Command): """select focussed element: - if it is a message summary, toggle visibility of the message; - if it is an attachment line, open the attachment""" - def apply(self, ui): + async def apply(self, ui): focus = ui.get_deep_focus() if isinstance(focus, AttachmentWidget): logging.info('open attachment') - ui.apply_command(OpenAttachmentCommand(focus.get_attachment())) + await ui.apply_command(OpenAttachmentCommand(focus.get_attachment())) else: - ui.apply_command(ChangeDisplaymodeCommand(visible='toggle')) + await ui.apply_command(ChangeDisplaymodeCommand(visible='toggle')) RetagPromptCommand = registerCommand(MODE, 'retagprompt')(RetagPromptCommand) @@ -1144,7 +1145,7 @@ class TagCommand(Command): self.flush = flush Command.__init__(self, **kwargs) - def apply(self, ui): + async def apply(self, ui): tbuffer = ui.current_buffer if self.all: messagetrees = tbuffer.messagetrees() @@ -1192,4 +1193,4 @@ class TagCommand(Command): # flush index if self.flush: - ui.apply_command(FlushCommand()) + await ui.apply_command(FlushCommand()) @@ -7,6 +7,7 @@ import signal import codecs import contextlib import asyncio +import traceback from twisted.internet import defer import urwid @@ -38,7 +39,7 @@ class UI(object): """ This class integrates all components of alot and offers methods for user interaction like :meth:`prompt`, :meth:`notify` etc. - It handles the urwid widget tree and mainloop (we use twisted) and is + It handles the urwid widget tree and mainloop (we use asyncio) and is responsible for opening, closing and focussing buffers. """ @@ -153,6 +154,16 @@ class UI(object): msg = "{}\n(check the log for details)".format(errmsg) self.notify(msg, priority='error') + def _error_handler2(self, exception): + if isinstance(exception, CommandParseError): + self.notify(str(exception), priority='error') + elif isinstance(exception, CommandCanceled): + self.notify("operation cancelled", priority='error') + else: + logging.error(traceback.format_exc()) + msg = "{}\n(check the log for details)".format(exception) + self.notify(msg, priority='error') + def _input_filter(self, keys, raw): """ handles keypresses. @@ -260,7 +271,7 @@ class UI(object): # store cmdline for use with 'repeat' command if cmd.repeatable: self.last_commandline = cmdline - return self.apply_command(cmd) + return defer.ensureDeferred(self.apply_command(cmd)) # we initialize a deferred which is already triggered # so that the first callbacks will be called immediately @@ -695,7 +706,7 @@ class UI(object): footer_att = settings.get_theming_attribute('global', 'footer') return urwid.AttrMap(columns, footer_att) - def apply_command(self, cmd): + async def apply_command(self, cmd): """ applies a command @@ -705,27 +716,22 @@ class UI(object): :param cmd: an applicable command :type cmd: :class:`~alot.commands.Command` """ + # FIXME: What are we guarding for here? We don't mention that None is + # allowed as a value fo cmd. if cmd: - def call_posthook(_): - """Callback function that will invoke the post-hook.""" + if cmd.prehook: + await cmd.prehook(ui=self, dbm=self.dbman, cmd=cmd) + try: + if asyncio.iscoroutinefunction(cmd.apply): + await cmd.apply(self) + else: + cmd.apply(self) + except Exception as e: + self._error_handler2(e) + else: if cmd.posthook: logging.info('calling post-hook') - return defer.maybeDeferred(cmd.posthook, - ui=self, - dbm=self.dbman, - cmd=cmd) - - # call cmd.apply - def call_apply(_): - if asyncio.iscoroutinefunction(cmd.apply): - return defer.ensureDeferred(cmd.apply(self)) - return defer.maybeDeferred(cmd.apply, self) - - prehook = cmd.prehook or (lambda **kwargs: None) - d = defer.maybeDeferred(prehook, ui=self, dbm=self.dbman, cmd=cmd) - d.addCallback(call_apply) - d.addCallbacks(call_posthook, self._error_handler) - return d + await cmd.posthook(ui=self, dbm=self.dbman, cmd=cmd) def handle_signal(self, signum, frame): """ @@ -741,7 +747,7 @@ class UI(object): # it is a SIGINT ? if signum == signal.SIGINT: logging.info('shut down cleanly') - self.apply_command(globals.ExitCommand()) + asyncio.ensure_future(self.apply_command(globals.ExitCommand())) elif signum == signal.SIGUSR1: if isinstance(self.current_buffer, SearchBuffer): self.current_buffer.rebuild() |