Merge remote-tracking branch 'origin/pr/3790'
This commit is contained in:
commit
89a1c43b4c
@ -123,6 +123,7 @@ class Command:
|
|||||||
self.pos_args = []
|
self.pos_args = []
|
||||||
self.desc = None
|
self.desc = None
|
||||||
self.flags_with_args = []
|
self.flags_with_args = []
|
||||||
|
self._has_vararg = False
|
||||||
|
|
||||||
# This is checked by future @cmdutils.argument calls so they fail
|
# This is checked by future @cmdutils.argument calls so they fail
|
||||||
# (as they'd be silently ignored otherwise)
|
# (as they'd be silently ignored otherwise)
|
||||||
@ -170,6 +171,8 @@ class Command:
|
|||||||
|
|
||||||
def get_pos_arg_info(self, pos):
|
def get_pos_arg_info(self, pos):
|
||||||
"""Get an ArgInfo tuple for the given positional parameter."""
|
"""Get an ArgInfo tuple for the given positional parameter."""
|
||||||
|
if pos >= len(self.pos_args) and self._has_vararg:
|
||||||
|
pos = len(self.pos_args) - 1
|
||||||
name = self.pos_args[pos][0]
|
name = self.pos_args[pos][0]
|
||||||
return self._qute_args.get(name, ArgInfo())
|
return self._qute_args.get(name, ArgInfo())
|
||||||
|
|
||||||
@ -233,6 +236,8 @@ class Command:
|
|||||||
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
log.commands.vdebug('Adding arg {} of type {} -> {}'.format(
|
||||||
param.name, typ, callsig))
|
param.name, typ, callsig))
|
||||||
self.parser.add_argument(*args, **kwargs)
|
self.parser.add_argument(*args, **kwargs)
|
||||||
|
if param.kind == inspect.Parameter.VAR_POSITIONAL:
|
||||||
|
self._has_vararg = True
|
||||||
return signature.parameters.values()
|
return signature.parameters.values()
|
||||||
|
|
||||||
def _param_to_argparse_kwargs(self, param, is_bool):
|
def _param_to_argparse_kwargs(self, param, is_bool):
|
||||||
|
@ -49,7 +49,7 @@ class Completer(QObject):
|
|||||||
_last_cursor_pos: The old cursor position so we avoid double completion
|
_last_cursor_pos: The old cursor position so we avoid double completion
|
||||||
updates.
|
updates.
|
||||||
_last_text: The old command text 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):
|
def __init__(self, *, cmd, win_id, parent=None):
|
||||||
@ -62,7 +62,7 @@ class Completer(QObject):
|
|||||||
self._timer.timeout.connect(self._update_completion)
|
self._timer.timeout.connect(self._update_completion)
|
||||||
self._last_cursor_pos = -1
|
self._last_cursor_pos = -1
|
||||||
self._last_text = None
|
self._last_text = None
|
||||||
self._last_completion_func = None
|
self._last_before_cursor = None
|
||||||
self._cmd.update_completion.connect(self.schedule_completion_update)
|
self._cmd.update_completion.connect(self.schedule_completion_update)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -228,7 +228,7 @@ class Completer(QObject):
|
|||||||
# FIXME complete searches
|
# FIXME complete searches
|
||||||
# https://github.com/qutebrowser/qutebrowser/issues/32
|
# https://github.com/qutebrowser/qutebrowser/issues/32
|
||||||
completion.set_model(None)
|
completion.set_model(None)
|
||||||
self._last_completion_func = None
|
self._last_before_cursor = None
|
||||||
return
|
return
|
||||||
|
|
||||||
before_cursor, pattern, after_cursor = self._partition()
|
before_cursor, pattern, after_cursor = self._partition()
|
||||||
@ -242,11 +242,11 @@ class Completer(QObject):
|
|||||||
if func is None:
|
if func is None:
|
||||||
log.completion.debug('Clearing completion')
|
log.completion.debug('Clearing completion')
|
||||||
completion.set_model(None)
|
completion.set_model(None)
|
||||||
self._last_completion_func = None
|
self._last_before_cursor = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if func != self._last_completion_func:
|
if before_cursor != self._last_before_cursor:
|
||||||
self._last_completion_func = func
|
self._last_before_cursor = before_cursor
|
||||||
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, 'Starting {} completion'
|
with debug.log_time(log.completion, 'Starting {} completion'
|
||||||
.format(func.__name__)):
|
.format(func.__name__)):
|
||||||
|
@ -47,12 +47,12 @@ def customized_option(*, info):
|
|||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def value(optname, *_values, info):
|
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.
|
info: A CompletionInfo instance.
|
||||||
"""
|
"""
|
||||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||||
@ -64,13 +64,18 @@ def value(optname, *_values, info):
|
|||||||
|
|
||||||
opt = info.config.get_opt(optname)
|
opt = info.config.get_opt(optname)
|
||||||
default = opt.typ.to_str(opt.default)
|
default = opt.typ.to_str(opt.default)
|
||||||
cur_cat = listcategory.ListCategory(
|
cur_def = []
|
||||||
"Current/Default",
|
if current not in values:
|
||||||
[(current, "Current value"), (default, "Default value")])
|
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)
|
model.add_category(cur_cat)
|
||||||
|
|
||||||
vals = opt.typ.complete()
|
vals = opt.typ.complete() or []
|
||||||
if vals is not None:
|
vals = [x for x in vals if x[0] not in values]
|
||||||
|
if vals:
|
||||||
model.add_category(listcategory.ListCategory("Completions", vals))
|
model.add_category(listcategory.ListCategory("Completions", vals))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
@ -129,12 +129,20 @@ def cmdutils_patch(monkeypatch, stubs, miscmodels_patch):
|
|||||||
"""docstring."""
|
"""docstring."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@cmdutils.argument('option', completion=miscmodels_patch.option)
|
||||||
|
@cmdutils.argument('values', completion=miscmodels_patch.value)
|
||||||
|
def config_cycle(option, *values):
|
||||||
|
"""For testing varargs."""
|
||||||
|
pass
|
||||||
|
|
||||||
cmd_utils = stubs.FakeCmdUtils({
|
cmd_utils = stubs.FakeCmdUtils({
|
||||||
'set': command.Command(name='set', handler=set_command),
|
'set': command.Command(name='set', handler=set_command),
|
||||||
'help': command.Command(name='help', handler=show_help),
|
'help': command.Command(name='help', handler=show_help),
|
||||||
'open': command.Command(name='open', handler=openurl, maxsplit=0),
|
'open': command.Command(name='open', handler=openurl, maxsplit=0),
|
||||||
'bind': command.Command(name='bind', handler=bind),
|
'bind': command.Command(name='bind', handler=bind),
|
||||||
'tab-detach': command.Command(name='tab-detach', handler=tab_detach),
|
'tab-detach': command.Command(name='tab-detach', handler=tab_detach),
|
||||||
|
'config-cycle': command.Command(name='config-cycle',
|
||||||
|
handler=config_cycle),
|
||||||
})
|
})
|
||||||
monkeypatch.setattr(completer, 'cmdutils', cmd_utils)
|
monkeypatch.setattr(completer, 'cmdutils', cmd_utils)
|
||||||
|
|
||||||
@ -191,6 +199,10 @@ def _set_cmd_prompt(cmd, txt):
|
|||||||
('/:help|', None, '', []),
|
('/:help|', None, '', []),
|
||||||
('::bind|', 'command', ':bind', []),
|
('::bind|', 'command', ':bind', []),
|
||||||
(':-w open |', None, '', []),
|
(':-w open |', None, '', []),
|
||||||
|
# varargs
|
||||||
|
(':config-cycle option |', 'value', '', ['option']),
|
||||||
|
(':config-cycle option one |', 'value', '', ['option', 'one']),
|
||||||
|
(':config-cycle option one two |', 'value', '', ['option', 'one', 'two']),
|
||||||
])
|
])
|
||||||
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, config_stub,
|
completer_obj, completion_widget_stub, config_stub,
|
||||||
@ -211,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)
|
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', [
|
@pytest.mark.parametrize('before, newtxt, after', [
|
||||||
(':|', 'set', ':set|'),
|
(':|', 'set', ':set|'),
|
||||||
(':| ', 'set', ':set|'),
|
(':| ', 'set', ':set|'),
|
||||||
|
@ -739,6 +739,44 @@ def test_setting_value_completion_invalid(info):
|
|||||||
assert configmodel.value(optname='foobarbaz', info=info) is None
|
assert configmodel.value(optname='foobarbaz', info=info) is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('args, expected', [
|
||||||
|
([], {
|
||||||
|
"Current/Default": [
|
||||||
|
('true', 'Current value', None),
|
||||||
|
('true', 'Default value', None),
|
||||||
|
],
|
||||||
|
"Completions": [
|
||||||
|
('false', '', None),
|
||||||
|
('true', '', None),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
(['false'], {
|
||||||
|
"Current/Default": [
|
||||||
|
('true', 'Current value', None),
|
||||||
|
('true', 'Default value', None),
|
||||||
|
],
|
||||||
|
"Completions": [
|
||||||
|
('true', '', None),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
(['true'], {
|
||||||
|
"Completions": [
|
||||||
|
('false', '', None),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
(['false', 'true'], {}),
|
||||||
|
])
|
||||||
|
def test_setting_value_cycle(qtmodeltester, config_stub, configdata_stub,
|
||||||
|
info, args, expected):
|
||||||
|
opt = 'content.javascript.enabled'
|
||||||
|
|
||||||
|
model = configmodel.value(opt, *args, info=info)
|
||||||
|
model.set_pattern('')
|
||||||
|
qtmodeltester.data_display_may_return_none = True
|
||||||
|
qtmodeltester.check(model)
|
||||||
|
_check_completions(model, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_bind_completion(qtmodeltester, cmdutils_stub, config_stub,
|
def test_bind_completion(qtmodeltester, cmdutils_stub, config_stub,
|
||||||
key_config_stub, configdata_stub, info):
|
key_config_stub, configdata_stub, info):
|
||||||
"""Test the results of keybinding command completion.
|
"""Test the results of keybinding command completion.
|
||||||
|
Loading…
Reference in New Issue
Block a user