From c8848a26411523e90c59ebe6b0a3f259f254f535 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Mon, 4 Apr 2016 20:33:08 -0400 Subject: [PATCH 01/11] Implement edit-url to craft a url with an editor. The edit-url command opens a url (by default, the current url) in the user's external editor and navigates to the result when the editor is closed. This makes it easy to tweak the current url to navigate within a site. `edit-url` accepts the same flags as `open` (e.g. -t will open in a new tab. One may provide a url as an argument to create a shortcut to pre-populate part of a url and allow filling in the rest. There is no default keybinding. Resolves #1261. --- doc/help/commands.asciidoc | 16 ++++++++++++++++ qutebrowser/browser/commands.py | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index d3819741e..7d70614d3 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -20,6 +20,7 @@ |<>|Open the last/[count]th download. |<>|Remove the last/[count]th download from the list. |<>|Retry the first failed/[count]th download. +|<>|Compose a url in an external editor and navigate to it. |<>|Send a fake keypress or key string to the website or qutebrowser. |<>|Go forward in the history of the current tab. |<>|Toggle fullscreen mode. @@ -223,6 +224,21 @@ Retry the first failed/[count]th download. ==== count The index of the download to retry. +[[edit-url]] +=== edit-url +Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+ + +Open a URL in an external editor and navigate to it upon closing the editor. + +==== optional arguments +* +'url'+: A URL to pre-populate the editor with; defaults to the current URL. +* +*-b*+, +*--bg*+: Open in a new background tab. +* +*-t*+, +*--tab*+: Open in a new tab. +* +*-w*+, +*--window*+: Open in a new window. + +==== count +The tab index to open the URL in. + [[fake-key]] === fake-key Syntax: +:fake-key [*--global*] 'keystring'+ diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 3cae6941c..cb935ed58 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1847,3 +1847,27 @@ class CommandDispatcher: """Clear remembered SSL error answers.""" nam = self._current_widget().page().networkAccessManager() nam.clear_all_ssl_errors() + + @cmdutils.register(instance='command-dispatcher', scope='window') + def edit_url(self, url=None, bg=False, tab=False, window=False, count=None): + """Navigate to a url formed in an external editor. + + The editor which should be launched can be configured via the + `general -> editor` config option. + + Args: + url: URL to edit; defaults to the current page url. + bg: Open in a new background tab. + tab: Open in a new tab. + window: Open in a new window. + count: The tab index to open the URL in, or None. + """ + self._editor = editor.ExternalEditor( + self._win_id, self._tabbed_browser) + + # Passthrough for openurl args (e.g. -t, -b, -w) + self._editor.editing_finished.connect( + functools.partial(self.openurl, + bg = bg, tab = tab, window = window, count = count)) + + self._editor.edit(url or self._current_url().toString()) From e0d1e527d058daf7edfcce64024141e75c5f68d3 Mon Sep 17 00:00:00 2001 From: Ryan Roden-Corrent Date: Tue, 5 Apr 2016 07:49:01 -0400 Subject: [PATCH 02/11] Fix up edit-url implementation. Remove spaces around '=' for kwargs, don't set the _editor member. --- qutebrowser/browser/commands.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index cb935ed58..e65676bb3 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1862,12 +1862,11 @@ class CommandDispatcher: window: Open in a new window. count: The tab index to open the URL in, or None. """ - self._editor = editor.ExternalEditor( - self._win_id, self._tabbed_browser) + ed = editor.ExternalEditor(self._win_id, self._tabbed_browser) # Passthrough for openurl args (e.g. -t, -b, -w) - self._editor.editing_finished.connect( + ed.editing_finished.connect( functools.partial(self.openurl, - bg = bg, tab = tab, window = window, count = count)) + bg=bg, tab=tab, window=window, count=count)) - self._editor.edit(url or self._current_url().toString()) + ed.edit(url or self._current_url().toString()) From d56cdd64db855417748d0d625eb9f551296c755c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Apr 2016 18:35:47 +0200 Subject: [PATCH 03/11] Update changelog --- CHANGELOG.asciidoc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 344995874..4ffb2be6f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -14,6 +14,14 @@ This project adheres to http://semver.org/[Semantic Versioning]. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. +v0.7.0 (unreleased) +------------------- + +Added +~~~~~ + +- New `:edit-url` command to edit the URL in an external editor. + v0.6.0 ------ From fc921377063b198697c6e396f662dd156007e15c Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Apr 2016 18:37:03 +0200 Subject: [PATCH 04/11] Handle count correctly for :edit-url --- qutebrowser/browser/commands.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index e65676bb3..1254a71a2 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1848,7 +1848,8 @@ class CommandDispatcher: nam = self._current_widget().page().networkAccessManager() nam.clear_all_ssl_errors() - @cmdutils.register(instance='command-dispatcher', scope='window') + @cmdutils.register(instance='command-dispatcher', scope='window', + count='count') def edit_url(self, url=None, bg=False, tab=False, window=False, count=None): """Navigate to a url formed in an external editor. From 6b3ee5306409ef972214c79337c8e3294addd59e Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Apr 2016 18:37:50 +0200 Subject: [PATCH 05/11] Fix lint --- qutebrowser/browser/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 1254a71a2..bfa14f886 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1850,7 +1850,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', count='count') - def edit_url(self, url=None, bg=False, tab=False, window=False, count=None): + def edit_url(self, url=None, bg=False, tab=False, window=False, + count=None): """Navigate to a url formed in an external editor. The editor which should be launched can be configured via the @@ -1866,8 +1867,7 @@ class CommandDispatcher: ed = editor.ExternalEditor(self._win_id, self._tabbed_browser) # Passthrough for openurl args (e.g. -t, -b, -w) - ed.editing_finished.connect( - functools.partial(self.openurl, - bg=bg, tab=tab, window=window, count=count)) + ed.editing_finished.connect(functools.partial( + self.openurl, bg=bg, tab=tab, window=window, count=count)) ed.edit(url or self._current_url().toString()) From 107126c42ed446b054b5699addf66d969076e1ae Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Apr 2016 18:38:56 +0200 Subject: [PATCH 06/11] Regenerate docs --- README.asciidoc | 2 +- doc/help/commands.asciidoc | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 94df11d17..b35f7a643 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -183,6 +183,7 @@ Contributors, sorted by the number of commits in descending order: * Zach-Button * Halfwit * rikn00 +* Ryan Roden-Corrent * Michael Ilsaas * Martin Zimmermann * Brian Jackson @@ -195,7 +196,6 @@ Contributors, sorted by the number of commits in descending order: * Larry Hynes * Johannes Altmanninger * Samir Benmendil -* Ryan Roden-Corrent * Regina Hug * Mathias Fussenegger * Marcelo Santos diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 7d70614d3..45d19ad86 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -20,7 +20,7 @@ |<>|Open the last/[count]th download. |<>|Remove the last/[count]th download from the list. |<>|Retry the first failed/[count]th download. -|<>|Compose a url in an external editor and navigate to it. +|<>|Navigate to a url formed in an external editor. |<>|Send a fake keypress or key string to the website or qutebrowser. |<>|Go forward in the history of the current tab. |<>|Toggle fullscreen mode. @@ -228,10 +228,14 @@ The index of the download to retry. === edit-url Syntax: +:edit-url [*--bg*] [*--tab*] [*--window*] ['url']+ -Open a URL in an external editor and navigate to it upon closing the editor. +Navigate to a url formed in an external editor. + +The editor which should be launched can be configured via the `general -> editor` config option. + +==== positional arguments +* +'url'+: URL to edit; defaults to the current page url. ==== optional arguments -* +'url'+: A URL to pre-populate the editor with; defaults to the current URL. * +*-b*+, +*--bg*+: Open in a new background tab. * +*-t*+, +*--tab*+: Open in a new tab. * +*-w*+, +*--window*+: Open in a new window. From 9db697452da3c9db619a49c231a3f0cfa298ac59 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Apr 2016 18:48:58 +0200 Subject: [PATCH 07/11] Remove self._editor in CommandDispatcher This was needed before there was editor.ExternalEditor as there were various commands which needed to access the editor object. Since this is encapsulated in ExternalEditor now, no need to keep a reference to the object around. --- qutebrowser/browser/commands.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index bfa14f886..01b1608c5 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -65,7 +65,6 @@ class CommandDispatcher: """ def __init__(self, win_id, tabbed_browser): - self._editor = None self._win_id = win_id self._tabbed_browser = tabbed_browser @@ -1330,11 +1329,10 @@ class CommandDispatcher: text = str(elem) else: text = elem.evaluateJavaScript('this.value') - self._editor = editor.ExternalEditor( - self._win_id, self._tabbed_browser) - self._editor.editing_finished.connect( - functools.partial(self.on_editing_finished, elem)) - self._editor.edit(text) + ed = editor.ExternalEditor(self._win_id, self._tabbed_browser) + ed.editing_finished.connect(functools.partial( + self.on_editing_finished, elem)) + ed.edit(text) def on_editing_finished(self, elem, text): """Write the editor text into the form field and clean up tempfile. From 2f520f3b174d5ea7dcdad511b3adc2840c671b4a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 5 Apr 2016 19:49:01 +0200 Subject: [PATCH 08/11] Rename test_editor.py to test_editor_unit.py --- tests/unit/misc/{test_editor.py => test_editor_unit.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/misc/{test_editor.py => test_editor_unit.py} (100%) diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor_unit.py similarity index 100% rename from tests/unit/misc/test_editor.py rename to tests/unit/misc/test_editor_unit.py From 776c4c4400b2b4c15c3104403151bb44d16d53ce Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 6 Apr 2016 06:44:50 +0200 Subject: [PATCH 09/11] Ensure -t/-b/-w are exclusive in :edit-url Otherwise those would be passed as-is to :open and an unhandled cmdexc.CommandError would be raised there. --- qutebrowser/browser/commands.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 01b1608c5..7322da0f0 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1862,6 +1862,8 @@ class CommandDispatcher: window: Open in a new window. count: The tab index to open the URL in, or None. """ + cmdutils.check_exclusive((tab, bg, window), 'tbw') + ed = editor.ExternalEditor(self._win_id, self._tabbed_browser) # Passthrough for openurl args (e.g. -t, -b, -w) From c4878bb7ed40f9e2682fe467d9efba9184db2392 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 6 Apr 2016 06:46:42 +0200 Subject: [PATCH 10/11] Don't raise cmdexc.CommandError in :open :open can be called via :edit-url async, so we need to use message.error by hand there. --- qutebrowser/browser/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 7322da0f0..4fd64c1bf 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -248,7 +248,10 @@ class CommandDispatcher: try: url = urlutils.fuzzy_url(url) except urlutils.InvalidUrlError as e: - raise cmdexc.CommandError(e) + # We don't use cmdexc.CommandError here as this can be called + # async from edit_url + message.error(self._win_id, str(e)) + return if tab or bg or window: self._open(url, tab, bg, window) else: From 86dec02fe806dd5f25147c667ef24dbf86f00c7f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 6 Apr 2016 06:47:19 +0200 Subject: [PATCH 11/11] bdd: Add tests for :edit-url --- tests/integration/features/editor.feature | 72 +++++++++++++++++++++++ tests/integration/features/test_editor.py | 45 ++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tests/integration/features/editor.feature create mode 100644 tests/integration/features/test_editor.py diff --git a/tests/integration/features/editor.feature b/tests/integration/features/editor.feature new file mode 100644 index 000000000..ef3798564 --- /dev/null +++ b/tests/integration/features/editor.feature @@ -0,0 +1,72 @@ +Feature: Opening external editors + + ## :edit-url + + Scenario: Editing an URL + When I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url + Then data/numbers/2.txt should be loaded + + Scenario: Editing an URL with -t + When I run :tab-only + And I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -t + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/1.txt + - data/numbers/2.txt (active) + + Scenario: Editing an URL with -b + When I run :tab-only + And I open data/numbers/1.txt + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -b + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/1.txt (active) + - data/numbers/2.txt + + Scenario: Editing an URL with -w + When I open data/numbers/1.txt in a new tab + And I run :tab-only + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url -w + Then data/numbers/2.txt should be loaded + And the session should look like: + windows: + - tabs: + - active: true + history: + - active: true + url: http://localhost:*/data/numbers/1.txt + - tabs: + - active: true + history: + - active: true + url: http://localhost:*/data/numbers/2.txt + + Scenario: Editing an URL with count + Given I have a fresh instance + When I open data/numbers/1.txt + And I run :tab-only + And I open about:blank in a new tab + And I run :tab-focus 1 + And I set up a fake editor replacing "1.txt" by "2.txt" + And I run :edit-url with count 2 + Then data/numbers/2.txt should be loaded + And the following tabs should be open: + - data/numbers/1.txt (active) + - data/numbers/2.txt + + Scenario: Editing an URL with -t and -b + When I run :edit-url -t -b + Then the error "Only one of -t/-b/-w can be given!" should be shown + + Scenario: Editing an URL with invalid URL + When I set general -> auto-search to false + And I open data/hello.txt + And I set up a fake editor replacing "http://localhost:(port)/data/hello.txt" by "foo!" + And I run :edit-url + Then the error "Invalid URL" should be shown diff --git a/tests/integration/features/test_editor.py b/tests/integration/features/test_editor.py new file mode 100644 index 000000000..2fc76eb89 --- /dev/null +++ b/tests/integration/features/test_editor.py @@ -0,0 +1,45 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +import sys +import textwrap + +import pytest_bdd as bdd +bdd.scenarios('editor.feature') + + +@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by ' + '"{replacement}"')) +def set_up_editor_replacement(quteproc, httpbin, tmpdir, text, replacement): + """Set up general -> editor to a small python script doing a replacement""" + text = text.replace('(port)', str(httpbin.port)) + script = tmpdir / 'script.py' + script.write(textwrap.dedent(""" + import sys + + with open(sys.argv[1], encoding='utf-8') as f: + data = f.read() + + data = data.replace("{text}", "{replacement}") + + with open(sys.argv[1], 'w', encoding='utf-8') as f: + f.write(data) + """.format(text=text, replacement=replacement))) + editor = '"{}" "{}" {{}}'.format(sys.executable, script) + quteproc.set_setting('general', 'editor', editor)