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 ------ 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 d3819741e..45d19ad86 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. +|<>|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. @@ -223,6 +224,25 @@ 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']+ + +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 +* +*-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..4fd64c1bf 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 @@ -249,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: @@ -1330,11 +1332,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. @@ -1847,3 +1848,29 @@ 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', + 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. + + 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. + """ + 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) + ed.editing_finished.connect(functools.partial( + self.openurl, bg=bg, tab=tab, window=window, count=count)) + + ed.edit(url or self._current_url().toString()) 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) 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