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."""
import collections
from PyQt5.QtCore import pyqtSlot, QObject, QTimer
from qutebrowser.config import config
@ -27,6 +29,11 @@ from qutebrowser.utils import log, utils, debug
from qutebrowser.completion.models import miscmodels
# Context passed into all completion functions
CompletionInfo = collections.namedtuple('CompletionInfo',
['config', 'keyconf'])
class Completer(QObject):
"""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('-'))
with debug.log_time(log.completion,
'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'):
completion.set_model(model)

View File

@ -19,31 +19,32 @@
"""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.commands import cmdutils
def option():
def option(*, info):
"""A CompletionModel filled with settings and their descriptions."""
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())
model.add_category(listcategory.ListCategory("Options", sorted(options)))
return model
def value(optname, *_values):
def value(optname, *_values, info):
"""A CompletionModel filled with setting values.
Args:
optname: The name of the config option this model shows.
_values: The values already provided on the command line.
info: A CompletionInfo instance.
"""
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
try:
current = str(config.instance.get(optname) or '""')
current = str(info.config.get(optname) or '""')
except configexc.NoOptionError:
return None
@ -60,14 +61,14 @@ def value(optname, *_values):
return model
def bind(key):
def bind(key, *, info):
"""A CompletionModel filled with all bindable commands and descriptions.
Args:
key: the key being bound.
"""
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:
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
def command():
def command(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with non-hidden commands and descriptions."""
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
cmdlist = util.get_cmd_completions(include_aliases=True,
@ -33,7 +33,7 @@ def command():
return model
def helptopic():
def helptopic(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with help topics."""
model = completionmodel.CompletionModel()
@ -47,7 +47,7 @@ def helptopic():
return model
def quickmark():
def quickmark(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with all quickmarks."""
def delete(data):
"""Delete a quickmark from the completion menu."""
@ -63,7 +63,7 @@ def quickmark():
return model
def bookmark():
def bookmark(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with all bookmarks."""
def delete(data):
"""Delete a bookmark from the completion menu."""
@ -79,7 +79,7 @@ def bookmark():
return model
def session():
def session(*, info=None): # pylint: disable=unused-argument
"""A CompletionModel filled with session names."""
model = completionmodel.CompletionModel()
try:
@ -92,7 +92,7 @@ def session():
return model
def buffer():
def buffer(*, info=None): # pylint: disable=unused-argument
"""A model to complete on open tabs across all windows.
Used for switching the buffer command.

View File

@ -22,7 +22,6 @@
from qutebrowser.completion.models import (completionmodel, listcategory,
histcategory)
from qutebrowser.utils import log, objreg
from qutebrowser.config import config
_URLCOL = 0
@ -50,7 +49,7 @@ def _delete_quickmark(data):
quickmark_manager.delete(name)
def url():
def url(*, info):
"""A model which combines bookmarks, quickmarks and web history URLs.
Used for the `open` command.
@ -66,7 +65,7 @@ def url():
model.add_category(listcategory.ListCategory(
'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)
model.add_category(hist_cat)
return model

View File

@ -33,10 +33,11 @@ class FakeCompletionModel(QStandardItemModel):
"""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)
self.kind = kind
self.pos_args = list(pos_args)
self.info = info
class CompletionWidgetStub(QObject):
@ -77,17 +78,21 @@ def miscmodels_patch(mocker):
"""
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)
def func(name):
return lambda *args, info: FakeCompletionModel(name, *args, info=info)
m.command = func('command')
m.helptopic = func('helptopic')
m.quickmark = func('quickmark')
m.bookmark = func('bookmark')
m.session = func('session')
m.buffer = func('buffer')
m.bind = func('bind')
m.url = func('url')
m.section = func('section')
m.option = func('option')
m.value = func('value')
return m
@ -186,7 +191,8 @@ def _set_cmd_prompt(cmd, txt):
('::bind|', 'command', ':bind', []),
])
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."""
# this test uses | as a placeholder for the current cursor position
_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]
assert model.kind == kind
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)

View File

@ -27,6 +27,7 @@ from datetime import datetime
import pytest
from PyQt5.QtCore import QUrl
from qutebrowser.completion import completer
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.config import configdata, configtypes
from qutebrowser.utils import objreg
@ -181,6 +182,12 @@ def web_history_populated(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,
key_config_stub):
"""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,
quickmarks, bookmarks):
quickmarks, bookmarks, info):
"""Test the results of url completion.
Verify that:
@ -318,7 +325,7 @@ def test_url_completion(qtmodeltester, web_history_populated,
- entries are sorted by access time
- only the most recent entry is included for each url
"""
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -361,20 +368,20 @@ def test_url_completion(qtmodeltester, web_history_populated,
('foobar', '', '%', 0),
])
def test_url_completion_pattern(web_history, quickmark_manager_stub,
bookmark_manager_stub, url, title, pattern,
rowcount):
bookmark_manager_stub, info,
url, title, pattern, rowcount):
"""Test that url completion filters by url and title."""
web_history.add_url(QUrl(url), title)
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern(pattern)
# 2, 0 is History
assert model.rowCount(model.index(2, 0)) == rowcount
def test_url_completion_delete_bookmark(qtmodeltester, bookmarks,
web_history, quickmarks):
web_history, quickmarks, info):
"""Test deleting a bookmark from the url completion model."""
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -393,11 +400,10 @@ def test_url_completion_delete_bookmark(qtmodeltester, bookmarks,
assert len_before == len(bookmarks.marks) + 1
def test_url_completion_delete_quickmark(qtmodeltester,
quickmarks, web_history, bookmarks,
qtbot):
def test_url_completion_delete_quickmark(qtmodeltester, info, qtbot,
quickmarks, web_history, bookmarks):
"""Test deleting a bookmark from the url completion model."""
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -416,11 +422,11 @@ def test_url_completion_delete_quickmark(qtmodeltester,
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,
quickmarks, bookmarks):
"""Test deleting a history entry."""
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -437,11 +443,11 @@ def test_url_completion_delete_history(qtmodeltester,
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):
"""Make sure there's no history if the limit was set to zero."""
config_stub.val.completion.web_history_max_items = 0
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern('')
category = model.index(2, 0) # "History" normally
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,
configdata_stub):
model = configmodel.option()
configdata_stub, info):
model = configmodel.option(info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
@ -536,7 +542,7 @@ def test_setting_option_completion(qtmodeltester, 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.
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
- aliases are included
"""
model = configmodel.bind('ZQ')
model = configmodel.bind('ZQ', info=info)
model.set_pattern('')
qtmodeltester.data_display_may_return_none = True
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,
bookmark_manager_stub,
web_history):
@ -586,7 +592,7 @@ def test_url_completion_benchmark(benchmark,
for i in range(1000)])
def bench():
model = urlmodel.url()
model = urlmodel.url(info=info)
model.set_pattern('')
model.set_pattern('e')
model.set_pattern('ex')