Minor fixes and increased debugging verbosity (which necessitated some minor tweaks to timers)

This commit is contained in:
brightonanc 2023-08-30 23:31:23 -04:00
parent 80ee62f1b9
commit 04f9073f9d
5 changed files with 83 additions and 31 deletions

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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)