Skip to content

Commit

Permalink
Remove support for ambiguous keybindings
Browse files Browse the repository at this point in the history
  • Loading branch information
The-Compiler committed Sep 13, 2017
1 parent bf9d401 commit 1fc9817
Show file tree
Hide file tree
Showing 5 changed files with 12 additions and 126 deletions.
8 changes: 0 additions & 8 deletions doc/help/settings.asciidoc
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 0 additions & 13 deletions qutebrowser/config/configdata.yml
Expand Up @@ -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:
Expand Down
68 changes: 5 additions & 63 deletions qutebrowser/keyinput/basekeyparser.py
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -189,7 +183,6 @@ def _handle_single_key(self, e):
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
Expand All @@ -207,10 +200,6 @@ def _handle_single_key(self, e):
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))
Expand All @@ -230,11 +219,9 @@ def _match_key(self, cmd_input):
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
Expand All @@ -252,58 +239,13 @@ def _match_key(self, cmd_input):
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.
Expand Down
10 changes: 6 additions & 4 deletions tests/end2end/features/keyinput.feature
Expand Up @@ -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
Expand Down
39 changes: 1 addition & 38 deletions tests/unit/keyinput/test_basekeyparser.py
Expand Up @@ -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
Expand Down Expand Up @@ -242,51 +241,15 @@ def test_0_press(self, handle_text, keyparser):
'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:


Expand Down

0 comments on commit 1fc9817

Please sign in to comment.