diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | alot/buffers.py | 3 | ||||
-rw-r--r-- | alot/commands/envelope.py | 59 | ||||
-rw-r--r-- | alot/commands/search.py | 4 | ||||
-rw-r--r-- | alot/commands/thread.py | 7 | ||||
-rw-r--r-- | alot/completion.py | 5 | ||||
-rw-r--r-- | docs/source/usage/modes/envelope.rst | 40 | ||||
-rw-r--r-- | tests/commands/envelope_test.py | 47 |
8 files changed, 162 insertions, 4 deletions
@@ -1,5 +1,6 @@ next: * Add command to reload configuration files in running session +* new command "tag" (and friends) in EnvelopeBuffer to add additional tags after sending 0.5: * save command prompt, recipient and sender history across program restarts diff --git a/alot/buffers.py b/alot/buffers.py index 80ec0855..09cf1fe8 100644 --- a/alot/buffers.py +++ b/alot/buffers.py @@ -178,6 +178,9 @@ class EnvelopeBuffer(Buffer): description += key.uids[0].uid lines.append(('GPG encrypt', description)) + if self.envelope.tags: + lines.append(('Tags', ','.join(self.envelope.tags))) + # add header list widget iff header values exists if lines: key_att = settings.get_theming_attribute('envelope', 'header_key') diff --git a/alot/commands/envelope.py b/alot/commands/envelope.py index 82b87888..a2945fbe 100644 --- a/alot/commands/envelope.py +++ b/alot/commands/envelope.py @@ -141,7 +141,7 @@ class SaveCommand(Command): ui.notify(msg + ' to %s' % path) logging.debug('adding new mail to index') try: - ui.dbman.add_message(path, account.draft_tags) + ui.dbman.add_message(path, account.draft_tags + envelope.tags) ui.apply_command(globals.FlushCommand()) ui.apply_command(commands.globals.BufferCloseCommand()) except DatabaseError as e: @@ -570,3 +570,60 @@ class EncryptCommand(Command): envelope.encrypt_keys = {} # reload buffer ui.current_buffer.rebuild() + + +@registerCommand( + MODE, 'tag', forced={'action': 'add'}, + arguments=[(['tags'], {'help': 'comma separated list of tags'})], + help='add tags to message', +) +@registerCommand( + MODE, 'retag', forced={'action': 'set'}, + arguments=[(['tags'], {'help': 'comma separated list of tags'})], + help='set message tags.', +) +@registerCommand( + MODE, 'untag', forced={'action': 'remove'}, + arguments=[(['tags'], {'help': 'comma separated list of tags'})], + help='remove tags from message', +) +@registerCommand( + MODE, 'toggletags', forced={'action': 'toggle'}, + arguments=[(['tags'], {'help': 'comma separated list of tags'})], + help='flip presence of tags on message', +) +class TagCommand(Command): + + """manipulate message tags""" + repeatable = True + + def __init__(self, tags=u'', action='add', **kwargs): + """ + :param tags: comma separated list of tagstrings to set + :type tags: unicode + :param action: adds tags if 'add', removes them if 'remove', adds tags + and removes all other if 'set' or toggle individually if + 'toggle' + :type action: str + """ + assert isinstance(tags, unicode), 'tags should be a unicode string' + self.tagsstring = tags + self.action = action + Command.__init__(self, **kwargs) + + def apply(self, ui): + ebuffer = ui.current_buffer + envelope = ebuffer.envelope + tags = {t for t in self.tagsstring.split(',') if t} + old = set(envelope.tags) + if self.action == 'add': + new = old.union(tags) + elif self.action == 'remove': + new = old.difference(tags) + elif self.action == 'set': + new = tags + elif self.action == 'toggle': + new = old.symmetric_difference(tags) + envelope.tags = sorted(new) + # reload buffer + ui.current_buffer.rebuild() diff --git a/alot/commands/search.py b/alot/commands/search.py index f304973d..49d95ebe 100644 --- a/alot/commands/search.py +++ b/alot/commands/search.py @@ -170,8 +170,8 @@ class TagCommand(Command): and removes all other if 'set' or toggle individually if 'toggle' :type action: str - :param all: tag all messages in search result - :type all: bool + :param allmessages: tag all messages in search result + :type allmessages: bool :param flush: imediately write out to the index :type flush: bool """ diff --git a/alot/commands/thread.py b/alot/commands/thread.py index 8f1ae039..ce5da649 100644 --- a/alot/commands/thread.py +++ b/alot/commands/thread.py @@ -489,9 +489,14 @@ class EditNewCommand(Command): if not self.message: self.message = ui.current_buffer.get_selected_message() mail = self.message.get_email() + # copy most tags to the envelope + tags = set(self.message.get_tags()) + tags.difference_update({'inbox', 'sent', 'draft', 'killed', 'replied', + 'signed', 'encrypted', 'unread', 'attachment'}) + tags = list(tags) # set body text mailcontent = self.message.accumulate_body() - envelope = Envelope(bodytext=mailcontent) + envelope = Envelope(bodytext=mailcontent, tags=tags) # copy selected headers to_copy = ['Subject', 'From', 'To', 'Cc', 'Bcc', 'In-Reply-To', diff --git a/alot/completion.py b/alot/completion.py index 046f4571..69e622eb 100644 --- a/alot/completion.py +++ b/alot/completion.py @@ -464,6 +464,11 @@ class CommandCompleter(Completer): '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) diff --git a/docs/source/usage/modes/envelope.rst b/docs/source/usage/modes/envelope.rst index 896bc7d0..8644a6c1 100644 --- a/docs/source/usage/modes/envelope.rst +++ b/docs/source/usage/modes/envelope.rst @@ -65,6 +65,26 @@ The following commands are available in envelope mode :---spawn: spawn editor in new terminal. :---refocus: refocus envelope after editing (Defaults to: 'True'). +.. _cmd.envelope.retag: + +.. describe:: retag + + set message tags. + + argument + comma separated list of tags + + +.. _cmd.envelope.tag: + +.. describe:: tag + + add tags to message + + argument + comma separated list of tags + + .. _cmd.envelope.send: .. describe:: send @@ -82,6 +102,16 @@ The following commands are available in envelope mode which key id to use +.. _cmd.envelope.untag: + +.. describe:: untag + + remove tags from message + + argument + comma separated list of tags + + .. _cmd.envelope.attach: .. describe:: attach @@ -148,6 +178,16 @@ The following commands are available in envelope mode mark mail not to be signed before sending +.. _cmd.envelope.toggletags: + +.. describe:: toggletags + + flip presence of tags on message + + argument + comma separated list of tags + + .. _cmd.envelope.unset: .. describe:: unset diff --git a/tests/commands/envelope_test.py b/tests/commands/envelope_test.py index cd6684a8..67caef02 100644 --- a/tests/commands/envelope_test.py +++ b/tests/commands/envelope_test.py @@ -26,6 +26,7 @@ import unittest import mock from alot.commands import envelope +from alot.db.envelope import Envelope # When using an assert from a mock a TestCase method might not use self. That's # okay. @@ -110,3 +111,49 @@ class TestAttachCommand(unittest.TestCase): cmd = envelope.AttachCommand(path=os.path.join(d, 'doesnt-exist')) cmd.apply(ui) ui.notify.assert_called() + + +class TestTagCommands(unittest.TestCase): + + def _test(self, tagstring, action, expected): + """Common steps for envelope.TagCommand tests + + :param tagstring: the string to pass to the TagCommand + :type tagstring: str + :param action: the action to pass to the TagCommand + :type action: str + :param expected: the expected output to assert in the test + :type expected: list(str) + """ + env = Envelope(tags=['one', 'two', 'three']) + ui = mock.Mock() + ui.current_buffer = mock.Mock() + ui.current_buffer.envelope = env + cmd = envelope.TagCommand(tags=tagstring, action=action) + cmd.apply(ui) + actual = env.tags + self.assertListEqual(sorted(actual), sorted(expected)) + + def test_add_new_tags(self): + self._test(u'four', 'add', ['one', 'two', 'three', 'four']) + + def test_adding_existing_tags_has_no_effect(self): + self._test(u'one', 'add', ['one', 'two', 'three']) + + def test_remove_existing_tags(self): + self._test(u'one', 'remove', ['two', 'three']) + + def test_remove_non_existing_tags_has_no_effect(self): + self._test(u'four', 'remove', ['one', 'two', 'three']) + + def test_set_tags(self): + self._test(u'a,b,c', 'set', ['a', 'b', 'c']) + + def test_toggle_will_remove_existing_tags(self): + self._test(u'one', 'toggle', ['two', 'three']) + + def test_toggle_will_add_new_tags(self): + self._test(u'four', 'toggle', ['one', 'two', 'three', 'four']) + + def test_toggle_can_remove_and_add_in_one_run(self): + self._test(u'one,four', 'toggle', ['two', 'three', 'four']) |