diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index 8bcb7ff37..08122958a 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -435,6 +435,22 @@ def qute_settings(url): return 'text/html', html +@add_handler('bindings') +def qute_bindings(_url): + """Handler for qute://bindings. View keybindings.""" + bindings = {} + defaults = config.val.bindings.default + modes = set(defaults.keys()).union(config.val.bindings.commands) + modes.remove('normal') + modes = ['normal'] + sorted(list(modes)) + for mode in modes: + bindings[mode] = config.key_instance.get_bindings_for(mode) + + html = jinja.render('bindings.html', title='Bindings', + bindings=bindings) + return 'text/html', html + + @add_handler('back') def qute_back(url): """Handler for qute://back. diff --git a/qutebrowser/config/configcommands.py b/qutebrowser/config/configcommands.py index 7d9adb475..cc3eae514 100644 --- a/qutebrowser/config/configcommands.py +++ b/qutebrowser/config/configcommands.py @@ -96,7 +96,9 @@ class ConfigCommands: @cmdutils.register(instance='config-commands', maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('command', completion=configmodel.bind) - def bind(self, key, command=None, *, mode='normal', default=False): + @cmdutils.argument('win_id', win_id=True) + def bind(self, win_id, key=None, command=None, *, mode='normal', + default=False): """Bind a key to a command. Args: @@ -108,6 +110,12 @@ class ConfigCommands: available modes. default: If given, restore a default binding. """ + if key is None: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window=win_id) + tabbed_browser.openurl(QUrl('qute://bindings'), newtab=True) + return + if command is None: if default: # :bind --default: Restore default diff --git a/qutebrowser/html/bindings.html b/qutebrowser/html/bindings.html new file mode 100644 index 000000000..fe6913402 --- /dev/null +++ b/qutebrowser/html/bindings.html @@ -0,0 +1,32 @@ +{% extends "styled.html" %} + +{% block style %} +{{ super() }} +th { text-align:left; } +.key { width: 25%; } +.command { width: 75% } +{% endblock %} + +{% block content %} +

{{ title }}

+{% for mode, binding in bindings.items() %} +

{{ mode | capitalize }} mode

+ + + + + + {% for key,command in binding.items() %} + + + + + {% endfor %} +
KeyCommand
+

{{ key }}

+
+

{{ command }}

+
+{% endfor %} + +{% endblock %} diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 4c0c833a1..92796e8e5 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -429,6 +429,16 @@ class TestBind: """Get a dict with no bindings.""" return {'normal': {}} + def test_bind_no_args(self, commands, config_stub, no_bindings, + tabbed_browser_stubs): + """Run ':bind'. + + Should open qute://bindings.""" + config_stub.val.bindings.default = no_bindings + config_stub.val.bindings.commands = no_bindings + commands.bind(win_id=0) + assert tabbed_browser_stubs[0].opened_url == QUrl('qute://bindings') + @pytest.mark.parametrize('command', ['nop', 'nope']) def test_bind(self, commands, config_stub, no_bindings, key_config_stub, command): @@ -437,7 +447,7 @@ class TestBind: config_stub.val.bindings.default = no_bindings config_stub.val.bindings.commands = no_bindings - commands.bind('a', command) + commands.bind(0, 'a', command) assert key_config_stub.get_command('a', 'normal') == command yaml_bindings = config_stub._yaml['bindings.commands']['normal'] assert yaml_bindings['a'] == command @@ -474,7 +484,7 @@ class TestBind: 'normal': {'c': 'message-info c'} } - commands.bind(key, mode=mode) + commands.bind(0, key, mode=mode) msg = message_mock.getmsg(usertypes.MessageLevel.info) assert msg.text == expected @@ -486,7 +496,7 @@ class TestBind: """ with pytest.raises(cmdexc.CommandError, match='Invalid mode wrongmode!'): - commands.bind('a', 'nop', mode='wrongmode') + commands.bind(0, 'a', 'nop', mode='wrongmode') def test_bind_print_invalid_mode(self, commands): """Run ':bind --mode=wrongmode a'. @@ -495,7 +505,7 @@ class TestBind: """ with pytest.raises(cmdexc.CommandError, match='Invalid mode wrongmode!'): - commands.bind('a', mode='wrongmode') + commands.bind(0, 'a', mode='wrongmode') @pytest.mark.parametrize('key', ['a', 'b', '']) def test_bind_duplicate(self, commands, config_stub, key_config_stub, key): @@ -510,12 +520,12 @@ class TestBind: 'normal': {'b': 'nop'}, } - commands.bind(key, 'message-info foo', mode='normal') + commands.bind(0, key, 'message-info foo', mode='normal') assert key_config_stub.get_command(key, 'normal') == 'message-info foo' def test_bind_none(self, commands, config_stub): config_stub.val.bindings.commands = None - commands.bind(',x', 'nop') + commands.bind(0, ',x', 'nop') def test_bind_default(self, commands, key_config_stub, config_stub): """Bind a key to its default.""" @@ -525,7 +535,7 @@ class TestBind: config_stub.val.bindings.commands = {'normal': {'a': bound_cmd}} assert key_config_stub.get_command('a', mode='normal') == bound_cmd - commands.bind('a', mode='normal', default=True) + commands.bind(0, 'a', mode='normal', default=True) assert key_config_stub.get_command('a', mode='normal') == default_cmd @@ -539,7 +549,7 @@ class TestBind: Should show an error. """ with pytest.raises(cmdexc.CommandError, match=expected): - commands.bind(key, mode=mode, default=True) + commands.bind(0, key, mode=mode, default=True) def test_unbind_none(self, commands, config_stub): config_stub.val.bindings.commands = None @@ -563,7 +573,7 @@ class TestBind: } if key == 'c': # Test :bind and :unbind - commands.bind(key, 'nop') + commands.bind(0, key, 'nop') commands.unbind(key) assert key_config_stub.get_command(key, 'normal') is None