From b2e067d36245f39267b7c114c8480080ebaa7388 Mon Sep 17 00:00:00 2001 From: Max Schillinger Date: Sat, 1 May 2021 12:09:29 +0200 Subject: [PATCH] New command 'append': Append results of another (notmuch) query to search buffer --- alot/buffers/search.py | 43 +++++++++++++++++++++------------- alot/commands/globals.py | 2 +- alot/commands/search.py | 50 +++++++++++++++++++++++++++++++++------- alot/walker.py | 4 ++++ 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/alot/buffers/search.py b/alot/buffers/search.py index b33587acc..4c3eea88c 100644 --- a/alot/buffers/search.py +++ b/alot/buffers/search.py @@ -21,7 +21,9 @@ class SearchBuffer(Buffer): def __init__(self, ui, initialquery='', sort_order=None): self.dbman = ui.dbman self.ui = ui - self.querystring = initialquery + # We store a list of notmuch query strings and this buffer will + # display the results for each query one after the other + self.querystrings = [initialquery] default_order = settings.get('search_threads_sort_order') self.sort_order = sort_order or default_order self.result_count = 0 @@ -36,12 +38,13 @@ def __init__(self, ui, initialquery='', sort_order=None): def __str__(self): formatstring = '[search] for "%s" (%d message%s)' - return formatstring % (self.querystring, self.result_count, + return formatstring % ('" + "'.join(self.querystrings), + self.result_count, 's' if self.result_count > 1 else '') def get_info(self): info = {} - info['querystring'] = self.querystring + info['querystring'] = '" + "'.join(self.querystrings) info['result_count'] = self.result_count info['result_count_positive'] = 's' if self.result_count > 1 else '' return info @@ -59,19 +62,27 @@ def rebuild(self, reverse=False, restore_focus=True): if restore_focus and self.threadlist: selected_thread = self.get_selected_thread() - try: - self.result_count = self.dbman.count_messages(self.querystring) - threads = self.dbman.get_threads(self.querystring, order) - except NotmuchError: - self.ui.notify('malformed query string: %s' % self.querystring, - 'error') - self.listbox = urwid.ListBox([]) - self.body = self.listbox - return - - self.threadlist = IterableWalker(threads, ThreadlineWidget, - dbman=self.dbman, - reverse=reverse) + self.result_count = 0 + self.threadlist = None + for query in self.querystrings: + try: + self.result_count += self.dbman.count_messages(query) + threads = self.dbman.get_threads( + query, order) + except NotmuchError: + self.ui.notify('malformed query string: %s' % query, + 'error') + self.listbox = urwid.ListBox([]) + self.body = self.listbox + return + + iterablewalker = IterableWalker(threads, ThreadlineWidget, + dbman=self.dbman, + reverse=reverse) + if self.threadlist: + self.threadlist.append(iterablewalker) + else: + self.threadlist = iterablewalker self.listbox = urwid.ListBox(self.threadlist) self.body = self.listbox diff --git a/alot/commands/globals.py b/alot/commands/globals.py index aa1fd4368..8ea09834d 100644 --- a/alot/commands/globals.py +++ b/alot/commands/globals.py @@ -112,7 +112,7 @@ def apply(self, ui): open_searches = ui.get_buffers_of_type(buffers.SearchBuffer) to_be_focused = None for sb in open_searches: - if sb.querystring == self.query: + if sb.querystrings == [self.query]: to_be_focused = sb if to_be_focused: if ui.current_buffer != to_be_focused: diff --git a/alot/commands/search.py b/alot/commands/search.py index 4d58cfa64..dddeeb39f 100644 --- a/alot/commands/search.py +++ b/alot/commands/search.py @@ -35,12 +35,13 @@ def apply(self, ui): if not self.thread: self.thread = ui.current_buffer.get_selected_thread() if self.thread: - query = ui.current_buffer.querystring + query_list = ui.current_buffer.querystrings logging.info('open thread view for %s', self.thread) tb = buffers.ThreadBuffer(ui, self.thread) ui.buffer_open(tb) - tb.unfold_matching(query) + for query in query_list: + tb.unfold_matching(query) @registerCommand(MODE, 'refine', help='refine query', arguments=[ @@ -70,9 +71,9 @@ def __init__(self, query=None, sort=None, **kwargs): def apply(self, ui): if self.querystring or self.sort_order: sbuffer = ui.current_buffer - oldquery = sbuffer.querystring + oldquery = sbuffer.querystrings[-1] if self.querystring not in [None, oldquery]: - sbuffer.querystring = self.querystring + sbuffer.querystrings[-1] = self.querystring sbuffer = ui.current_buffer if self.sort_order: sbuffer.sort_order = self.sort_order @@ -90,13 +91,46 @@ class RefinePromptCommand(Command): async def apply(self, ui): sbuffer = ui.current_buffer - oldquery = sbuffer.querystring + oldquery = sbuffer.querystrings[-1] return await ui.apply_command(PromptCommand('refine ' + oldquery)) RetagPromptCommand = registerCommand(MODE, 'retagprompt')(RetagPromptCommand) +@registerCommand(MODE, 'append', usage='append query', arguments=[ + (['--sort'], {'help': 'sort order', 'choices': [ + 'oldest_first', 'newest_first', 'message_id', 'unsorted']}), + (['query'], {'nargs': argparse.REMAINDER, 'help': 'search string'})]) +class AppendCommand(Command): + + """append the results of a new query to this buffer. Search obeys the notmuch + :ref:`search.exclude_tags ` setting.""" + repeatable = True + + def __init__(self, query, sort=None, **kwargs): + """ + :param query: notmuch querystring + :type query: str + :param sort: how to order results. Must be one of + 'oldest_first', 'newest_first', 'message_id' or + 'unsorted'. + :type sort: str + """ + self.query = ' '.join(query) + self.order = sort + Command.__init__(self, **kwargs) + + def apply(self, ui): + if self.query: + sbuffer = ui.current_buffer + sbuffer.querystrings.append(self.query) + sbuffer.rebuild() + ui.update() + else: + ui.notify('empty query string') + + @registerCommand( MODE, 'tag', forced={'action': 'add'}, arguments=[ @@ -176,7 +210,7 @@ async def apply(self, ui): if threadline_widget is None: return - testquery = searchbuffer.querystring + testquery = searchbuffer.querystrings[-1] thread = threadline_widget.get_thread() if not self.allm: testquery = "(%s) AND thread:%s" % (testquery, @@ -198,7 +232,7 @@ def refresh(): else: threadline_widget.rebuild() searchbuffer.result_count = searchbuffer.dbman.count_messages( - searchbuffer.querystring) + searchbuffer.querystrings[-1]) else: searchbuffer.rebuild() @@ -257,5 +291,5 @@ class SaveQueryCommand(GlobalSaveQueryCommand): def apply(self, ui): searchbuffer = ui.current_buffer if not self.query: - self.query = searchbuffer.querystring + self.query = searchbuffer.querystrings[-1] GlobalSaveQueryCommand.apply(self, ui) diff --git a/alot/walker.py b/alot/walker.py index cfefc9e61..bdb42797d 100644 --- a/alot/walker.py +++ b/alot/walker.py @@ -2,6 +2,7 @@ # 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 itertools import logging import urwid @@ -92,3 +93,6 @@ def _get_next_item(self): def get_lines(self): return self.lines + + def append(self, iterableWalker): + self.iterable = itertools.chain(self.iterable, iterableWalker.iterable)