diff --git a/qutebrowser/completion/models/instances.py b/qutebrowser/completion/models/instances.py index d3b49c0ec..6359d3771 100644 --- a/qutebrowser/completion/models/instances.py +++ b/qutebrowser/completion/models/instances.py @@ -114,6 +114,13 @@ def init_session_completion(): _instances[usertypes.Completion.sessions] = model +def _init_bind_completion(): + """Initialize the command completion model.""" + log.completion.debug("Initializing bind completion.") + model = miscmodels.BindCompletionModel() + _instances[usertypes.Completion.bind] = model + + INITIALIZERS = { usertypes.Completion.command: _init_command_completion, usertypes.Completion.helptopic: _init_helptopic_completion, @@ -125,6 +132,7 @@ INITIALIZERS = { usertypes.Completion.quickmark_by_name: init_quickmark_completions, usertypes.Completion.bookmark_by_url: init_bookmark_completions, usertypes.Completion.sessions: init_session_completion, + usertypes.Completion.bind: _init_bind_completion, } @@ -182,5 +190,7 @@ def init(): keyconf = objreg.get('key-config') keyconf.changed.connect( functools.partial(update, [usertypes.Completion.command])) + keyconf.changed.connect( + functools.partial(update, [usertypes.Completion.bind])) objreg.get('config').changed.connect(_update_aliases) diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 56d436079..68ba40839 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -30,7 +30,7 @@ from qutebrowser.completion.models import base class CommandCompletionModel(base.BaseCompletionModel): - """A CompletionModel filled with all commands and descriptions.""" + """A CompletionModel filled with non-hidden commands and descriptions.""" # https://github.com/The-Compiler/qutebrowser/issues/545 # pylint: disable=abstract-method @@ -259,3 +259,33 @@ class TabCompletionModel(base.BaseCompletionModel): tabbed_browser = objreg.get('tabbed-browser', scope='window', window=int(win_id)) tabbed_browser.on_tab_close_requested(int(tab_index) - 1) + + +class BindCompletionModel(base.BaseCompletionModel): + + """A CompletionModel filled with all bindable commands and descriptions.""" + + # https://github.com/The-Compiler/qutebrowser/issues/545 + # pylint: disable=abstract-method + + COLUMN_WIDTHS = (20, 60, 20) + + def __init__(self, parent=None): + super().__init__(parent) + assert cmdutils.cmd_dict + cmdlist = [] + for obj in set(cmdutils.cmd_dict.values()): + if ((obj.debug and not objreg.get('args').debug) or + obj.deprecated): + pass + else: + cmdlist.append((obj.name, obj.desc)) + for name, cmd in config.section('aliases').items(): + cmdlist.append((name, "Alias for '{}'".format(cmd))) + cat = self.new_category("Commands") + + # map each command to its bound keys and show these in the misc column + key_config = objreg.get('key-config') + cmd_to_keys = key_config.get_reverse_bindings_for('normal') + for (name, desc) in sorted(cmdlist): + self.new_item(cat, name, desc, ', '.join(cmd_to_keys[name])) diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index 2eebd6bd9..0a88a45bb 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -153,7 +153,7 @@ class KeyConfigParser(QObject): @cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) - @cmdutils.argument('command', completion=usertypes.Completion.command) + @cmdutils.argument('command', completion=usertypes.Completion.bind) def bind(self, key, win_id, command=None, *, mode='normal', force=False): """Bind a key to a command. diff --git a/qutebrowser/utils/usertypes.py b/qutebrowser/utils/usertypes.py index 1cd1f0385..ea7662e41 100644 --- a/qutebrowser/utils/usertypes.py +++ b/qutebrowser/utils/usertypes.py @@ -238,7 +238,8 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt', # Available command completions Completion = enum('Completion', ['command', 'section', 'option', 'value', 'helptopic', 'quickmark_by_name', - 'bookmark_by_url', 'url', 'tab', 'sessions']) + 'bookmark_by_url', 'url', 'tab', 'sessions', + 'bind']) # Exit statuses for errors. Needs to be an int for sys.exit. diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 3f85c0eff..c4fc82830 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -448,3 +448,34 @@ def test_setting_value_completion(qtmodeltester, monkeypatch, stubs, ('11', '', ''), ]) ] + + +def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub, + key_config_stub): + """Test the results of command completion. + + Validates that: + - only non-hidden and non-deprecated commands are included + - commands are sorted by name + - the command description is shown in the desc column + - the binding (if any) is shown in the misc column + - aliases are included + """ + _patch_cmdutils(monkeypatch, stubs, + 'qutebrowser.completion.models.miscmodels.cmdutils') + config_stub.data['aliases'] = {'rock': 'roll'} + key_config_stub.set_bindings_for('normal', {'s': 'stop', 'rr': 'roll'}) + model = miscmodels.BindCompletionModel() + qtmodeltester.data_display_may_return_none = True + qtmodeltester.check(model) + + actual = _get_completions(model) + assert actual == [ + ("Commands", [ + ('drop', 'drop all user data', ''), + ('hide', '', ''), + ('rock', "Alias for 'roll'", ''), + ('roll', 'never gonna give you up', 'rr'), + ('stop', 'stop qutebrowser', 's') + ]) + ]