diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 2da5c3180..0f21f8ba7 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -230,6 +230,9 @@ class BaseKeyParser(QObject): self._mode.name) log.keyboard.debug(prefix + msg) + def get_do_log(self) -> bool: + return self._do_log + def _match_key(self, sequence: keyutils.KeySequence) -> MatchResult: """Try to match a given keystring with any bound keychain. @@ -311,7 +314,7 @@ class BaseKeyParser(QObject): self._debug_log(f"Got key: {info!r} (dry_run {dry_run})") # Modifier keys should be previously handled by modeman - if keyutils.is_modifier_key(key): + if info.is_modifier_key(): self._debug_log("Ignoring, only modifier") return QKeySequence.SequenceMatch.NoMatch @@ -433,6 +436,8 @@ class BaseKeyParser(QObject): raise utils.Unreachable("Invalid match value {!r}".format( result.match_type)) + return result.match_type + @config.change_filter('bindings') def _on_config_changed(self) -> None: self._read_config() diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 9fd94daec..a6851661d 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -565,10 +565,10 @@ class QueuedKeyEventPair: def to_events(self) -> Sequence[QKeyEvent]: """Get a QKeyEvent from this QueuedEvent.""" if self.key_info_release is None: - return (self.key_info_press.to_event(QEvent.KeyPress),) + return (self.key_info_press.to_event(QEvent.Type.KeyPress),) else: - return (self.key_info_press.to_event(QEvent.KeyPress), - self.key_info_release.to_event(QEvent.KeyRelease)) + return (self.key_info_press.to_event(QEvent.Type.KeyPress), + self.key_info_release.to_event(QEvent.Type.KeyRelease)) class KeySequence: diff --git a/qutebrowser/keyinput/modeman.py b/qutebrowser/keyinput/modeman.py index a2d82d613..b84d3d2c2 100644 --- a/qutebrowser/keyinput/modeman.py +++ b/qutebrowser/keyinput/modeman.py @@ -4,7 +4,7 @@ """Mode manager (per window) which handles the current keyboard mode.""" -from PyQt5.QtWidgets import QApplication +from qutebrowser.qt.widgets import QApplication import functools import dataclasses @@ -278,7 +278,8 @@ class ModeManager(QObject): """ curmode = self.mode parser = self.parsers[curmode] - if curmode != usertypes.KeyMode.insert: + do_log = parser.get_do_log() + if do_log: log.modes.debug("got keypress in mode {} - delegating to " "{}".format(curmode, utils.qualname(parser))) @@ -286,11 +287,14 @@ class ModeManager(QObject): if parser.allow_forward and (not dry_run) and (not had_empty_queue): # Immediately record the event so that parser.handle may forward if # appropriate from its logic. + if do_log: + log.modes.debug("Enqueuing key event due to non-empty queue: " + "{}".format(event)) self._partial_match_events.append( keyutils.QueuedKeyEventPair.from_event_press(event)) - if keyutils.is_modifier_key(Qt.Key(event.key())): - if curmode != usertypes.KeyMode.insert: + if keyutils.KeyInfo.from_event(event).is_modifier_key(): + if do_log: log.modes.debug("Ignoring, only modifier") if not dry_run: # Since this is a NoMatch without a call to parser.handle, we @@ -304,30 +308,44 @@ class ModeManager(QObject): if match == QKeySequence.SequenceMatch.ExactMatch: filter_this = True if not dry_run: + if do_log: + log.modes.debug("Stopping partial timer.") self._stop_partial_timer() + if do_log: + log.modes.debug("Clearing partial match events.") self.clear_partial_match_events() elif match == QKeySequence.SequenceMatch.PartialMatch: filter_this = True if parser.allow_forward and (not dry_run): if had_empty_queue: # Begin recording partial match events + if do_log: + log.modes.debug("Enqueuing key event as first entry " + "in an empty queue: {}".format(event)) self._partial_match_events.append( keyutils.QueuedKeyEventPair.from_event_press(event)) + if do_log: + log.modes.debug("Starting partial timer.") self._start_partial_timer() elif not had_empty_queue: # Since partial events were recorded, this event must be filtered. # Since a NoMatch was found, this event has already been forwarded filter_this = True if not dry_run: + if do_log: + log.modes.debug("Stopping partial timer.") self._stop_partial_timer() else: key_info = keyutils.KeyInfo.from_event(event) filter_this = self._should_filter_event(key_info, parser) if not filter_this and not dry_run: + if do_log: + log.modes.debug("Adding release event for pass: " + "{}".format(event)) self._releaseevents_to_pass.add(keyutils.KeyEvent.from_event(event)) - if curmode != usertypes.KeyMode.insert: + if do_log: if machinery.IS_QT5: # FIXME:v4 needed for Qt 5 typing ignored_modifiers = [ cast(Qt.KeyboardModifiers, Qt.KeyboardModifier.NoModifier), @@ -365,7 +383,11 @@ class ModeManager(QObject): """ # handle like matching KeyPress keyevent = keyutils.KeyEvent.from_event(event) + do_log = (self.mode in self.parsers) and \ + self.parsers[self.mode].get_do_log() if keyevent in self._releaseevents_to_pass: + if do_log: + log.modes.debug("Passing release event: {}".format(keyevent)) self._releaseevents_to_pass.remove(keyevent) filter_this = False else: @@ -375,7 +397,7 @@ class ModeManager(QObject): if match_event.add_event_release(event): break filter_this = True - if self.mode != usertypes.KeyMode.insert: + if do_log: log.modes.debug("filter: {}".format(filter_this)) return filter_this @@ -413,6 +435,7 @@ class ModeManager(QObject): raise ValueError("Can't forward partial key: No keyparser for " "mode {}".format(mode)) parser = self.parsers[mode] + do_log = parser.get_do_log() if not self._partial_match_events: if parser.allow_forward: log.modes.warning("Attempting to forward for mode {} " @@ -423,7 +446,7 @@ class ModeManager(QObject): match_event = self._partial_match_events.pop(0) if parser.allow_forward and (not self._should_filter_event(match_event.key_info_press, parser)): - if mode != usertypes.KeyMode.insert: + if do_log: log.modes.debug("Forwarding partial match event in mode " "{}.".format(mode)) text_actual = str(match_event.key_info_press) @@ -447,22 +470,33 @@ class ModeManager(QObject): window=self._win_id) send_event = functools.partial(QApplication.sendEvent, widget) for event_ in match_event.to_events(): + if do_log: + log.modes.debug("Sending event {}".format(event_)) send_event(event_) if not match_event.is_released(): + if do_log: + log.modes.debug("Adding release event for pass: " + "{}".format(match_event.key_event)) self._releaseevents_to_pass.add(match_event.key_event) @pyqtSlot(usertypes.KeyMode) def forward_all_partial_match_events(self, mode: usertypes.KeyMode, *, - stop_timer: bool = False) -> None: + stop_timer: bool = False, + is_timeout: bool = False) -> None: """Forward all partial match events for a given mode. Args: mode: The mode from which the forwarded match is. stop_timer: If true, stop the partial timer (and any nested timers) as well. Default is False. + is_timeout: True if this invocation is the result of a timeout. """ - log.modes.debug("Forwarding all partial matches.") + do_log = (mode in self.parsers) and self.parsers[mode].get_do_log() + if do_log: + log.modes.debug(f"Forwarding all partial matches ({is_timeout=}).") if stop_timer: + if do_log: + log.modes.debug("Stopping partial timer.") self._stop_partial_timer() if mode in self.parsers: parser = self.parsers[mode] @@ -494,7 +528,8 @@ class ModeManager(QObject): except TypeError: pass self._partial_timer.timeout.connect(functools.partial( - self.forward_all_partial_match_events, self.mode)) + self.forward_all_partial_match_events, self.mode, + is_timeout=True)) self._partial_timer.start() def _stop_partial_timer(self) -> None: diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index f1ea32d92..fb1edd0d9 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -10,6 +10,7 @@ Module attributes: import traceback import enum +import functools from typing import TYPE_CHECKING, List from collections.abc import Sequence @@ -154,8 +155,8 @@ class HintKeyParser(basekeyparser.BaseKeyParser): self.clear_partial_match_events) self._partial_timer = usertypes.Timer(self, 'partial-match') self._partial_timer.setSingleShot(True) - self._partial_timer.timeout.connect( - self.forward_all_partial_match_events) + self._partial_timer.timeout.connect(functools.partial( + self.forward_all_partial_match_events, is_timeout=True)) def _handle_filter_key(self, e: QKeyEvent) -> QKeySequence.SequenceMatch: """Handle keys for string filtering.""" @@ -206,11 +207,14 @@ class HintKeyParser(basekeyparser.BaseKeyParser): if not had_empty_queue: # Immediately record the event so that parser.handle may forward if # appropriate from its logic. + self._debug_log("Enqueuing key event due to non-empty queue: " + "{}".format(e)) self._partial_match_events.append( keyutils.QueuedKeyEventPair.from_event_press(e)) result = self._command_parser.handle(e) if result == QKeySequence.SequenceMatch.ExactMatch: + self._debug_log("Stopping partial timer.") self._stop_partial_timer() self.clear_partial_match_events() log.keyboard.debug("Handling key via command parser") @@ -220,11 +224,15 @@ class HintKeyParser(basekeyparser.BaseKeyParser): log.keyboard.debug("Handling key via command parser") if had_empty_queue: # Begin recording partial match events + self._debug_log("Enqueuing key event as first entry in an " + "empty queue: {}".format(e)) self._partial_match_events.append( keyutils.QueuedKeyEventPair.from_event_press(e)) + self._debug_log("Staring partial timer.") self._start_partial_timer() return result elif not had_empty_queue: + self._debug_log("Stopping partial timer.") self._stop_partial_timer() # It's unclear exactly what the return here should be. The safest # bet seems to be PartialMatch as it won't clear the unused @@ -275,14 +283,18 @@ class HintKeyParser(basekeyparser.BaseKeyParser): @pyqtSlot() def forward_all_partial_match_events(self, *, - stop_timer: bool = False) -> None: + stop_timer: bool = False, + is_timeout: bool = False) -> None: """Forward all partial match events. Args: - stop_timer: If true, stop the partial timer as well. Default is False. + stop_timer: If true, stop the partial timer as well. Default is + False. + is_timeout: True if this invocation is the result of a timeout. """ - self._debug_log("Forwarding all partial matches.") + self._debug_log(f"Forwarding all partial matches ({is_timeout=}).") if stop_timer: + self._debug_log("Stopping partial timer.") self._stop_partial_timer() if self._partial_match_events: while self._partial_match_events: diff --git a/tests/unit/keyinput/test_modeman.py b/tests/unit/keyinput/test_modeman.py index b21bbb0c8..6a83cc530 100644 --- a/tests/unit/keyinput/test_modeman.py +++ b/tests/unit/keyinput/test_modeman.py @@ -192,38 +192,38 @@ def test_release_forwarding(modeman_with_basekeyparser): info_b = keyutils.KeyInfo(Qt.Key.Key_B, Qt.KeyboardModifier.NoModifier) info_c = keyutils.KeyInfo(Qt.Key.Key_C, Qt.KeyboardModifier.NoModifier) - res = mwb.handle_event(info_b.to_event(QEvent.KeyPress)) + res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyPress)) assert (True, 1, 0) == helper_data(res, mwb) assert not mwb._partial_match_events[0].is_released() - res = mwb.handle_event(info_c.to_event(QEvent.KeyPress)) + res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyPress)) assert (True, 0, 2) == helper_data(res, mwb) - res = mwb.handle_event(info_b.to_event(QEvent.KeyRelease)) + res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyRelease)) assert (False, 0, 1) == helper_data(res, mwb) - res = mwb.handle_event(info_c.to_event(QEvent.KeyRelease)) + res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyRelease)) assert (False, 0, 0) == helper_data(res, mwb) info_y = keyutils.KeyInfo(Qt.Key.Key_Y, Qt.KeyboardModifier.NoModifier) - res = mwb.handle_event(info_b.to_event(QEvent.KeyPress)) + res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyPress)) assert (True, 1, 0) == helper_data(res, mwb) assert not mwb._partial_match_events[0].is_released() - res = mwb.handle_event(info_y.to_event(QEvent.KeyPress)) + res = mwb.handle_event(info_y.to_event(QEvent.Type.KeyPress)) assert (True, 2, 0) == helper_data(res, mwb) assert not mwb._partial_match_events[0].is_released() assert not mwb._partial_match_events[1].is_released() - res = mwb.handle_event(info_y.to_event(QEvent.KeyRelease)) + res = mwb.handle_event(info_y.to_event(QEvent.Type.KeyRelease)) assert (True, 2, 0) == helper_data(res, mwb) assert not mwb._partial_match_events[0].is_released() assert mwb._partial_match_events[1].is_released() - res = mwb.handle_event(info_c.to_event(QEvent.KeyPress)) + res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyPress)) assert (True, 0, 2) == helper_data(res, mwb) - res = mwb.handle_event(info_c.to_event(QEvent.KeyRelease)) + res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyRelease)) assert (False, 0, 1) == helper_data(res, mwb) - res = mwb.handle_event(info_b.to_event(QEvent.KeyRelease)) + res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyRelease)) assert (False, 0, 0) == helper_data(res, mwb) - res = mwb.handle_event(info_b.to_event(QEvent.KeyPress)) + res = mwb.handle_event(info_b.to_event(QEvent.Type.KeyPress)) assert (True, 1, 0) == helper_data(res, mwb) assert not mwb._partial_match_events[0].is_released() - res = mwb.handle_event(info_c.to_event(QEvent.KeyRelease)) + res = mwb.handle_event(info_c.to_event(QEvent.Type.KeyRelease)) assert (True, 1, 0) == helper_data(res, mwb)