Refactor keyparser

This commit is contained in:
Florian Bruhin 2014-04-22 17:21:29 +02:00
parent aedf1889dd
commit 0f29526f91

View File

@ -38,7 +38,7 @@ class KeyParser(QObject):
MATCH_PARTIAL: Constant for a partial match (no keychain matched yet, MATCH_PARTIAL: Constant for a partial match (no keychain matched yet,
but it's still possible in the future. but it's still possible in the future.
MATCH_DEFINITIVE: Constant for a full match (keychain matches exactly). MATCH_DEFINITIVE: Constant for a full match (keychain matches exactly).
MATCH_BOTH: There are both a partial and a definitive match. MATCH_AMBIGUOUS: There are both a partial and a definitive match.
MATCH_NONE: Constant for no match (no more matches possible). MATCH_NONE: Constant for no match (no more matches possible).
supports_count: If the keyparser should support counts. supports_count: If the keyparser should support counts.
@ -57,7 +57,7 @@ class KeyParser(QObject):
MATCH_PARTIAL = 0 MATCH_PARTIAL = 0
MATCH_DEFINITIVE = 1 MATCH_DEFINITIVE = 1
MATCH_BOTH = 2 MATCH_AMBIGUOUS = 2
MATCH_NONE = 3 MATCH_NONE = 3
supports_count = False supports_count = False
@ -123,54 +123,27 @@ class KeyParser(QObject):
logging.debug('Ignoring, no text') logging.debug('Ignoring, no text')
return return
if self._timer is not None: self._stop_delayed_exec()
logging.debug("Stopping delayed execution.")
self._timer.stop()
self._timer = None
self._keystring += txt self._keystring += txt
if self.supports_count: if self.supports_count:
(countstr, cmdstr_needle) = re.match(r'^(\d*)(.*)', (countstr, cmd_input) = re.match(r'^(\d*)(.*)',
self._keystring).groups() self._keystring).groups()
count = int(countstr) if countstr else None
else: else:
countstr = None cmd_input = self._keystring
cmdstr_needle = self._keystring count = None
if not cmdstr_needle: if not cmd_input:
return return
# FIXME this doesn't handle ambigious keys correctly. (match, binding) = self._match_key(cmd_input)
#
# If a keychain is ambigious, we probably should set up a QTimer with a
# configurable timeout, which triggers cmd.run() when expiring. Then
# when we enter _handle() again in time we stop the timer.
(match, cmdstr_hay) = self._match_key(cmdstr_needle)
if match == self.MATCH_DEFINITIVE: if match == self.MATCH_DEFINITIVE:
self._keystring = '' self._keystring = ''
count = int(countstr) if countstr else None self.execute(binding, count)
self.execute(cmdstr_hay, count=count) elif match == self.MATCH_AMBIGUOUS:
elif match == self.MATCH_BOTH: self._handle_ambiguous_match(binding, count)
logging.debug("Partial and definitive match for \"{}\"".format(
self._keystring))
time = config.get('general', 'cmd_timeout')
count = int(countstr) if countstr else None
if time == 0:
# execute immediately
self._keystring = ''
self.execute(cmdstr_hay, count=count)
else:
# execute in `time' ms
logging.debug("Scheduling execution of {} in {}ms".format(
cmdstr_hay, time))
self._timer = QTimer(self)
self._timer.setSingleShot(True)
self._timer.setInterval(time)
self._timer.timeout.connect(
partial(self.delayed_exec, cmdstr_hay, count))
self._timer.start()
elif match == self.MATCH_PARTIAL: elif match == self.MATCH_PARTIAL:
logging.debug('No match for "{}" (added {})'.format( logging.debug('No match for "{}" (added {})'.format(
self._keystring, txt)) self._keystring, txt))
@ -179,41 +152,41 @@ class KeyParser(QObject):
self._keystring)) self._keystring))
self._keystring = '' self._keystring = ''
def _match_key(self, cmdstr_needle): def _match_key(self, cmd_input):
"""Try to match a given keystring with any bound keychain. """Try to match a given keystring with any bound keychain.
Args: Args:
cmdstr_needle: The command string to find. cmd_input: The command string to find.
Return: Return:
A tuple (matchtype, hay) where matchtype is MATCH_DEFINITIVE, A tuple (matchtype, binding).
MATCH_PARTIAL or MATCH_NONE and hay is the long keystring where the matchtype: MATCH_DEFINITIVE, MATCH_AMBIGUOUS, MATCH_PARTIAL or
part was found in. MATCH_NONE
binding: - None with MATCH_PARTIAL/MATCH_NONE
- The found binding with MATCH_DEFINITIVE/
MATCH_AMBIGUOUS
""" """
# Check definitive match # A (cmd_input, binding) tuple (k, v of bindings) or None.
definitive_match = None definitive_match = None
partial_match = False partial_match = False
# Check definitive match
try: try:
cmdstr_hay = self.bindings[cmdstr_needle] definitive_match = (cmd_input, self.bindings[cmd_input])
except KeyError: except KeyError:
pass pass
else:
definitive_match = (cmdstr_needle, cmdstr_hay)
# Check partial match # Check partial match
for hay in self.bindings: for binding in self.bindings:
if definitive_match is not None and hay == definitive_match[0]: if definitive_match is not None and binding == definitive_match[0]:
# We already matched that one # We already matched that one
continue continue
try: if len(binding) < len(cmd_input):
if cmdstr_needle[-1] == hay[len(cmdstr_needle) - 1]: # binding is shorter than cmd_input, so it can't possibly match
partial_match = True
break
except IndexError:
# current cmd is shorter than our cmdstr_needle, so it
# won't match
continue continue
elif cmd_input[-1] == binding[len(cmd_input) - 1]:
partial_match = True
break
if definitive_match is not None and partial_match: if definitive_match is not None and partial_match:
return (self.MATCH_BOTH, definitive_match[1]) return (self.MATCH_AMBIGUOUS, definitive_match[1])
elif definitive_match is not None: 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:
@ -221,6 +194,37 @@ class KeyParser(QObject):
else: else:
return (self.MATCH_NONE, None) return (self.MATCH_NONE, None)
def _stop_delayed_exec(self):
"""Stop a delayed execution if any is running."""
if self._timer is not None:
logging.debug("Stopping delayed execution.")
self._timer.stop()
self._timer = None
def _handle_ambiguous_match(self, binding, count):
"""Handle an ambiguous match.
Args:
binding: The command-string to execute.
count: The count to pass.
"""
logging.debug("Ambiguous match for \"{}\"".format(self._keystring))
time = config.get('general', 'cmd_timeout')
if time == 0:
# execute immediately
self._keystring = ''
self.execute(binding, count)
else:
# execute in `time' ms
logging.debug("Scheduling execution of {} in {}ms".format(binding,
time))
self._timer = QTimer(self)
self._timer.setSingleShot(True)
self._timer.setInterval(time)
self._timer.timeout.connect(partial(self.delayed_exec, binding,
count))
self._timer.start()
def _normalize_keystr(self, keystr): def _normalize_keystr(self, keystr):
"""Normalize a keystring like Ctrl-Q to a keystring like Ctrl+Q. """Normalize a keystring like Ctrl-Q to a keystring like Ctrl+Q.