Remove :<count>:cmd syntax support.

CommandRunner.parse had some logic for handling commands of form
:<count>:cmd. However, this complicated the parsing logic for something
that appears to only be used in tests. One could use it in a
userscript, but this is unlikely as it is undocumented. Removing
support for this simplifies the logic of parse.

The commnd `run-with-count` is added to provide this functionality.
It works like `repeat` but passes the count along to the command
instead of running the command multiple times.

This resolves #1997: Qutebrowser crashes when pasting commands.
This bug was caused by excess stripping of ':' from the command string
by _parse_count.
This commit is contained in:
Ryan Roden-Corrent 2016-09-30 16:55:51 -04:00
parent aba67d0822
commit fbc084e416
8 changed files with 39 additions and 52 deletions

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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):