# Copyright (C) 2011-2018 Patrick Totzke # This file is released under the GNU GPL, version 3 or a later revision. # For further details see the COPYING file import urwid from notmuch2 import NotmuchError from .buffer import Buffer from ..settings.const import settings from ..widgets.search import ThreadlineWidget class PipeWalker(urwid.ListWalker): """ urwid.ListWalker that reads next items from a pipe and wraps them in ThreadlineWidget widgets for displaying """ _pipe = None _dbman = None _pipe_eof = False # list of the thread IDs _tids = None # a dictionary of threadline widgets, indexed by position _wgts = None _focus = None def __init__(self, pipe, dbman): self._pipe = pipe self._dbman = dbman self._tids = [] self._wgts = {} self._focus = 0 super().__init__() def __len__(self): while not self._pipe_eof: self._get_next_item() return len(self._tids) def __getitem__(self, pos): self._check_pos(pos) if not pos in self._wgts: # make sure an exception while constructing the widget does not get # swallowed by urwid try: self._wgts[pos] = ThreadlineWidget(self._tids[pos], self._dbman) except (KeyError, IndexError, TypeError) as e: raise ValueError('Exception while constructing threadline widget') from e return self._wgts[pos] def _check_pos(self, pos): if pos < 0: raise IndexError while not self._pipe_eof and pos >= len(self._tids): self._get_next_item() if pos >= len(self._tids): raise IndexError return pos def next_position(self, pos): return self._check_pos(pos + 1) def prev_position(self, pos): return self._check_pos(pos - 1) @property def focus(self): return self._focus def set_focus(self, pos): self._check_pos(pos) self._focus = pos self._modified() def _get_next_item(self): if self._pipe_eof: return None try: self._tids.append(self._pipe.recv()) except EOFError: self._pipe_eof = True class SearchBuffer(Buffer): """shows a result list of threads for a query""" modename = 'search' _result_count_val = None _thread_count_val = None def __init__(self, ui, initialquery='', sort_order=None): self.dbman = ui.dbman self.ui = ui self.querystring = initialquery default_order = settings.get('search_threads_sort_order') self.sort_order = sort_order or default_order self.proc = None # process that fills our pipe self.rebuild() super().__init__() def __str__(self): formatstring = '[search] for "%s" (%d message%s in %d thread%s)' return formatstring % (self.querystring, self._result_count, 's' if self._result_count > 1 else '', self._thread_count, 's' if self._thread_count > 1 else '') @property def _result_count(self): if self._result_count_val is None: self._result_count_val = self.dbman.count_messages(self.querystring) return self._result_count_val @property def _thread_count(self): if self._thread_count_val is None: self._thread_count_val = self.dbman.count_threads(self.querystring) return self._thread_count_val def get_info(self): info = {} info['querystring'] = self.querystring info['result_count'] = self._result_count info['thread_count'] = self._thread_count info['result_count_positive'] = 's' if info['result_count'] > 1 else '' return info def cleanup(self): self.kill_filler_process() def kill_filler_process(self): """ terminates the process that fills this buffers :class:`~alot.walker.PipeWalker`. """ if self.proc: if self.proc.is_alive(): self.proc.terminate() def rebuild(self): self.kill_filler_process() exclude_tags = settings.get_notmuch_setting('search', 'exclude_tags') if exclude_tags: exclude_tags = frozenset([t for t in exclude_tags.split(';') if t]) else: exclude_tags = frozenset() self._result_count_val = None self._thread_count_val = None try: self.pipe, self.proc = self.dbman.get_threads(self.querystring, self.sort_order, exclude_tags) except NotmuchError: self.ui.notify('malformed query string: %s' % self.querystring, 'error') self.listbox = urwid.ListBox([]) self.body = self.listbox return self.threadlist = PipeWalker(self.pipe, self.dbman) self.listbox = urwid.ListBox(self.threadlist) self.body = self.listbox def get_selected_threadline(self): """ returns curently focussed :class:`alot.widgets.ThreadlineWidget` from the result list. """ return self.threadlist[self.threadlist.focus] def get_selected_thread(self): """returns currently selected :class:`~alot.db.Thread`""" threadlinewidget = self.get_selected_threadline() thread = None if threadlinewidget: thread = threadlinewidget.get_thread() return thread def focus_first(self): self.body.set_focus(0) def focus_last(self): self.body.set_focus(len(self.threadlist) - 1)