From ad5fde9d05546958d88d0d8a62205c44d1c1d14f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 Mar 2018 14:10:22 +0100 Subject: Fall back to non-keypad keys without any keypad bindings Fixes #3701 (cherry picked from commit b88ac51d25da043ca431b2cc12a353f34bce06f7) --- qutebrowser/keyinput/basekeyparser.py | 10 +++++++ qutebrowser/keyinput/keyutils.py | 6 +++++ tests/unit/keyinput/test_basekeyparser.py | 45 ++++++++++++++++++++++++++++--- tests/unit/keyinput/test_keyutils.py | 9 +++++++ 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index 1582a5485..014b16f80 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -147,10 +147,18 @@ class BaseKeyParser(QObject): return QKeySequence.NoMatch # First, try a straightforward match + self._debug_log("Trying simple match") match, binding = self._match_key(sequence) + # Then try without optional modifiers + if match == QKeySequence.NoMatch: + self._debug_log("Trying match without modifiers") + sequence = sequence.strip_modifiers() + match, binding = self._match_key(sequence) + # If that doesn't match, try a key_mapping if match == QKeySequence.NoMatch: + self._debug_log("Trying match with key_mappings") mapped = sequence.with_mappings(config.val.bindings.key_mappings) if sequence != mapped: self._debug_log("Mapped {} -> {}".format( @@ -159,10 +167,12 @@ class BaseKeyParser(QObject): sequence = mapped # If that doesn't match either, try treating it as count. + txt = str(sequence[-1]) # To account for sequences changed above. if (match == QKeySequence.NoMatch and txt.isdigit() and self._supports_count and not (not self._count and txt == '0')): + self._debug_log("Trying match as count") assert len(txt) == 1, txt if not dry_run: self._count += txt diff --git a/qutebrowser/keyinput/keyutils.py b/qutebrowser/keyinput/keyutils.py index a56efeab8..d0a17eca8 100644 --- a/qutebrowser/keyinput/keyutils.py +++ b/qutebrowser/keyinput/keyutils.py @@ -510,6 +510,12 @@ class KeySequence: return self.__class__(*keys) + def strip_modifiers(self): + """Strip optional modifiers from keys.""" + modifiers = Qt.KeypadModifier + keys = [key & ~modifiers for key in self._iter_keys()] + return self.__class__(*keys) + def with_mappings(self, mappings): """Get a new KeySequence with the given mappings applied.""" keys = [] diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py index 6465db875..e4837783a 100644 --- a/tests/unit/keyinput/test_basekeyparser.py +++ b/tests/unit/keyinput/test_basekeyparser.py @@ -198,13 +198,34 @@ class TestHandle: keyparser.execute.assert_called_with('message-info ba', None) assert not keyparser._sequence - @pytest.mark.parametrize('key, number', [(Qt.Key_0, 0), (Qt.Key_1, 1)]) - def test_number_press(self, handle_text, keyparser, key, number): - handle_text(key) + @pytest.mark.parametrize('key, modifiers, number', [ + (Qt.Key_0, Qt.NoModifier, 0), + (Qt.Key_1, Qt.NoModifier, 1), + (Qt.Key_1, Qt.KeypadModifier, 1), + ]) + def test_number_press(self, fake_keyevent, keyparser, + key, modifiers, number): + keyparser.handle(fake_keyevent(key, modifiers)) command = 'message-info {}'.format(number) keyparser.execute.assert_called_once_with(command, None) assert not keyparser._sequence + @pytest.mark.parametrize('modifiers, text', [ + (Qt.NoModifier, '2'), + (Qt.KeypadModifier, 'num-2'), + ]) + def test_number_press_keypad(self, fake_keyevent, keyparser, config_stub, + modifiers, text): + """Make sure a binding overrides the 2 binding.""" + config_stub.val.bindings.commands = {'normal': { + '2': 'message-info 2', + '': 'message-info num-2'}} + keyparser._read_config('normal') + keyparser.handle(fake_keyevent(Qt.Key_2, modifiers)) + command = 'message-info {}'.format(text) + keyparser.execute.assert_called_once_with(command, None) + assert not keyparser._sequence + def test_umlauts(self, handle_text, keyparser, config_stub): config_stub.val.bindings.commands = {'normal': {'ü': 'message-info ü'}} keyparser._read_config('normal') @@ -215,6 +236,15 @@ class TestHandle: handle_text(Qt.Key_X) keyparser.execute.assert_called_once_with('message-info a', None) + def test_mapping_keypad(self, config_stub, fake_keyevent, keyparser): + """Make sure falling back to non-numpad keys works with mappings.""" + config_stub.val.bindings.commands = {'normal': {'a': 'nop'}} + config_stub.val.bindings.key_mappings = {'1': 'a'} + keyparser._read_config('normal') + + keyparser.handle(fake_keyevent(Qt.Key_1, Qt.KeypadModifier)) + keyparser.execute.assert_called_once_with('nop', None) + def test_binding_and_mapping(self, config_stub, handle_text, keyparser): """with a conflicting binding/mapping, the binding should win.""" handle_text(Qt.Key_B) @@ -296,6 +326,15 @@ class TestCount: assert sig1.args == ('4',) assert sig2.args == ('42',) + def test_numpad(self, fake_keyevent, keyparser): + """Make sure we can enter a count via numpad.""" + for key, modifiers in [(Qt.Key_4, Qt.KeypadModifier), + (Qt.Key_2, Qt.KeypadModifier), + (Qt.Key_B, Qt.NoModifier), + (Qt.Key_A, Qt.NoModifier)]: + keyparser.handle(fake_keyevent(key, modifiers)) + keyparser.execute.assert_called_once_with('message-info ba', 42) + def test_clear_keystring(qtbot, keyparser): """Test that the keystring is cleared and the signal is emitted.""" diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py index 0bc78ca12..92e9292ef 100644 --- a/tests/unit/keyinput/test_keyutils.py +++ b/tests/unit/keyinput/test_keyutils.py @@ -377,6 +377,15 @@ class TestKeySequence: with pytest.raises(keyutils.KeyParseError): seq.append_event(event) + def test_strip_modifiers(self): + seq = keyutils.KeySequence(Qt.Key_0, + Qt.Key_1 | Qt.KeypadModifier, + Qt.Key_A | Qt.ControlModifier) + expected = keyutils.KeySequence(Qt.Key_0, + Qt.Key_1, + Qt.Key_A | Qt.ControlModifier) + assert seq.strip_modifiers() == expected + def test_with_mappings(self): seq = keyutils.KeySequence.parse('foobar') mappings = {keyutils.KeySequence('b'): keyutils.KeySequence('t')} -- cgit v1.2.3-54-g00ecf