From 8fd371d83682d7f42cbd6068ab1d8c0733b50e7f Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 27 Apr 2016 16:47:36 -0400 Subject: [PATCH 1/8] Proposed addition for issue #1386 --- qutebrowser/commands/runners.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index a0851c1b3..963f855ab 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -172,6 +172,20 @@ class CommandRunner(QObject): return self.parse(new_cmd, aliases=False, fallback=fallback, keep=keep) try: + + """ If the command given has only one completion match, replace + the given command with the match. + Ex: If they type "bac" and the only completion is "back", + turn the command into "back". + """ + + matches = [] + for valid_command in cmdutils.cmd_dict.keys(): + if valid_command.find(cmdstr) == 0: + matches.append(valid_command) + if len(matches) == 1: + cmdstr = matches[0] + cmd = cmdutils.cmd_dict[cmdstr] except KeyError: if fallback: From 5eea9d0605b9f85b319d10e450cc994164521d9a Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 28 Apr 2016 09:20:16 -0400 Subject: [PATCH 2/8] Cleanup for flake8/pylint --- qutebrowser/commands/runners.py | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 963f855ab..a1220864a 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -171,21 +171,10 @@ class CommandRunner(QObject): log.commands.debug("Re-parsing with '{}'.".format(new_cmd)) return self.parse(new_cmd, aliases=False, fallback=fallback, keep=keep) - try: - - """ If the command given has only one completion match, replace - the given command with the match. - Ex: If they type "bac" and the only completion is "back", - turn the command into "back". - """ - matches = [] - for valid_command in cmdutils.cmd_dict.keys(): - if valid_command.find(cmdstr) == 0: - matches.append(valid_command) - if len(matches) == 1: - cmdstr = matches[0] - + cmdstr = self._completion_match(cmdstr) + + try: cmd = cmdutils.cmd_dict[cmdstr] except KeyError: if fallback: @@ -209,6 +198,23 @@ class CommandRunner(QObject): cmdline = [cmdstr] + args[:] return ParseResult(cmd=cmd, args=args, cmdline=cmdline, count=count) + def _completion_match(self, cmdstr): + """Replace cmdstr with a matching completion if there's only one match. + + Args: + cmdstr: The string representing the entered command so far + + Return: + cmdstr modified to the matching completion or unmodified + """ + matches = [] + for valid_command in cmdutils.cmd_dict.keys(): + if valid_command.find(cmdstr) == 0: + matches.append(valid_command) + if len(matches) == 1: + cmdstr = matches[0] + return cmdstr + def _split_args(self, cmd, argstr, keep): """Split the arguments from an arg string. From ec869686c2dd290be3023614e0a2d0992f71bdcc Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 Jun 2016 15:53:33 +0200 Subject: [PATCH 3/8] Get rid of TabbedBrowser.got_cmd Seems like it's not used anywhere anymore --- qutebrowser/mainwindow/mainwindow.py | 1 - qutebrowser/mainwindow/tabbedbrowser.py | 1 - 2 files changed, 2 deletions(-) diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 9be30806b..b542f6fa7 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -296,7 +296,6 @@ class MainWindow(QWidget): status.keystring.setText) cmd.got_cmd.connect(self._commandrunner.run_safely) cmd.returnPressed.connect(tabs.on_cmd_return_pressed) - tabs.got_cmd.connect(self._commandrunner.run_safely) # key hint popup for mode, parser in keyparsers.items(): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 701397877..aa18f7997 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -99,7 +99,6 @@ class TabbedBrowser(tabwidget.TabWidget): cur_load_status_changed = pyqtSignal(str) close_window = pyqtSignal() resized = pyqtSignal('QRect') - got_cmd = pyqtSignal(str) current_tab_changed = pyqtSignal(webview.WebView) new_tab = pyqtSignal(webview.WebView, int) From 4a7a2e61d3447de5f3463e6d1e1402d8be1278d6 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 Jun 2016 15:56:56 +0200 Subject: [PATCH 4/8] Only do partial matching with main CommandParser --- qutebrowser/commands/runners.py | 7 +++++-- qutebrowser/mainwindow/mainwindow.py | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index ea4b2ac0f..0368f7b0c 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -72,10 +72,12 @@ class CommandRunner(QObject): Attributes: _win_id: The window this CommandRunner is associated with. + _partial_match: Whether to allow partial command matches. """ - def __init__(self, win_id, parent=None): + def __init__(self, win_id, partial_match=False, parent=None): super().__init__(parent) + self._partial_match = partial_match self._win_id = win_id def _get_alias(self, text): @@ -173,7 +175,8 @@ class CommandRunner(QObject): return self.parse(new_cmd, aliases=False, fallback=fallback, keep=keep) - cmdstr = self._completion_match(cmdstr) + if self._partial_match: + cmdstr = self._completion_match(cmdstr) try: cmd = cmdutils.cmd_dict[cmdstr] diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index b542f6fa7..20488df57 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -158,7 +158,8 @@ class MainWindow(QWidget): self._completion = completionwidget.CompletionView(self.win_id, self) - self._commandrunner = runners.CommandRunner(self.win_id) + self._commandrunner = runners.CommandRunner(self.win_id, + partial_match=True) self._keyhint = keyhintwidget.KeyHintView(self.win_id, self) From 2f60073cdf6a6c3f5cca22c4dbf0027ddde4644e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 Jun 2016 16:10:01 +0200 Subject: [PATCH 5/8] bdd: Allow to run invalid commands via quteproc --- tests/end2end/features/conftest.py | 10 +++++++++- tests/end2end/fixtures/quteprocess.py | 14 ++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index e9125d809..74b76833b 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -123,10 +123,18 @@ def run_command(quteproc, httpbin, command): count = int(count) else: count = None + + invalid_tag = ' (invalid command)' + if command.endswith(invalid_tag): + command = command[:-len(invalid_tag)] + invalid = True + else: + invalid = False + command = command.replace('(port)', str(httpbin.port)) command = command.replace('(testdata)', utils.abs_datapath()) - quteproc.send_cmd(command, count=count) + quteproc.send_cmd(command, count=count, invalid=invalid) @bdd.when(bdd.parsers.parse("I reload")) diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 9ee758775..8d462e6ea 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -330,8 +330,13 @@ class QuteProc(testprocess.Process): finally: super().after_test() - def send_cmd(self, command, count=None): - """Send a command to the running qutebrowser instance.""" + def send_cmd(self, command, count=None, invalid=False): + """Send a command to the running qutebrowser instance. + + Args: + count: The count to pass to the command. + invalid: If True, we don't wait for "command called: ..." in the log + """ summary = command if count is not None: summary += ' (count {})'.format(count) @@ -346,8 +351,9 @@ class QuteProc(testprocess.Process): ipc.send_to_running_instance(self._ipc_socket, [command], target_arg='') - self.wait_for(category='commands', module='command', function='run', - message='command called: *') + if not invalid: + self.wait_for(category='commands', module='command', + function='run', message='command called: *') def get_setting(self, sect, opt): """Get the value of a qutebrowser setting.""" From c9d85d3a123656a55641d03ce1419e881500e6c4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 Jun 2016 16:10:10 +0200 Subject: [PATCH 6/8] bdd: Add tests for partial commandline matching --- tests/end2end/features/misc.feature | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature index 7915f45ea..afa0f053c 100644 --- a/tests/end2end/features/misc.feature +++ b/tests/end2end/features/misc.feature @@ -441,3 +441,17 @@ Feature: Various utility commands. Scenario: Completing a single option argument When I run :set-cmd-text -s :-- Then no crash should happen + + ## https://github.com/The-Compiler/qutebrowser/issues/1386 + + Scenario: Partial commandline matching with startup command + When I run :message-i "Hello World" (invalid command) + Then the error "message-i: no such command" should be shown + + # We can't run :message-i as startup command, so we use + # :set-cmd-text + + Scenario: Partial commandline matching + When I run :set-cmd-text :message-i "Hello World" + And I run :command-accept + Then the message "Hello World" should be shown From 520572321af2c870fc80eb2b5fe922cc2194a47c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 Jun 2016 16:18:49 +0200 Subject: [PATCH 7/8] Add unittests for partial command parsing --- tests/helpers/fixtures.py | 2 ++ tests/unit/commands/test_runners.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py index c87301cae..dbd6de06b 100644 --- a/tests/helpers/fixtures.py +++ b/tests/helpers/fixtures.py @@ -146,6 +146,8 @@ def _generate_cmdline_tests(): # Command with no_cmd_split combined with an "invalid" command -> valid for item in itertools.product(['bind x open'], separators, invalid): yield TestCase(''.join(item), True) + # Partial command + yield TestCase('message-i', False) @pytest.fixture(params=_generate_cmdline_tests(), ids=lambda e: e.cmd) diff --git a/tests/unit/commands/test_runners.py b/tests/unit/commands/test_runners.py index b12f5e9f3..11d85ea90 100644 --- a/tests/unit/commands/test_runners.py +++ b/tests/unit/commands/test_runners.py @@ -51,3 +51,12 @@ class TestCommandRunner: 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. + + The same with it being disabled is tested by test_parse_all. + """ + cr = runners.CommandRunner(0, partial_match=True) + result = cr.parse('message-i', aliases=False) + assert result.cmd.name == 'message-info' From a38ec6e5a3114029230cc208a5855a7a832ade9c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Mon, 6 Jun 2016 16:19:42 +0200 Subject: [PATCH 8/8] Update docs --- CHANGELOG.asciidoc | 1 + README.asciidoc | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index e817f92fe..4606bceb1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -59,6 +59,7 @@ Changed a new page. - `:zoom-in` or `:zoom-out` (`+`/`-`) with a too large count now zooms to the smallest/largest zoom instead of doing nothing. +- The commandline now accepts partially typed commands if they're unique. Fixed ----- diff --git a/README.asciidoc b/README.asciidoc index 5b99c28e2..4b6805d94 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -195,6 +195,7 @@ Contributors, sorted by the number of commits in descending order: * Larry Hynes * Johannes Altmanninger * Ismail +* adam * Samir Benmendil * Regina Hug * Mathias Fussenegger