Initial refactoring for new key parsing
This commit is contained in:
parent
7fe0f9fb16
commit
ddcb5445a2
@ -1651,6 +1651,7 @@ class Key(BaseType):
|
|||||||
self._basic_py_validation(value, str)
|
self._basic_py_validation(value, str)
|
||||||
if not value:
|
if not value:
|
||||||
return None
|
return None
|
||||||
if utils.is_special_key(value):
|
#if utils.is_special_key(value):
|
||||||
value = '<{}>'.format(utils.normalize_keystr(value[1:-1]))
|
# value = '<{}>'.format(utils.normalize_keystr(value[1:-1]))
|
||||||
return value
|
#return value
|
||||||
|
return utils.parse_keystring(value)
|
||||||
|
@ -24,6 +24,7 @@ import re
|
|||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, QObject
|
from PyQt5.QtCore import pyqtSignal, QObject
|
||||||
|
from PyQt5.QtGui import QKeySequence
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import usertypes, log, utils
|
from qutebrowser.utils import usertypes, log, utils
|
||||||
@ -53,7 +54,6 @@ class BaseKeyParser(QObject):
|
|||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
bindings: Bound key bindings
|
bindings: Bound key bindings
|
||||||
special_bindings: Bound special bindings (<Foo>).
|
|
||||||
_win_id: The window ID this keyparser is associated with.
|
_win_id: The window ID this keyparser is associated with.
|
||||||
_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.
|
||||||
@ -76,7 +76,6 @@ class BaseKeyParser(QObject):
|
|||||||
do_log = True
|
do_log = True
|
||||||
passthrough = False
|
passthrough = False
|
||||||
|
|
||||||
Match = enum.Enum('Match', ['partial', 'definitive', 'other', 'none'])
|
|
||||||
Type = enum.Enum('Type', ['chain', 'special'])
|
Type = enum.Enum('Type', ['chain', 'special'])
|
||||||
|
|
||||||
def __init__(self, win_id, parent=None, supports_count=None,
|
def __init__(self, win_id, parent=None, supports_count=None,
|
||||||
@ -91,7 +90,6 @@ class BaseKeyParser(QObject):
|
|||||||
self._supports_chains = supports_chains
|
self._supports_chains = supports_chains
|
||||||
self._warn_on_keychains = True
|
self._warn_on_keychains = True
|
||||||
self.bindings = {}
|
self.bindings = {}
|
||||||
self.special_bindings = {}
|
|
||||||
config.instance.changed.connect(self._on_config_changed)
|
config.instance.changed.connect(self._on_config_changed)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -118,6 +116,7 @@ class BaseKeyParser(QObject):
|
|||||||
Return:
|
Return:
|
||||||
True if event has been handled, False otherwise.
|
True if event has been handled, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
# FIXME remove?
|
||||||
binding = utils.keyevent_to_string(e)
|
binding = utils.keyevent_to_string(e)
|
||||||
if binding is None:
|
if binding is None:
|
||||||
self._debug_log("Ignoring only-modifier keyeevent.")
|
self._debug_log("Ignoring only-modifier keyeevent.")
|
||||||
@ -161,8 +160,8 @@ class BaseKeyParser(QObject):
|
|||||||
count = None
|
count = None
|
||||||
return count, cmd_input
|
return count, cmd_input
|
||||||
|
|
||||||
def _handle_single_key(self, e):
|
def _handle_key(self, e):
|
||||||
"""Handle a new keypress with a single key (no modifiers).
|
"""Handle a new keypress.
|
||||||
|
|
||||||
Separate the keypress into count/command, then check if it matches
|
Separate the keypress into count/command, then check if it matches
|
||||||
any possible command, and either run the command, ignore it, or
|
any possible command, and either run the command, ignore it, or
|
||||||
@ -186,11 +185,11 @@ class BaseKeyParser(QObject):
|
|||||||
|
|
||||||
if (not txt) or is_control_char:
|
if (not txt) or is_control_char:
|
||||||
self._debug_log("Ignoring, no text char")
|
self._debug_log("Ignoring, no text char")
|
||||||
return self.Match.none
|
return QKeySequence.NoMatch
|
||||||
|
|
||||||
count, cmd_input = self._split_count(self._keystring + txt)
|
count, cmd_input = self._split_count(self._keystring + txt)
|
||||||
match, binding = self._match_key(cmd_input)
|
match, binding = self._match_key(cmd_input)
|
||||||
if match == self.Match.none:
|
if match == QKeySequence.NoMatch:
|
||||||
mappings = config.val.bindings.key_mappings
|
mappings = config.val.bindings.key_mappings
|
||||||
mapped = mappings.get(txt, None)
|
mapped = mappings.get(txt, None)
|
||||||
if mapped is not None:
|
if mapped is not None:
|
||||||
@ -199,19 +198,19 @@ class BaseKeyParser(QObject):
|
|||||||
match, binding = self._match_key(cmd_input)
|
match, binding = self._match_key(cmd_input)
|
||||||
|
|
||||||
self._keystring += txt
|
self._keystring += txt
|
||||||
if match == self.Match.definitive:
|
if match == QKeySequence.ExactMatch:
|
||||||
self._debug_log("Definitive match for '{}'.".format(
|
self._debug_log("Definitive match for '{}'.".format(
|
||||||
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.partial:
|
elif match == QKeySequence.PartialMatch:
|
||||||
self._debug_log("No match for '{}' (added {})".format(
|
self._debug_log("No match for '{}' (added {})".format(
|
||||||
self._keystring, txt))
|
self._keystring, txt))
|
||||||
elif match == self.Match.none:
|
elif match == QKeySequence.NoMatch:
|
||||||
self._debug_log("Giving up with '{}', no matches".format(
|
self._debug_log("Giving up with '{}', no matches".format(
|
||||||
self._keystring))
|
self._keystring))
|
||||||
self.clear_keystring()
|
self.clear_keystring()
|
||||||
elif match == self.Match.other:
|
elif match is None:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise utils.Unreachable("Invalid match value {!r}".format(match))
|
raise utils.Unreachable("Invalid match value {!r}".format(match))
|
||||||
@ -231,29 +230,14 @@ class BaseKeyParser(QObject):
|
|||||||
"""
|
"""
|
||||||
if not cmd_input:
|
if not cmd_input:
|
||||||
# Only a count, no command yet, but we handled it
|
# Only a count, no command yet, but we handled it
|
||||||
return (self.Match.other, None)
|
return (None, None)
|
||||||
# A (cmd_input, binding) tuple (k, v of bindings) or None.
|
|
||||||
definitive_match = None
|
for seq, cmd in self.bindings.items():
|
||||||
partial_match = False
|
match = seq.matches(utils.parse_keystring(cmd_input))
|
||||||
# Check definitive match
|
if match != QKeySequence.NoMatch:
|
||||||
try:
|
return (match, cmd)
|
||||||
definitive_match = (cmd_input, self.bindings[cmd_input])
|
|
||||||
except KeyError:
|
return (QKeySequence.NoMatch, None)
|
||||||
pass
|
|
||||||
# Check partial match
|
|
||||||
for binding in self.bindings:
|
|
||||||
if definitive_match is not None and binding == definitive_match[0]:
|
|
||||||
# We already matched that one
|
|
||||||
continue
|
|
||||||
elif binding.startswith(cmd_input):
|
|
||||||
partial_match = True
|
|
||||||
break
|
|
||||||
if definitive_match is not None:
|
|
||||||
return (self.Match.definitive, definitive_match[1])
|
|
||||||
elif partial_match:
|
|
||||||
return (self.Match.partial, None)
|
|
||||||
else:
|
|
||||||
return (self.Match.none, None)
|
|
||||||
|
|
||||||
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.
|
||||||
@ -264,15 +248,17 @@ class BaseKeyParser(QObject):
|
|||||||
Return:
|
Return:
|
||||||
True if the event was handled, False otherwise.
|
True if the event was handled, False otherwise.
|
||||||
"""
|
"""
|
||||||
handled = self._handle_special_key(e)
|
match = self._handle_key(e)
|
||||||
|
|
||||||
|
# FIXME
|
||||||
|
# if handled or not self._supports_chains:
|
||||||
|
# return handled
|
||||||
|
|
||||||
if handled or not self._supports_chains:
|
|
||||||
return handled
|
|
||||||
match = self._handle_single_key(e)
|
|
||||||
# don't emit twice if the keystring was cleared in self.clear_keystring
|
# don't emit twice if the keystring was cleared in self.clear_keystring
|
||||||
if self._keystring:
|
if self._keystring:
|
||||||
self.keystring_updated.emit(self._keystring)
|
self.keystring_updated.emit(self._keystring)
|
||||||
return match != self.Match.none
|
|
||||||
|
return match != QKeySequence.NoMatch
|
||||||
|
|
||||||
@config.change_filter('bindings')
|
@config.change_filter('bindings')
|
||||||
def _on_config_changed(self):
|
def _on_config_changed(self):
|
||||||
@ -295,22 +281,18 @@ class BaseKeyParser(QObject):
|
|||||||
else:
|
else:
|
||||||
self._modename = modename
|
self._modename = modename
|
||||||
self.bindings = {}
|
self.bindings = {}
|
||||||
self.special_bindings = {}
|
|
||||||
|
|
||||||
for key, cmd in config.key_instance.get_bindings_for(modename).items():
|
for key, cmd in config.key_instance.get_bindings_for(modename).items():
|
||||||
assert cmd
|
assert cmd
|
||||||
self._parse_key_command(modename, key, cmd)
|
self.bindings[key] = cmd
|
||||||
|
|
||||||
def _parse_key_command(self, modename, key, cmd):
|
def _parse_key_command(self, modename, key, cmd):
|
||||||
"""Parse the keys and their command and store them in the object."""
|
"""Parse the keys and their command and store them in the object."""
|
||||||
if utils.is_special_key(key):
|
# FIXME
|
||||||
self.special_bindings[key[1:-1]] = cmd
|
# elif self._warn_on_keychains:
|
||||||
elif self._supports_chains:
|
# log.keyboard.warning("Ignoring keychain '{}' in mode '{}' because "
|
||||||
self.bindings[key] = cmd
|
# "keychains are not supported there."
|
||||||
elif self._warn_on_keychains:
|
# .format(key, modename))
|
||||||
log.keyboard.warning("Ignoring keychain '{}' in mode '{}' because "
|
|
||||||
"keychains are not supported there."
|
|
||||||
.format(key, modename))
|
|
||||||
|
|
||||||
def execute(self, cmdstr, keytype, count=None):
|
def execute(self, cmdstr, keytype, count=None):
|
||||||
"""Handle a completed keychain.
|
"""Handle a completed keychain.
|
||||||
|
@ -511,12 +511,26 @@ def _parse_single_key(keystr):
|
|||||||
return KeyInfo(key, modifiers, text)
|
return KeyInfo(key, modifiers, text)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_keystring(keystr):
|
||||||
|
key = ''
|
||||||
|
special = False
|
||||||
|
for c in keystr:
|
||||||
|
if c == '>':
|
||||||
|
yield normalize_keystr(key)
|
||||||
|
key = ''
|
||||||
|
special = False
|
||||||
|
elif c == '<':
|
||||||
|
special = True
|
||||||
|
elif special:
|
||||||
|
key += c
|
||||||
|
else:
|
||||||
|
yield 'Shift+' + c if c.isupper() else c
|
||||||
|
|
||||||
|
|
||||||
def parse_keystring(keystr):
|
def parse_keystring(keystr):
|
||||||
"""Parse a keystring like <Ctrl-x> or xyz and return a KeyInfo list."""
|
"""Parse a keystring like <Ctrl-x> or xyz and return a KeyInfo list."""
|
||||||
if is_special_key(keystr):
|
s = ', '.join(_parse_keystring(keystr))
|
||||||
return [_parse_single_key(keystr)]
|
return QKeySequence(s)
|
||||||
else:
|
|
||||||
return [_parse_single_key(char) for char in keystr]
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_keystr(keystr):
|
def normalize_keystr(keystr):
|
||||||
|
Loading…
Reference in New Issue
Block a user