From 1a6907b4b0a74075a438eec974858f74ca7e0af4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 20 Jan 2014 17:01:15 +0100 Subject: [PATCH] Refactor command/key handling again --- qutebrowser/app.py | 5 ++-- qutebrowser/commands/keys.py | 51 +++++++++++++++++++++-------------- qutebrowser/commands/utils.py | 35 ++++++++++++++++++------ 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/qutebrowser/app.py b/qutebrowser/app.py index ac408f1f7..5d4e0a698 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -33,12 +33,13 @@ class QuteBrowser(QApplication): self.mainwindow = MainWindow() self.commandparser = cmdutils.CommandParser() - self.keyparser = KeyParser(self.mainwindow) + self.keyparser = KeyParser(self.mainwindow, self.commandparser) self.aboutToQuit.connect(self.config.save) self.mainwindow.tabs.keypress.connect(self.keyparser.handle) self.keyparser.set_cmd_text.connect(self.mainwindow.status.cmd.set_cmd) - self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.parse) + self.mainwindow.status.cmd.got_cmd.connect( + self.commandparser.parse_check_run) self.mainwindow.status.cmd.got_cmd.connect( self.mainwindow.tabs.setFocus) self.commandparser.error.connect(self.mainwindow.status.disp_error) diff --git a/qutebrowser/commands/keys.py b/qutebrowser/commands/keys.py index d42bee18f..0ab6734bb 100644 --- a/qutebrowser/commands/keys.py +++ b/qutebrowser/commands/keys.py @@ -3,7 +3,7 @@ import re from PyQt5.QtCore import QObject, pyqtSignal -import qutebrowser.commands.utils as cmdutils +from qutebrowser.commands.utils import ArgumentCountError class KeyParser(QObject): """Parser for vim-like key sequences""" @@ -13,12 +13,17 @@ class KeyParser(QObject): # Signal emitted when the keystring is updated keystring_updated = pyqtSignal(str) # Keybindings - key_to_cmd = {} + bindings = {} + cmdparser = None MATCH_PARTIAL = 0 MATCH_DEFINITIVE = 1 MATCH_NONE = 2 + def __init__(self, mainwindow, commandparser): + super().__init__(mainwindow) + self.cmdparser = commandparser + def from_config_sect(self, sect): """Loads keybindings from a ConfigParser section, in the config format key = command, e.g. @@ -26,7 +31,7 @@ class KeyParser(QObject): """ for (key, cmd) in sect.items(): logging.debug('registered key: {} -> {}'.format(key, cmd)) - self.key_to_cmd[key] = cmdutils.cmd_dict[cmd] + self.bindings[key] = cmd def handle(self, e): """Wrapper for _handle to emit keystring_updated after _handle""" @@ -51,9 +56,10 @@ class KeyParser(QObject): self.keystring = '' return - (countstr, cmdstr) = re.match('^(\d*)(.*)', self.keystring).groups() + (countstr, cmdstr_needle) = re.match('^(\d*)(.*)', + self.keystring).groups() - if not cmdstr: + if not cmdstr_needle: return # FIXME this doesn't handle ambigious keys correctly. @@ -62,7 +68,7 @@ class KeyParser(QObject): # configurable timeout, which triggers cmd.run() when expiring. Then # when we enter _handle() again in time we stop the timer. - (match, cmd) = self._match_key(cmdstr) + (match, cmdstr_hay) = self._match_key(cmdstr_needle) if match == self.MATCH_DEFINITIVE: pass @@ -79,28 +85,33 @@ class KeyParser(QObject): self.keystring = '' count = int(countstr) if countstr else None - if cmd.nargs and cmd.nargs != 0: - logging.debug('Filling statusbar with partial command {}'.format( - cmd.name)) - self.set_cmd_text.emit(':{} '.format(cmd.name)) - elif count is not None: - cmd.run(count=count) - else: - cmd.run() + # If we get a ValueError (invalid cmd) here, something is very wrong, + # so we don't catch it - def _match_key(self, cmdstr): + (cmd, args) = self.cmdparser.parse(cmdstr_hay) + try: + self.cmdparser.check(cmd, args) + except ArgumentCountError: + logging.debug('Filling statusbar with partial command {}'.format( + cmdstr_hay)) + self.set_cmd_text.emit(':{} '.format(cmdstr_hay)) + return + self.cmdparser.run(cmd, args, count=count) + + def _match_key(self, cmdstr_needle): """Tries to match a given cmdstr with any defined command""" try: - cmd = self.key_to_cmd[cmdstr] - return (self.MATCH_DEFINITIVE, cmd) + cmdstr_hay = self.bindings[cmdstr_needle] + return (self.MATCH_DEFINITIVE, cmdstr_hay) except KeyError: # No definitive match, check if there's a chance of a partial match - for cmd in self.key_to_cmd: + for hay in self.bindings: try: - if cmdstr[-1] == cmd[len(cmdstr) - 1]: + if cmdstr_needle[-1] == hay[len(cmdstr_needle) - 1]: return (self.MATCH_PARTIAL, None) except IndexError: - # current cmd is shorter than our cmdstr, so it won't match + # current cmd is shorter than our cmdstr_needle, so it + # won't match continue # no definitive and no partial matches if we arrived here return (self.MATCH_NONE, None) diff --git a/qutebrowser/commands/utils.py b/qutebrowser/commands/utils.py index da5478dbb..74de5a18e 100644 --- a/qutebrowser/commands/utils.py +++ b/qutebrowser/commands/utils.py @@ -23,6 +23,10 @@ def register_all(): cmd_dict[obj.name] = obj class CommandParser(QObject): + # FIXME + # + # this should work differently somehow, e.g. more than one instance, and + # remember args/cmd in between. """Parser for qutebrowser commandline commands""" error = pyqtSignal(str) # Emitted if there's an error @@ -32,26 +36,41 @@ class CommandParser(QObject): # FIXME maybe we should handle unambigious shorthands for commands # here? Or at least we should add :q for :quit. - cmd = parts[0] + cmdstr = parts[0] try: - obj = cmd_dict[cmd] + cmd = cmd_dict[cmdstr] except KeyError: - self.error.emit("{}: no such command".format(cmd)) - return + self.error.emit("{}: no such command".format(cmdstr)) + raise ValueError if len(parts) == 1: args = [] - elif obj.split_args: + elif cmd.split_args: args = shlex.split(parts[1]) else: args = [parts[1]] + return (cmd, args) + def check(self, cmd, args): try: - obj.check(args) + cmd.check(args) except ArgumentCountError: self.error.emit("{}: invalid argument count".format(cmd)) + raise + + def parse_check_run(self, text, count=None): + try: + (cmd, args) = self.parse(text) + self.check(cmd, args) + except (ArgumentCountError, ValueError): return - obj.run(args) + self.run(cmd, args) + + def run(self, cmd, args, count=None): + if count is not None: + cmd.run(args, count=count) + else: + cmd.run(args) class Command(QObject): """Base skeleton for a command. See the module help for @@ -89,7 +108,7 @@ class Command(QObject): if args: dbgout += args if count is not None: - dbgout.append("* {}".format(count)) + dbgout.append("(count={})".format(count)) logging.debug(' '.join(dbgout)) argv = [self.name]