diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index fba70f1a1..75703f8de 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -335,6 +335,10 @@ DATA = collections.OrderedDict([ SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '500'), "Timeout for ambiguous keybindings."), + ('partial-timeout', + SettingValue(typ.Int(minval=0, maxval=MAXVALS['int']), '1000'), + "Timeout for partially typed keybindings."), + ('insert-mode-on-plugins', SettingValue(typ.Bool(), 'false'), "Whether to switch to insert mode when clicking flash and other " diff --git a/qutebrowser/keyinput/basekeyparser.py b/qutebrowser/keyinput/basekeyparser.py index a416d3a8e..0e3a3c517 100644 --- a/qutebrowser/keyinput/basekeyparser.py +++ b/qutebrowser/keyinput/basekeyparser.py @@ -175,7 +175,7 @@ class BaseKeyParser(QObject): self._debug_log("Ignoring, no text char") return self.Match.none - self._stop_delayed_exec() + self._stop_timers() self._keystring += txt count, cmd_input = self._split_count() @@ -246,7 +246,7 @@ class BaseKeyParser(QObject): else: return (self.Match.none, None) - def _stop_delayed_exec(self): + def _stop_timers(self): """Stop a delayed execution if any is running.""" if self._ambigious_timer.isActive() and self.do_log: log.keyboard.debug("Stopping delayed execution.") diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index a4ec39409..802d17681 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -37,12 +37,18 @@ LastPress = usertypes.enum('LastPress', ['none', 'filtertext', 'keystring']) class NormalKeyParser(keyparser.CommandKeyParser): - """KeyParser for normalmode with added STARTCHARS detection.""" + """KeyParser for normalmode with added STARTCHARS detection and more. + + Attributes: + _partial_timer: Timer to clear partial keypresses. + """ def __init__(self, win_id, parent=None): super().__init__(win_id, parent, supports_count=True, supports_chains=True) self.read_config('normal') + self._partial_timer = usertypes.Timer(self, 'partial-match') + self._partial_timer.setSingleShot(True) def __repr__(self): return utils.get_repr(self) @@ -60,7 +66,32 @@ class NormalKeyParser(keyparser.CommandKeyParser): if not self._keystring and any(txt == c for c in STARTCHARS): message.set_cmd_text(self._win_id, txt) return self.Match.definitive - return super()._handle_single_key(e) + match = super()._handle_single_key(e) + if match == self.Match.partial: + timeout = config.get('input', 'partial-timeout') + if timeout != 0: + self._partial_timer.setInterval(timeout) + self._partial_timer.timeout.connect(self._clear_partial_match) + self._partial_timer.start() + return match + + @pyqtSlot() + def _clear_partial_match(self): + """Clear a partial keystring after a timeout.""" + self._debug_log("Clearing partial keystring {}".format( + self._keystring)) + self._keystring = '' + self.keystring_updated.emit(self._keystring) + + @pyqtSlot() + def _stop_timers(self): + super()._stop_timers() + self._partial_timer.stop() + try: + self._partial_timer.timeout.disconnect(self._clear_partial_match) + except TypeError: + # no connections + pass class PromptKeyParser(keyparser.CommandKeyParser):