Pass CompletionInfo to completion functions.

In python3.4, there is a circular dependency between the config module
and configmodel.bind. This is resolved by dependency injection. The
config/keyconfig instances are embedded in a struct passed to every
completion function, so the functions no longer depend on the modules.

This will also enable completion functions to access other previously
inaccessible info, such as the window id.
See #2814.
This commit is contained in:
Ryan Roden-Corrent 2017-09-04 13:55:30 -04:00
parent 6a292f9d56
commit 3bfa01f0d0
6 changed files with 74 additions and 51 deletions

View File

@ -19,6 +19,8 @@
"""Completer attached to a CompletionView.""" """Completer attached to a CompletionView."""
import collections
from PyQt5.QtCore import pyqtSlot, QObject, QTimer from PyQt5.QtCore import pyqtSlot, QObject, QTimer
from qutebrowser.config import config from qutebrowser.config import config
@ -27,6 +29,11 @@ from qutebrowser.utils import log, utils, debug
from qutebrowser.completion.models import miscmodels from qutebrowser.completion.models import miscmodels
# Context passed into all completion functions
CompletionInfo = collections.namedtuple('CompletionInfo',
['config', 'keyconf'])
class Completer(QObject): class Completer(QObject):
"""Completer which manages completions in a CompletionView. """Completer which manages completions in a CompletionView.
@ -231,7 +238,9 @@ class Completer(QObject):
args = (x for x in before_cursor[1:] if not x.startswith('-')) args = (x for x in before_cursor[1:] if not x.startswith('-'))
with debug.log_time(log.completion, with debug.log_time(log.completion,
'Starting {} completion'.format(func.__name__)): 'Starting {} completion'.format(func.__name__)):
model = func(*args) info = CompletionInfo(config=config.instance,
keyconf=config.key_instance)
model = func(*args, info=info)
with debug.log_time(log.completion, 'Set completion model'): with debug.log_time(log.completion, 'Set completion model'):
completion.set_model(model) completion.set_model(model)

View File

@ -19,31 +19,32 @@
"""Functions that return config-related completion models.""" """Functions that return config-related completion models."""
from qutebrowser.config import configdata, configexc, config from qutebrowser.config import configdata, configexc
from qutebrowser.completion.models import completionmodel, listcategory, util from qutebrowser.completion.models import completionmodel, listcategory, util
from qutebrowser.commands import cmdutils from qutebrowser.commands import cmdutils
def option(): def option(*, info):
"""A CompletionModel filled with settings and their descriptions.""" """A CompletionModel filled with settings and their descriptions."""
model = completionmodel.CompletionModel(column_widths=(20, 70, 10)) model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
options = ((x.name, x.description, config.instance.get_str(x.name)) options = ((x.name, x.description, info.config.get_str(x.name))
for x in configdata.DATA.values()) for x in configdata.DATA.values())
model.add_category(listcategory.ListCategory("Options", sorted(options))) model.add_category(listcategory.ListCategory("Options", sorted(options)))
return model return model
def value(optname, *_values): def value(optname, *_values, info):
"""A CompletionModel filled with setting values. """A CompletionModel filled with setting values.
Args: Args:
optname: The name of the config option this model shows. optname: The name of the config option this model shows.
_values: The values already provided on the command line. _values: The values already provided on the command line.
info: A CompletionInfo instance.
""" """
model = completionmodel.CompletionModel(column_widths=(30, 70, 0)) model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
try: try:
current = str(config.instance.get(optname) or '""') current = str(info.config.get(optname) or '""')
except configexc.NoOptionError: except configexc.NoOptionError:
return None return None
@ -60,14 +61,14 @@ def value(optname, *_values):
return model return model
def bind(key): def bind(key, *, info):
"""A CompletionModel filled with all bindable commands and descriptions. """A CompletionModel filled with all bindable commands and descriptions.
Args: Args:
key: the key being bound. key: the key being bound.
""" """
model = completionmodel.CompletionModel(column_widths=(20, 60, 20)) model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmd_text = config.key_instance.get_bindings_for('normal').get(key) cmd_text = info.keyconf.get_bindings_for('normal').get(key)
if cmd_text: if cmd_text:
cmd_name = cmd_text.split(' ')[0] cmd_name = cmd_text.split(' ')[0]

View File

@ -24,7 +24,7 @@ from qutebrowser.utils import objreg, log
from qutebrowser.completion.models import completionmodel, listcategory, util from qutebrowser.completion.models import completionmodel, listcategory, util
def command(): def command(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with non-hidden commands and descriptions.""" """A CompletionModel filled with non-hidden commands and descriptions."""
model = completionmodel.CompletionModel(column_widths=(20, 60, 20)) model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmdlist = util.get_cmd_completions(include_aliases=True, cmdlist = util.get_cmd_completions(include_aliases=True,
@ -33,7 +33,7 @@ def command():
return model return model
def helptopic(): def helptopic(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with help topics.""" """A CompletionModel filled with help topics."""
model = completionmodel.CompletionModel() model = completionmodel.CompletionModel()
@ -47,7 +47,7 @@ def helptopic():
return model return model
def quickmark(): def quickmark(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with all quickmarks.""" """A CompletionModel filled with all quickmarks."""
def delete(data): def delete(data):
"""Delete a quickmark from the completion menu.""" """Delete a quickmark from the completion menu."""
@ -63,7 +63,7 @@ def quickmark():
return model return model
def bookmark(): def bookmark(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with all bookmarks.""" """A CompletionModel filled with all bookmarks."""
def delete(data): def delete(data):
"""Delete a bookmark from the completion menu.""" """Delete a bookmark from the completion menu."""
@ -79,7 +79,7 @@ def bookmark():
return model return model
def session(): def session(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with session names.""" """A CompletionModel filled with session names."""
model = completionmodel.CompletionModel() model = completionmodel.CompletionModel()
try: try:
@ -92,7 +92,7 @@ def session():
return model return model
def buffer(): def buffer(*, info=None): # pylint: disable=unused-argument
"""A model to complete on open tabs across all windows. """A model to complete on open tabs across all windows.
Used for switching the buffer command. Used for switching the buffer command.

View File

@ -22,7 +22,6 @@
from qutebrowser.completion.models import (completionmodel, listcategory, from qutebrowser.completion.models import (completionmodel, listcategory,
histcategory) histcategory)
from qutebrowser.utils import log, objreg from qutebrowser.utils import log, objreg
from qutebrowser.config import config
_URLCOL = 0 _URLCOL = 0
@ -50,7 +49,7 @@ def _delete_quickmark(data):
quickmark_manager.delete(name) quickmark_manager.delete(name)
def url(): def url(*, info):
"""A model which combines bookmarks, quickmarks and web history URLs. """A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command. Used for the `open` command.
@ -66,7 +65,7 @@ def url():
model.add_category(listcategory.ListCategory( model.add_category(listcategory.ListCategory(
'Bookmarks', bookmarks, delete_func=_delete_bookmark)) 'Bookmarks', bookmarks, delete_func=_delete_bookmark))
if config.val.completion.web_history_max_items != 0: if info.config.get('completion.web_history_max_items') != 0:
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history) hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
model.add_category(hist_cat) model.add_category(hist_cat)
return model return model

View File

@ -33,10 +33,11 @@ class FakeCompletionModel(QStandardItemModel):
"""Stub for a completion model.""" """Stub for a completion model."""
def __init__(self, kind, *pos_args, parent=None): def __init__(self, kind, *pos_args, info, parent=None):
super().__init__(parent) super().__init__(parent)
self.kind = kind self.kind = kind
self.pos_args = list(pos_args) self.pos_args = list(pos_args)
self.info = info
class CompletionWidgetStub(QObject): class CompletionWidgetStub(QObject):
@ -77,17 +78,21 @@ def miscmodels_patch(mocker):
""" """
m = mocker.patch('qutebrowser.completion.completer.miscmodels', m = mocker.patch('qutebrowser.completion.completer.miscmodels',
autospec=True) autospec=True)
m.command = lambda *args: FakeCompletionModel('command', *args)
m.helptopic = lambda *args: FakeCompletionModel('helptopic', *args) def func(name):
m.quickmark = lambda *args: FakeCompletionModel('quickmark', *args) return lambda *args, info: FakeCompletionModel(name, *args, info=info)
m.bookmark = lambda *args: FakeCompletionModel('bookmark', *args)
m.session = lambda *args: FakeCompletionModel('session', *args) m.command = func('command')
m.buffer = lambda *args: FakeCompletionModel('buffer', *args) m.helptopic = func('helptopic')
m.bind = lambda *args: FakeCompletionModel('bind', *args) m.quickmark = func('quickmark')
m.url = lambda *args: FakeCompletionModel('url', *args) m.bookmark = func('bookmark')
m.section = lambda *args: FakeCompletionModel('section', *args) m.session = func('session')
m.option = lambda *args: FakeCompletionModel('option', *args) m.buffer = func('buffer')
m.value = lambda *args: FakeCompletionModel('value', *args) m.bind = func('bind')
m.url = func('url')
m.section = func('section')
m.option = func('option')
m.value = func('value')
return m return m
@ -186,7 +191,8 @@ def _set_cmd_prompt(cmd, txt):
('::bind|', 'command', ':bind', []), ('::bind|', 'command', ':bind', []),
]) ])
def test_update_completion(txt, kind, pattern, pos_args, status_command_stub, def test_update_completion(txt, kind, pattern, pos_args, status_command_stub,
completer_obj, completion_widget_stub): completer_obj, completion_widget_stub, config_stub,
key_config_stub):
"""Test setting the completion widget's model based on command text.""" """Test setting the completion widget's model based on command text."""
# this test uses | as a placeholder for the current cursor position # this test uses | as a placeholder for the current cursor position
_set_cmd_prompt(status_command_stub, txt) _set_cmd_prompt(status_command_stub, txt)
@ -198,6 +204,8 @@ def test_update_completion(txt, kind, pattern, pos_args, status_command_stub,
model = completion_widget_stub.set_model.call_args[0][0] model = completion_widget_stub.set_model.call_args[0][0]
assert model.kind == kind assert model.kind == kind
assert model.pos_args == pos_args assert model.pos_args == pos_args
assert model.info.config == config_stub
assert model.info.keyconf == key_config_stub
completion_widget_stub.set_pattern.assert_called_once_with(pattern) completion_widget_stub.set_pattern.assert_called_once_with(pattern)

View File

@ -27,6 +27,7 @@ from datetime import datetime
import pytest import pytest
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from qutebrowser.completion import completer
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.config import configdata, configtypes from qutebrowser.config import configdata, configtypes
from qutebrowser.utils import objreg from qutebrowser.utils import objreg
@ -181,6 +182,12 @@ def web_history_populated(web_history):
return web_history return web_history
@pytest.fixture
def info(config_stub, key_config_stub):
return completer.CompletionInfo(config=config_stub,
keyconf=key_config_stub)
def test_command_completion(qtmodeltester, cmdutils_stub, configdata_stub, def test_command_completion(qtmodeltester, cmdutils_stub, configdata_stub,
key_config_stub): key_config_stub):
"""Test the results of command completion. """Test the results of command completion.
@ -310,7 +317,7 @@ def test_bookmark_completion_delete(qtmodeltester, bookmarks, row, removed):
def test_url_completion(qtmodeltester, web_history_populated, def test_url_completion(qtmodeltester, web_history_populated,
quickmarks, bookmarks): quickmarks, bookmarks, info):
"""Test the results of url completion. """Test the results of url completion.
Verify that: Verify that:
@ -318,7 +325,7 @@ def test_url_completion(qtmodeltester, web_history_populated,
- entries are sorted by access time - entries are sorted by access time
- only the most recent entry is included for each url - only the most recent entry is included for each url
""" """
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern('') model.set_pattern('')
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
@ -361,20 +368,20 @@ def test_url_completion(qtmodeltester, web_history_populated,
('foobar', '', '%', 0), ('foobar', '', '%', 0),
]) ])
def test_url_completion_pattern(web_history, quickmark_manager_stub, def test_url_completion_pattern(web_history, quickmark_manager_stub,
bookmark_manager_stub, url, title, pattern, bookmark_manager_stub, info,
rowcount): url, title, pattern, rowcount):
"""Test that url completion filters by url and title.""" """Test that url completion filters by url and title."""
web_history.add_url(QUrl(url), title) web_history.add_url(QUrl(url), title)
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern(pattern) model.set_pattern(pattern)
# 2, 0 is History # 2, 0 is History
assert model.rowCount(model.index(2, 0)) == rowcount assert model.rowCount(model.index(2, 0)) == rowcount
def test_url_completion_delete_bookmark(qtmodeltester, bookmarks, def test_url_completion_delete_bookmark(qtmodeltester, bookmarks,
web_history, quickmarks): web_history, quickmarks, info):
"""Test deleting a bookmark from the url completion model.""" """Test deleting a bookmark from the url completion model."""
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern('') model.set_pattern('')
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
@ -393,11 +400,10 @@ def test_url_completion_delete_bookmark(qtmodeltester, bookmarks,
assert len_before == len(bookmarks.marks) + 1 assert len_before == len(bookmarks.marks) + 1
def test_url_completion_delete_quickmark(qtmodeltester, def test_url_completion_delete_quickmark(qtmodeltester, info, qtbot,
quickmarks, web_history, bookmarks, quickmarks, web_history, bookmarks):
qtbot):
"""Test deleting a bookmark from the url completion model.""" """Test deleting a bookmark from the url completion model."""
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern('') model.set_pattern('')
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
@ -416,11 +422,11 @@ def test_url_completion_delete_quickmark(qtmodeltester,
assert len_before == len(quickmarks.marks) + 1 assert len_before == len(quickmarks.marks) + 1
def test_url_completion_delete_history(qtmodeltester, def test_url_completion_delete_history(qtmodeltester, info,
web_history_populated, web_history_populated,
quickmarks, bookmarks): quickmarks, bookmarks):
"""Test deleting a history entry.""" """Test deleting a history entry."""
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern('') model.set_pattern('')
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
@ -437,11 +443,11 @@ def test_url_completion_delete_history(qtmodeltester,
assert 'https://python.org' not in web_history_populated assert 'https://python.org' not in web_history_populated
def test_url_completion_zero_limit(config_stub, web_history, quickmarks, def test_url_completion_zero_limit(config_stub, web_history, quickmarks, info,
bookmarks): bookmarks):
"""Make sure there's no history if the limit was set to zero.""" """Make sure there's no history if the limit was set to zero."""
config_stub.val.completion.web_history_max_items = 0 config_stub.val.completion.web_history_max_items = 0
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern('') model.set_pattern('')
category = model.index(2, 0) # "History" normally category = model.index(2, 0) # "History" normally
assert model.data(category) is None assert model.data(category) is None
@ -518,8 +524,8 @@ def test_tab_completion_delete(qtmodeltester, fake_web_tab, app_stub,
def test_setting_option_completion(qtmodeltester, config_stub, def test_setting_option_completion(qtmodeltester, config_stub,
configdata_stub): configdata_stub, info):
model = configmodel.option() model = configmodel.option(info=info)
model.set_pattern('') model.set_pattern('')
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
@ -536,7 +542,7 @@ def test_setting_option_completion(qtmodeltester, config_stub,
def test_bind_completion(qtmodeltester, cmdutils_stub, config_stub, def test_bind_completion(qtmodeltester, cmdutils_stub, config_stub,
key_config_stub, configdata_stub): key_config_stub, configdata_stub, info):
"""Test the results of keybinding command completion. """Test the results of keybinding command completion.
Validates that: Validates that:
@ -545,7 +551,7 @@ def test_bind_completion(qtmodeltester, cmdutils_stub, config_stub,
- the binding (if any) is shown in the misc column - the binding (if any) is shown in the misc column
- aliases are included - aliases are included
""" """
model = configmodel.bind('ZQ') model = configmodel.bind('ZQ', info=info)
model.set_pattern('') model.set_pattern('')
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
@ -563,7 +569,7 @@ def test_bind_completion(qtmodeltester, cmdutils_stub, config_stub,
}) })
def test_url_completion_benchmark(benchmark, def test_url_completion_benchmark(benchmark, info,
quickmark_manager_stub, quickmark_manager_stub,
bookmark_manager_stub, bookmark_manager_stub,
web_history): web_history):
@ -586,7 +592,7 @@ def test_url_completion_benchmark(benchmark,
for i in range(1000)]) for i in range(1000)])
def bench(): def bench():
model = urlmodel.url() model = urlmodel.url(info=info)
model.set_pattern('') model.set_pattern('')
model.set_pattern('e') model.set_pattern('e')
model.set_pattern('ex') model.set_pattern('ex')