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
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)

View File

@ -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
@ -39,23 +39,11 @@ class CommandCompletionModel(base.BaseCompletionModel):
def __init__(self, parent=None):
super().__init__(parent)
assert cmdutils.cmd_dict
cmdlist = []
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)))
cmdlist = _get_cmd_completions(include_aliases=True,
include_hidden=False)
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]))
for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc, misc)
class HelpCompletionModel(base.BaseCompletionModel):
@ -72,17 +60,11 @@ class HelpCompletionModel(base.BaseCompletionModel):
def _init_commands(self):
"""Fill completion with :command entries."""
assert cmdutils.cmd_dict
cmdlist = []
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))
cmdlist = _get_cmd_completions(include_aliases=False,
include_hidden=True, prefix=':')
cat = self.new_category("Commands")
for (name, desc) in sorted(cmdlist):
self.new_item(cat, name, desc)
for (name, desc, misc) in cmdlist:
self.new_item(cat, name, desc, misc)
def _init_settings(self):
"""Fill completion with section->option entries."""
@ -259,3 +241,49 @@ 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)
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,
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.
@ -424,8 +424,9 @@ class KeyConfigParser(QObject):
def get_reverse_bindings_for(self, 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():
cmd_to_keys.setdefault(cmd, [])
# put special bindings last
if utils.is_special_key(key):
cmd_to_keys[cmd].append(key)

View File

@ -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.

View File

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