From 3b30b422118c918fc19fcbc4837be28b00387fa8 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Tue, 20 Sep 2016 06:52:52 -0400 Subject: [PATCH] Remove completion.instances, usertypes.Completion. The new completion API no longer needs either of these. Instead of referencing an enum member, cmdutils.argument.completion now points to a function that returnsthe desired completion model. This vastly simplifies the addition of new completion types. Previously it was necessary to define the new model as well as editing usertypes and completion.models.instances. Now it is only necessary to define a single function under completion.models. This is the next step of Completion Model/View Revamping (#74). --- qutebrowser/app.py | 7 +- qutebrowser/browser/commands.py | 20 ++-- qutebrowser/completion/completer.py | 8 +- qutebrowser/completion/models/instances.py | 53 ----------- qutebrowser/config/config.py | 8 +- qutebrowser/config/parsers/keyconf.py | 5 +- qutebrowser/misc/sessions.py | 11 ++- qutebrowser/utils/usertypes.py | 7 -- tests/unit/completion/test_completer.py | 102 +++++++++++---------- 9 files changed, 84 insertions(+), 137 deletions(-) delete mode 100644 qutebrowser/completion/models/instances.py diff --git a/qutebrowser/app.py b/qutebrowser/app.py index a3a855f9a..2b216ca44 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -42,7 +42,7 @@ except ImportError: import qutebrowser import qutebrowser.resources -from qutebrowser.completion.models import instances as completionmodels +from qutebrowser.completion.models import miscmodels from qutebrowser.commands import cmdutils, runners, cmdexc from qutebrowser.config import style, config, websettings, configexc from qutebrowser.browser import (urlmarks, adblock, history, browsertab, @@ -459,9 +459,6 @@ def _init_modules(args, crash_handler): diskcache = cache.DiskCache(standarddir.cache(), parent=qApp) objreg.register('cache', diskcache) - log.init.debug("Initializing completions...") - completionmodels.init() - log.init.debug("Misc initialization...") if config.get('ui', 'hide-wayland-decoration'): os.environ['QT_WAYLAND_DISABLE_WINDOWDECORATION'] = '1' @@ -753,7 +750,7 @@ class Quitter: QTimer.singleShot(0, functools.partial(qApp.exit, status)) @cmdutils.register(instance='quitter', name='wq') - @cmdutils.argument('name', completion=usertypes.Completion.sessions) + @cmdutils.argument('name', completion=miscmodels.session) def save_and_quit(self, name=sessions.default): """Save open pages and quit. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 97a764c68..8914d4ac2 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -41,7 +41,7 @@ from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils, objreg, utils, typing) from qutebrowser.utils.usertypes import KeyMode from qutebrowser.misc import editor, guiprocess -from qutebrowser.completion.models import instances, sortfilter +from qutebrowser.completion.models import sortfilter, urlmodel, miscmodels class CommandDispatcher: @@ -284,7 +284,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', name='open', maxsplit=0, scope='window') - @cmdutils.argument('url', completion=usertypes.Completion.url) + @cmdutils.argument('url', completion=urlmodel.url) @cmdutils.argument('count', count=True) def openurl(self, url=None, implicit=False, bg=False, tab=False, window=False, count=None, secure=False, @@ -1007,7 +1007,7 @@ class CommandDispatcher: self._open(url, tab, bg, window) @cmdutils.register(instance='command-dispatcher', scope='window') - @cmdutils.argument('index', completion=usertypes.Completion.tab) + @cmdutils.argument('index', completion=miscmodels.buffer) def buffer(self, index): """Select tab by index or url/title best match. @@ -1023,7 +1023,7 @@ class CommandDispatcher: for part in index_parts: int(part) except ValueError: - model = instances.get(usertypes.Completion.tab) + model = miscmodels.buffer() sf = sortfilter.CompletionFilterModel(source=model) sf.set_pattern(index) if sf.count() > 0: @@ -1229,8 +1229,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0) - @cmdutils.argument('name', - completion=usertypes.Completion.quickmark_by_name) + @cmdutils.argument('name', completion=miscmodels.quickmark) def quickmark_load(self, name, tab=False, bg=False, window=False): """Load a quickmark. @@ -1248,8 +1247,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0) - @cmdutils.argument('name', - completion=usertypes.Completion.quickmark_by_name) + @cmdutils.argument('name', completion=miscmodels.quickmark) def quickmark_del(self, name=None): """Delete a quickmark. @@ -1311,7 +1309,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0) - @cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url) + @cmdutils.argument('url', completion=miscmodels.bookmark) def bookmark_load(self, url, tab=False, bg=False, window=False, delete=False): """Load a bookmark. @@ -1333,7 +1331,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0) - @cmdutils.argument('url', completion=usertypes.Completion.bookmark_by_url) + @cmdutils.argument('url', completion=miscmodels.bookmark) def bookmark_del(self, url=None): """Delete a bookmark. @@ -1507,7 +1505,7 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', name='help', scope='window') - @cmdutils.argument('topic', completion=usertypes.Completion.helptopic) + @cmdutils.argument('topic', completion=miscmodels.helptopic) def show_help(self, tab=False, bg=False, window=False, topic=None): r"""Show help about a command or setting. diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py index 21af74da5..bd4f43009 100644 --- a/qutebrowser/completion/completer.py +++ b/qutebrowser/completion/completer.py @@ -23,8 +23,8 @@ from PyQt5.QtCore import pyqtSlot, QObject, QTimer from qutebrowser.config import config from qutebrowser.commands import cmdutils, runners -from qutebrowser.utils import usertypes, log, utils -from qutebrowser.completion.models import instances, sortfilter +from qutebrowser.utils import log, utils +from qutebrowser.completion.models import sortfilter, miscmodels class Completer(QObject): @@ -72,7 +72,7 @@ class Completer(QObject): Return: A completion model or None. """ - model = instances.get(completion)(*pos_args) + model = completion(*pos_args) if model is None: return None else: @@ -96,7 +96,7 @@ class Completer(QObject): log.completion.debug("After removing flags: {}".format(before_cursor)) if not before_cursor: # '|' or 'set|' - model = instances.get(usertypes.Completion.command)() + model = miscmodels.command() return sortfilter.CompletionFilterModel(source=model, parent=self) try: cmd = cmdutils.cmd_dict[before_cursor[0]] diff --git a/qutebrowser/completion/models/instances.py b/qutebrowser/completion/models/instances.py deleted file mode 100644 index ce109ae7a..000000000 --- a/qutebrowser/completion/models/instances.py +++ /dev/null @@ -1,53 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2015-2017 Florian Bruhin (The Compiler) -# -# This file is part of qutebrowser. -# -# qutebrowser 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. -# -# qutebrowser 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 qutebrowser. If not, see . - -"""Global instances of the completion models.""" - -from qutebrowser.utils import usertypes -from qutebrowser.completion.models import miscmodels, urlmodel, configmodel - - -def get(completion): - """Get a certain completion. Initializes the completion if needed.""" - if completion == usertypes.Completion.command: - return miscmodels.command - if completion == usertypes.Completion.helptopic: - return miscmodels.helptopic - if completion == usertypes.Completion.tab: - return miscmodels.buffer - if completion == usertypes.Completion.quickmark_by_name: - return miscmodels.quickmark - if completion == usertypes.Completion.bookmark_by_url: - return miscmodels.bookmark - if completion == usertypes.Completion.sessions: - return miscmodels.session - if completion == usertypes.Completion.bind: - return miscmodels.bind - if completion == usertypes.Completion.section: - return configmodel.section - if completion == usertypes.Completion.option: - return configmodel.option - if completion == usertypes.Completion.value: - return configmodel.value - if completion == usertypes.Completion.url: - return urlmodel.url - - -def init(): - pass diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index bb52a2417..dd71f5333 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -43,7 +43,7 @@ from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.utils import (message, objreg, utils, standarddir, log, qtutils, error, usertypes) from qutebrowser.misc import objects -from qutebrowser.utils.usertypes import Completion +from qutebrowser.completion.models import configmodel UNSET = object() @@ -794,9 +794,9 @@ class ConfigManager(QObject): e.__class__.__name__, e)) @cmdutils.register(name='set', instance='config', star_args_optional=True) - @cmdutils.argument('section_', completion=Completion.section) - @cmdutils.argument('option', completion=Completion.option) - @cmdutils.argument('values', completion=Completion.value) + @cmdutils.argument('section_', completion=configmodel.section) + @cmdutils.argument('option', completion=configmodel.option) + @cmdutils.argument('values', completion=configmodel.value) @cmdutils.argument('win_id', win_id=True) def set_command(self, win_id, section_=None, option=None, *values, temp=False, print_=False): diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index 53f23d7c0..e4adb8676 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -27,7 +27,8 @@ from PyQt5.QtCore import pyqtSignal, QObject from qutebrowser.config import configdata, textwrapper from qutebrowser.commands import cmdutils, cmdexc -from qutebrowser.utils import log, utils, qtutils, message, usertypes +from qutebrowser.utils import log, utils, qtutils, message +from qutebrowser.completion.models import miscmodels class KeyConfigError(Exception): @@ -153,7 +154,7 @@ class KeyConfigParser(QObject): @cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True, no_replace_variables=True) - @cmdutils.argument('command', completion=usertypes.Completion.bind) + @cmdutils.argument('command', completion=miscmodels.bind) def bind(self, key, command=None, *, mode='normal', force=False): """Bind a key to a command. diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py index aa9a1be97..124b78053 100644 --- a/qutebrowser/misc/sessions.py +++ b/qutebrowser/misc/sessions.py @@ -31,10 +31,11 @@ try: except ImportError: # pragma: no cover from yaml import SafeLoader as YamlLoader, SafeDumper as YamlDumper -from qutebrowser.utils import (standarddir, objreg, qtutils, log, usertypes, - message, utils) +from qutebrowser.utils import (standarddir, objreg, qtutils, log, message, + utils) from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.config import config +from qutebrowser.completion.models import miscmodels default = object() # Sentinel value @@ -433,7 +434,7 @@ class SessionManager(QObject): return sessions @cmdutils.register(instance='session-manager') - @cmdutils.argument('name', completion=usertypes.Completion.sessions) + @cmdutils.argument('name', completion=miscmodels.session) def session_load(self, name, clear=False, temp=False, force=False): """Load a session. @@ -461,7 +462,7 @@ class SessionManager(QObject): win.close() @cmdutils.register(name=['session-save', 'w'], instance='session-manager') - @cmdutils.argument('name', completion=usertypes.Completion.sessions) + @cmdutils.argument('name', completion=miscmodels.session) @cmdutils.argument('win_id', win_id=True) @cmdutils.argument('with_private', flag='p') def session_save(self, name: str = default, current=False, quiet=False, @@ -500,7 +501,7 @@ class SessionManager(QObject): message.info("Saved session {}.".format(name)) @cmdutils.register(instance='session-manager') - @cmdutils.argument('name', completion=usertypes.Completion.sessions) + @cmdutils.argument('name', completion=miscmodels.session) def session_delete(self, name, force=False): """Delete a session. diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 7d31ba6ac..31f2f79cb 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -236,13 +236,6 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', 'jump_mark', 'record_macro', 'run_macro']) -# Available command completions -Completion = enum('Completion', ['command', 'section', 'option', 'value', - 'helptopic', 'quickmark_by_name', - 'bookmark_by_url', 'url', 'tab', 'sessions', - 'bind']) - - # Exit statuses for errors. Needs to be an int for sys.exit. Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init', 'err_config', 'err_key_config'], is_int=True, start=0) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 937107f75..48f619104 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -26,7 +26,6 @@ from PyQt5.QtCore import QObject from PyQt5.QtGui import QStandardItemModel from qutebrowser.completion import completer -from qutebrowser.utils import usertypes from qutebrowser.commands import command, cmdutils @@ -70,30 +69,45 @@ def completer_obj(qtbot, status_command_stub, config_stub, monkeypatch, stubs, @pytest.fixture(autouse=True) -def instances(monkeypatch): - """Mock the instances module so get returns a fake completion model.""" - # populate a model for each completion type, with a nested structure for - # option and value completion - get = lambda kind: lambda *args: FakeCompletionModel(kind, *args) - monkeypatch.setattr(completer, 'instances', get) +def miscmodels_patch(mocker): + """Patch the miscmodels module to provide fake completion functions. + + Technically some of these are not part of miscmodels, but rolling them into + one module is easier and sufficient for mocking. The only one referenced + directly by Completer is miscmodels.command. + """ + m = mocker.patch('qutebrowser.completion.completer.miscmodels', + autospec=True) + m.command = lambda *args: FakeCompletionModel('command', *args) + m.helptopic = lambda *args: FakeCompletionModel('helptopic', *args) + m.quickmark = lambda *args: FakeCompletionModel('quickmark', *args) + m.bookmark = lambda *args: FakeCompletionModel('bookmark', *args) + m.session = lambda *args: FakeCompletionModel('session', *args) + m.buffer = lambda *args: FakeCompletionModel('buffer', *args) + m.bind = lambda *args: FakeCompletionModel('bind', *args) + m.url = lambda *args: FakeCompletionModel('url', *args) + m.section = lambda *args: FakeCompletionModel('section', *args) + m.option = lambda *args: FakeCompletionModel('option', *args) + m.value = lambda *args: FakeCompletionModel('value', *args) + return m @pytest.fixture(autouse=True) -def cmdutils_patch(monkeypatch, stubs): +def cmdutils_patch(monkeypatch, stubs, miscmodels_patch): """Patch the cmdutils module to provide fake commands.""" - @cmdutils.argument('section_', completion=usertypes.Completion.section) - @cmdutils.argument('option', completion=usertypes.Completion.option) - @cmdutils.argument('value', completion=usertypes.Completion.value) + @cmdutils.argument('section_', completion=miscmodels_patch.section) + @cmdutils.argument('option', completion=miscmodels_patch.option) + @cmdutils.argument('value', completion=miscmodels_patch.value) def set_command(section_=None, option=None, value=None): """docstring.""" pass - @cmdutils.argument('topic', completion=usertypes.Completion.helptopic) + @cmdutils.argument('topic', completion=miscmodels_patch.helptopic) def show_help(tab=False, bg=False, window=False, topic=None): """docstring.""" pass - @cmdutils.argument('url', completion=usertypes.Completion.url) + @cmdutils.argument('url', completion=miscmodels_patch.url) @cmdutils.argument('count', count=True) def openurl(url=None, implicit=False, bg=False, tab=False, window=False, count=None): @@ -101,7 +115,7 @@ def cmdutils_patch(monkeypatch, stubs): pass @cmdutils.argument('win_id', win_id=True) - @cmdutils.argument('command', completion=usertypes.Completion.command) + @cmdutils.argument('command', completion=miscmodels_patch.command) def bind(key, win_id, command=None, *, mode='normal', force=False): """docstring.""" pass @@ -132,49 +146,45 @@ def _set_cmd_prompt(cmd, txt): @pytest.mark.parametrize('txt, kind, pattern, pos_args', [ - (':nope|', usertypes.Completion.command, 'nope', []), + (':nope|', 'command', 'nope', []), (':nope |', None, '', []), - (':set |', usertypes.Completion.section, '', []), - (':set gen|', usertypes.Completion.section, 'gen', []), - (':set general |', usertypes.Completion.option, '', ['general']), - (':set what |', usertypes.Completion.option, '', ['what']), - (':set general editor |', usertypes.Completion.value, '', + (':set |', 'section', '', []), + (':set gen|', 'section', 'gen', []), + (':set general |', 'option', '', ['general']), + (':set what |', 'option', '', ['what']), + (':set general editor |', 'value', '', ['general', 'editor']), + (':set general editor gv|', 'value', 'gv', ['general', 'editor']), + (':set general editor "gvim -f"|', 'value', 'gvim -f', ['general', 'editor']), - (':set general editor gv|', usertypes.Completion.value, 'gv', - ['general', 'editor']), - (':set general editor "gvim -f"|', usertypes.Completion.value, 'gvim -f', - ['general', 'editor']), - (':set general editor "gvim |', usertypes.Completion.value, 'gvim', - ['general', 'editor']), - (':set general huh |', usertypes.Completion.value, '', ['general', 'huh']), - (':help |', usertypes.Completion.helptopic, '', []), - (':help |', usertypes.Completion.helptopic, '', []), - (':open |', usertypes.Completion.url, '', []), + (':set general editor "gvim |', 'value', 'gvim', ['general', 'editor']), + (':set general huh |', 'value', '', ['general', 'huh']), + (':help |', 'helptopic', '', []), + (':help |', 'helptopic', '', []), + (':open |', 'url', '', []), (':bind |', None, '', []), - (':bind |', usertypes.Completion.command, '', ['']), - (':bind foo|', usertypes.Completion.command, 'foo', ['']), + (':bind |', 'command', '', ['']), + (':bind foo|', 'command', 'foo', ['']), (':bind | foo', None, '', []), - (':set| general ', usertypes.Completion.command, 'set', []), - (':|set general ', usertypes.Completion.command, 'set', []), - (':set gene|ral ignore-case', usertypes.Completion.section, 'general', []), - (':|', usertypes.Completion.command, '', []), - (': |', usertypes.Completion.command, '', []), + (':set| general ', 'command', 'set', []), + (':|set general ', 'command', 'set', []), + (':set gene|ral ignore-case', 'section', 'general', []), + (':|', 'command', '', []), + (': |', 'command', '', []), ('/|', None, '', []), (':open -t|', None, '', []), (':open --tab|', None, '', []), - (':open -t |', usertypes.Completion.url, '', []), - (':open --tab |', usertypes.Completion.url, '', []), - (':open | -t', usertypes.Completion.url, '', []), + (':open -t |', 'url', '', []), + (':open --tab |', 'url', '', []), + (':open | -t', 'url', '', []), (':tab-detach |', None, '', []), - (':bind --mode=caret |', usertypes.Completion.command, '', - ['']), - pytest.param(':bind --mode caret |', usertypes.Completion.command, - '', [], marks=pytest.mark.xfail(reason='issue #74')), - (':set -t -p |', usertypes.Completion.section, '', []), + (':bind --mode=caret |', 'command', '', ['']), + pytest.param(':bind --mode caret |', 'command', '', [], + marks=pytest.mark.xfail(reason='issue #74')), + (':set -t -p |', 'section', '', []), (':open -- |', None, '', []), (':gibberish nonesense |', None, '', []), ('/:help|', None, '', []), - ('::bind|', usertypes.Completion.command, ':bind'), + ('::bind|', 'command', ':bind', []), ]) def test_update_completion(txt, kind, pattern, pos_args, status_command_stub, completer_obj, completion_widget_stub):