diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index fddb3f6a5..8095f1bd8 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -57,6 +57,7 @@ Added when restoring a session. - New `hist_importer.py` script to import history from Firefox/Chromium. - New `{protocol}` replacement for `tabs.title.format` and friends. +- New `-o` flag for `:spawn` to show stdout/stderr in a new tab. Changed ~~~~~~~ @@ -118,6 +119,7 @@ Fixed - Fixed crash when closing the tab an external editor was opened in. - When using `:search-next` before a search is finished, no warning about no results being found is shown anymore. +- Fix :click-element with an ID containing non-alphanumeric characters. Deprecated ~~~~~~~~~~ diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 5d026bfca..3da81a391 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -1148,7 +1148,7 @@ Set a mark at the current scroll position in the current tab. [[spawn]] === spawn -Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+ +Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--detach*] 'cmdline'+ Spawn a command in a shell. @@ -1163,6 +1163,7 @@ Spawn a command in a shell. - `/usr/share/qutebrowser/userscripts` * +*-v*+, +*--verbose*+: Show notifications when the command started/exited. +* +*-o*+, +*--output*+: Whether the output should be shown in a new tab. * +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser. ==== note diff --git a/misc/requirements/requirements-codecov.txt b/misc/requirements/requirements-codecov.txt index 31c319c39..6601cfb12 100644 --- a/misc/requirements/requirements-codecov.txt +++ b/misc/requirements/requirements-codecov.txt @@ -2,7 +2,7 @@ certifi==2017.11.5 chardet==3.0.4 -codecov==2.0.9 +codecov==2.0.10 coverage==4.4.2 idna==2.6 requests==2.18.4 diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt index 8ca5a867e..b7914cac5 100644 --- a/misc/requirements/requirements-pip.txt +++ b/misc/requirements/requirements-pip.txt @@ -3,6 +3,6 @@ appdirs==1.4.3 packaging==16.8 pyparsing==2.2.0 -setuptools==38.2.3 +setuptools==38.2.4 six==1.11.0 wheel==0.30.0 diff --git a/misc/requirements/requirements-pylint.txt b/misc/requirements/requirements-pylint.txt index cab15c497..346fa4227 100644 --- a/misc/requirements/requirements-pylint.txt +++ b/misc/requirements/requirements-pylint.txt @@ -8,7 +8,7 @@ idna==2.6 isort==4.2.15 lazy-object-proxy==1.3.1 mccabe==0.6.1 -pylint==1.7.4 +pylint==1.7.5 ./scripts/dev/pylint_checkers requests==2.18.4 six==1.11.0 diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt index 46905f497..bf80acee7 100644 --- a/misc/requirements/requirements-tests.txt +++ b/misc/requirements/requirements-tests.txt @@ -2,7 +2,7 @@ attrs==17.3.0 beautifulsoup4==4.6.0 -cheroot==5.10.0 +cheroot==6.0.0 click==6.7 # colorama==0.3.9 coverage==4.4.2 @@ -11,7 +11,7 @@ fields==5.0.0 Flask==0.12.2 glob2==0.6 hunter==2.0.2 -hypothesis==3.40.1 +hypothesis==3.42.1 itsdangerous==0.24 # Jinja2==2.10 Mako==1.0.7 @@ -21,7 +21,7 @@ parse-type==0.4.2 pluggy==0.6.0 py==1.5.2 py-cpuinfo==3.3.0 -pytest==3.3.0 +pytest==3.3.1 pytest-bdd==2.19.0 pytest-benchmark==3.1.1 pytest-cov==2.5.1 @@ -36,4 +36,4 @@ pytest-xvfb==1.0.0 PyVirtualDisplay==0.2.1 six==1.11.0 vulture==0.26 -Werkzeug==0.12.2 +Werkzeug==0.13 diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index bced4daf4..3075a24da 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1177,7 +1177,8 @@ class CommandDispatcher: @cmdutils.register(instance='command-dispatcher', scope='window', maxsplit=0, no_replace_variables=True) - def spawn(self, cmdline, userscript=False, verbose=False, detach=False): + def spawn(self, cmdline, userscript=False, verbose=False, + output=False, detach=False): """Spawn a command in a shell. Args: @@ -1188,6 +1189,7 @@ class CommandDispatcher: (or `$XDG_DATA_DIR`) - `/usr/share/qutebrowser/userscripts` verbose: Show notifications when the command started/exited. + output: Whether the output should be shown in a new tab. detach: Whether the command should be detached from qutebrowser. cmdline: The commandline to execute. """ @@ -1214,6 +1216,11 @@ class CommandDispatcher: else: proc.start(cmd, args) + if output: + tabbed_browser = objreg.get('tabbed-browser', scope='window', + window='last-focused') + tabbed_browser.openurl(QUrl('qute://spawn-output'), newtab=True) + @cmdutils.register(instance='command-dispatcher', scope='window') def home(self): """Open main startpage in current tab.""" diff --git a/qutebrowser/browser/qutescheme.py b/qutebrowser/browser/qutescheme.py index e6262a007..32bc5806a 100644 --- a/qutebrowser/browser/qutescheme.py +++ b/qutebrowser/browser/qutescheme.py @@ -42,6 +42,7 @@ from qutebrowser.misc import objects pyeval_output = ":pyeval was never called" +spawn_output = ":spawn was never called" _HANDLERS = {} @@ -268,6 +269,13 @@ def qute_pyeval(_url): return 'text/html', html +@add_handler('spawn-output') +def qute_spawn_output(_url): + """Handler for qute://spawn-output.""" + html = jinja.render('pre.html', title='spawn output', content=spawn_output) + return 'text/html', html + + @add_handler('version') @add_handler('verizon') def qute_version(_url): diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 43bec3456..f16beb7fe 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -19,6 +19,7 @@ """Wrapper over our (QtWebKit) WebView.""" +import re import functools import xml.etree.ElementTree @@ -545,6 +546,10 @@ class WebKitElements(browsertab.AbstractElements): callback(None) else: callback(elems[0]) + + # Escape non-alphanumeric characters in the selector + # https://www.w3.org/TR/CSS2/syndata.html#value-def-identifier + elem_id = re.sub(r'[^a-zA-Z0-9_-]', r'\\\g<0>', elem_id) self.find_css('#' + elem_id, find_id_cb) def find_focused(self, callback): diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 1adf6817e..4fb887de0 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -25,6 +25,7 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment) from qutebrowser.utils import message, log +from qutebrowser.browser import qutescheme # A mapping of QProcess::ErrorCode's to human-readable strings. @@ -96,6 +97,13 @@ class GUIProcess(QObject): self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) + + stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') + stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') + + qutescheme.spawn_output = self._spawn_format(code, status, + stdout, stderr) + if status == QProcess.CrashExit: message.error("{} crashed!".format(self._what.capitalize())) elif status == QProcess.NormalExit and code == 0: @@ -109,13 +117,22 @@ class GUIProcess(QObject): message.error("{} exited with status {}, see :messages for " "details.".format(self._what.capitalize(), code)) - stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') - stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) + def _spawn_format(self, code=0, status=0, stdout="", stderr=""): + """Produce a formatted string for spawn output.""" + stdout = (stdout or "(No output)").strip() + stderr = (stderr or "(No output)").strip() + + spawn_string = ("Process finished with code {}, status {}\n" + "\nProcess stdout:\n {}" + "\nProcess stderr:\n {}").format(code, status, + stdout, stderr) + return spawn_string + @pyqtSlot() def on_started(self): """Called when the process started successfully.""" diff --git a/scripts/dev/ci/travis_run.sh b/scripts/dev/ci/travis_run.sh index b7d44968e..55ca7c11e 100644 --- a/scripts/dev/ci/travis_run.sh +++ b/scripts/dev/ci/travis_run.sh @@ -26,7 +26,7 @@ elif [[ $TESTENV == shellcheck ]]; then koalaman/shellcheck:latest "${scripts[@]}" else args=() - [[ $TRAVIS_OS_NAME == osx ]] && args=('--qute-bdd-webengine' '--no-xvfb') + [[ $TRAVIS_OS_NAME == osx ]] && args=('--qute-bdd-webengine' '--no-xvfb' 'tests/unit') tox -e "$TESTENV" -- "${args[@]}" fi diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 11cfce792..2bc7283d0 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -82,6 +82,7 @@ def whitelist_generator(): # noqa yield 'qutebrowser.utils.jinja.Loader.get_source' yield 'qutebrowser.utils.log.QtWarningFilter.filter' yield 'qutebrowser.browser.pdfjs.is_available' + yield 'qutebrowser.misc.guiprocess.spawn_output' yield 'QEvent.posted' yield 'log_stack' # from message.py yield 'propagate' # logging.getLogger('...).propagate = False diff --git a/tests/end2end/data/click_element.html b/tests/end2end/data/click_element.html index 55bf8b88c..acf0cf77c 100644 --- a/tests/end2end/data/click_element.html +++ b/tests/end2end/data/click_element.html @@ -9,5 +9,6 @@ Duplicate
link + ID with dot