diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 3d74fc266..a4644b2a3 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -31,8 +31,7 @@ from qutebrowser.utils import message, objreg, qtutils, utils from qutebrowser.misc import split -ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline', - 'count']) +ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline']) last_command = {} @@ -154,26 +153,6 @@ class CommandRunner(QObject): for sub in sub_texts: yield self.parse(sub, *args, **kwargs) - def _parse_count(self, cmdstr): - """Split a count prefix off from a command for parse(). - - Args: - cmdstr: The command/args including the count. - - Return: - A (count, cmdstr) tuple, with count being None or int. - """ - if ':' not in cmdstr: - return (None, cmdstr) - - count, cmdstr = cmdstr.split(':', maxsplit=1) - try: - count = int(count) - except ValueError: - # We just ignore invalid prefixes - count = None - return (count, cmdstr) - def parse(self, text, *, fallback=False, keep=False): """Split the commandline text into command and arguments. @@ -187,7 +166,6 @@ class CommandRunner(QObject): A ParseResult tuple. """ cmdstr, sep, argstr = text.partition(' ') - count, cmdstr = self._parse_count(cmdstr) if not cmdstr and not fallback: raise cmdexc.NoSuchCommandError("No command given") @@ -202,8 +180,7 @@ class CommandRunner(QObject): raise cmdexc.NoSuchCommandError( '{}: no such command'.format(cmdstr)) cmdline = split.split(text, keep=keep) - return ParseResult(cmd=None, args=None, cmdline=cmdline, - count=count) + return ParseResult(cmd=None, args=None, cmdline=cmdline) args = self._split_args(cmd, argstr, keep) if keep and args: @@ -213,7 +190,7 @@ class CommandRunner(QObject): else: cmdline = [cmdstr] + args[:] - return ParseResult(cmd=cmd, args=args, cmdline=cmdline, count=count) + return ParseResult(cmd=cmd, args=args, cmdline=cmdline) def _completion_match(self, cmdstr): """Replace cmdstr with a matching completion if there's only one match. @@ -291,20 +268,10 @@ class CommandRunner(QObject): args = result.args else: args = replace_variables(self._win_id, result.args) - if count is not None: - if result.count is not None: - raise cmdexc.CommandMetaError("Got count via command and " - "prefix!") - result.cmd.run(self._win_id, args, count=count) - elif result.count is not None: - result.cmd.run(self._win_id, args, count=result.count) - else: - result.cmd.run(self._win_id, args) + result.cmd.run(self._win_id, args, count=count) if result.cmdline[0] != 'repeat-command': - last_command[cur_mode] = ( - self._parse_count(text)[1], - count if count is not None else result.count) + last_command[cur_mode] = (text, count) @pyqtSlot(str, int) @pyqtSlot(str) diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index 2447afd53..62202998c 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -86,6 +86,23 @@ def repeat(times: int, command, win_id): commandrunner.run_safely(command) +@cmdutils.register(maxsplit=1, hide=True, no_cmd_split=True, + no_replace_variables=True) +@cmdutils.argument('win_id', win_id=True) +@cmdutils.argument('count', count=True) +def run_with_count(count_arg: int, command, win_id, count=1): + """Run a command with the given count. + + If run_with_count itself is run with a count, it multiplies count_arg. + + Args: + count_arg: The count to pass to the command. + command: The command to run, with optional args. + count: The count that run_with_count itself received. + """ + runners.CommandRunner(win_id).run(command, count_arg * count) + + @cmdutils.register(hide=True) def message_error(text): """Show an error message in the statusbar. diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature index fdfb7f6d8..47b00622d 100644 --- a/tests/end2end/features/caret.feature +++ b/tests/end2end/features/caret.feature @@ -3,7 +3,7 @@ Feature: Caret mode Background: Given I open data/caret.html - And I run :tab-only ;; :enter-mode caret + And I run :tab-only ;; enter-mode caret # document diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 29fab26ad..701d9d7cf 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -499,7 +499,7 @@ Feature: Various utility commands. Scenario: Using :debug-log-capacity When I run :debug-log-capacity 100 And I run :message-info oldstuff - And I run :repeat 20 :message-info otherstuff + And I run :repeat 20 message-info otherstuff And I run :message-info newstuff And I open qute:log Then the page should contain the plaintext "newstuff" @@ -723,3 +723,13 @@ Feature: Various utility commands. And I run :command-accept And I set general -> private-browsing to false Then the message "blah" should be shown + + ## :run-with-count + + Scenario: :run-with-count + When I run :run-with-count 2 scroll down + Then "command called: scroll ['down'] (count=2)" should be logged + + Scenario: :run-with-count with count + When I run :run-with-count 2 scroll down with count 3 + Then "command called: scroll ['down'] (count=6)" should be logged diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature index 7ae98619c..b138397c5 100644 --- a/tests/end2end/features/tabs.feature +++ b/tests/end2end/features/tabs.feature @@ -987,13 +987,13 @@ Feature: Tab management Scenario: Using :tab-next after closing last tab (#1448) When I set tabs -> last-close to close And I run :tab-only - And I run :tab-close ;; :tab-next + And I run :tab-close ;; tab-next Then qutebrowser should quit And no crash should happen Scenario: Using :tab-prev after closing last tab (#1448) When I set tabs -> last-close to close And I run :tab-only - And I run :tab-close ;; :tab-prev + And I run :tab-close ;; tab-prev Then qutebrowser should quit And no crash should happen diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index acd6b0f49..46a0d2ff7 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -437,7 +437,8 @@ class QuteProc(testprocess.Process): command = command.replace('\\', r'\\') if count is not None: - command = ':{}:{}'.format(count, command.lstrip(':')) + command = ':run-with-count {} {}'.format(count, + command.lstrip(':')) self.send_ipc([command]) if not invalid: diff --git a/tests/unit/commands/test_runners.py b/tests/unit/commands/test_runners.py index 50fcbde1c..77c9d8f72 100644 --- a/tests/unit/commands/test_runners.py +++ b/tests/unit/commands/test_runners.py @@ -64,15 +64,6 @@ class TestCommandRunner: with pytest.raises(cmdexc.NoSuchCommandError): list(cr.parse_all(command)) - def test_parse_with_count(self): - """Test parsing of commands with a count.""" - cr = runners.CommandRunner(0) - result = cr.parse('20:scroll down') - assert result.cmd.name == 'scroll' - assert result.count == 20 - assert result.args == ['down'] - assert result.cmdline == ['scroll', 'down'] - def test_partial_parsing(self): """Test partial parsing with a runner where it's enabled. diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 7e9fdbb48..2c02efa44 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -181,6 +181,7 @@ def _set_cmd_prompt(cmd, txt): (':open -- |', None, ''), (':gibberish nonesense |', None, ''), ('/:help|', None, ''), + ('::bind|', usertypes.Completion.command, ':bind'), ]) def test_update_completion(txt, kind, pattern, status_command_stub, completer_obj, completion_widget_stub):