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 %}
+
+{% for mode, binding in bindings.items() %}
+
{{ mode | capitalize }} mode
+
+
+ Key |
+ Command |
+
+ {% for key,command in binding.items() %}
+
+
+ {{ key }}
+ |
+
+ {{ command }}
+ |
+
+ {% endfor %}
+
+{% 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