summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2018-03-13 14:10:22 +0100
committerFlorian Bruhin <git@the-compiler.org>2018-03-13 14:42:43 +0100
commitad5fde9d05546958d88d0d8a62205c44d1c1d14f (patch)
tree9156bb17d9a6be7f43f9230151a2b3bd0766bac3
parent4cc4426428e764b0cfd263d8519bd916671cc207 (diff)
downloadqutebrowser-ad5fde9d05546958d88d0d8a62205c44d1c1d14f.tar.gz
qutebrowser-ad5fde9d05546958d88d0d8a62205c44d1c1d14f.zip
Fall back to non-keypad keys without any keypad bindings
Fixes #3701 (cherry picked from commit b88ac51d25da043ca431b2cc12a353f34bce06f7)
-rw-r--r--qutebrowser/keyinput/basekeyparser.py10
-rw-r--r--qutebrowser/keyinput/keyutils.py6
-rw-r--r--tests/unit/keyinput/test_basekeyparser.py45
-rw-r--r--tests/unit/keyinput/test_keyutils.py9
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 <Num+2> binding overrides the 2 binding."""
+ config_stub.val.bindings.commands = {'normal': {
+ '2': 'message-info 2',
+ '<Num+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')}