Remove support for ambiguous keybindings
This commit is contained in:
parent
bf9d401198
commit
1fc9817cd4
@ -191,7 +191,6 @@
|
|||||||
|<<hints.uppercase,hints.uppercase>>|Make chars in hint strings uppercase.
|
|<<hints.uppercase,hints.uppercase>>|Make chars in hint strings uppercase.
|
||||||
|<<history_gap_interval,history_gap_interval>>|The maximum time in minutes between two history items for them to be considered being from the same browsing session.
|
|<<history_gap_interval,history_gap_interval>>|The maximum time in minutes between two history items for them to be considered being from the same browsing session.
|
||||||
|<<ignore_case,ignore_case>>|Find text on a page case-insensitively.
|
|<<ignore_case,ignore_case>>|Find text on a page case-insensitively.
|
||||||
|<<input.ambiguous_timeout,input.ambiguous_timeout>>|Timeout (in milliseconds) for ambiguous key bindings.
|
|
||||||
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Forward unbound keys to the webview in normal mode.
|
|<<input.forward_unbound_keys,input.forward_unbound_keys>>|Forward unbound keys to the webview in normal mode.
|
||||||
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|
|<<input.insert_mode.auto_leave,input.insert_mode.auto_leave>>|Leave insert mode if a non-editable element is clicked.
|
||||||
|<<input.insert_mode.auto_load,input.insert_mode.auto_load>>|Automatically enter insert mode if an editable element is focused after loading the page.
|
|<<input.insert_mode.auto_load,input.insert_mode.auto_load>>|Automatically enter insert mode if an editable element is focused after loading the page.
|
||||||
@ -2050,13 +2049,6 @@ Valid values:
|
|||||||
|
|
||||||
Default: +pass:[smart]+
|
Default: +pass:[smart]+
|
||||||
|
|
||||||
[[input.ambiguous_timeout]]
|
|
||||||
== input.ambiguous_timeout
|
|
||||||
Timeout (in milliseconds) for ambiguous key bindings.
|
|
||||||
If the current input forms both a complete match and a partial match, the complete match will be executed after this time.
|
|
||||||
|
|
||||||
Default: +pass:[500]+
|
|
||||||
|
|
||||||
[[input.forward_unbound_keys]]
|
[[input.forward_unbound_keys]]
|
||||||
== input.forward_unbound_keys
|
== input.forward_unbound_keys
|
||||||
Forward unbound keys to the webview in normal mode.
|
Forward unbound keys to the webview in normal mode.
|
||||||
|
@ -794,19 +794,6 @@ hints.uppercase:
|
|||||||
|
|
||||||
## input
|
## input
|
||||||
|
|
||||||
# FIXME:conf get rid of this?
|
|
||||||
input.ambiguous_timeout:
|
|
||||||
default: 500
|
|
||||||
type:
|
|
||||||
name: Int
|
|
||||||
minval: 0
|
|
||||||
maxval: maxint
|
|
||||||
desc: >-
|
|
||||||
Timeout (in milliseconds) for ambiguous key bindings.
|
|
||||||
|
|
||||||
If the current input forms both a complete match and a partial match, the complete
|
|
||||||
match will be executed after this time.
|
|
||||||
|
|
||||||
input.forward_unbound_keys:
|
input.forward_unbound_keys:
|
||||||
default: auto
|
default: auto
|
||||||
type:
|
type:
|
||||||
|
@ -20,7 +20,6 @@
|
|||||||
"""Base class for vim-like key sequence parser."""
|
"""Base class for vim-like key sequence parser."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import functools
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, QObject
|
from PyQt5.QtCore import pyqtSignal, QObject
|
||||||
@ -41,7 +40,6 @@ class BaseKeyParser(QObject):
|
|||||||
partial: No keychain matched yet, but it's still possible in the
|
partial: No keychain matched yet, but it's still possible in the
|
||||||
future.
|
future.
|
||||||
definitive: Keychain matches exactly.
|
definitive: Keychain matches exactly.
|
||||||
ambiguous: There are both a partial and a definitive match.
|
|
||||||
none: No more matches possible.
|
none: No more matches possible.
|
||||||
|
|
||||||
Types: type of a key binding.
|
Types: type of a key binding.
|
||||||
@ -59,7 +57,6 @@ class BaseKeyParser(QObject):
|
|||||||
_warn_on_keychains: Whether a warning should be logged when binding
|
_warn_on_keychains: Whether a warning should be logged when binding
|
||||||
keychains in a section which does not support them.
|
keychains in a section which does not support them.
|
||||||
_keystring: The currently entered key sequence
|
_keystring: The currently entered key sequence
|
||||||
_ambiguous_timer: Timer for delayed execution with ambiguous bindings.
|
|
||||||
_modename: The name of the input mode associated with this keyparser.
|
_modename: The name of the input mode associated with this keyparser.
|
||||||
_supports_count: Whether count is supported
|
_supports_count: Whether count is supported
|
||||||
_supports_chains: Whether keychains are supported
|
_supports_chains: Whether keychains are supported
|
||||||
@ -78,16 +75,13 @@ class BaseKeyParser(QObject):
|
|||||||
do_log = True
|
do_log = True
|
||||||
passthrough = False
|
passthrough = False
|
||||||
|
|
||||||
Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous',
|
Match = usertypes.enum('Match', ['partial', 'definitive', 'other', 'none'])
|
||||||
'other', 'none'])
|
|
||||||
Type = usertypes.enum('Type', ['chain', 'special'])
|
Type = usertypes.enum('Type', ['chain', 'special'])
|
||||||
|
|
||||||
def __init__(self, win_id, parent=None, supports_count=None,
|
def __init__(self, win_id, parent=None, supports_count=None,
|
||||||
supports_chains=False):
|
supports_chains=False):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._win_id = win_id
|
self._win_id = win_id
|
||||||
self._ambiguous_timer = usertypes.Timer(self, 'ambiguous-match')
|
|
||||||
self._ambiguous_timer.setSingleShot(True)
|
|
||||||
self._modename = None
|
self._modename = None
|
||||||
self._keystring = ''
|
self._keystring = ''
|
||||||
if supports_count is None:
|
if supports_count is None:
|
||||||
@ -189,7 +183,6 @@ class BaseKeyParser(QObject):
|
|||||||
self._debug_log("Ignoring, no text char")
|
self._debug_log("Ignoring, no text char")
|
||||||
return self.Match.none
|
return self.Match.none
|
||||||
|
|
||||||
self._stop_timers()
|
|
||||||
key_mappings = config.val.bindings.key_mappings
|
key_mappings = config.val.bindings.key_mappings
|
||||||
txt = key_mappings.get(txt, txt)
|
txt = key_mappings.get(txt, txt)
|
||||||
self._keystring += txt
|
self._keystring += txt
|
||||||
@ -207,10 +200,6 @@ class BaseKeyParser(QObject):
|
|||||||
self._keystring))
|
self._keystring))
|
||||||
self.clear_keystring()
|
self.clear_keystring()
|
||||||
self.execute(binding, self.Type.chain, count)
|
self.execute(binding, self.Type.chain, count)
|
||||||
elif match == self.Match.ambiguous:
|
|
||||||
self._debug_log("Ambiguous match for '{}'.".format(
|
|
||||||
self._keystring))
|
|
||||||
self._handle_ambiguous_match(binding, count)
|
|
||||||
elif match == self.Match.partial:
|
elif match == self.Match.partial:
|
||||||
self._debug_log("No match for '{}' (added {})".format(
|
self._debug_log("No match for '{}' (added {})".format(
|
||||||
self._keystring, txt))
|
self._keystring, txt))
|
||||||
@ -230,11 +219,9 @@ class BaseKeyParser(QObject):
|
|||||||
|
|
||||||
Return:
|
Return:
|
||||||
A tuple (matchtype, binding).
|
A tuple (matchtype, binding).
|
||||||
matchtype: Match.definitive, Match.ambiguous, Match.partial or
|
matchtype: Match.definitive, Match.partial or Match.none.
|
||||||
Match.none
|
binding: - None with Match.partial/Match.none.
|
||||||
binding: - None with Match.partial/Match.none
|
- The found binding with Match.definitive.
|
||||||
- The found binding with Match.definitive/
|
|
||||||
Match.ambiguous
|
|
||||||
"""
|
"""
|
||||||
# A (cmd_input, binding) tuple (k, v of bindings) or None.
|
# A (cmd_input, binding) tuple (k, v of bindings) or None.
|
||||||
definitive_match = None
|
definitive_match = None
|
||||||
@ -252,58 +239,13 @@ class BaseKeyParser(QObject):
|
|||||||
elif binding.startswith(cmd_input):
|
elif binding.startswith(cmd_input):
|
||||||
partial_match = True
|
partial_match = True
|
||||||
break
|
break
|
||||||
if definitive_match is not None and partial_match:
|
if definitive_match is not None:
|
||||||
return (self.Match.ambiguous, definitive_match[1])
|
|
||||||
elif definitive_match is not None:
|
|
||||||
return (self.Match.definitive, definitive_match[1])
|
return (self.Match.definitive, definitive_match[1])
|
||||||
elif partial_match:
|
elif partial_match:
|
||||||
return (self.Match.partial, None)
|
return (self.Match.partial, None)
|
||||||
else:
|
else:
|
||||||
return (self.Match.none, None)
|
return (self.Match.none, None)
|
||||||
|
|
||||||
def _stop_timers(self):
|
|
||||||
"""Stop a delayed execution if any is running."""
|
|
||||||
if self._ambiguous_timer.isActive() and self.do_log:
|
|
||||||
log.keyboard.debug("Stopping delayed execution.")
|
|
||||||
self._ambiguous_timer.stop()
|
|
||||||
try:
|
|
||||||
self._ambiguous_timer.timeout.disconnect()
|
|
||||||
except TypeError:
|
|
||||||
# no connections
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _handle_ambiguous_match(self, binding, count):
|
|
||||||
"""Handle an ambiguous match.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
binding: The command-string to execute.
|
|
||||||
count: The count to pass.
|
|
||||||
"""
|
|
||||||
self._debug_log("Ambiguous match for '{}'".format(self._keystring))
|
|
||||||
time = config.val.input.ambiguous_timeout
|
|
||||||
if time == 0:
|
|
||||||
# execute immediately
|
|
||||||
self.clear_keystring()
|
|
||||||
self.execute(binding, self.Type.chain, count)
|
|
||||||
else:
|
|
||||||
# execute in `time' ms
|
|
||||||
self._debug_log("Scheduling execution of {} in {}ms".format(
|
|
||||||
binding, time))
|
|
||||||
self._ambiguous_timer.setInterval(time)
|
|
||||||
self._ambiguous_timer.timeout.connect(
|
|
||||||
functools.partial(self.delayed_exec, binding, count))
|
|
||||||
self._ambiguous_timer.start()
|
|
||||||
|
|
||||||
def delayed_exec(self, command, count):
|
|
||||||
"""Execute a delayed command.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
command/count: As if passed to self.execute()
|
|
||||||
"""
|
|
||||||
self._debug_log("Executing delayed command now!")
|
|
||||||
self.clear_keystring()
|
|
||||||
self.execute(command, self.Type.chain, count)
|
|
||||||
|
|
||||||
def handle(self, e):
|
def handle(self, e):
|
||||||
"""Handle a new keypress and call the respective handlers.
|
"""Handle a new keypress and call the respective handlers.
|
||||||
|
|
||||||
|
@ -7,11 +7,13 @@ Feature: Keyboard input
|
|||||||
# :clear-keychain
|
# :clear-keychain
|
||||||
|
|
||||||
Scenario: Clearing the keychain
|
Scenario: Clearing the keychain
|
||||||
When I run :bind foo message-error test12
|
When I run :bind ,foo message-error test12
|
||||||
And I run :bind bar message-info test12-2
|
And I run :bind ,bar message-info test12-2
|
||||||
And I press the keys "fo"
|
And I press the keys ",fo"
|
||||||
And I run :clear-keychain
|
And I run :clear-keychain
|
||||||
And I press the keys "bar"
|
And I press the keys ",bar"
|
||||||
|
And I run :unbind ,foo
|
||||||
|
And I run :unbind ,bar
|
||||||
Then the message "test12-2" should be shown
|
Then the message "test12-2" should be shown
|
||||||
|
|
||||||
# input.forward_unbound_keys
|
# input.forward_unbound_keys
|
||||||
|
@ -37,7 +37,6 @@ def keyparser(key_config_stub):
|
|||||||
0, supports_count=True, supports_chains=True)
|
0, supports_count=True, supports_chains=True)
|
||||||
kp.execute = mock.Mock()
|
kp.execute = mock.Mock()
|
||||||
yield kp
|
yield kp
|
||||||
assert not kp._ambiguous_timer.isActive()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -242,51 +241,15 @@ class TestKeyChain:
|
|||||||
'message-info 0', keyparser.Type.chain, None)
|
'message-info 0', keyparser.Type.chain, None)
|
||||||
assert keyparser._keystring == ''
|
assert keyparser._keystring == ''
|
||||||
|
|
||||||
def test_ambiguous_keychain(self, qapp, handle_text, config_stub,
|
def test_ambiguous_keychain(self, handle_text, keyparser):
|
||||||
keyparser):
|
|
||||||
config_stub.val.input.ambiguous_timeout = 100
|
|
||||||
timer = keyparser._ambiguous_timer
|
|
||||||
assert not timer.isActive()
|
|
||||||
# We start with 'a' where the keychain gives us an ambiguous result.
|
|
||||||
# Then we check if the timer has been set up correctly
|
|
||||||
handle_text((Qt.Key_A, 'a'))
|
|
||||||
assert not keyparser.execute.called
|
|
||||||
assert timer.isSingleShot()
|
|
||||||
assert timer.interval() == 100
|
|
||||||
assert timer.isActive()
|
|
||||||
# Now we type an 'x' and check 'ax' has been executed and the timer
|
|
||||||
# stopped.
|
|
||||||
handle_text((Qt.Key_X, 'x'))
|
|
||||||
keyparser.execute.assert_called_once_with(
|
|
||||||
'message-info ax', keyparser.Type.chain, None)
|
|
||||||
assert not timer.isActive()
|
|
||||||
assert keyparser._keystring == ''
|
|
||||||
|
|
||||||
def test_ambiguous_keychain_no_timeout(self, handle_text, config_stub,
|
|
||||||
keyparser):
|
|
||||||
config_stub.val.input.ambiguous_timeout = 0
|
|
||||||
handle_text((Qt.Key_A, 'a'))
|
handle_text((Qt.Key_A, 'a'))
|
||||||
assert keyparser.execute.called
|
assert keyparser.execute.called
|
||||||
assert not keyparser._ambiguous_timer.isActive()
|
|
||||||
|
|
||||||
def test_invalid_keychain(self, handle_text, keyparser):
|
def test_invalid_keychain(self, handle_text, keyparser):
|
||||||
handle_text((Qt.Key_B, 'b'))
|
handle_text((Qt.Key_B, 'b'))
|
||||||
handle_text((Qt.Key_C, 'c'))
|
handle_text((Qt.Key_C, 'c'))
|
||||||
assert keyparser._keystring == ''
|
assert keyparser._keystring == ''
|
||||||
|
|
||||||
def test_ambiguous_delayed_exec(self, handle_text, config_stub, qtbot,
|
|
||||||
keyparser):
|
|
||||||
config_stub.val.input.ambiguous_timeout = 100
|
|
||||||
|
|
||||||
# 'a' is an ambiguous result.
|
|
||||||
handle_text((Qt.Key_A, 'a'))
|
|
||||||
assert not keyparser.execute.called
|
|
||||||
assert keyparser._ambiguous_timer.isActive()
|
|
||||||
# We wait for the timeout to occur.
|
|
||||||
with qtbot.waitSignal(keyparser.keystring_updated):
|
|
||||||
pass
|
|
||||||
assert keyparser.execute.called
|
|
||||||
|
|
||||||
|
|
||||||
class TestCount:
|
class TestCount:
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user