Merge remote-tracking branch 'upstream/master' into regex-match
This commit is contained in:
commit
3cf4e8ba67
@ -57,6 +57,7 @@ Added
|
|||||||
when restoring a session.
|
when restoring a session.
|
||||||
- New `hist_importer.py` script to import history from Firefox/Chromium.
|
- New `hist_importer.py` script to import history from Firefox/Chromium.
|
||||||
- New `{protocol}` replacement for `tabs.title.format` and friends.
|
- New `{protocol}` replacement for `tabs.title.format` and friends.
|
||||||
|
- New `-o` flag for `:spawn` to show stdout/stderr in a new tab.
|
||||||
|
|
||||||
Changed
|
Changed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
@ -118,6 +119,7 @@ Fixed
|
|||||||
- Fixed crash when closing the tab an external editor was opened in.
|
- 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
|
- When using `:search-next` before a search is finished, no warning about no
|
||||||
results being found is shown anymore.
|
results being found is shown anymore.
|
||||||
|
- Fix :click-element with an ID containing non-alphanumeric characters.
|
||||||
|
|
||||||
Deprecated
|
Deprecated
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
@ -1148,7 +1148,7 @@ Set a mark at the current scroll position in the current tab.
|
|||||||
|
|
||||||
[[spawn]]
|
[[spawn]]
|
||||||
=== spawn
|
=== spawn
|
||||||
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--detach*] 'cmdline'+
|
Syntax: +:spawn [*--userscript*] [*--verbose*] [*--output*] [*--detach*] 'cmdline'+
|
||||||
|
|
||||||
Spawn a command in a shell.
|
Spawn a command in a shell.
|
||||||
|
|
||||||
@ -1163,6 +1163,7 @@ Spawn a command in a shell.
|
|||||||
- `/usr/share/qutebrowser/userscripts`
|
- `/usr/share/qutebrowser/userscripts`
|
||||||
|
|
||||||
* +*-v*+, +*--verbose*+: Show notifications when the command started/exited.
|
* +*-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.
|
* +*-d*+, +*--detach*+: Whether the command should be detached from qutebrowser.
|
||||||
|
|
||||||
==== note
|
==== note
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
certifi==2017.11.5
|
certifi==2017.11.5
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
codecov==2.0.9
|
codecov==2.0.10
|
||||||
coverage==4.4.2
|
coverage==4.4.2
|
||||||
idna==2.6
|
idna==2.6
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
appdirs==1.4.3
|
appdirs==1.4.3
|
||||||
packaging==16.8
|
packaging==16.8
|
||||||
pyparsing==2.2.0
|
pyparsing==2.2.0
|
||||||
setuptools==38.2.3
|
setuptools==38.2.4
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
wheel==0.30.0
|
wheel==0.30.0
|
||||||
|
@ -8,7 +8,7 @@ idna==2.6
|
|||||||
isort==4.2.15
|
isort==4.2.15
|
||||||
lazy-object-proxy==1.3.1
|
lazy-object-proxy==1.3.1
|
||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
pylint==1.7.4
|
pylint==1.7.5
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.4
|
requests==2.18.4
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
attrs==17.3.0
|
attrs==17.3.0
|
||||||
beautifulsoup4==4.6.0
|
beautifulsoup4==4.6.0
|
||||||
cheroot==5.10.0
|
cheroot==6.0.0
|
||||||
click==6.7
|
click==6.7
|
||||||
# colorama==0.3.9
|
# colorama==0.3.9
|
||||||
coverage==4.4.2
|
coverage==4.4.2
|
||||||
@ -11,7 +11,7 @@ fields==5.0.0
|
|||||||
Flask==0.12.2
|
Flask==0.12.2
|
||||||
glob2==0.6
|
glob2==0.6
|
||||||
hunter==2.0.2
|
hunter==2.0.2
|
||||||
hypothesis==3.40.1
|
hypothesis==3.42.1
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
# Jinja2==2.10
|
# Jinja2==2.10
|
||||||
Mako==1.0.7
|
Mako==1.0.7
|
||||||
@ -21,7 +21,7 @@ parse-type==0.4.2
|
|||||||
pluggy==0.6.0
|
pluggy==0.6.0
|
||||||
py==1.5.2
|
py==1.5.2
|
||||||
py-cpuinfo==3.3.0
|
py-cpuinfo==3.3.0
|
||||||
pytest==3.3.0
|
pytest==3.3.1
|
||||||
pytest-bdd==2.19.0
|
pytest-bdd==2.19.0
|
||||||
pytest-benchmark==3.1.1
|
pytest-benchmark==3.1.1
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
@ -36,4 +36,4 @@ pytest-xvfb==1.0.0
|
|||||||
PyVirtualDisplay==0.2.1
|
PyVirtualDisplay==0.2.1
|
||||||
six==1.11.0
|
six==1.11.0
|
||||||
vulture==0.26
|
vulture==0.26
|
||||||
Werkzeug==0.12.2
|
Werkzeug==0.13
|
||||||
|
@ -1177,7 +1177,8 @@ class CommandDispatcher:
|
|||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
maxsplit=0, no_replace_variables=True)
|
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.
|
"""Spawn a command in a shell.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -1188,6 +1189,7 @@ class CommandDispatcher:
|
|||||||
(or `$XDG_DATA_DIR`)
|
(or `$XDG_DATA_DIR`)
|
||||||
- `/usr/share/qutebrowser/userscripts`
|
- `/usr/share/qutebrowser/userscripts`
|
||||||
verbose: Show notifications when the command started/exited.
|
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.
|
detach: Whether the command should be detached from qutebrowser.
|
||||||
cmdline: The commandline to execute.
|
cmdline: The commandline to execute.
|
||||||
"""
|
"""
|
||||||
@ -1214,6 +1216,11 @@ class CommandDispatcher:
|
|||||||
else:
|
else:
|
||||||
proc.start(cmd, args)
|
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')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def home(self):
|
def home(self):
|
||||||
"""Open main startpage in current tab."""
|
"""Open main startpage in current tab."""
|
||||||
|
@ -42,6 +42,7 @@ from qutebrowser.misc import objects
|
|||||||
|
|
||||||
|
|
||||||
pyeval_output = ":pyeval was never called"
|
pyeval_output = ":pyeval was never called"
|
||||||
|
spawn_output = ":spawn was never called"
|
||||||
|
|
||||||
|
|
||||||
_HANDLERS = {}
|
_HANDLERS = {}
|
||||||
@ -268,6 +269,13 @@ def qute_pyeval(_url):
|
|||||||
return 'text/html', html
|
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('version')
|
||||||
@add_handler('verizon')
|
@add_handler('verizon')
|
||||||
def qute_version(_url):
|
def qute_version(_url):
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
"""Wrapper over our (QtWebKit) WebView."""
|
"""Wrapper over our (QtWebKit) WebView."""
|
||||||
|
|
||||||
|
import re
|
||||||
import functools
|
import functools
|
||||||
import xml.etree.ElementTree
|
import xml.etree.ElementTree
|
||||||
|
|
||||||
@ -545,6 +546,10 @@ class WebKitElements(browsertab.AbstractElements):
|
|||||||
callback(None)
|
callback(None)
|
||||||
else:
|
else:
|
||||||
callback(elems[0])
|
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)
|
self.find_css('#' + elem_id, find_id_cb)
|
||||||
|
|
||||||
def find_focused(self, callback):
|
def find_focused(self, callback):
|
||||||
|
@ -25,6 +25,7 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess,
|
|||||||
QProcessEnvironment)
|
QProcessEnvironment)
|
||||||
|
|
||||||
from qutebrowser.utils import message, log
|
from qutebrowser.utils import message, log
|
||||||
|
from qutebrowser.browser import qutescheme
|
||||||
|
|
||||||
# A mapping of QProcess::ErrorCode's to human-readable strings.
|
# A mapping of QProcess::ErrorCode's to human-readable strings.
|
||||||
|
|
||||||
@ -96,6 +97,13 @@ class GUIProcess(QObject):
|
|||||||
self._started = False
|
self._started = False
|
||||||
log.procs.debug("Process finished with code {}, status {}.".format(
|
log.procs.debug("Process finished with code {}, status {}.".format(
|
||||||
code, status))
|
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:
|
if status == QProcess.CrashExit:
|
||||||
message.error("{} crashed!".format(self._what.capitalize()))
|
message.error("{} crashed!".format(self._what.capitalize()))
|
||||||
elif status == QProcess.NormalExit and code == 0:
|
elif status == QProcess.NormalExit and code == 0:
|
||||||
@ -109,13 +117,22 @@ class GUIProcess(QObject):
|
|||||||
message.error("{} exited with status {}, see :messages for "
|
message.error("{} exited with status {}, see :messages for "
|
||||||
"details.".format(self._what.capitalize(), code))
|
"details.".format(self._what.capitalize(), code))
|
||||||
|
|
||||||
stderr = bytes(self._proc.readAllStandardError()).decode('utf-8')
|
|
||||||
stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8')
|
|
||||||
if stdout:
|
if stdout:
|
||||||
log.procs.error("Process stdout:\n" + stdout.strip())
|
log.procs.error("Process stdout:\n" + stdout.strip())
|
||||||
if stderr:
|
if stderr:
|
||||||
log.procs.error("Process stderr:\n" + stderr.strip())
|
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()
|
@pyqtSlot()
|
||||||
def on_started(self):
|
def on_started(self):
|
||||||
"""Called when the process started successfully."""
|
"""Called when the process started successfully."""
|
||||||
|
@ -26,7 +26,7 @@ elif [[ $TESTENV == shellcheck ]]; then
|
|||||||
koalaman/shellcheck:latest "${scripts[@]}"
|
koalaman/shellcheck:latest "${scripts[@]}"
|
||||||
else
|
else
|
||||||
args=()
|
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[@]}"
|
tox -e "$TESTENV" -- "${args[@]}"
|
||||||
fi
|
fi
|
||||||
|
@ -82,6 +82,7 @@ def whitelist_generator(): # noqa
|
|||||||
yield 'qutebrowser.utils.jinja.Loader.get_source'
|
yield 'qutebrowser.utils.jinja.Loader.get_source'
|
||||||
yield 'qutebrowser.utils.log.QtWarningFilter.filter'
|
yield 'qutebrowser.utils.log.QtWarningFilter.filter'
|
||||||
yield 'qutebrowser.browser.pdfjs.is_available'
|
yield 'qutebrowser.browser.pdfjs.is_available'
|
||||||
|
yield 'qutebrowser.misc.guiprocess.spawn_output'
|
||||||
yield 'QEvent.posted'
|
yield 'QEvent.posted'
|
||||||
yield 'log_stack' # from message.py
|
yield 'log_stack' # from message.py
|
||||||
yield 'propagate' # logging.getLogger('...).propagate = False
|
yield 'propagate' # logging.getLogger('...).propagate = False
|
||||||
|
@ -9,5 +9,6 @@
|
|||||||
<span>Duplicate</span>
|
<span>Duplicate</span>
|
||||||
<form><input id='qute-input'></input></form>
|
<form><input id='qute-input'></input></form>
|
||||||
<a href="/data/hello.txt" id='link'>link</a>
|
<a href="/data/hello.txt" id='link'>link</a>
|
||||||
|
<span id='foo.bar' onclick='console.log("id with dot")'>ID with dot</span>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -449,6 +449,11 @@ Feature: Various utility commands.
|
|||||||
And I run :click-element id qute-input
|
And I run :click-element id qute-input
|
||||||
Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged
|
Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged
|
||||||
|
|
||||||
|
Scenario: Clicking an element by ID with dot
|
||||||
|
When I open data/click_element.html
|
||||||
|
And I run :click-element id foo.bar
|
||||||
|
Then the javascript message "id with dot" should be logged
|
||||||
|
|
||||||
Scenario: Clicking an element with tab target
|
Scenario: Clicking an element with tab target
|
||||||
When I open data/click_element.html
|
When I open data/click_element.html
|
||||||
And I run :tab-only
|
And I run :tab-only
|
||||||
|
@ -26,6 +26,8 @@ import signal
|
|||||||
import pytest_bdd as bdd
|
import pytest_bdd as bdd
|
||||||
bdd.scenarios('editor.feature')
|
bdd.scenarios('editor.feature')
|
||||||
|
|
||||||
|
from qutebrowser.utils import utils
|
||||||
|
|
||||||
|
|
||||||
@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by '
|
@bdd.when(bdd.parsers.parse('I set up a fake editor replacing "{text}" by '
|
||||||
'"{replacement}"'))
|
'"{replacement}"'))
|
||||||
@ -71,6 +73,7 @@ def set_up_editor_empty(quteproc, tmpdir):
|
|||||||
@bdd.when(bdd.parsers.parse('I set up a fake editor that waits'))
|
@bdd.when(bdd.parsers.parse('I set up a fake editor that waits'))
|
||||||
def set_up_editor_wait(quteproc, tmpdir):
|
def set_up_editor_wait(quteproc, tmpdir):
|
||||||
"""Set up editor.command to a small python script inserting a text."""
|
"""Set up editor.command to a small python script inserting a text."""
|
||||||
|
assert not utils.is_windows
|
||||||
pidfile = tmpdir / 'editor_pid'
|
pidfile = tmpdir / 'editor_pid'
|
||||||
script = tmpdir / 'script.py'
|
script = tmpdir / 'script.py'
|
||||||
script.write(textwrap.dedent("""
|
script.write(textwrap.dedent("""
|
||||||
|
@ -300,8 +300,16 @@ class Process(QObject):
|
|||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
"""Clean up and shut down the process."""
|
"""Clean up and shut down the process."""
|
||||||
|
if not self.is_running():
|
||||||
|
return
|
||||||
|
|
||||||
|
if quteutils.is_windows:
|
||||||
|
self.proc.kill()
|
||||||
|
else:
|
||||||
self.proc.terminate()
|
self.proc.terminate()
|
||||||
self.proc.waitForFinished()
|
|
||||||
|
ok = self.proc.waitForFinished()
|
||||||
|
assert ok
|
||||||
|
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
"""Check if the process is currently running."""
|
"""Check if the process is currently running."""
|
||||||
|
@ -172,11 +172,6 @@ class WebserverProcess(testprocess.Process):
|
|||||||
def _default_args(self):
|
def _default_args(self):
|
||||||
return [str(self.port)]
|
return [str(self.port)]
|
||||||
|
|
||||||
def cleanup(self):
|
|
||||||
"""Clean up and shut down the process."""
|
|
||||||
self.proc.terminate()
|
|
||||||
self.proc.waitForFinished()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session', autouse=True)
|
@pytest.fixture(scope='session', autouse=True)
|
||||||
def server(qapp):
|
def server(qapp):
|
||||||
@ -184,7 +179,7 @@ def server(qapp):
|
|||||||
server = WebserverProcess('webserver_sub')
|
server = WebserverProcess('webserver_sub')
|
||||||
server.start()
|
server.start()
|
||||||
yield server
|
yield server
|
||||||
server.cleanup()
|
server.terminate()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@ -208,4 +203,4 @@ def ssl_server(request, qapp):
|
|||||||
server.start()
|
server.start()
|
||||||
yield server
|
yield server
|
||||||
server.after_test()
|
server.after_test()
|
||||||
server.cleanup()
|
server.terminate()
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
"""Tests for qutebrowser.misc.guiprocess."""
|
"""Tests for qutebrowser.misc.guiprocess."""
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -27,6 +26,7 @@ from PyQt5.QtCore import QProcess, QIODevice
|
|||||||
|
|
||||||
from qutebrowser.misc import guiprocess
|
from qutebrowser.misc import guiprocess
|
||||||
from qutebrowser.utils import usertypes
|
from qutebrowser.utils import usertypes
|
||||||
|
from qutebrowser.browser import qutescheme
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@ -60,7 +60,7 @@ def test_start(proc, qtbot, message_mock, py_proc):
|
|||||||
proc.start(*argv)
|
proc.start(*argv)
|
||||||
|
|
||||||
assert not message_mock.messages
|
assert not message_mock.messages
|
||||||
assert bytes(proc._proc.readAll()).rstrip() == b'test'
|
assert qutescheme.spawn_output == proc._spawn_format(stdout="test")
|
||||||
|
|
||||||
|
|
||||||
def test_start_verbose(proc, qtbot, message_mock, py_proc):
|
def test_start_verbose(proc, qtbot, message_mock, py_proc):
|
||||||
@ -77,7 +77,7 @@ def test_start_verbose(proc, qtbot, message_mock, py_proc):
|
|||||||
assert msgs[1].level == usertypes.MessageLevel.info
|
assert msgs[1].level == usertypes.MessageLevel.info
|
||||||
assert msgs[0].text.startswith("Executing:")
|
assert msgs[0].text.startswith("Executing:")
|
||||||
assert msgs[1].text == "Testprocess exited successfully."
|
assert msgs[1].text == "Testprocess exited successfully."
|
||||||
assert bytes(proc._proc.readAll()).rstrip() == b'test'
|
assert qutescheme.spawn_output == proc._spawn_format(stdout="test")
|
||||||
|
|
||||||
|
|
||||||
def test_start_env(monkeypatch, qtbot, py_proc):
|
def test_start_env(monkeypatch, qtbot, py_proc):
|
||||||
@ -99,10 +99,9 @@ def test_start_env(monkeypatch, qtbot, py_proc):
|
|||||||
order='strict'):
|
order='strict'):
|
||||||
proc.start(*argv)
|
proc.start(*argv)
|
||||||
|
|
||||||
data = bytes(proc._proc.readAll()).decode('utf-8')
|
data = qutescheme.spawn_output
|
||||||
ret_env = json.loads(data)
|
assert 'QUTEBROWSER_TEST_1' in data
|
||||||
assert 'QUTEBROWSER_TEST_1' in ret_env
|
assert 'QUTEBROWSER_TEST_2' in data
|
||||||
assert 'QUTEBROWSER_TEST_2' in ret_env
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.qt_log_ignore('QIODevice::read.*: WriteOnly device')
|
@pytest.mark.qt_log_ignore('QIODevice::read.*: WriteOnly device')
|
||||||
|
Loading…
Reference in New Issue
Block a user