diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index c97570369..624e80309 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -11,7 +11,7 @@ import traceback from typing import Optional from collections.abc import Mapping, MutableMapping, Sequence -from qutebrowser.qt.core import QObject, pyqtSignal +from qutebrowser.qt.core import QObject, pyqtSignal, pyqtSlot from qutebrowser.qt.gui import QKeySequence, QKeyEvent from qutebrowser.config import config @@ -153,6 +153,7 @@ class BaseKeyParser(QObject): passthrough: Whether unbound keys should be passed through with this handler. _supports_count: Whether count is supported. + _partial_timer: Timer to clear partial keypresses. Signals: keystring_updated: Emitted when the keystring is updated. @@ -182,6 +183,8 @@ class BaseKeyParser(QObject): self._supports_count = supports_count self.bindings = BindingTrie() self._read_config() + self._partial_timer = usertypes.Timer(self, 'partial-match') + self._partial_timer.setSingleShot(True) config.instance.changed.connect(self._on_config_changed) def __repr__(self) -> str: @@ -302,6 +305,7 @@ class BaseKeyParser(QObject): if result.match_type == QKeySequence.SequenceMatch.NoMatch: was_count = self._match_count(result.sequence, dry_run) if was_count: + self._set_partial_timeout() return QKeySequence.SequenceMatch.ExactMatch if dry_run: @@ -332,6 +336,7 @@ class BaseKeyParser(QObject): self._debug_log("No match for '{}' (added {})".format( result.sequence, info)) self.keystring_updated.emit(self._count + str(result.sequence)) + self._set_partial_timeout() elif result.match_type == QKeySequence.SequenceMatch.NoMatch: self._debug_log("Giving up with '{}', no matches".format( result.sequence)) @@ -362,6 +367,24 @@ class BaseKeyParser(QObject): """ raise NotImplementedError + def _set_partial_timeout(self): + """Set a timeout to clear a partial keystring.""" + timeout = config.val.input.partial_timeout + if timeout != 0: + self._partial_timer.setInterval(timeout) + self._partial_timer.timeout.connect(self.clear_partial_match) + self._partial_timer.start() + + @pyqtSlot() + def clear_partial_match(self): + """Clear a partial keystring after a timeout.""" + self._debug_log("Clearing partial keystring {}".format( + self._sequence)) + if self._count: + self._count = '' + self._sequence = keyutils.KeySequence() + self.keystring_updated.emit(str(self._sequence)) + def clear_keystring(self) -> None: """Clear the currently entered key sequence.""" if self._sequence: diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 218fc70c9..b24607aa6 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -66,11 +66,7 @@ class CommandKeyParser(basekeyparser.BaseKeyParser): class NormalKeyParser(CommandKeyParser): - """KeyParser for normal mode with added STARTCHARS detection and more. - - Attributes: - _partial_timer: Timer to clear partial keypresses. - """ + """KeyParser for normal mode with added STARTCHARS detection and more.""" _sequence: keyutils.KeySequence @@ -131,6 +127,15 @@ class NormalKeyParser(CommandKeyParser): self._debug_log("Releasing inhibition state of normal mode.") self._inhibited = False + @pyqtSlot() + def _stop_timers(self): + super()._stop_timers() + self._inhibited_timer.stop() + try: + self._inhibited_timer.timeout.disconnect(self._clear_inhibited) + except TypeError: + # no connections + pass class PassthroughKeyParser(CommandKeyParser): @@ -187,9 +192,13 @@ class PassthroughKeyParser(CommandKeyParser): if dry_run or len(orig_sequence) == 1 or match != QKeySequence.NoMatch: return match + self._forward_keystring(orig_sequence) + return QKeySequence.ExactMatch + + def _forward_keystring(self, orig_sequence): window = QApplication.focusWindow() if window is None: - return match + return first = True for keyinfo in orig_sequence: @@ -201,7 +210,11 @@ class PassthroughKeyParser(CommandKeyParser): QApplication.postEvent(window, press_event) QApplication.postEvent(window, release_event) - return QKeySequence.ExactMatch + @pyqtSlot() + def clear_partial_match(self): + """Override to forward the original sequence to browser.""" + self._forward_keystring(self._orig_sequence) + self.clear_keystring() def clear_keystring(self): """Override to also clear the original sequence.""" @@ -330,6 +343,19 @@ class HintKeyParser(CommandKeyParser): assert count is None self._hintmanager.handle_partial_key(cmdstr) + @pyqtSlot() + def clear_partial_match(self): + """Override to avoid clearing filter text after a timeout.""" + if self._last_press != LastPress.filtertext: + super().clear_partial_match() + + @pyqtSlot(str) + def on_keystring_updated(self, keystr): + """Update hintmanager when the keystring was updated.""" + hintmanager = objreg.get('hintmanager', scope='tab', + window=self._win_id, tab='current') + hintmanager.handle_partial_key(keystr) + class RegisterKeyParser(CommandKeyParser):