diff --git a/qutebrowser/completion/models/miscmodels.py b/qutebrowser/completion/models/miscmodels.py index 831243312..4b48290d9 100644 --- a/qutebrowser/completion/models/miscmodels.py +++ b/qutebrowser/completion/models/miscmodels.py @@ -24,7 +24,7 @@ from PyQt5.QtCore import Qt, QTimer, pyqtSlot from qutebrowser.browser import webview from qutebrowser.config import config, configdata -from qutebrowser.utils import objreg, log, qtutils +from qutebrowser.utils import objreg, log, qtutils, utils from qutebrowser.commands import cmdutils from qutebrowser.completion.models import base @@ -57,7 +57,7 @@ class CommandCompletionModel(base.BaseCompletionModel): cmd_to_keys = defaultdict(list) for key, cmd in keyconf.get_bindings_for('normal').items(): # put special bindings last - if key.startswith('<') and key.endswith('>'): + if utils.is_special_key(key): cmd_to_keys[cmd].append(key) else: cmd_to_keys[cmd].insert(0, key) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 4accafcb6..dd8535234 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -335,7 +335,7 @@ class BaseKeyParser(QObject): def _parse_key_command(self, modename, key, cmd): """Parse the keys and their command and store them in the object.""" - if key.startswith('<') and key.endswith('>'): + if utils.is_special_key(key): keystr = utils.normalize_keystr(key[1:-1]) self.special_bindings[keystr] = cmd elif self._supports_chains: diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index 9a4d99d95..c3864c691 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -94,28 +94,31 @@ class KeyHintView(QLabel): self.hide() return + keyconf = objreg.get('key-config') + bindings = [(k, v) for (k, v) + in keyconf.get_bindings_for(modename).items() + if k.startswith(prefix) and not utils.is_special_key(k)] + + if not bindings: + return + self.show() suffix_color = html.escape(config.get('colors', 'keyhint.fg.suffix')) text = '' - keyconf = objreg.get('key-config') - # this is only fired in normal mode - for key, cmd in keyconf.get_bindings_for(modename).items(): - # for now, special keys can't be part of keychains, so ignore them - is_special_binding = key.startswith('<') and key.endswith('>') - if key.startswith(prefix) and not is_special_binding: - text += ( - "" - "{}" - "{}" - "{}" - "" - ).format( - html.escape(prefix), - suffix_color, - html.escape(key[len(prefix):]), - html.escape(cmd) - ) + for key, cmd in bindings: + text += ( + "" + "{}" + "{}" + "{}" + "" + ).format( + html.escape(prefix), + suffix_color, + html.escape(key[len(prefix):]), + html.escape(cmd) + ) text = '{}
'.format(text) self.setText(text) diff --git a/qutebrowser/utils/utils.py b/qutebrowser/utils/utils.py index c04516d36..febceca76 100644 --- a/qutebrowser/utils/utils.py +++ b/qutebrowser/utils/utils.py @@ -440,9 +440,14 @@ class KeyParseError(Exception): super().__init__("Could not parse {!r}: {}".format(keystr, error)) +def is_special_key(keystr): + """True if keystr is a 'special' keystring (e.g. or ).""" + return keystr.startswith('<') and keystr.endswith('>') + + def _parse_single_key(keystr): """Convert a single key string to a (Qt.Key, Qt.Modifiers, text) tuple.""" - if keystr.startswith('<') and keystr.endswith('>'): + if is_special_key(keystr): # Special key keystr = keystr[1:-1] elif len(keystr) == 1: @@ -489,7 +494,7 @@ def _parse_single_key(keystr): def parse_keystring(keystr): """Parse a keystring like or xyz and return a KeyInfo list.""" - if keystr.startswith('<') and keystr.endswith('>'): + if is_special_key(keystr): return [_parse_single_key(keystr)] else: return [_parse_single_key(char) for char in keystr] diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py index 85f6c55ce..2f312e8b3 100644 --- a/tests/unit/misc/test_keyhints.py +++ b/tests/unit/misc/test_keyhints.py @@ -110,3 +110,12 @@ def test_color_switch(keyhint, config_stub, key_config_stub): ('aa', 'cmd-aa')])) keyhint.update_keyhint('normal', 'a') assert keyhint.text() == expected_text(('a', '#ABCDEF', 'a', 'cmd-aa')) + + +def test_no_matches(keyhint, key_config_stub): + """Ensure the widget isn't visible if there are no keystrings to show.""" + key_config_stub.set_bindings_for('normal', OrderedDict([ + ('aa', 'cmd-aa'), + ('ab', 'cmd-ab')])) + keyhint.update_keyhint('normal', 'z') + assert not keyhint.isVisible() diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py index b6950e969..52c36eee3 100644 --- a/tests/unit/utils/test_utils.py +++ b/tests/unit/utils/test_utils.py @@ -984,3 +984,19 @@ class TestGetSetClipboard: def test_supports_selection(self, clipboard_mock, selection): clipboard_mock.supportsSelection.return_value = selection assert utils.supports_selection() == selection + + +@pytest.mark.parametrize('keystr, expected', [ + ('', True), + ('', True), + ('', True), + ('x', False), + ('X', False), + ('', True), + ('foobar', False), + ('foo>', False), + ('