Skip to content

Commit

Permalink
Merge pull request #1256 from pazz/namedqueries
Browse files Browse the repository at this point in the history
New buffer type for notmuch's named query strings
  • Loading branch information
dcbaker committed Jul 24, 2018
2 parents bf891ce + 6ab2f27 commit 4cba47a
Show file tree
Hide file tree
Showing 32 changed files with 544 additions and 26 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
0.8:
* Port to python 3. Python 2.x no longer supported
* feature: Add a new 'namedqueries' buffer type for displaying named queries.

0.7:
* info: missing html mailcap entry now reported as mail body text
Expand Down
3 changes: 2 additions & 1 deletion alot/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from alot.utils import argparse as cargparse


_SUBCOMMANDS = ['search', 'compose', 'bufferlist', 'taglist', 'pyshell']
_SUBCOMMANDS = ['search', 'compose', 'bufferlist', 'taglist', 'namedqueries',
'pyshell']


def parser():
Expand Down
1 change: 1 addition & 0 deletions alot/buffers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
from .search import SearchBuffer
from .taglist import TagListBuffer
from .thread import ThreadBuffer
from .namedqueries import NamedQueriesBuffer
78 changes: 78 additions & 0 deletions alot/buffers/namedqueries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright (C) 2011-2018 Patrick Totzke <patricktotzke@gmail.com>
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
import urwid

from .buffer import Buffer
from ..settings.const import settings
from ..widgets.namedqueries import QuerylineWidget


class NamedQueriesBuffer(Buffer):
"""lists named queries present in the notmuch database"""

modename = 'namedqueries'

def __init__(self, ui, filtfun):
self.ui = ui
self.filtfun = filtfun
self.isinitialized = False
self.querylist = None
self.rebuild()
Buffer.__init__(self, ui, self.body)

def rebuild(self):
self.queries = self.ui.dbman.get_named_queries()

if self.isinitialized:
focusposition = self.querylist.get_focus()[1]
else:
focusposition = 0

lines = []
for (num, key) in enumerate(self.queries):
value = self.queries[key]
count = self.ui.dbman.count_messages('query:"%s"' % key)
count_unread = self.ui.dbman.count_messages('query:"%s" and '
'tag:unread' % key)
line = QuerylineWidget(key, value, count, count_unread)

if (num % 2) == 0:
attr = settings.get_theming_attribute('namedqueries',
'line_even')
else:
attr = settings.get_theming_attribute('namedqueries',
'line_odd')
focus_att = settings.get_theming_attribute('namedqueries',
'line_focus')

line = urwid.AttrMap(line, attr, focus_att)
lines.append(line)

self.querylist = urwid.ListBox(urwid.SimpleListWalker(lines))
self.body = self.querylist

self.querylist.set_focus(focusposition % len(self.queries))

self.isinitialized = True

def focus_first(self):
"""Focus the first line in the query list."""
self.body.set_focus(0)

def focus_last(self):
allpos = self.querylist.body.positions(reverse=True)
if allpos:
lastpos = allpos[0]
self.body.set_focus(lastpos)

def get_selected_query(self):
"""returns selected query"""
return self.querylist.get_focus()[0].original_widget.query

def get_info(self):
info = {}

info['query_count'] = len(self.queries)

return info
1 change: 1 addition & 0 deletions alot/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class CommandCanceled(Exception):
'envelope': {},
'bufferlist': {},
'taglist': {},
'namedqueries': {},
'thread': {},
'global': {},
}
Expand Down
106 changes: 106 additions & 0 deletions alot/commands/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,21 @@ def apply(self, ui):
ui.buffer_open(buffers.TagListBuffer(ui, tags, self.filtfun))


@registerCommand(MODE, 'namedqueries')
class NamedQueriesCommand(Command):
"""opens named queries buffer"""
def __init__(self, filtfun=bool, **kwargs):
"""
:param filtfun: filter to apply to displayed list
:type filtfun: callable (str->bool)
"""
self.filtfun = filtfun
Command.__init__(self, **kwargs)

def apply(self, ui):
ui.buffer_open(buffers.NamedQueriesBuffer(ui, self.filtfun))


@registerCommand(MODE, 'flush')
class FlushCommand(Command):

Expand Down Expand Up @@ -979,3 +994,94 @@ def apply(self, ui):
except ConfigError as e:
ui.notify('Error when reloading config files:\n {}'.format(e),
priority='error')


@registerCommand(
MODE, 'savequery',
arguments=[
(['--no-flush'], {'action': 'store_false', 'dest': 'flush',
'default': 'True',
'help': 'postpone a writeout to the index'}),
(['alias'], {'help': 'alias to use for query string'}),
(['query'], {'help': 'query string to store',
'nargs': '+'})
],
help='store query string as a "named query" in the database')
class SaveQueryCommand(Command):

"""save alias for query string"""
repeatable = False

def __init__(self, alias, query=None, flush=True, **kwargs):
"""
:param alias: name to use for query string
:type alias: str
:param query: query string to save
:type query: str or None
:param flush: immediately write out to the index
:type flush: bool
"""
self.alias = alias
if query is None:
self.query = ''
else:
self.query = ' '.join(query)
self.flush = flush
Command.__init__(self, **kwargs)

def apply(self, ui):
msg = 'saved alias "%s" for query string "%s"' % (self.alias,
self.query)

try:
ui.dbman.save_named_query(self.alias, self.query)
logging.debug(msg)
ui.notify(msg)
except DatabaseROError:
ui.notify('index in read-only mode', priority='error')
return

# flush index
if self.flush:
ui.apply_command(commands.globals.FlushCommand())


@registerCommand(
MODE, 'removequery',
arguments=[
(['--no-flush'], {'action': 'store_false', 'dest': 'flush',
'default': 'True',
'help': 'postpone a writeout to the index'}),
(['alias'], {'help': 'alias to remove'}),
],
help='removes a "named query" from the database')
class RemoveQueryCommand(Command):

"""remove named query string for given alias"""
repeatable = False

def __init__(self, alias, flush=True, **kwargs):
"""
:param alias: name to use for query string
:type alias: str
:param flush: immediately write out to the index
:type flush: bool
"""
self.alias = alias
self.flush = flush
Command.__init__(self, **kwargs)

def apply(self, ui):
msg = 'removed alias "%s"' % (self.alias)

try:
ui.dbman.remove_named_query(self.alias)
logging.debug(msg)
ui.notify(msg)
except DatabaseROError:
ui.notify('index in read-only mode', priority='error')
return

# flush index
if self.flush:
ui.apply_command(commands.globals.FlushCommand())
30 changes: 30 additions & 0 deletions alot/commands/namedqueries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright (C) 2011-2018 Patrick Totzke <patricktotzke@gmail.com>
# This file is released under the GNU GPL, version 3 or a later revision.
# For further details see the COPYING file
import argparse

from . import Command, registerCommand
from .globals import SearchCommand

MODE = 'namedqueries'


@registerCommand(MODE, 'select', arguments=[
(['filt'], {'nargs': argparse.REMAINDER,
'help': 'additional filter to apply to query'}),
])
class NamedqueriesSelectCommand(Command):

"""search for messages with selected query"""
def __init__(self, filt=None, **kwargs):
self._filt = filt
Command.__init__(self, **kwargs)

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)
22 changes: 22 additions & 0 deletions alot/commands/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from . import Command, registerCommand
from .globals import PromptCommand
from .globals import MoveCommand
from .globals import SaveQueryCommand as GlobalSaveQueryCommand
from .common import RetagPromptCommand
from .. import commands

Expand Down Expand Up @@ -241,3 +242,24 @@ def apply(self, ui):
ui.update()
else:
MoveCommand.apply(self, ui)


@registerCommand(
MODE, 'savequery',
arguments=[
(['--no-flush'], {'action': 'store_false', 'dest': 'flush',
'default': 'True',
'help': 'postpone a writeout to the index'}),
(['alias'], {'help': 'alias to use for query string'}),
(['query'], {'help': 'query string to store',
'nargs': argparse.REMAINDER,
}),
],
help='store query string as a "named query" in the database. '
'This falls back to the current search query in search buffers.')
class SaveQueryCommand(GlobalSaveQueryCommand):
def apply(self, ui):
searchbuffer = ui.current_buffer
if not self.query:
self.query = searchbuffer.querystring
GlobalSaveQueryCommand.apply(self, ui)
23 changes: 20 additions & 3 deletions alot/completion.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ def complete(self, original, pos):
return res


class NamedQueryCompleter(StringlistCompleter):
"""complete the name of a named query string"""

def __init__(self, dbman):
"""
:param dbman: used to look up named query strings in the DB
:type dbman: :class:`~alot.db.DBManager`
"""
# mapping of alias to query string (dict str -> str)
nqueries = dbman.get_named_queries()
StringlistCompleter.__init__(self, list(nqueries))


class QueryCompleter(Completer):
"""completion for a notmuch query string"""
def __init__(self, dbman):
Expand All @@ -134,19 +147,23 @@ def __init__(self, dbman):
abooks = settings.get_addressbooks()
self._abookscompleter = AbooksCompleter(abooks, addressesonly=True)
self._tagcompleter = TagCompleter(dbman)
self._nquerycompleter = NamedQueryCompleter(dbman)
self.keywords = ['tag', 'from', 'to', 'subject', 'attachment',
'is', 'id', 'thread', 'folder']
'is', 'id', 'thread', 'folder', 'query']

def complete(self, original, pos):
mypart, start, end, mypos = self.relevant_part(original, pos)
myprefix = mypart[:mypos]
m = re.search(r'(tag|is|to|from):(\w*)', myprefix)
m = re.search(r'(tag|is|to|from|query):(\w*)', myprefix)
if m:
cmd, _ = m.groups()
cmdlen = len(cmd) + 1 # length of the keyword part incld colon
cmdlen = len(cmd) + 1 # length of the keyword part including colon
if cmd in ['to', 'from']:
localres = self._abookscompleter.complete(mypart[cmdlen:],
mypos - cmdlen)
elif cmd in ['query']:
localres = self._nquerycompleter.complete(mypart[cmdlen:],
mypos - cmdlen)
else:
localres = self._tagcompleter.complete(mypart[cmdlen:],
mypos - cmdlen)
Expand Down

0 comments on commit 4cba47a

Please sign in to comment.