summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alot/buffers/thread.py5
-rw-r--r--alot/commands/bufferlist.py16
-rw-r--r--alot/commands/common.py6
-rw-r--r--alot/commands/envelope.py21
-rw-r--r--alot/commands/globals.py23
-rw-r--r--alot/commands/namedqueries.py4
-rw-r--r--alot/commands/search.py10
-rw-r--r--alot/commands/taglist.py5
-rw-r--r--alot/commands/thread.py47
-rw-r--r--alot/ui.py50
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())
diff --git a/alot/ui.py b/alot/ui.py
index 1c13f05a..cf7574f0 100644
--- a/alot/ui.py
+++ b/alot/ui.py
@@ -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()