diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 5068283d4..b66ef2969 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -341,6 +341,7 @@ class ConfigManager(QObject): ('colors', 'tab.indicator.system'): 'tabs.indicator.system', ('completion', 'history-length'): 'cmd-history-max-items', ('colors', 'downloads.fg'): 'downloads.fg.start', + ('ui', 'show-keyhints'): 'keyhint-blacklist', } DELETED_OPTIONS = [ ('colors', 'tab.separator'), @@ -360,6 +361,8 @@ class ConfigManager(QObject): _get_value_transformer({'false': '-1', 'true': '1000'}), ('general', 'log-javascript-console'): _get_value_transformer({'false': 'none', 'true': 'debug'}), + ('ui', 'keyhint-blacklist'): + _get_value_transformer({'false': '*', 'true': ''}), } changed = pyqtSignal(str, str) diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 0bde65b0c..659ef08b2 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -355,9 +355,11 @@ def data(readonly=False): "Hide the window decoration when using wayland " "(requires restart)"), - ('show-keyhints', - SettingValue(typ.Bool(), 'true'), - "Show possible keychains based on the current keystring"), + ('keyhint-blacklist', + SettingValue(typ.List(none_ok=True), ''), + "Keychains that shouldn't be shown in the keyhint dialog\n\n" + "Globs are supported, so ';*' will blacklist all keychains" + "starting with ';'. Use '*' to disable keyhints"), readonly=readonly )), diff --git a/qutebrowser/misc/keyhintwidget.py b/qutebrowser/misc/keyhintwidget.py index c3864c691..f9589edef 100644 --- a/qutebrowser/misc/keyhintwidget.py +++ b/qutebrowser/misc/keyhintwidget.py @@ -25,12 +25,13 @@ It is intended to help discoverability of keybindings. """ import html +import fnmatch from PyQt5.QtWidgets import QLabel, QSizePolicy from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt from qutebrowser.config import config, style -from qutebrowser.utils import objreg, utils +from qutebrowser.utils import objreg, utils, usertypes class KeyHintView(QLabel): @@ -39,7 +40,6 @@ class KeyHintView(QLabel): Attributes: _win_id: Window ID of parent. - _enabled: If False, do not show the window at all Signals: reposition_keyhint: Emitted when this widget should be resized. @@ -61,23 +61,16 @@ class KeyHintView(QLabel): super().__init__(parent) self.setTextFormat(Qt.RichText) self._win_id = win_id - self.set_enabled() - cfg = objreg.get('config') - cfg.changed.connect(self.set_enabled) style.set_register_stylesheet(self) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) self.hide() + self._show_timer = usertypes.Timer(self, 'keyhint_show') + self._show_timer.setInterval(500) + self._show_timer.timeout.connect(self.show) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) - @config.change_filter('ui', 'show-keyhints') - def set_enabled(self): - """Update self._enabled when the config changed.""" - self._enabled = config.get('ui', 'show-keyhints') - if not self._enabled: - self.hide() - def showEvent(self, e): """Adjust the keyhint size when it's freshly shown.""" self.reposition_keyhint.emit() @@ -90,19 +83,29 @@ class KeyHintView(QLabel): Args: prefix: The current partial keystring. """ - if not prefix or not self._enabled: + if not prefix: + self._show_timer.stop() self.hide() return + blacklist = config.get('ui', 'keyhint-blacklist') or [] keyconf = objreg.get('key-config') + + def blacklisted(keychain): + return any(fnmatch.fnmatchcase(keychain, glob) + for glob in blacklist) + 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 k.startswith(prefix) and not utils.is_special_key(k) and + not blacklisted(k)] if not bindings: + self._show_timer.stop() return - self.show() + # delay so a quickly typed keychain doesn't display hints + self._show_timer.start() suffix_color = html.escape(config.get('colors', 'keyhint.fg.suffix')) text = '' diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py index 2f312e8b3..6f705721d 100644 --- a/tests/unit/misc/test_keyhints.py +++ b/tests/unit/misc/test_keyhints.py @@ -34,7 +34,6 @@ def expected_text(*args): """ text = '
{} | " "{} | " @@ -54,7 +53,7 @@ def keyhint(qtbot, config_stub, key_config_stub): 'keyhint.bg': 'black' }, 'fonts': {'keyhint': 'Comic Sans'}, - 'ui': {'show-keyhints': True}, + 'ui': {'keyhint-blacklist': ''}, } keyhint = KeyHintView(0, None) qtbot.add_widget(keyhint) @@ -63,7 +62,7 @@ def keyhint(qtbot, config_stub, key_config_stub): def test_suggestions(keyhint, key_config_stub): - """Test cursor position based on the prompt.""" + """Test that keyhints are shown based on a prefix.""" # we want the dict to return sorted items() for reliable testing key_config_stub.set_bindings_for('normal', OrderedDict([ ('aa', 'cmd-aa'), @@ -95,14 +94,6 @@ def test_special_bindings(keyhint, key_config_stub): ('<', 'yellow', 'b', 'cmd-<b')) -def test_disable(keyhint, config_stub): - """Ensure the widget isn't visible if disabled.""" - config_stub.set('ui', 'show-keyhints', False) - keyhint.update_keyhint('normal', 'a') - assert not keyhint.text() - assert not keyhint.isVisible() - - def test_color_switch(keyhint, config_stub, key_config_stub): """Ensure the the keyhint suffix color can be updated at runtime.""" config_stub.set('colors', 'keyhint.fg.suffix', '#ABCDEF') @@ -118,4 +109,37 @@ def test_no_matches(keyhint, key_config_stub): ('aa', 'cmd-aa'), ('ab', 'cmd-ab')])) keyhint.update_keyhint('normal', 'z') + assert not keyhint.text() assert not keyhint.isVisible() + + +def test_blacklist(keyhint, config_stub, key_config_stub): + """Test that blacklisted keychaints aren't hinted.""" + config_stub.set('ui', 'keyhint-blacklist', ['ab*']) + # we want the dict to return sorted items() for reliable testing + key_config_stub.set_bindings_for('normal', OrderedDict([ + ('aa', 'cmd-aa'), + ('ab', 'cmd-ab'), + ('aba', 'cmd-aba'), + ('abb', 'cmd-abb'), + ('xd', 'cmd-xd'), + ('xe', 'cmd-xe')])) + + keyhint.update_keyhint('normal', 'a') + assert keyhint.text() == expected_text(('a', 'yellow', 'a', 'cmd-aa')) + + +def test_blacklist_all(keyhint, config_stub, key_config_stub): + """Test that setting the blacklist to * disables keyhints.""" + config_stub.set('ui', 'keyhint-blacklist', ['*']) + # we want the dict to return sorted items() for reliable testing + key_config_stub.set_bindings_for('normal', OrderedDict([ + ('aa', 'cmd-aa'), + ('ab', 'cmd-ab'), + ('aba', 'cmd-aba'), + ('abb', 'cmd-abb'), + ('xd', 'cmd-xd'), + ('xe', 'cmd-xe')])) + + keyhint.update_keyhint('normal', 'a') + assert not keyhint.text()