diff --git a/.travis.yml b/.travis.yml
index 84f61df7f..8c7175f51 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,7 +19,12 @@ matrix:
env: DOCKER=ubuntu-xenial
services: docker
- os: osx
- env: TESTENV=py35
+ env: TESTENV=py35 OSX=elcapitan
+ osx_image: xcode7.3
+ # https://github.com/The-Compiler/qutebrowser/issues/2013
+ # - os: osx
+ # env: TESTENV=py35 OSX=yosemite
+ # osx_image: xcode6.4
- os: linux
env: TESTENV=pylint
- os: linux
@@ -36,6 +41,10 @@ matrix:
env: TESTENV=check-manifest
- os: linux
env: TESTENV=eslint
+ allow_failures:
+ - os: osx
+ env: TESTENV=py35 OSX=elcapitan
+ osx_image: xcode7.3
cache:
directories:
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 051f86487..09dcca173 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -47,6 +47,8 @@ Added
https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API[HTML5 page visibility API]
- New `readability` userscript which shows a readable version of a page (using
the `readability-lxml` python package)
+- New `cast` userscript to show a video on a Google Chromecast
+- New `:run-with-count` command which replaces the (undocumented) `:count:command` syntax.
Changed
~~~~~~~
@@ -179,6 +181,7 @@ Fixed
- Fixed hang when using multiple spaces in a row with the URL completion
- Fixed crash when closing a window without focusing it
- Userscripts now can access QUTE_FIFO correctly on Windows
+- Compatibility with pdfjs v1.6.210
v0.8.3 (unreleased)
-------------------
diff --git a/CONTRIBUTING.asciidoc b/CONTRIBUTING.asciidoc
index da5dc7025..e7ed06481 100644
--- a/CONTRIBUTING.asciidoc
+++ b/CONTRIBUTING.asciidoc
@@ -652,4 +652,4 @@ as closed.
* OS X: Run `python3 scripts/dev/build_release.py --upload v0.X.Y` (replace X/Y by hand)
* On server: Run `python3 scripts/dev/download_release.py v0.X.Y` (replace X/Y by hand)
* Update `qutebrowser-git` PKGBUILD if dependencies/install changed
-* Announce to qutebrowser mailinglist
+* Announce to qutebrowser and qutebrowser-announce mailinglist
diff --git a/README.asciidoc b/README.asciidoc
index 8574cb119..86743bc71 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -70,6 +70,9 @@ message to the
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at
mailto:qutebrowser@lists.qutebrowser.org[].
+There's also a https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist]
+at mailto:qutebrowser-announce@lists.qutebrowser.org[].
+
Contributions / Bugs
--------------------
@@ -159,10 +162,10 @@ Contributors, sorted by the number of commits in descending order:
* Claude
* Corentin Julé
* meles5
+* Kevin Velghe
* Philipp Hansch
* Daniel Karbach
* Panagiotis Ktistakis
-* Kevin Velghe
* Artur Shaik
* Nathan Isom
* Thorsten Wißmann
@@ -180,6 +183,7 @@ Contributors, sorted by the number of commits in descending order:
* knaggita
* Oliver Caldwell
* Julian Weigt
+* Sebastian Frysztak
* Jonas Schürmann
* error800
* Michael Hoang
@@ -244,6 +248,7 @@ Contributors, sorted by the number of commits in descending order:
* Tim Harder
* Thiago Barroso Perrotta
* Sorokin Alexei
+* Simon Désaulniers
* Rok Mandeljc
* Noah Huesser
* Moez Bouhlel
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 5d7cdaf87..7fb91bf6c 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -1017,6 +1017,7 @@ How many steps to zoom out.
|<>|Remove chars backward from the cursor to the beginning of the line.
|<>|Remove chars from the cursor to the beginning of the word.
|<>|Paste the most recently deleted text.
+|<>|Run a command with the given count.
|<>|Scroll the current tab in the given direction.
|<>|Scroll the frame page-wise.
|<>|Scroll to a specific percentage of the page.
@@ -1365,6 +1366,26 @@ Paste the most recently deleted text.
This acts like readline's yank.
+[[run-with-count]]
+=== run-with-count
+Syntax: +:run-with-count 'count-arg' 'command'+
+
+Run a command with the given count.
+
+If run_with_count itself is run with a count, it multiplies count_arg.
+
+==== positional arguments
+* +'count-arg'+: The count to pass to the command.
+* +'command'+: The command to run, with optional args.
+
+==== count
+The count that run_with_count itself received.
+
+==== note
+* This command does not split arguments after the last argument and handles quotes literally.
+* With this command, +;;+ is interpreted literally instead of splitting off a second command.
+* This command does not replace variables like +\{url\}+.
+
[[scroll]]
=== scroll
Syntax: +:scroll 'direction'+
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 8f416d3a4..d738a68ca 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -69,6 +69,7 @@
|<>|Whether to validate SSL handshakes.
|<>|Whether to try to pre-fetch DNS entries to speed up browsing.
|<>|Set custom headers for qutebrowser HTTP requests.
+|<>|Set location of a netrc-file for HTTP authentication. If empty, ~/.netrc is used.
|==============
.Quick reference for section ``completion''
@@ -808,6 +809,12 @@ Set custom headers for qutebrowser HTTP requests.
Default: empty
+[[network-netrc-file]]
+=== netrc-file
+Set location of a netrc-file for HTTP authentication. If empty, ~/.netrc is used.
+
+Default: empty
+
== completion
Options related to completion and command history.
diff --git a/doc/quickstart.asciidoc b/doc/quickstart.asciidoc
index 9d5375898..dc1426931 100644
--- a/doc/quickstart.asciidoc
+++ b/doc/quickstart.asciidoc
@@ -32,7 +32,8 @@ image:http://qutebrowser.org/img/cheatsheet-small.png["qutebrowser key binding c
`scripts/asciidoc2html.py` to generate the documentation.
* Go to the link:qute://settings[settings page] to set up qutebrowser the way you want it.
* Subscribe to
-https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist]
+https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[the mailinglist] or
+https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[the announce-only mailinglist].
* Let me know what features you are missing or things that need (even small!)
improvements.
diff --git a/doc/qutebrowser.1.asciidoc b/doc/qutebrowser.1.asciidoc
index 1da675498..2e1c4762c 100644
--- a/doc/qutebrowser.1.asciidoc
+++ b/doc/qutebrowser.1.asciidoc
@@ -162,6 +162,8 @@ this program. If not, see .
* Website: http://www.qutebrowser.org/
* Mailinglist: mailto:qutebrowser@lists.qutebrowser.org[] /
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser
+* Announce-only mailinglist: mailto:qutebrowser-announce@lists.qutebrowser.org[] /
+https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce
* IRC: irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on
http://freenode.net/[Freenode]
* Github: https://github.com/The-Compiler/qutebrowser
diff --git a/misc/requirements/requirements-flake8.txt b/misc/requirements/requirements-flake8.txt
index 855a5d7ea..16db859b6 100644
--- a/misc/requirements/requirements-flake8.txt
+++ b/misc/requirements/requirements-flake8.txt
@@ -19,7 +19,7 @@ pbr==1.10.0
pep8==1.7.0
pep8-naming==0.4.1
pycodestyle==2.0.0
-pydocstyle==1.0.0 # rq.filter: != 1.1.0
+pydocstyle==1.1.1
pyflakes==1.3.0
-pyparsing==2.1.9
+pyparsing==2.1.10
six==1.10.0
diff --git a/misc/requirements/requirements-flake8.txt-raw b/misc/requirements/requirements-flake8.txt-raw
index 29e2e7c6f..442ed2d54 100644
--- a/misc/requirements/requirements-flake8.txt-raw
+++ b/misc/requirements/requirements-flake8.txt-raw
@@ -12,7 +12,7 @@ flake8-tidy-imports
flake8-tuple
hacking
pep8-naming
-pydocstyle!=1.1.0
+pydocstyle
pyflakes
# Pinned to 1.5.7 by hacking otherwise
@@ -23,6 +23,3 @@ pep8==1.7.0
# https://github.com/JBKahn/flake8-debugger/issues/5
#@ filter: flake8-debugger != 2.0.0
-
-# https://gitlab.com/pycqa/flake8-docstrings/issues/16
-#@ filter: pydocstyle != 1.1.0
diff --git a/misc/requirements/requirements-pip.txt b/misc/requirements/requirements-pip.txt
index 8d7d67eb4..ddf8adbb4 100644
--- a/misc/requirements/requirements-pip.txt
+++ b/misc/requirements/requirements-pip.txt
@@ -1,2 +1,2 @@
pip==8.1.2
-setuptools==28.2.0
+setuptools==28.6.0
diff --git a/misc/requirements/requirements-tests.txt b/misc/requirements/requirements-tests.txt
index cf095233d..6608275ab 100644
--- a/misc/requirements/requirements-tests.txt
+++ b/misc/requirements/requirements-tests.txt
@@ -8,7 +8,7 @@ decorator==4.0.10
Flask==0.11.1
glob2==0.4.1
httpbin==0.5.0
-hypothesis==3.5.2
+hypothesis==3.5.3
itsdangerous==0.24
# Jinja2==2.8
Mako==1.0.4
@@ -19,7 +19,7 @@ py==1.4.31
pytest==3.0.3
pytest-bdd==2.18.0
pytest-catchlog==1.2.2
-pytest-cov==2.3.1
+pytest-cov==2.4.0
pytest-faulthandler==1.3.0
pytest-instafail==0.3.0
pytest-mock==1.2
@@ -31,7 +31,7 @@ pytest-warnings==0.1.0
pytest-xvfb==0.3.0
six==1.10.0
spark-parser==1.4.0
-uncompyle6==2.8.3
+uncompyle6==2.9.2
vulture==0.10
Werkzeug==0.11.11
-xdis==2.3.1
+xdis==3.1.0
diff --git a/misc/requirements/requirements-tox.txt b/misc/requirements/requirements-tox.txt
index f077153ff..acb3c0012 100644
--- a/misc/requirements/requirements-tox.txt
+++ b/misc/requirements/requirements-tox.txt
@@ -2,5 +2,5 @@
pluggy==0.4.0
py==1.4.31
-tox==2.3.1
+tox==2.4.1
virtualenv==15.0.3
diff --git a/misc/userscripts/cast b/misc/userscripts/cast
new file mode 100755
index 000000000..fd1bab4d5
--- /dev/null
+++ b/misc/userscripts/cast
@@ -0,0 +1,150 @@
+#!/usr/bin/env bash
+#
+# Behaviour
+# Userscript for qutebrowser which casts the url passed in $1 to the default
+# ChromeCast device in the network using the program `castnow`
+#
+# Usage
+# You can launch the script from qutebrowser as follows:
+# spawn --userscript ${PATH_TO_FILE} {url}
+#
+# Then, you can control the chromecast by launching the simple command
+# `castnow` in a shell which will connect to the running castnow instance.
+#
+# For stopping the script, issue the command `pkill -f castnow` which would
+# then let the rest of the userscript execute for cleaning temporary file.
+#
+# Thanks
+# This userscript borrows Thorsten Wißmann's javascript code from his `mpv`
+# userscript.
+#
+# Dependencies
+# - castnow, https://github.com/xat/castnow
+#
+# Author
+# Simon Désaulniers
+
+if [ -z "$QUTE_FIFO" ] ; then
+ cat 1>&2 <&2
+ else
+ echo "message-$cmd '${msg//\'/\\\'}'" >> "$QUTE_FIFO"
+ fi
+}
+
+js() {
+cat <
+ The video is being cast on your ChromeCast device.
+
+
+ In order to restore this particular video
+ click here.
+
+ ";
+ replacement.style.position = "relative";
+ replacement.style.zIndex = "100003000000";
+ replacement.style.fontSize = "1rem";
+ replacement.style.textAlign = "center";
+ replacement.style.verticalAlign = "middle";
+ replacement.style.height = "100%";
+ replacement.style.background = "#101010";
+ replacement.style.color = "white";
+ replacement.style.border = "4px dashed #545454";
+ replacement.style.padding = "2em";
+ replacement.style.margin = "auto";
+ App.all_replacements[i] = replacement;
+ App.backup_videos[i] = video;
+ video.parentNode.replaceChild(replacement, video);
+ }
+
+ function restore_video(obj, index) {
+ obj = App.all_replacements[index];
+ video = App.backup_videos[index];
+ console.log(video);
+ obj.parentNode.replaceChild(video, obj);
+ }
+
+ /** force repainting the video, thanks to:
+ * http://martinwolf.org/2014/06/10/force-repaint-of-an-element-with-javascript/
+ */
+ var siteHeader = document.getElementById('header');
+ siteHeader.style.display='none';
+ siteHeader.offsetHeight; // no need to store this anywhere, the reference is enough
+ siteHeader.style.display='block';
+
+EOF
+}
+
+printjs() {
+ js | sed 's,//.*$,,' | tr '\n' ' '
+}
+echo "jseval -q $(printjs)" >> "$QUTE_FIFO"
+
+tmpdir=$(mktemp -d)
+file_to_cast=${tmpdir}/qutecast
+
+# kill any running instance of castnow
+pkill -f /usr/bin/castnow
+
+# start youtube download in stream mode (-o -) into temporary file
+youtube-dl -qo - "$1" > ${file_to_cast} &
+ytdl_pid=$!
+
+msg info "Casting $1" >> "$QUTE_FIFO"
+# start castnow in stream mode to cast on ChromeCast
+tail -F "${file_to_cast}" | castnow -
+
+# cleanup remaining background process and file on disk
+kill ${ytdl_pid}
+rm -rf ${tmpdir}
diff --git a/misc/userscripts/qutebrowser_viewsource b/misc/userscripts/qutebrowser_viewsource
index 535855bb3..b528c41e8 100755
--- a/misc/userscripts/qutebrowser_viewsource
+++ b/misc/userscripts/qutebrowser_viewsource
@@ -24,9 +24,9 @@
# Caveat: Does not use authentication of any kind. Add it in if you want it to.
#
-path=/tmp/qutebrowser_$(mktemp XXXXXXXX).html
+path=$(mktemp --tmpdir qutebrowser_XXXXXXXX.html)
-curl "$QUTE_URL" > $path
+curl "$QUTE_URL" > "$path"
urxvt -e vim "$path"
rm "$path"
diff --git a/pytest.ini b/pytest.ini
index 2b68a77af..0c2d00f21 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -30,6 +30,9 @@ qt_log_ignore =
^QProcess: Destroyed while process .* is still running\.
^"Method "GetAll" with signature "s" on interface "org\.freedesktop\.DBus\.Properties" doesn't exist
^"Method \\"GetAll\\" with signature \\"s\\" on interface \\"org\.freedesktop\.DBus\.Properties\\" doesn't exist\\n"
+ ^propsReply "Method \\"GetAll\\" with signature \\"s\\" on interface \\"org\.freedesktop\.DBus\.Properties\\" doesn't exist\\n"
+ ^nmReply "Method \\"GetDevices\\" with signature \\"\\" on interface \\"org\.freedesktop\.NetworkManager\\" doesn't exist\\n"
+ ^"Object path cannot be empty"
^virtual void QSslSocketBackendPrivate::transmit\(\) SSL write failed with error: -9805
^virtual void QSslSocketBackendPrivate::transmit\(\) SSLRead failed with: -9805
^Type conversion already registered from type .*
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 345d49b97..68397bbd3 100644
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -50,7 +50,8 @@ from qutebrowser.browser.webkit import cookies, cache, downloads
from qutebrowser.browser.webkit.network import (webkitqutescheme, proxy,
networkmanager)
from qutebrowser.mainwindow import mainwindow
-from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal
+from qutebrowser.misc import (readline, ipc, savemanager, sessions,
+ crashsignal, earlyinit)
from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir, error, debug)
@@ -198,6 +199,9 @@ def _process_args(args):
_open_startpage()
_open_quickstart(args)
+ delta = datetime.datetime.now() - earlyinit.START_TIME
+ log.init.debug("Init finished after {}s".format(delta.total_seconds()))
+
def _load_session(name):
"""Load the default session.
diff --git a/qutebrowser/browser/pdfjs.py b/qutebrowser/browser/pdfjs.py
index 51ed2dfbe..3bc2c2dc3 100644
--- a/qutebrowser/browser/pdfjs.py
+++ b/qutebrowser/browser/pdfjs.py
@@ -62,8 +62,10 @@ def _generate_pdfjs_script(url):
url: The url of the pdf page as QUrl.
"""
return (
- 'PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
- 'PDFView.open("{url}");\n'
+ 'document.addEventListener("DOMContentLoaded", function() {{\n'
+ ' PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
+ ' (window.PDFView || window.PDFViewerApplication).open("{url}");\n'
+ '}});\n'
).format(url=javascript.string_escape(url.toString(QUrl.FullyEncoded)))
diff --git a/qutebrowser/browser/webkit/mhtml.py b/qutebrowser/browser/webkit/mhtml.py
index cc9d75d00..41d4e7355 100644
--- a/qutebrowser/browser/webkit/mhtml.py
+++ b/qutebrowser/browser/webkit/mhtml.py
@@ -37,14 +37,6 @@ from PyQt5.QtCore import QUrl
from qutebrowser.browser.webkit import webkitelem, downloads
from qutebrowser.utils import log, objreg, message, usertypes, utils, urlutils
-try:
- import cssutils
-except (ImportError, re.error):
- # Catching re.error because cssutils in earlier releases (<= 1.0) is broken
- # on Python 3.5
- # See https://bitbucket.org/cthedot/cssutils/issues/52
- cssutils = None
-
_File = collections.namedtuple('_File',
['content', 'content_type', 'content_location',
'transfer_encoding'])
@@ -85,6 +77,14 @@ def _get_css_imports_cssutils(data, inline=False):
data: The content of the stylesheet to scan as string.
inline: True if the argument is an inline HTML style attribute.
"""
+ try:
+ import cssutils
+ except (ImportError, re.error):
+ # Catching re.error because cssutils in earlier releases (<= 1.0) is
+ # broken on Python 3.5
+ # See https://bitbucket.org/cthedot/cssutils/issues/52
+ return None
+
# We don't care about invalid CSS data, this will only litter the log
# output with CSS errors
parser = cssutils.CSSParser(loglevel=100,
@@ -114,10 +114,10 @@ def _get_css_imports(data, inline=False):
data: The content of the stylesheet to scan as string.
inline: True if the argument is an inline HTML style attribute.
"""
- if cssutils is None:
- return _get_css_imports_regex(data)
- else:
- return _get_css_imports_cssutils(data, inline)
+ imports = _get_css_imports_cssutils(data, inline)
+ if imports is None:
+ imports = _get_css_imports_regex(data)
+ return imports
def _check_rel(element):
diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py
index 04ce58975..d28bd6be4 100644
--- a/qutebrowser/browser/webkit/network/networkmanager.py
+++ b/qutebrowser/browser/webkit/network/networkmanager.py
@@ -330,7 +330,7 @@ class NetworkManager(QNetworkAccessManager):
# altogether.
reply.netrc_used = True
try:
- net = netrc.netrc()
+ net = netrc.netrc(config.get('network', 'netrc-file'))
authenticators = net.authenticators(reply.url().host())
if authenticators is not None:
(user, _account, password) = authenticators
diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py
index 35464cb9f..5d068422e 100644
--- a/qutebrowser/commands/runners.py
+++ b/qutebrowser/commands/runners.py
@@ -31,8 +31,7 @@ from qutebrowser.utils import message, objreg, qtutils, usertypes, utils
from qutebrowser.misc import split
-ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline',
- 'count'])
+ParseResult = collections.namedtuple('ParseResult', ['cmd', 'args', 'cmdline'])
last_command = {}
macro = {}
@@ -158,26 +157,6 @@ class CommandRunner(QObject):
for sub in sub_texts:
yield self.parse(sub, *args, **kwargs)
- def _parse_count(self, cmdstr):
- """Split a count prefix off from a command for parse().
-
- Args:
- cmdstr: The command/args including the count.
-
- Return:
- A (count, cmdstr) tuple, with count being None or int.
- """
- if ':' not in cmdstr:
- return (None, cmdstr)
-
- count, cmdstr = cmdstr.split(':', maxsplit=1)
- try:
- count = int(count)
- except ValueError:
- # We just ignore invalid prefixes
- count = None
- return (count, cmdstr)
-
def parse(self, text, *, fallback=False, keep=False):
"""Split the commandline text into command and arguments.
@@ -191,7 +170,6 @@ class CommandRunner(QObject):
A ParseResult tuple.
"""
cmdstr, sep, argstr = text.partition(' ')
- count, cmdstr = self._parse_count(cmdstr)
if not cmdstr and not fallback:
raise cmdexc.NoSuchCommandError("No command given")
@@ -206,8 +184,7 @@ class CommandRunner(QObject):
raise cmdexc.NoSuchCommandError(
'{}: no such command'.format(cmdstr))
cmdline = split.split(text, keep=keep)
- return ParseResult(cmd=None, args=None, cmdline=cmdline,
- count=count)
+ return ParseResult(cmd=None, args=None, cmdline=cmdline)
args = self._split_args(cmd, argstr, keep)
if keep and args:
@@ -217,7 +194,7 @@ class CommandRunner(QObject):
else:
cmdline = [cmdstr] + args[:]
- return ParseResult(cmd=cmd, args=args, cmdline=cmdline, count=count)
+ return ParseResult(cmd=cmd, args=args, cmdline=cmdline)
def _completion_match(self, cmdstr):
"""Replace cmdstr with a matching completion if there's only one match.
@@ -295,18 +272,9 @@ class CommandRunner(QObject):
args = result.args
else:
args = replace_variables(self._win_id, result.args)
- if count is not None:
- if result.count is not None:
- raise cmdexc.CommandMetaError("Got count via command and "
- "prefix!")
- result.cmd.run(self._win_id, args, count=count)
- elif result.count is not None:
- result.cmd.run(self._win_id, args, count=result.count)
- else:
- result.cmd.run(self._win_id, args)
+ result.cmd.run(self._win_id, args, count=count)
- parsed_command = (self._parse_count(text)[1],
- count if count is not None else result.count)
+ parsed_command = (text, count)
if result.cmdline[0] != 'repeat-command':
last_command[cur_mode] = parsed_command
diff --git a/qutebrowser/completion/completer.py b/qutebrowser/completion/completer.py
index 21f7b99ce..c5c1915ca 100644
--- a/qutebrowser/completion/completer.py
+++ b/qutebrowser/completion/completer.py
@@ -157,6 +157,7 @@ class Completer(QObject):
result = runner.parse(text, fallback=True, keep=True)
parts = [x for x in result.cmdline if x]
pos = self._cmd.cursorPosition() - len(self._cmd.prefix())
+ pos = min(pos, len(text)) # Qt treats 2-byte UTF-16 chars as 2 chars
log.completion.debug('partitioning {} around position {}'.format(parts,
pos))
for i, part in enumerate(parts):
diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py
index 672382ae9..5cd9136c6 100644
--- a/qutebrowser/config/configdata.py
+++ b/qutebrowser/config/configdata.py
@@ -438,6 +438,11 @@ def data(readonly=False):
SettingValue(typ.HeaderDict(none_ok=True), ''),
"Set custom headers for qutebrowser HTTP requests."),
+ ('netrc-file',
+ SettingValue(typ.File(none_ok=True), ''),
+ "Set location of a netrc-file for HTTP authentication. If empty, "
+ "~/.netrc is used."),
+
readonly=readonly
)),
diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py
index 593ed04bd..ebc4f28ad 100644
--- a/qutebrowser/mainwindow/mainwindow.py
+++ b/qutebrowser/mainwindow/mainwindow.py
@@ -503,8 +503,8 @@ class MainWindow(QWidget):
# Ask if multiple downloads running
if 'downloads' in confirm_quit and download_count > 0:
quit_texts.append("{} {} running.".format(
- tab_count,
- "download is" if tab_count == 1 else "downloads are"))
+ download_count,
+ "download is" if download_count == 1 else "downloads are"))
# Process all quit messages that user must confirm
if quit_texts or 'always' in confirm_quit:
text = '\n'.join(['Really quit?'] + quit_texts)
diff --git a/qutebrowser/mainwindow/tabwidget.py b/qutebrowser/mainwindow/tabwidget.py
index baeb7d620..657a38dc3 100644
--- a/qutebrowser/mainwindow/tabwidget.py
+++ b/qutebrowser/mainwindow/tabwidget.py
@@ -703,13 +703,10 @@ class TabBarStyle(QCommonStyle):
text_rect.adjust(indicator_width + indicator_padding.left +
indicator_padding.right, 0, 0, 0)
- if opt.icon.isNull():
- icon_rect = QRect()
- else:
+ icon_rect = self._get_icon_rect(opt, text_rect)
+ if icon_rect.isValid():
icon_padding = self.pixelMetric(PixelMetrics.icon_padding, opt)
- icon_rect = self._get_icon_rect(opt, text_rect)
- if icon_rect.isValid():
- text_rect.adjust(icon_rect.width() + icon_padding, 0, 0, 0)
+ text_rect.adjust(icon_rect.width() + icon_padding, 0, 0, 0)
text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect)
return Layouts(text=text_rect, icon=icon_rect,
@@ -733,9 +730,17 @@ class TabBarStyle(QCommonStyle):
else QIcon.Disabled)
icon_state = (QIcon.On if opt.state & QStyle.State_Selected
else QIcon.Off)
- tab_icon_size = opt.icon.actualSize(icon_size, icon_mode, icon_state)
- tab_icon_size = QSize(min(tab_icon_size.width(), icon_size.width()),
- min(tab_icon_size.height(), icon_size.height()))
+ # reserve space for favicon when tab bar is vertical (issue #1968)
+ position = config.get('tabs', 'position')
+ if (opt.icon.isNull() and
+ position in [QTabWidget.East, QTabWidget.West] and
+ config.get('tabs', 'show-favicons')):
+ tab_icon_size = icon_size
+ else:
+ actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state)
+ tab_icon_size = QSize(
+ min(actual_size.width(), icon_size.width()),
+ min(actual_size.height(), icon_size.height()))
icon_rect = QRect(text_rect.left(), text_rect.top() + 1,
tab_icon_size.width(), tab_icon_size.height())
icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect)
diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py
index dd498edf3..08c933a6e 100644
--- a/qutebrowser/misc/earlyinit.py
+++ b/qutebrowser/misc/earlyinit.py
@@ -37,6 +37,7 @@ import signal
import operator
import importlib
import pkg_resources
+import datetime
try:
import tkinter
except ImportError:
@@ -45,6 +46,9 @@ except ImportError:
# initialization needs to take place before that!
+START_TIME = datetime.datetime.now()
+
+
def _missing_str(name, *, windows=None, pip=None, webengine=False):
"""Get an error string for missing packages.
diff --git a/qutebrowser/misc/sessions.py b/qutebrowser/misc/sessions.py
index cd3d2f3bc..c3a3a8e34 100644
--- a/qutebrowser/misc/sessions.py
+++ b/qutebrowser/misc/sessions.py
@@ -481,3 +481,5 @@ class SessionManager(QObject):
log.sessions.exception("Error while deleting session!")
raise cmdexc.CommandError("Error while deleting session: {}"
.format(e))
+ else:
+ log.sessions.debug("Deleted session {}.".format(name))
diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py
index 010d9f33b..4213dbc97 100644
--- a/qutebrowser/misc/utilcmds.py
+++ b/qutebrowser/misc/utilcmds.py
@@ -86,6 +86,23 @@ def repeat(times: int, command, win_id):
commandrunner.run_safely(command)
+@cmdutils.register(maxsplit=1, hide=True, no_cmd_split=True,
+ no_replace_variables=True)
+@cmdutils.argument('win_id', win_id=True)
+@cmdutils.argument('count', count=True)
+def run_with_count(count_arg: int, command, win_id, count=1):
+ """Run a command with the given count.
+
+ If run_with_count itself is run with a count, it multiplies count_arg.
+
+ Args:
+ count_arg: The count to pass to the command.
+ command: The command to run, with optional args.
+ count: The count that run_with_count itself received.
+ """
+ runners.CommandRunner(win_id).run(command, count_arg * count)
+
+
@cmdutils.register(hide=True)
def message_error(text):
"""Show an error message in the statusbar.
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index c88df8a38..044b75b65 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -148,7 +148,11 @@ console_filter = None
def stub(suffix=''):
"""Show a STUB: message for the calling function."""
- function = inspect.stack()[1][3]
+ try:
+ function = inspect.stack()[1][3]
+ except IndexError: # pragma: no cover
+ misc.exception("Failed to get stack")
+ function = ''
text = "STUB: {}".format(function)
if suffix:
text = '{} ({})'.format(text, suffix)
diff --git a/scripts/dev/ci/travis_install.sh b/scripts/dev/ci/travis_install.sh
index 435af6389..4edb587a4 100644
--- a/scripts/dev/ci/travis_install.sh
+++ b/scripts/dev/ci/travis_install.sh
@@ -88,11 +88,13 @@ if [[ $DOCKER ]]; then
elif [[ $TRAVIS_OS_NAME == osx ]]; then
# Disable App Nap
defaults write NSGlobalDomain NSAppSleepDisabled -bool YES
- curl -LO https://github.com/The-Compiler/homebrew-qt5-webkit/releases/download/v5.6.0-1/pyqt5-5.6.el_capitan.bottle.1.tar.gz
- curl -LO https://github.com/The-Compiler/homebrew-qt5-webkit/releases/download/v5.6.1_1-1/qt5-5.6.1-1.yosemite.bottle.1.tar.gz
+
+ curl -LO https://bootstrap.pypa.io/get-pip.py
+ sudo -H python get-pip.py
+
brew --version
- brew_install python3 {qt5,pyqt5}-*.bottle.1.tar.gz
- pip_install pip
+ brew_install python3 qt5 pyqt5
+
pip_install tox
pip --version
tox --version
diff --git a/tests/end2end/data/insert_mode_settings/html/autofocus.html b/tests/end2end/data/insert_mode_settings/html/autofocus.html
index db5399252..5b707426f 100644
--- a/tests/end2end/data/insert_mode_settings/html/autofocus.html
+++ b/tests/end2end/data/insert_mode_settings/html/autofocus.html
@@ -3,8 +3,17 @@
Inputs Autofocus
+
-
+
diff --git a/tests/end2end/data/insert_mode_settings/html/input.html b/tests/end2end/data/insert_mode_settings/html/input.html
index 2965693d3..0617cae3f 100644
--- a/tests/end2end/data/insert_mode_settings/html/input.html
+++ b/tests/end2end/data/insert_mode_settings/html/input.html
@@ -3,8 +3,17 @@
Input
+
-
+
diff --git a/tests/end2end/data/insert_mode_settings/html/textarea.html b/tests/end2end/data/insert_mode_settings/html/textarea.html
index 403809a3a..1ab9d7524 100644
--- a/tests/end2end/data/insert_mode_settings/html/textarea.html
+++ b/tests/end2end/data/insert_mode_settings/html/textarea.html
@@ -3,8 +3,16 @@
Textarea
+
-
+
diff --git a/tests/end2end/features/caret.feature b/tests/end2end/features/caret.feature
index fdfb7f6d8..47b00622d 100644
--- a/tests/end2end/features/caret.feature
+++ b/tests/end2end/features/caret.feature
@@ -3,7 +3,7 @@ Feature: Caret mode
Background:
Given I open data/caret.html
- And I run :tab-only ;; :enter-mode caret
+ And I run :tab-only ;; enter-mode caret
# document
diff --git a/tests/end2end/features/completion.feature b/tests/end2end/features/completion.feature
index 18f2141d8..4fec2d81b 100644
--- a/tests/end2end/features/completion.feature
+++ b/tests/end2end/features/completion.feature
@@ -22,3 +22,8 @@ Feature: Command bar completion
# Make sure qutebrowser doesn't hang
And I run :message-info "Still alive!"
Then the message "Still alive!" should be shown
+
+ Scenario: Crash when pasting emoji into the command line (#2007)
+ Given I open about:blank
+ When I run :set-cmd-text -s :🌀
+ Then no crash should happen
diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature
index 08e6d718a..d0622b9c0 100644
--- a/tests/end2end/features/hints.feature
+++ b/tests/end2end/features/hints.feature
@@ -182,10 +182,11 @@ Feature: Using hints
### hints -> auto-follow-timeout
+ @not_osx
Scenario: Ignoring key presses after auto-following hints
- When I set hints -> auto-follow-timeout to 500
+ When I set hints -> auto-follow-timeout to 1000
And I set hints -> mode to number
- And I run :bind --force , message-error "This should not happen"
+ And I run :bind --force , message-error "This error message was triggered via a keybinding which should have been inhibited"
And I open data/hints/html/simple.html
And I hint with args "all"
And I press the key "f"
diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature
index 48703d940..c60cb0a17 100644
--- a/tests/end2end/features/keyinput.feature
+++ b/tests/end2end/features/keyinput.feature
@@ -165,7 +165,7 @@ Feature: Keyboard input
Then the javascript message "key press: 88" should be logged
And the javascript message "key release: 88" should be logged
- @no_xvfb @posix
+ @no_xvfb @posix @qtwebengine_skip
Scenario: :fake-key sending key to the website with other window focused
When I open data/keyinput/log.html
And I set general -> developer-extras to true
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index bb8811292..0727f97cb 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -149,7 +149,7 @@ Feature: Various utility commands.
And I run :inspector
Then the error "Please enable developer-extras before using the webinspector!" should be shown
- @no_xvfb @posix
+ @no_xvfb @posix @qtwebengine_skip
Scenario: Inspector smoke test
When I set general -> developer-extras to true
And I run :inspector
@@ -165,7 +165,7 @@ Feature: Various utility commands.
Then the error "Please enable developer-extras before using the webinspector!" should be shown
# Different code path as an inspector got created now
- @no_xvfb @posix
+ @no_xvfb @posix @qtwebengine_skip
Scenario: Inspector smoke test 2
When I set general -> developer-extras to true
And I run :inspector
@@ -499,7 +499,7 @@ Feature: Various utility commands.
Scenario: Using :debug-log-capacity
When I run :debug-log-capacity 100
And I run :message-info oldstuff
- And I run :repeat 20 :message-info otherstuff
+ And I run :repeat 20 message-info otherstuff
And I run :message-info newstuff
And I open qute:log
Then the page should contain the plaintext "newstuff"
@@ -549,6 +549,7 @@ Feature: Various utility commands.
And I open cookies/set?qute-test=42 without waiting
And I wait until cookies is loaded
And I open cookies in a new tab
+ And I set general -> private-browsing to false
Then the cookie qute-test should be set to 42
## https://github.com/The-Compiler/qutebrowser/issues/1742
@@ -557,6 +558,7 @@ Feature: Various utility commands.
Scenario: Private browsing is activated in QtWebKit without restart
When I set general -> private-browsing to true
And I open data/javascript/localstorage.html
+ And I set general -> private-browsing to false
Then the page should contain the plaintext "Local storage status: not working"
Scenario: :repeat-command
@@ -695,3 +697,63 @@ Feature: Various utility commands.
And I wait until the scroll position changed
And I run :click-element id link
Then the error "Element position is out of view!" should be shown
+
+ ## :command-history-{prev,next}
+
+ Scenario: Calling previous command
+ When I run :set-cmd-text :message-info blah
+ And I run :command-accept
+ And I wait for "blah" in the log
+ And I run :set-cmd-text :
+ And I run :command-history-prev
+ And I run :command-accept
+ Then the message "blah" should be shown
+
+ Scenario: Browsing through commands
+ When I run :set-cmd-text :message-info blarg
+ And I run :command-accept
+ And I wait for "blarg" in the log
+ And I run :set-cmd-text :
+ And I run :command-history-prev
+ And I run :command-history-prev
+ And I run :command-history-next
+ And I run :command-history-next
+ And I run :command-accept
+ Then the message "blarg" should be shown
+
+ Scenario: Calling previous command when history is empty
+ Given I have a fresh instance
+ When I run :set-cmd-text :
+ And I run :command-history-prev
+ And I run :command-accept
+ Then the error "No command given" should be shown
+
+ Scenario: Calling next command when there's no next command
+ When I run :set-cmd-text :
+ And I run :command-history-next
+ And I run :command-accept
+ Then the error "No command given" should be shown
+
+ @qtwebengine_todo: private browsing is not implemented yet
+ Scenario: Calling previous command with private-browsing mode
+ When I run :set-cmd-text :message-info blah
+ And I run :command-accept
+ And I set general -> private-browsing to true
+ And I run :set-cmd-text :message-error "This should only be shown once"
+ And I run :command-accept
+ And I wait for the error "This should only be shown once"
+ And I run :set-cmd-text :
+ And I run :command-history-prev
+ And I run :command-accept
+ And I set general -> private-browsing to false
+ Then the message "blah" should be shown
+
+ ## :run-with-count
+
+ Scenario: :run-with-count
+ When I run :run-with-count 2 scroll down
+ Then "command called: scroll ['down'] (count=2)" should be logged
+
+ Scenario: :run-with-count with count
+ When I run :run-with-count 2 scroll down with count 3
+ Then "command called: scroll ['down'] (count=6)" should be logged
diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature
index 234a428ae..af3e43f11 100644
--- a/tests/end2end/features/sessions.feature
+++ b/tests/end2end/features/sessions.feature
@@ -294,11 +294,13 @@ Feature: Saving and loading sessions
Scenario: Deleting internal session with --force
When I run :session-save --force _internal
And I run :session-delete --force _internal
+ And I wait for "Deleted session _internal." in the log
Then the session _internal should not exist
Scenario: Normally deleting a session
When I run :session-save deleted_session
And I run :session-delete deleted_session
+ And I wait for "Deleted session deleted_session." in the log
Then the session deleted_session should not exist
Scenario: Deleting a session which doesn't exist
diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature
index 7ae98619c..b138397c5 100644
--- a/tests/end2end/features/tabs.feature
+++ b/tests/end2end/features/tabs.feature
@@ -987,13 +987,13 @@ Feature: Tab management
Scenario: Using :tab-next after closing last tab (#1448)
When I set tabs -> last-close to close
And I run :tab-only
- And I run :tab-close ;; :tab-next
+ And I run :tab-close ;; tab-next
Then qutebrowser should quit
And no crash should happen
Scenario: Using :tab-prev after closing last tab (#1448)
When I set tabs -> last-close to close
And I run :tab-only
- And I run :tab-close ;; :tab-prev
+ And I run :tab-close ;; tab-prev
Then qutebrowser should quit
And no crash should happen
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index acd6b0f49..46a0d2ff7 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -437,7 +437,8 @@ class QuteProc(testprocess.Process):
command = command.replace('\\', r'\\')
if count is not None:
- command = ':{}:{}'.format(count, command.lstrip(':'))
+ command = ':run-with-count {} {}'.format(count,
+ command.lstrip(':'))
self.send_ipc([command])
if not invalid:
diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py
index 791faf9c3..0fa1d7139 100644
--- a/tests/end2end/fixtures/webserver_sub.py
+++ b/tests/end2end/fixtures/webserver_sub.py
@@ -80,8 +80,11 @@ def redirect_later():
@app.route('/custom/redirect-later-continue')
def redirect_later_continue():
"""Continue a redirect-later request."""
- _redirect_later_event.set()
- return flask.Response(b'Continued redirect.')
+ if _redirect_later_event is None:
+ return flask.Response(b'Timed out or no redirect pending.')
+ else:
+ _redirect_later_event.set()
+ return flask.Response(b'Continued redirect.')
@app.route('/custom/content-size')
diff --git a/tests/end2end/test_insert_mode.py b/tests/end2end/test_insert_mode.py
index cd57968ca..6939dae0e 100644
--- a/tests/end2end/test_insert_mode.py
+++ b/tests/end2end/test_insert_mode.py
@@ -56,6 +56,10 @@ def test_insert_mode(file_name, elem_id, source, input_text, auto_insert,
# second time.
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format(input_text))
quteproc.send_cmd(':insert-text {clipboard}')
+ else:
+ raise ValueError("Invalid source {!r}".format(source))
+
+ quteproc.wait_for_js('contents: {}'.format(input_text))
quteproc.send_cmd(':leave-mode')
quteproc.send_cmd(':hint all')
diff --git a/tests/unit/browser/test_pdfjs.py b/tests/unit/browser/test_pdfjs.py
index ad489bc7a..a33dae5bf 100644
--- a/tests/unit/browser/test_pdfjs.py
+++ b/tests/unit/browser/test_pdfjs.py
@@ -36,11 +36,11 @@ from qutebrowser.browser import pdfjs
'http://foobar/%22);alert(%22attack!%22);'),
])
def test_generate_pdfjs_script(url, expected):
- expected_code = ('PDFJS.verbosity = PDFJS.VERBOSITY_LEVELS.info;\n'
- 'PDFView.open("{}");\n'.format(expected))
+ expected_open = 'open("{}");'.format(expected)
url = QUrl(url)
actual = pdfjs._generate_pdfjs_script(url)
- assert actual == expected_code
+ assert expected_open in actual
+ assert 'PDFView' in actual
def test_fix_urls():
diff --git a/tests/unit/browser/webkit/test_mhtml.py b/tests/unit/browser/webkit/test_mhtml.py
index 7ea3d999e..3b33857e9 100644
--- a/tests/unit/browser/webkit/test_mhtml.py
+++ b/tests/unit/browser/webkit/test_mhtml.py
@@ -26,6 +26,15 @@ import pytest
mhtml = pytest.importorskip('qutebrowser.browser.webkit.mhtml')
+try:
+ import cssutils
+except (ImportError, re.error):
+ # Catching re.error because cssutils in earlier releases (<= 1.0) is
+ # broken on Python 3.5
+ # See https://bitbucket.org/cthedot/cssutils/issues/52
+ cssutils = None
+
+
@pytest.fixture(autouse=True)
def patch_uuid(monkeypatch):
monkeypatch.setattr("uuid.uuid4", lambda: "UUID")
@@ -248,8 +257,7 @@ def test_empty_content_type(checker):
@pytest.mark.parametrize('has_cssutils', [
- pytest.mark.skipif(mhtml.cssutils is None,
- reason="requires cssutils")(True),
+ pytest.mark.skipif(cssutils is None, reason="requires cssutils")(True),
False,
], ids=['with_cssutils', 'no_cssutils'])
@pytest.mark.parametrize('inline, style, expected_urls', [
@@ -267,7 +275,8 @@ def test_empty_content_type(checker):
def test_css_url_scanner(monkeypatch, has_cssutils, inline, style,
expected_urls):
if not has_cssutils:
- monkeypatch.setattr('qutebrowser.browser.webkit.mhtml.cssutils', None)
+ monkeypatch.setattr(mhtml, '_get_css_imports_cssutils',
+ lambda data, inline=False: None)
expected_urls.sort()
urls = mhtml._get_css_imports(style, inline=inline)
urls.sort()
diff --git a/tests/unit/commands/test_runners.py b/tests/unit/commands/test_runners.py
index 50fcbde1c..77c9d8f72 100644
--- a/tests/unit/commands/test_runners.py
+++ b/tests/unit/commands/test_runners.py
@@ -64,15 +64,6 @@ class TestCommandRunner:
with pytest.raises(cmdexc.NoSuchCommandError):
list(cr.parse_all(command))
- def test_parse_with_count(self):
- """Test parsing of commands with a count."""
- cr = runners.CommandRunner(0)
- result = cr.parse('20:scroll down')
- assert result.cmd.name == 'scroll'
- 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.
diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py
index 7e9fdbb48..2c02efa44 100644
--- a/tests/unit/completion/test_completer.py
+++ b/tests/unit/completion/test_completer.py
@@ -181,6 +181,7 @@ def _set_cmd_prompt(cmd, txt):
(':open -- |', None, ''),
(':gibberish nonesense |', None, ''),
('/:help|', None, ''),
+ ('::bind|', usertypes.Completion.command, ':bind'),
])
def test_update_completion(txt, kind, pattern, status_command_stub,
completer_obj, completion_widget_stub):
diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py
index 00f8b290d..bb84521d0 100644
--- a/tests/unit/mainwindow/test_tabwidget.py
+++ b/tests/unit/mainwindow/test_tabwidget.py
@@ -41,6 +41,7 @@ class TestTabWidget:
'position': 0,
'select-on-remove': 1,
'show': 'always',
+ 'show-favicons': True,
'padding': configtypes.PaddingValues(0, 0, 5, 5),
'indicator-width': 3,
'indicator-padding': configtypes.PaddingValues(2, 2, 0, 4),