From 8da878c77cb217f14057cea040e8406536f2282b Mon Sep 17 00:00:00 2001 From: Florian Bruhin <git@the-compiler.org> Date: Sun, 4 Mar 2018 17:46:26 +0100 Subject: [PATCH] Make KeySequence.matchs() work correctly --- qutebrowser/keyinput/keyutils.py | 35 +++++++++++++++++++++++----- tests/unit/keyinput/test_keyutils.py | 30 +++++++++++++++++++++++- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index 50041b75a..ddc78b2b4 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -361,15 +361,38 @@ class KeySequence: raise KeyParseError(keystr, "Got unknown key!") def matches(self, other): - """Check whether the given KeySequence matches with this one.""" + """Check whether the given KeySequence matches with this one. + + We store multiple QKeySequences with <= 4 keys each, so we need to match + those pair-wise, and account for an unequal amount of sequences as well. + """ # pylint: disable=protected-access - assert self._sequences - assert other._sequences - for seq1, seq2 in zip(self._sequences, other._sequences): - match = seq1.matches(seq2) + + if len(self._sequences) > len(other._sequences): + # If we entered more sequences than there are in the config, there's + # no way there can be a match. + return QKeySequence.NoMatch + + for entered, configured in zip(self._sequences, other._sequences): + # If we get NoMatch/PartialMatch in a sequence, we can abort there. + match = entered.matches(configured) if match != QKeySequence.ExactMatch: return match - return QKeySequence.ExactMatch + + # We checked all common sequences and they had an ExactMatch. + # + # If there's still more sequences configured than entered, that's a + # PartialMatch, as more keypresses can still follow and new sequences + # will appear which we didn't check above. + # + # If there's the same amount of sequences configured and entered, that's + # an EqualMatch. + if len(self._sequences) == len(other._sequences): + return QKeySequence.ExactMatch + elif len(self._sequences) < len(other._sequences): + return QKeySequence.PartialMatch + else: + assert False, (self, other) def append_event(self, ev): """Create a new KeySequence object with the given QKeyEvent added. diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 0528618a8..0f13c762d 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -21,7 +21,7 @@ import operator import pytest from PyQt5.QtCore import Qt, QEvent, pyqtSignal -from PyQt5.QtGui import QKeyEvent +from PyQt5.QtGui import QKeyEvent, QKeySequence from PyQt5.QtWidgets import QWidget from tests.unit.keyinput import key_data @@ -291,6 +291,34 @@ class TestKeySequence: assert s1[3:5] == s2 assert seq[3:5] == expected + @pytest.mark.parametrize('entered, configured, expected', [ + # config: abcd + ('abc', 'abcd', QKeySequence.PartialMatch), + ('abcd', 'abcd', QKeySequence.ExactMatch), + ('ax', 'abcd', QKeySequence.NoMatch), + ('abcdef', 'abcd', QKeySequence.NoMatch), + + # config: abcd ef + ('abc', 'abcdef', QKeySequence.PartialMatch), + ('abcde', 'abcdef', QKeySequence.PartialMatch), + ('abcd', 'abcdef', QKeySequence.PartialMatch), + ('abcdx', 'abcdef', QKeySequence.NoMatch), + ('ax', 'abcdef', QKeySequence.NoMatch), + ('abcdefg', 'abcdef', QKeySequence.NoMatch), + ('abcdef', 'abcdef', QKeySequence.ExactMatch), + + # other examples + ('ab', 'a', QKeySequence.NoMatch), + + # empty strings + ('', '', QKeySequence.ExactMatch), + ('', 'a', QKeySequence.PartialMatch), + ('a', '', QKeySequence.NoMatch), + ]) + def test_matches(self, entered, configured, expected): + entered = keyutils.KeySequence.parse(entered) + configured = keyutils.KeySequence.parse(configured) + assert entered.matches(configured) == expected @pytest.mark.parametrize('keystr, expected', [ ('<Control-x>', keyutils.KeySequence(Qt.ControlModifier | Qt.Key_X)),