Commit cfd66d5f authored by Jacek Sowiński's avatar Jacek Sowiński

Reorganize code for completers

1.Rename completers

topydo.ui.prompt.TopydoCompleter is now topydo.ui.prompt.PromptCompleter
topydo.lib.Completer is now topydo.ui.CompleterBase

2. Reuse CompleterBase code in prompt completer.
3. Sort completion suggestions
4. Introduce completion of due: dates in column completer.
5. Store subcommands for completers in cache (via lru_cache).
parent 0e2bbbb7
......@@ -23,7 +23,7 @@ import sys
from topydo.lib.Config import config, ConfigError
_SUBCOMMAND_MAP = {
SUBCOMMAND_MAP = {
'add': 'AddCommand',
'app': 'AppendCommand',
'append': 'AppendCommand',
......@@ -63,9 +63,9 @@ def get_subcommand(p_args):
"""
Returns the class of the requested subcommand. An invalid p_subcommand
will result in an ImportError, since this is a programming mistake
(most likely an error in the _SUBCOMMAND_MAP).
(most likely an error in the SUBCOMMAND_MAP).
"""
classname = _SUBCOMMAND_MAP[p_subcommand]
classname = SUBCOMMAND_MAP[p_subcommand]
modulename = 'topydo.commands.{}'.format(classname)
__import__(modulename, globals(), locals(), [classname], 0)
......@@ -111,14 +111,14 @@ def get_subcommand(p_args):
if subcommand in alias_map:
result, args = resolve_alias(subcommand, args[1:])
elif subcommand in _SUBCOMMAND_MAP:
elif subcommand in SUBCOMMAND_MAP:
result = import_subcommand(subcommand)
args = args[1:]
elif subcommand == 'help':
try:
subcommand = args[1]
if subcommand in _SUBCOMMAND_MAP:
if subcommand in SUBCOMMAND_MAP:
args = [subcommand, 'help']
return get_subcommand(args)
except IndexError:
......@@ -128,14 +128,14 @@ def get_subcommand(p_args):
p_command = config().default_command()
if p_command in alias_map:
result, args = resolve_alias(p_command, args)
elif p_command in _SUBCOMMAND_MAP:
elif p_command in SUBCOMMAND_MAP:
result = import_subcommand(p_command)
# leave args unchanged
except IndexError:
p_command = config().default_command()
if p_command in alias_map:
result, args = resolve_alias(p_command, args)
elif p_command in _SUBCOMMAND_MAP:
elif p_command in SUBCOMMAND_MAP:
result = import_subcommand(p_command)
return (result, args)
......@@ -14,34 +14,71 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from topydo.Commands import _SUBCOMMAND_MAP
import datetime
from functools import lru_cache
from topydo.Commands import SUBCOMMAND_MAP
from topydo.lib.Config import config
@lru_cache(maxsize=1)
def _get_subcmds():
subcmd_map = config().aliases()
subcmd_map.update(_SUBCOMMAND_MAP)
subcmd_map = config().aliases().copy()
subcmd_map.update(SUBCOMMAND_MAP)
return sorted(subcmd_map.keys())
def date_suggestions():
"""
Returns a list of relative date that is presented to the user as auto
complete suggestions.
"""
# don't use strftime, prevent locales to kick in
days_of_week = {
0: "Monday",
1: "Tuesday",
2: "Wednesday",
3: "Thursday",
4: "Friday",
5: "Saturday",
6: "Sunday"
}
dates = [
'today',
'tomorrow',
]
# show days of week up to next week
dow = datetime.date.today().weekday()
for i in range(dow + 2 % 7, dow + 7):
dates.append(days_of_week[i % 7])
# and some more relative days starting from next week
dates += ["1w", "2w", "1m", "2m", "3m", "1y"]
return dates
class CompleterBase(object):
def __init__(self, p_todolist):
self.todolist = p_todolist
self._subcmds = _get_subcmds()
self._all_subcmds = _get_subcmds()
def _complete_context(self, p_word):
def _contexts(self, p_word):
completions = ['@' + context for context in self.todolist.contexts() if
context.startswith(p_word[1:])]
return completions
return sorted(completions)
def _complete_project(self, p_word):
def _projects(self, p_word):
completions = ['+' + project for project in self.todolist.projects() if
project.startswith(p_word[1:])]
return completions
return sorted(completions)
def _complete_subcmd(self, p_word):
completions = [cmd for cmd in self._subcmds if
def _subcmds(self, p_word):
completions = [cmd for cmd in self._all_subcmds if
cmd.startswith(p_word)]
return completions
......@@ -49,10 +86,10 @@ class CompleterBase(object):
completions = []
if p_word.startswith('+'):
completions = self._complete_project(p_word)
completions = self._projects(p_word)
elif p_word.startswith('@'):
completions = self._complete_context(p_word)
completions = self._contexts(p_word)
elif p_is_first_word:
completions = self._complete_subcmd(p_word)
completions = self._subcmds(p_word)
return completions
# Topydo - A todo.txt client written in Python.
# Copyright (C) 2017 Bram Schoenmakers <bram@topydo.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
This module provides a completer class that can be used by the Column UI
CommmandLineWidget.
"""
from topydo.lib.Config import config
from topydo.ui.CompleterBase import CompleterBase, date_suggestions
class ColumnCompleter(CompleterBase):
"""
Completer class that completes projects, contexts, dates and
subcommands designed to work with CommandLineWidget for column UI.
"""
def get_completions(self, p_word, p_is_first_word=False):
def dates(p_word, p_tag):
dates = []
for date in date_suggestions():
candidate = p_tag + ':' + date
if candidate.startswith(p_word):
dates.append(candidate)
return dates
due_tag = config().tag_due()
start_tag = config().tag_start()
if p_word.startswith(due_tag + ':'):
return dates(p_word, due_tag)
elif p_word.startswith(start_tag + ':'):
return dates(p_word, start_tag)
else:
return super().get_completions(p_word, p_is_first_word)
......@@ -26,7 +26,7 @@ from string import ascii_uppercase
from topydo.Commands import get_subcommand
from topydo.lib.Config import config, ConfigError
from topydo.lib.Completer import CompleterBase
from topydo.ui.columns.ColumnCompleter import ColumnCompleter
from topydo.lib.Sorter import Sorter
from topydo.lib.Filter import get_filter_list, RelevanceFilter, DependencyFilter
from topydo.lib.Utils import get_terminal_size
......@@ -124,7 +124,7 @@ class UIApplication(CLIApplicationBase):
self.columns = urwid.Columns([], dividechars=0,
min_width=config().column_width())
completer = CompleterBase(self.todolist)
completer = ColumnCompleter(self.todolist)
self.commandline = CommandLineWidget(completer, 'topydo> ')
self.keystate_widget = KeystateWidget()
self.status_line = urwid.Columns([
......
......@@ -21,7 +21,7 @@ import shlex
import sys
from topydo.ui.CLIApplicationBase import CLIApplicationBase, error, GENERIC_HELP
from topydo.ui.prompt.TopydoCompleter import TopydoCompleter
from topydo.ui.prompt.PromptCompleter import PromptCompleter
from prompt_toolkit.shortcuts import prompt
from prompt_toolkit.history import InMemoryHistory
......@@ -61,7 +61,7 @@ class PromptApplication(CLIApplicationBase):
"""
self.todolist.erase()
self.todolist.add_list(self.todofile.read())
self.completer = TopydoCompleter(self.todolist)
self.completer = PromptCompleter(self.todolist)
def run(self):
""" Main entry function. """
......
......@@ -19,94 +19,38 @@ This module provides a completer class that can be used by the prompt provided
by the prompt toolkit.
"""
import datetime
import re
from prompt_toolkit.completion import Completer, Completion
from topydo.Commands import _SUBCOMMAND_MAP
from topydo.ui.CompleterBase import CompleterBase, date_suggestions
from topydo.lib.Config import config
from topydo.lib.RelativeDate import relative_date_to_date
def _subcommands(p_word_before_cursor):
""" Generator for subcommand name completion. """
sc_map = config().aliases()
sc_map.update(_SUBCOMMAND_MAP)
subcommands = [sc for sc in sorted(sc_map.keys()) if
sc.startswith(p_word_before_cursor)]
for command in subcommands:
yield Completion(command, -len(p_word_before_cursor))
def _dates(p_word_before_cursor):
""" Generator for date completion. """
def _date_suggestions():
"""
Returns a list of relative date that is presented to the user as auto
complete suggestions.
"""
# don't use strftime, prevent locales to kick in
days_of_week = {
0: "Monday",
1: "Tuesday",
2: "Wednesday",
3: "Thursday",
4: "Friday",
5: "Saturday",
6: "Sunday"
}
dates = [
'today',
'tomorrow',
]
# show days of week up to next week
dow = datetime.date.today().weekday()
for i in range(dow + 2 % 7, dow + 7):
dates.append(days_of_week[i % 7])
# and some more relative days starting from next week
dates += ["1w", "2w", "1m", "2m", "3m", "1y"]
return dates
to_absolute = lambda s: relative_date_to_date(s).isoformat()
start_value_pos = p_word_before_cursor.find(':') + 1
value = p_word_before_cursor[start_value_pos:]
for reldate in _date_suggestions():
for reldate in date_suggestions():
if not reldate.startswith(value):
continue
yield Completion(reldate, -len(value), display_meta=to_absolute(reldate))
class TopydoCompleter(Completer):
class PromptCompleter(CompleterBase, Completer):
"""
Completer class that completes projects, contexts, dates and
subcommands.
subcommands and is compatible with prompt toolkit.
"""
def __init__(self, p_todolist):
self.todolist = p_todolist
def _projects(self, p_word_before_cursor):
""" Generator for project completion. """
projects = [p for p in self.todolist.projects() if
p.startswith(p_word_before_cursor[1:])]
for project in projects:
yield Completion("+" + project, -len(p_word_before_cursor))
def _contexts(self, p_word_before_cursor):
""" Generator for context completion. """
contexts = [c for c in self.todolist.contexts() if
c.startswith(p_word_before_cursor[1:])]
for context in contexts:
yield Completion("@" + context, -len(p_word_before_cursor))
def _completion_generator(self, p_word, is_first_word):
candidates = super().get_completions(p_word, is_first_word)
for candidate in candidates:
yield Completion(candidate, -len(p_word))
def get_completions(self, p_document, _):
# include all characters except whitespaces (for + and @)
......@@ -114,15 +58,9 @@ class TopydoCompleter(Completer):
is_first_word = not re.match(r'\s*\S+\s',
p_document.current_line_before_cursor)
if is_first_word:
return _subcommands(word_before_cursor)
elif word_before_cursor.startswith('+'):
return self._projects(word_before_cursor)
elif word_before_cursor.startswith('@'):
return self._contexts(word_before_cursor)
elif word_before_cursor.startswith(config().tag_due() + ':'):
if word_before_cursor.startswith(config().tag_due() + ':'):
return _dates(word_before_cursor)
elif word_before_cursor.startswith(config().tag_start() + ':'):
return _dates(word_before_cursor)
return []
else:
return self._completion_generator(word_before_cursor, is_first_word)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment