Regen completion if args change.
Allow completion functions to react dynamically to args as the user inputs them. This allows config-cycle to filter out values that were already provided. Args provided after the maxsplit do not cause the completion to regen. For example, successive words typed after `:open` just set the filter pattern and do not spuriously regenerate the completion model.
This commit is contained in:
parent
f237a87ad0
commit
707fc1176d
@ -49,7 +49,7 @@ class Completer(QObject):
|
||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||
updates.
|
||||
_last_text: The old command text so we avoid double completion updates.
|
||||
_last_completion_func: The completion function used for the last text.
|
||||
_last_before_cursor: The prior value of before_cursor.
|
||||
"""
|
||||
|
||||
def __init__(self, *, cmd, win_id, parent=None):
|
||||
@ -62,7 +62,7 @@ class Completer(QObject):
|
||||
self._timer.timeout.connect(self._update_completion)
|
||||
self._last_cursor_pos = -1
|
||||
self._last_text = None
|
||||
self._last_completion_func = None
|
||||
self._last_before_cursor = None
|
||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||
|
||||
def __repr__(self):
|
||||
@ -228,7 +228,7 @@ class Completer(QObject):
|
||||
# FIXME complete searches
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/32
|
||||
completion.set_model(None)
|
||||
self._last_completion_func = None
|
||||
self._last_before_cursor = None
|
||||
return
|
||||
|
||||
before_cursor, pattern, after_cursor = self._partition()
|
||||
@ -242,11 +242,11 @@ class Completer(QObject):
|
||||
if func is None:
|
||||
log.completion.debug('Clearing completion')
|
||||
completion.set_model(None)
|
||||
self._last_completion_func = None
|
||||
self._last_before_cursor = None
|
||||
return
|
||||
|
||||
if func != self._last_completion_func:
|
||||
self._last_completion_func = func
|
||||
if before_cursor != self._last_before_cursor:
|
||||
self._last_before_cursor = before_cursor
|
||||
args = (x for x in before_cursor[1:] if not x.startswith('-'))
|
||||
with debug.log_time(log.completion, 'Starting {} completion'
|
||||
.format(func.__name__)):
|
||||
|
@ -47,12 +47,12 @@ def customized_option(*, info):
|
||||
return model
|
||||
|
||||
|
||||
def value(optname, *_values, info):
|
||||
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.
|
||||
values: The values already provided on the command line.
|
||||
info: A CompletionInfo instance.
|
||||
"""
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
@ -64,13 +64,18 @@ def value(optname, *_values, info):
|
||||
|
||||
opt = info.config.get_opt(optname)
|
||||
default = opt.typ.to_str(opt.default)
|
||||
cur_cat = listcategory.ListCategory(
|
||||
"Current/Default",
|
||||
[(current, "Current value"), (default, "Default value")])
|
||||
model.add_category(cur_cat)
|
||||
cur_def = []
|
||||
if current not in values:
|
||||
cur_def.append((current, "Current value"))
|
||||
if default not in values:
|
||||
cur_def.append((default, "Default value"))
|
||||
if cur_def:
|
||||
cur_cat = listcategory.ListCategory("Current/Default", cur_def)
|
||||
model.add_category(cur_cat)
|
||||
|
||||
vals = opt.typ.complete()
|
||||
if vals is not None:
|
||||
vals = opt.typ.complete() or []
|
||||
vals = [x for x in vals if x[0] not in values]
|
||||
if vals:
|
||||
model.add_category(listcategory.ListCategory("Completions", vals))
|
||||
return model
|
||||
|
||||
|
@ -223,6 +223,32 @@ def test_update_completion(txt, kind, pattern, pos_args, status_command_stub,
|
||||
completion_widget_stub.set_pattern.assert_called_once_with(pattern)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('txt1, txt2, regen', [
|
||||
(':config-cycle |', ':config-cycle a|', False),
|
||||
(':config-cycle abc|', ':config-cycle abc |', True),
|
||||
(':config-cycle abc |', ':config-cycle abc d|', False),
|
||||
(':config-cycle abc def|', ':config-cycle abc def |', True),
|
||||
# open has maxsplit=0, so all args just set the pattern, not the model
|
||||
(':open |', ':open a|', False),
|
||||
(':open abc|', ':open abc |', False),
|
||||
(':open abc |', ':open abc d|', False),
|
||||
(':open abc def|', ':open abc def |', False),
|
||||
])
|
||||
def test_regen_completion(txt1, txt2, regen, status_command_stub,
|
||||
completer_obj, completion_widget_stub, config_stub,
|
||||
key_config_stub):
|
||||
"""Test that the completion function is only called as needed."""
|
||||
# set the initial state
|
||||
_set_cmd_prompt(status_command_stub, txt1)
|
||||
completer_obj.schedule_completion_update()
|
||||
completion_widget_stub.set_model.reset_mock()
|
||||
|
||||
# "move" the cursor and check if the completion function was called
|
||||
_set_cmd_prompt(status_command_stub, txt2)
|
||||
completer_obj.schedule_completion_update()
|
||||
assert completion_widget_stub.set_model.called == regen
|
||||
|
||||
|
||||
@pytest.mark.parametrize('before, newtxt, after', [
|
||||
(':|', 'set', ':set|'),
|
||||
(':| ', 'set', ':set|'),
|
||||
|
Loading…
Reference in New Issue
Block a user