Merge branch 'complete-hidden-commands' of https://github.com/rcorre/qutebrowser into rcorre-complete-hidden-commands

This commit is contained in:
Florian Bruhin 2016-08-10 15:38:46 +02:00
commit f3c32308d3
5 changed files with 176 additions and 109 deletions

View File

@ -114,6 +114,13 @@ def init_session_completion():
_instances[usertypes.Completion.sessions] = model _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 = { INITIALIZERS = {
usertypes.Completion.command: _init_command_completion, usertypes.Completion.command: _init_command_completion,
usertypes.Completion.helptopic: _init_helptopic_completion, usertypes.Completion.helptopic: _init_helptopic_completion,
@ -125,6 +132,7 @@ INITIALIZERS = {
usertypes.Completion.quickmark_by_name: init_quickmark_completions, usertypes.Completion.quickmark_by_name: init_quickmark_completions,
usertypes.Completion.bookmark_by_url: init_bookmark_completions, usertypes.Completion.bookmark_by_url: init_bookmark_completions,
usertypes.Completion.sessions: init_session_completion, usertypes.Completion.sessions: init_session_completion,
usertypes.Completion.bind: _init_bind_completion,
} }
@ -182,5 +190,7 @@ def init():
keyconf = objreg.get('key-config') keyconf = objreg.get('key-config')
keyconf.changed.connect( keyconf.changed.connect(
functools.partial(update, [usertypes.Completion.command])) functools.partial(update, [usertypes.Completion.command]))
keyconf.changed.connect(
functools.partial(update, [usertypes.Completion.bind]))
objreg.get('config').changed.connect(_update_aliases) objreg.get('config').changed.connect(_update_aliases)

View File

@ -30,7 +30,7 @@ from qutebrowser.completion.models import base
class CommandCompletionModel(base.BaseCompletionModel): 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 # https://github.com/The-Compiler/qutebrowser/issues/545
# pylint: disable=abstract-method # pylint: disable=abstract-method
@ -39,23 +39,11 @@ class CommandCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
assert cmdutils.cmd_dict cmdlist = _get_cmd_completions(include_aliases=True,
cmdlist = [] include_hidden=False)
for obj in set(cmdutils.cmd_dict.values()):
if (obj.hide or (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") cat = self.new_category("Commands")
for (name, desc, misc) in cmdlist:
# map each command to its bound keys and show these in the misc column self.new_item(cat, name, desc, misc)
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]))
class HelpCompletionModel(base.BaseCompletionModel): class HelpCompletionModel(base.BaseCompletionModel):
@ -72,17 +60,11 @@ class HelpCompletionModel(base.BaseCompletionModel):
def _init_commands(self): def _init_commands(self):
"""Fill completion with :command entries.""" """Fill completion with :command entries."""
assert cmdutils.cmd_dict cmdlist = _get_cmd_completions(include_aliases=False,
cmdlist = [] include_hidden=True, prefix=':')
for obj in set(cmdutils.cmd_dict.values()):
if (obj.hide or (obj.debug and not objreg.get('args').debug) or
obj.deprecated):
pass
else:
cmdlist.append((':' + obj.name, obj.desc))
cat = self.new_category("Commands") cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist): for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc) self.new_item(cat, name, desc, misc)
def _init_settings(self): def _init_settings(self):
"""Fill completion with section->option entries.""" """Fill completion with section->option entries."""
@ -259,3 +241,49 @@ class TabCompletionModel(base.BaseCompletionModel):
tabbed_browser = objreg.get('tabbed-browser', scope='window', tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=int(win_id)) window=int(win_id))
tabbed_browser.on_tab_close_requested(int(tab_index) - 1) 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)
cmdlist = _get_cmd_completions(include_hidden=True,
include_aliases=True)
cat = self.new_category("Commands")
for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc, misc)
def _get_cmd_completions(include_hidden, include_aliases, prefix=''):
"""Get a list of completions info for commands, sorted by name.
Args:
include_hidden: True to include commands annotated with hide=True.
include_aliases: True to include command aliases.
prefix: String to append to the command name.
Return: A list of tuples of form (name, description, bindings).
"""
assert cmdutils.cmd_dict
cmdlist = []
cmd_to_keys = objreg.get('key-config').get_reverse_bindings_for('normal')
for obj in set(cmdutils.cmd_dict.values()):
hide_debug = obj.debug and not objreg.get('args').debug
hide_hidden = obj.hide and not include_hidden
if not (hide_debug or hide_hidden or obj.deprecated):
bindings = ', '.join(cmd_to_keys.get(obj.name, []))
cmdlist.append((prefix + obj.name, obj.desc, bindings))
if include_aliases:
for name, cmd in config.section('aliases').items():
bindings = ', '.join(cmd_to_keys.get(name, []))
cmdlist.append((name, "Alias for '{}'".format(cmd), bindings))
return cmdlist

View File

@ -153,7 +153,7 @@ class KeyConfigParser(QObject):
@cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True, @cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True,
no_replace_variables=True) no_replace_variables=True)
@cmdutils.argument('win_id', win_id=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): def bind(self, key, win_id, command=None, *, mode='normal', force=False):
"""Bind a key to a command. """Bind a key to a command.
@ -424,8 +424,9 @@ class KeyConfigParser(QObject):
def get_reverse_bindings_for(self, section): def get_reverse_bindings_for(self, section):
"""Get a dict of commands to a list of bindings for the section.""" """Get a dict of commands to a list of bindings for the section."""
cmd_to_keys = collections.defaultdict(list) cmd_to_keys = {}
for key, cmd in self.get_bindings_for(section).items(): for key, cmd in self.get_bindings_for(section).items():
cmd_to_keys.setdefault(cmd, [])
# put special bindings last # put special bindings last
if utils.is_special_key(key): if utils.is_special_key(key):
cmd_to_keys[cmd].append(key) cmd_to_keys[cmd].append(key)

View File

@ -238,7 +238,8 @@ KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
# Available command completions # Available command completions
Completion = enum('Completion', ['command', 'section', 'option', 'value', Completion = enum('Completion', ['command', 'section', 'option', 'value',
'helptopic', 'quickmark_by_name', '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. # Exit statuses for errors. Needs to be an int for sys.exit.

View File

@ -31,17 +31,18 @@ from qutebrowser.browser import history
from qutebrowser.config import sections, value from qutebrowser.config import sections, value
def _get_completions(model): def _check_completions(model, expected):
"""Collect all the completion entries of a model, organized by category. """Check that a model contains the expected items in any order.
The result is a list of form: Args:
[ expected: A dict of form
(CategoryName: [(name, desc, misc), ...]), {
(CategoryName: [(name, desc, misc), ...]), CategoryName: [(name, desc, misc), ...],
... CategoryName: [(name, desc, misc), ...],
] ...
}
""" """
completions = [] actual = {}
for i in range(0, model.rowCount()): for i in range(0, model.rowCount()):
category = model.item(i) category = model.item(i)
entries = [] entries = []
@ -50,8 +51,12 @@ def _get_completions(model):
desc = category.child(j, 1) desc = category.child(j, 1)
misc = category.child(j, 2) misc = category.child(j, 2)
entries.append((name.text(), desc.text(), misc.text())) entries.append((name.text(), desc.text(), misc.text()))
completions.append((category.text(), entries)) actual[category.text()] = entries
return completions for cat_name, expected_entries in expected.items():
assert cat_name in actual
actual_items = actual[cat_name]
for expected_item in expected_entries:
assert expected_item in actual_items
def _patch_cmdutils(monkeypatch, stubs, symbol): def _patch_cmdutils(monkeypatch, stubs, symbol):
@ -165,7 +170,6 @@ def test_command_completion(qtmodeltester, monkeypatch, stubs, config_stub,
Validates that: Validates that:
- only non-hidden and non-deprecated commands are included - only non-hidden and non-deprecated commands are included
- commands are sorted by name
- the command description is shown in the desc column - the command description is shown in the desc column
- 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
@ -173,55 +177,56 @@ def test_command_completion(qtmodeltester, monkeypatch, stubs, config_stub,
_patch_cmdutils(monkeypatch, stubs, _patch_cmdutils(monkeypatch, stubs,
'qutebrowser.completion.models.miscmodels.cmdutils') 'qutebrowser.completion.models.miscmodels.cmdutils')
config_stub.data['aliases'] = {'rock': 'roll'} config_stub.data['aliases'] = {'rock': 'roll'}
key_config_stub.set_bindings_for('normal', {'s': 'stop', 'rr': 'roll'}) key_config_stub.set_bindings_for('normal', {'s': 'stop',
'rr': 'roll',
'ro': 'rock'})
model = miscmodels.CommandCompletionModel() model = miscmodels.CommandCompletionModel()
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Commands": [
("Commands", [ ('stop', 'stop qutebrowser', 's'),
('drop', 'drop all user data', ''), ('drop', 'drop all user data', ''),
('rock', "Alias for 'roll'", ''),
('roll', 'never gonna give you up', 'rr'), ('roll', 'never gonna give you up', 'rr'),
('stop', 'stop qutebrowser', 's') ('rock', "Alias for 'roll'", 'ro'),
]) ]
] })
def test_help_completion(qtmodeltester, monkeypatch, stubs): def test_help_completion(qtmodeltester, monkeypatch, stubs, key_config_stub):
"""Test the results of command completion. """Test the results of command completion.
Validates that: Validates that:
- only non-hidden and non-deprecated commands are included - only non-deprecated commands are included
- commands are sorted by name
- the command description is shown in the desc column - the command description is shown in the desc column
- 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
- only the first line of a multiline description is shown - only the first line of a multiline description is shown
""" """
module = 'qutebrowser.completion.models.miscmodels' module = 'qutebrowser.completion.models.miscmodels'
key_config_stub.set_bindings_for('normal', {'s': 'stop', 'rr': 'roll'})
_patch_cmdutils(monkeypatch, stubs, module + '.cmdutils') _patch_cmdutils(monkeypatch, stubs, module + '.cmdutils')
_patch_configdata(monkeypatch, stubs, module + '.configdata.DATA') _patch_configdata(monkeypatch, stubs, module + '.configdata.DATA')
model = miscmodels.HelpCompletionModel() model = miscmodels.HelpCompletionModel()
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Commands": [
("Commands", [ (':stop', 'stop qutebrowser', 's'),
(':drop', 'drop all user data', ''), (':drop', 'drop all user data', ''),
(':roll', 'never gonna give you up', ''), (':roll', 'never gonna give you up', 'rr'),
(':stop', 'stop qutebrowser', '') (':hide', '', ''),
]), ],
("Settings", [ "Settings": [
('general->time', 'Is an illusion.', ''), ('general->time', 'Is an illusion.', ''),
('general->volume', 'Goes to 11', ''), ('general->volume', 'Goes to 11', ''),
('ui->gesture', 'Waggle your hands to control qutebrowser', ''), ('ui->gesture', 'Waggle your hands to control qutebrowser', ''),
('ui->mind', 'Enable mind-control ui (experimental)', ''), ('ui->mind', 'Enable mind-control ui (experimental)', ''),
('ui->voice', 'Whether to respond to voice commands', ''), ('ui->voice', 'Whether to respond to voice commands', ''),
]) ]
] })
def test_quickmark_completion(qtmodeltester, quickmarks): def test_quickmark_completion(qtmodeltester, quickmarks):
@ -230,14 +235,13 @@ def test_quickmark_completion(qtmodeltester, quickmarks):
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Quickmarks": [
("Quickmarks", [
('aw', 'https://wiki.archlinux.org', ''), ('aw', 'https://wiki.archlinux.org', ''),
('ddg', 'https://duckduckgo.com', ''), ('ddg', 'https://duckduckgo.com', ''),
('wiki', 'https://wikipedia.org', ''), ('wiki', 'https://wikipedia.org', ''),
]) ]
] })
def test_bookmark_completion(qtmodeltester, bookmarks): def test_bookmark_completion(qtmodeltester, bookmarks):
@ -246,14 +250,13 @@ def test_bookmark_completion(qtmodeltester, bookmarks):
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Bookmarks": [
("Bookmarks", [
('https://github.com', 'GitHub', ''), ('https://github.com', 'GitHub', ''),
('https://python.org', 'Welcome to Python.org', ''), ('https://python.org', 'Welcome to Python.org', ''),
('http://qutebrowser.org', 'qutebrowser | qutebrowser', ''), ('http://qutebrowser.org', 'qutebrowser | qutebrowser', ''),
]) ]
] })
def test_url_completion(qtmodeltester, config_stub, web_history, quickmarks, def test_url_completion(qtmodeltester, config_stub, web_history, quickmarks,
@ -271,23 +274,22 @@ def test_url_completion(qtmodeltester, config_stub, web_history, quickmarks,
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Quickmarks": [
("Quickmarks", [
('https://wiki.archlinux.org', 'aw', ''), ('https://wiki.archlinux.org', 'aw', ''),
('https://duckduckgo.com', 'ddg', ''), ('https://duckduckgo.com', 'ddg', ''),
('https://wikipedia.org', 'wiki', ''), ('https://wikipedia.org', 'wiki', ''),
]), ],
("Bookmarks", [ "Bookmarks": [
('https://github.com', 'GitHub', ''), ('https://github.com', 'GitHub', ''),
('https://python.org', 'Welcome to Python.org', ''), ('https://python.org', 'Welcome to Python.org', ''),
('http://qutebrowser.org', 'qutebrowser | qutebrowser', ''), ('http://qutebrowser.org', 'qutebrowser | qutebrowser', ''),
]), ],
("History", [ "History": [
('https://python.org', 'Welcome to Python.org', '2016-03-08'), ('https://python.org', 'Welcome to Python.org', '2016-03-08'),
('https://github.com', 'GitHub', '2016-05-01'), ('https://github.com', 'GitHub', '2016-05-01'),
]), ],
] })
def test_url_completion_delete_bookmark(qtmodeltester, config_stub, def test_url_completion_delete_bookmark(qtmodeltester, config_stub,
@ -332,10 +334,9 @@ def test_session_completion(qtmodeltester, session_manager_stub):
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Sessions": [('default', '', ''), ('1', '', ''), ('2', '', '')]
("Sessions", [('default', '', ''), ('1', '', ''), ('2', '', '')]) })
]
def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry, def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry,
@ -352,17 +353,16 @@ def test_tab_completion(qtmodeltester, fake_web_tab, app_stub, win_registry,
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ '0': [
('0', [
('0/1', 'https://github.com', 'GitHub'), ('0/1', 'https://github.com', 'GitHub'),
('0/2', 'https://wikipedia.org', 'Wikipedia'), ('0/2', 'https://wikipedia.org', 'Wikipedia'),
('0/3', 'https://duckduckgo.com', 'DuckDuckGo') ('0/3', 'https://duckduckgo.com', 'DuckDuckGo')
]), ],
('1', [ '1': [
('1/1', 'https://wiki.archlinux.org', 'ArchWiki'), ('1/1', 'https://wiki.archlinux.org', 'ArchWiki'),
]) ]
] })
def test_tab_completion_delete(qtmodeltester, fake_web_tab, qtbot, app_stub, def test_tab_completion_delete(qtmodeltester, fake_web_tab, qtbot, app_stub,
@ -397,13 +397,12 @@ def test_setting_section_completion(qtmodeltester, monkeypatch, stubs):
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Sections": [
("Sections", [
('general', 'General/miscellaneous options.', ''), ('general', 'General/miscellaneous options.', ''),
('ui', 'General options related to the user interface.', ''), ('ui', 'General options related to the user interface.', ''),
]) ]
] })
def test_setting_option_completion(qtmodeltester, monkeypatch, stubs, def test_setting_option_completion(qtmodeltester, monkeypatch, stubs,
@ -417,14 +416,13 @@ def test_setting_option_completion(qtmodeltester, monkeypatch, stubs,
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "ui": [
("ui", [
('gesture', 'Waggle your hands to control qutebrowser', 'off'), ('gesture', 'Waggle your hands to control qutebrowser', 'off'),
('mind', 'Enable mind-control ui (experimental)', 'on'), ('mind', 'Enable mind-control ui (experimental)', 'on'),
('voice', 'Whether to respond to voice commands', 'sometimes'), ('voice', 'Whether to respond to voice commands', 'sometimes'),
]) ]
] })
def test_setting_value_completion(qtmodeltester, monkeypatch, stubs, def test_setting_value_completion(qtmodeltester, monkeypatch, stubs,
@ -436,14 +434,43 @@ def test_setting_value_completion(qtmodeltester, monkeypatch, stubs,
qtmodeltester.data_display_may_return_none = True qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model) qtmodeltester.check(model)
actual = _get_completions(model) _check_completions(model, {
assert actual == [ "Current/Default": [
("Current/Default", [
('0', 'Current value', ''), ('0', 'Current value', ''),
('11', 'Default value', ''), ('11', 'Default value', ''),
]), ],
("Completions", [ "Completions": [
('0', '', ''), ('0', '', ''),
('11', '', ''), ('11', '', ''),
]) ]
] })
def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub,
key_config_stub):
"""Test the results of keybinding command completion.
Validates that:
- only non-hidden and non-deprecated commands are included
- 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',
'ro': 'rock'})
model = miscmodels.BindCompletionModel()
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(model)
_check_completions(model, {
"Commands": [
('stop', 'stop qutebrowser', 's'),
('drop', 'drop all user data', ''),
('hide', '', ''),
('rock', "Alias for 'roll'", 'ro'),
]
})