From c7c5a985062c1adc0e301ad16c0cf72b75ca501c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 4 Aug 2016 02:43:02 +0200 Subject: [PATCH 1/6] Allow {url} and {url:pretty} as parts of arguments This makes commands like `:open web.archive.org/web/{url}` possible. This commit also adds a no_replace_variables command register argument that stops the replacement from happening, which is important for commands like `:bind` and `:spawn` that take a command as an argument. --- qutebrowser/browser/commands.py | 2 +- qutebrowser/commands/command.py | 4 +++- qutebrowser/commands/runners.py | 17 ++++++++++------- qutebrowser/config/parsers/keyconf.py | 3 ++- qutebrowser/mainwindow/statusbar/command.py | 3 +-- qutebrowser/misc/utilcmds.py | 4 ++-- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 77811e0e4..9c031f116 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -992,7 +992,7 @@ class CommandDispatcher: self._tabbed_browser.setUpdatesEnabled(True) @cmdutils.register(instance='command-dispatcher', scope='window', - maxsplit=0) + maxsplit=0, no_replace_variables=True) def spawn(self, cmdline, userscript=False, verbose=False, detach=False): """Spawn a command in a shell. diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index aa75e2748..c06afe646 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -82,6 +82,7 @@ class Command: no_cmd_split: If true, ';;' to split sub-commands is ignored. backend: Which backend the command works with (or None if it works with both) + no_replace_variables: Whether or not to replace variables like {url} _qute_args: The saved data from @cmdutils.argument _needs_js: Whether the command needs javascript enabled _modes: The modes the command can be executed in. @@ -95,7 +96,7 @@ class Command: hide=False, modes=None, not_modes=None, needs_js=False, debug=False, ignore_args=False, deprecated=False, no_cmd_split=False, star_args_optional=False, scope='global', - backend=None): + backend=None, no_replace_variables=False): # I really don't know how to solve this in a better way, I tried. # pylint: disable=too-many-locals if modes is not None and not_modes is not None: @@ -127,6 +128,7 @@ class Command: self.handler = handler self.no_cmd_split = no_cmd_split self.backend = backend + self.no_replace_variables = no_replace_variables self.docparser = docutils.DocstringParser(handler) self.parser = argparser.ArgumentParser( diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index d1f15a415..7d2ecdd7a 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -52,16 +52,16 @@ def replace_variables(win_id, arglist): args = [] tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) - if '{url}' in arglist: + if any('{url}' in arg for arg in arglist): url = _current_url(tabbed_browser).toString(QUrl.FullyEncoded | QUrl.RemovePassword) - if '{url:pretty}' in arglist: + if any('{url:pretty}' in arg for arg in arglist): pretty_url = _current_url(tabbed_browser).toString(QUrl.RemovePassword) for arg in arglist: - if arg == '{url}': - args.append(url) - elif arg == '{url:pretty}': - args.append(pretty_url) + if '{url}' in arg: + args.append(arg.replace('{url}', url)) + elif '{url:pretty}' in arg: + args.append(arg.replace('{url:pretty}', pretty_url)) else: args.append(arg) return args @@ -279,7 +279,10 @@ class CommandRunner(QObject): window=self._win_id) cur_mode = mode_manager.mode - args = replace_variables(self._win_id, result.args) + if result.cmd.no_replace_variables: + 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 " diff --git a/qutebrowser/config/parsers/keyconf.py b/qutebrowser/config/parsers/keyconf.py index b2596fb62..d1c2c7ad1 100644 --- a/qutebrowser/config/parsers/keyconf.py +++ b/qutebrowser/config/parsers/keyconf.py @@ -150,7 +150,8 @@ class KeyConfigParser(QObject): data = str(self) f.write(data) - @cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True) + @cmdutils.register(instance='key-config', maxsplit=1, no_cmd_split=True, + no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) @cmdutils.argument('key', completion=usertypes.Completion.empty) @cmdutils.argument('command', completion=usertypes.Completion.command) diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 68eb615df..781df81ad 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -23,7 +23,7 @@ from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize from PyQt5.QtWidgets import QSizePolicy from qutebrowser.keyinput import modeman, modeparsers -from qutebrowser.commands import cmdexc, cmdutils, runners +from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.misc import cmdhistory, split from qutebrowser.misc import miscwidgets as misc from qutebrowser.utils import usertypes, log, objreg @@ -109,7 +109,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): append: If given, the text is appended to the current text. """ args = split.simple_split(text) - args = runners.replace_variables(self._win_id, args) text = ' '.join(args) if space: diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index e1d7c6a61..05d3fa7c8 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -39,7 +39,7 @@ from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import -@cmdutils.register(maxsplit=1, no_cmd_split=True) +@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) def later(ms: int, command, win_id): """Execute a command after some time. @@ -69,7 +69,7 @@ def later(ms: int, command, win_id): raise -@cmdutils.register(maxsplit=1, no_cmd_split=True) +@cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', win_id=True) def repeat(times: int, command, win_id): """Repeat a given command. From 827de1743d328b50778ea9b52bc5401a5c44419a Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 4 Aug 2016 13:21:19 +0200 Subject: [PATCH 2/6] Document no_replace_variables, misc fixes Add no_replace_variables to the asciidoc, improve its description in the decorator, remove now unnecessary argument parsing in set-cmd-text --- doc/help/commands.asciidoc | 4 ++++ qutebrowser/commands/command.py | 2 +- qutebrowser/mainwindow/statusbar/command.py | 3 --- scripts/dev/src2asciidoc.py | 4 +++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 9835f1a3f..cbb81235d 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -111,6 +111,7 @@ Bind a key to a command. ==== note * This command does not split arguments after the last argument and handles quotes literally. * With this command, +;;+ is interpreted literally instead of splitting off a second command. +* This command does not replace variables like +\{url\}+. [[bookmark-add]] === bookmark-add @@ -412,6 +413,7 @@ Execute a command after some time. ==== note * This command does not split arguments after the last argument and handles quotes literally. * With this command, +;;+ is interpreted literally instead of splitting off a second command. +* This command does not replace variables like +\{url\}+. [[messages]] === messages @@ -579,6 +581,7 @@ Repeat a given command. ==== note * This command does not split arguments after the last argument and handles quotes literally. * With this command, +;;+ is interpreted literally instead of splitting off a second command. +* This command does not replace variables like +\{url\}+. [[report]] === report @@ -714,6 +717,7 @@ Note the `{url}` and `{url:pretty}` variables might be useful here. `{url}` gets ==== note * This command does not split arguments after the last argument and handles quotes literally. +* This command does not replace variables like +\{url\}+. [[stop]] === stop diff --git a/qutebrowser/commands/command.py b/qutebrowser/commands/command.py index c06afe646..e49c497af 100644 --- a/qutebrowser/commands/command.py +++ b/qutebrowser/commands/command.py @@ -82,7 +82,7 @@ class Command: no_cmd_split: If true, ';;' to split sub-commands is ignored. backend: Which backend the command works with (or None if it works with both) - no_replace_variables: Whether or not to replace variables like {url} + no_replace_variables: Don't replace variables like {url} _qute_args: The saved data from @cmdutils.argument _needs_js: Whether the command needs javascript enabled _modes: The modes the command can be executed in. diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 781df81ad..62a01ef32 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -108,9 +108,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): space: If given, a space is added to the end. append: If given, the text is appended to the current text. """ - args = split.simple_split(text) - text = ' '.join(args) - if space: text += ' ' if append: diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index af1ba0936..5d7819ea0 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -239,7 +239,7 @@ def _get_command_doc_notes(cmd): Yield: Strings which should be added to the docs. """ - if cmd.maxsplit is not None or cmd.no_cmd_split: + if cmd.maxsplit is not None or cmd.no_cmd_split or cmd.no_replace_variables: yield "" yield "==== note" if cmd.maxsplit is not None: @@ -248,6 +248,8 @@ def _get_command_doc_notes(cmd): if cmd.no_cmd_split: yield ("* With this command, +;;+ is interpreted literally " "instead of splitting off a second command.") + if cmd.no_replace_variables: + yield "* This command does not replace variables like +\{url\}+." def _get_action_metavar(action, nargs=1): From 8b9f37cc8408feea595203c662e859e272e0d51c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 4 Aug 2016 13:45:46 +0200 Subject: [PATCH 3/6] Use raw string for asciidoc backslashes --- scripts/dev/src2asciidoc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 5d7819ea0..b680a5ae8 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -249,7 +249,7 @@ def _get_command_doc_notes(cmd): yield ("* With this command, +;;+ is interpreted literally " "instead of splitting off a second command.") if cmd.no_replace_variables: - yield "* This command does not replace variables like +\{url\}+." + yield r"* This command does not replace variables like +\{url\}+." def _get_action_metavar(action, nargs=1): From 7999c493ac7a2da9c79be9493600ede0b9e41a2c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 4 Aug 2016 15:16:35 +0200 Subject: [PATCH 4/6] Remove unnecessary import, split long line --- qutebrowser/mainwindow/statusbar/command.py | 2 +- scripts/dev/src2asciidoc.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 62a01ef32..df419c0dd 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -24,7 +24,7 @@ from PyQt5.QtWidgets import QSizePolicy from qutebrowser.keyinput import modeman, modeparsers from qutebrowser.commands import cmdexc, cmdutils -from qutebrowser.misc import cmdhistory, split +from qutebrowser.misc import cmdhistory from qutebrowser.misc import miscwidgets as misc from qutebrowser.utils import usertypes, log, objreg diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index b680a5ae8..2589d0fc5 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -239,7 +239,8 @@ def _get_command_doc_notes(cmd): Yield: Strings which should be added to the docs. """ - if cmd.maxsplit is not None or cmd.no_cmd_split or cmd.no_replace_variables: + if (cmd.maxsplit is not None or cmd.no_cmd_split or + cmd.no_replace_variables): yield "" yield "==== note" if cmd.maxsplit is not None: From 8a527b5faf6c3e80662461806c8ba4859e7b3d7c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 5 Aug 2016 16:52:47 +0200 Subject: [PATCH 5/6] Make :spawn parse normally without maxsplit --- doc/help/commands.asciidoc | 6 +----- qutebrowser/browser/commands.py | 14 +++----------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index cbb81235d..92d4cf02b 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -696,7 +696,7 @@ You can use the `{url}` and `{url:pretty}` variables here which will get replace [[spawn]] === spawn -Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+ +Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline' ['cmdline' ...]+ Spawn a command in a shell. @@ -715,10 +715,6 @@ Note the `{url}` and `{url:pretty}` variables might be useful here. `{url}` gets * +*-v*+, +*--verbose*+: Show notifications when the command started/exited. * +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser. -==== note -* This command does not split arguments after the last argument and handles quotes literally. -* This command does not replace variables like +\{url\}+. - [[stop]] === stop Stop loading in the current/[count]th tab. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 9c031f116..82062357f 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -991,9 +991,8 @@ class CommandDispatcher: finally: self._tabbed_browser.setUpdatesEnabled(True) - @cmdutils.register(instance='command-dispatcher', scope='window', - maxsplit=0, no_replace_variables=True) - def spawn(self, cmdline, userscript=False, verbose=False, detach=False): + @cmdutils.register(instance='command-dispatcher', scope='window') + def spawn(self, *cmdline, userscript=False, verbose=False, detach=False): """Spawn a command in a shell. Note the `{url}` and `{url:pretty}` variables might be useful here. @@ -1012,14 +1011,7 @@ class CommandDispatcher: detach: Whether the command should be detached from qutebrowser. cmdline: The commandline to execute. """ - try: - cmd, *args = shlex.split(cmdline) - except ValueError as e: - raise cmdexc.CommandError("Error while splitting command: " - "{}".format(e)) - - args = runners.replace_variables(self._win_id, args) - + cmd, *args = cmdline log.procs.debug("Executing {} with args {}, userscript={}".format( cmd, args, userscript)) if userscript: From 1e97247c63e321e28aaf947f07f8c8eb95584f7e Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 5 Aug 2016 18:04:14 +0200 Subject: [PATCH 6/6] Revert spawn's splitting, blacklist from doc Blacklist spawn from getting the no_replace_variables doc note. --- doc/help/commands.asciidoc | 5 ++++- qutebrowser/browser/commands.py | 14 +++++++++++--- scripts/dev/src2asciidoc.py | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 92d4cf02b..c0bb43f6c 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -696,7 +696,7 @@ You can use the `{url}` and `{url:pretty}` variables here which will get replace [[spawn]] === spawn -Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline' ['cmdline' ...]+ +Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+ Spawn a command in a shell. @@ -715,6 +715,9 @@ Note the `{url}` and `{url:pretty}` variables might be useful here. `{url}` gets * +*-v*+, +*--verbose*+: Show notifications when the command started/exited. * +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser. +==== note +* This command does not split arguments after the last argument and handles quotes literally. + [[stop]] === stop Stop loading in the current/[count]th tab. diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 82062357f..9c031f116 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -991,8 +991,9 @@ class CommandDispatcher: finally: self._tabbed_browser.setUpdatesEnabled(True) - @cmdutils.register(instance='command-dispatcher', scope='window') - def spawn(self, *cmdline, userscript=False, verbose=False, detach=False): + @cmdutils.register(instance='command-dispatcher', scope='window', + maxsplit=0, no_replace_variables=True) + def spawn(self, cmdline, userscript=False, verbose=False, detach=False): """Spawn a command in a shell. Note the `{url}` and `{url:pretty}` variables might be useful here. @@ -1011,7 +1012,14 @@ class CommandDispatcher: detach: Whether the command should be detached from qutebrowser. cmdline: The commandline to execute. """ - cmd, *args = cmdline + try: + cmd, *args = shlex.split(cmdline) + except ValueError as e: + raise cmdexc.CommandError("Error while splitting command: " + "{}".format(e)) + + args = runners.replace_variables(self._win_id, args) + log.procs.debug("Executing {} with args {}, userscript={}".format( cmd, args, userscript)) if userscript: diff --git a/scripts/dev/src2asciidoc.py b/scripts/dev/src2asciidoc.py index 2589d0fc5..84b774a03 100755 --- a/scripts/dev/src2asciidoc.py +++ b/scripts/dev/src2asciidoc.py @@ -240,7 +240,7 @@ def _get_command_doc_notes(cmd): Strings which should be added to the docs. """ if (cmd.maxsplit is not None or cmd.no_cmd_split or - cmd.no_replace_variables): + cmd.no_replace_variables and cmd.name != "spawn"): yield "" yield "==== note" if cmd.maxsplit is not None: @@ -249,7 +249,7 @@ def _get_command_doc_notes(cmd): if cmd.no_cmd_split: yield ("* With this command, +;;+ is interpreted literally " "instead of splitting off a second command.") - if cmd.no_replace_variables: + if cmd.no_replace_variables and cmd.name != "spawn": yield r"* This command does not replace variables like +\{url\}+."