Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
cf4b89efe3
13
.flake8
13
.flake8
@ -1,13 +0,0 @@
|
|||||||
# vim: ft=dosini fileencoding=utf-8:
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
# E265: Block comment should start with '#'
|
|
||||||
# E501: Line too long
|
|
||||||
# F841: unused variable
|
|
||||||
# F401: Unused import
|
|
||||||
# E402: module level import not at top of file
|
|
||||||
# E266: too many leading '#' for block comment
|
|
||||||
# W503: line break before binary operator
|
|
||||||
ignore=E265,E501,F841,F401,E402,E266,W503
|
|
||||||
max_complexity = 12
|
|
||||||
exclude=resources.py
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,3 +23,4 @@ __pycache__
|
|||||||
/htmlcov
|
/htmlcov
|
||||||
/.tox
|
/.tox
|
||||||
/testresults.html
|
/testresults.html
|
||||||
|
/.cache
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
ignore=resources.py
|
ignore=resources.py
|
||||||
extension-pkg-whitelist=PyQt5,sip
|
extension-pkg-whitelist=PyQt5,sip
|
||||||
load-plugins=pylint_checkers.config,
|
load-plugins=pylint_checkers.config,
|
||||||
pylint_checkers.crlf,
|
|
||||||
pylint_checkers.modeline,
|
pylint_checkers.modeline,
|
||||||
pylint_checkers.openencoding,
|
pylint_checkers.openencoding,
|
||||||
pylint_checkers.settrace
|
pylint_checkers.settrace
|
||||||
|
@ -37,6 +37,8 @@ Added
|
|||||||
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
|
- New flag `-d`/`--detach` for `:spawn` to detach the spawned process so it's not closed when qutebrowser is.
|
||||||
- New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`).
|
- New (hidden) command `:follow-selected` (bound to `Enter`/`Ctrl-Enter` by default) to follow the link which is currently selected (e.g. after searching via `/`).
|
||||||
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
|
- New setting `ui -> modal-js-dialog` to use the standard modal dialogs for javascript questions instead of using the statusbar.
|
||||||
|
- New setting `colors -> webpage.bg` to set the background color to use for websites which don't set one.
|
||||||
|
- New (hidden) command `:clear-keychain` to clear a partially entered keychain (bound to `<Escape>` by default, in addition to clearing search).
|
||||||
|
|
||||||
Changed
|
Changed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
@ -45,7 +47,7 @@ Changed
|
|||||||
- `:spawn` now shows the command being executed in the statusbar, use `-q`/`--quiet` for the old behavior.
|
- `:spawn` now shows the command being executed in the statusbar, use `-q`/`--quiet` for the old behavior.
|
||||||
- The `content -> geolocation` and `notifications` settings now support a `true` value to always allow those. However, this is *not recommended*.
|
- The `content -> geolocation` and `notifications` settings now support a `true` value to always allow those. However, this is *not recommended*.
|
||||||
- New bindings `<Ctrl-R>` (rapid), `<Ctrl-F>` (foreground) and `<Ctrl-B>` (background) to switch hint modes while hinting.
|
- New bindings `<Ctrl-R>` (rapid), `<Ctrl-F>` (foreground) and `<Ctrl-B>` (background) to switch hint modes while hinting.
|
||||||
- `<Ctrl-M>` is now accepted as an additional alias for `<Return>`/`<Ctrl-J>`
|
- `<Ctrl-M>` and numpad-enter are now bound by default for bindings where `<Return>` was bound.
|
||||||
- `:hint tab` and `F` now respect the `background-tabs` setting. To enforce a foreground tab (what `F` did before), use `:hint tab-fg` or `;f`.
|
- `:hint tab` and `F` now respect the `background-tabs` setting. To enforce a foreground tab (what `F` did before), use `:hint tab-fg` or `;f`.
|
||||||
- `:scroll` now takes a direction argument (`up`/`down`/`left`/`right`/`top`/`bottom`/`page-up`/`page-down`) instead of two pixel arguments (`dx`/`dy`). The old form still works but is deprecated.
|
- `:scroll` now takes a direction argument (`up`/`down`/`left`/`right`/`top`/`bottom`/`page-up`/`page-down`) instead of two pixel arguments (`dx`/`dy`). The old form still works but is deprecated.
|
||||||
|
|
||||||
@ -79,6 +81,8 @@ Fixed
|
|||||||
- Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug)
|
- Workaround for qutebrowser not starting when there are NUL-bytes in the history (because of a currently unknown bug)
|
||||||
- Fixed handling of keybindings containing Ctrl/Meta on OS X.
|
- Fixed handling of keybindings containing Ctrl/Meta on OS X.
|
||||||
- Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...".
|
- Fixed crash when downloading an URL without filename (e.g. magnet links) via "Save as...".
|
||||||
|
- Fixed exception when starting qutebrowser with `:set` as argument.
|
||||||
|
- Fixed horrible completion performance when the `shrink` option was set.
|
||||||
|
|
||||||
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1]
|
https://github.com/The-Compiler/qutebrowser/releases/tag/v0.2.1[v0.2.1]
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
@ -91,9 +91,10 @@ unittests and several linters/checkers.
|
|||||||
|
|
||||||
Currently, the following tools will be invoked when you run `tox`:
|
Currently, the following tools will be invoked when you run `tox`:
|
||||||
|
|
||||||
* Unit tests using the Python
|
* Unit tests using https://www.pytest.org[pytest].
|
||||||
https://docs.python.org/3.4/library/unittest.html[unittest] framework
|
* https://pypi.python.org/pypi/pyflakes[pyflakes] via https://pypi.python.org/pypi/pytest-flakes[pytest-flakes]
|
||||||
* https://pypi.python.org/pypi/flake8/[flake8]
|
* https://pypi.python.org/pypi/pep8[pep8] via https://pypi.python.org/pypi/pytest-pep8[pytest-pep8]
|
||||||
|
* https://pypi.python.org/pypi/mccabe[mccabe] via https://pypi.python.org/pypi/pytest-mccabe[pytest-mccabe]
|
||||||
* https://github.com/GreenSteam/pep257/[pep257]
|
* https://github.com/GreenSteam/pep257/[pep257]
|
||||||
* http://pylint.org/[pylint]
|
* http://pylint.org/[pylint]
|
||||||
* https://pypi.python.org/pypi/pyroma/[pyroma]
|
* https://pypi.python.org/pypi/pyroma/[pyroma]
|
||||||
|
@ -28,7 +28,6 @@ include doc/qutebrowser.1.asciidoc
|
|||||||
prune tests
|
prune tests
|
||||||
exclude qutebrowser.rcc
|
exclude qutebrowser.rcc
|
||||||
exclude .coveragerc
|
exclude .coveragerc
|
||||||
exclude .flake8
|
|
||||||
exclude .pylintrc
|
exclude .pylintrc
|
||||||
exclude .eslintrc
|
exclude .eslintrc
|
||||||
exclude doc/help
|
exclude doc/help
|
||||||
|
@ -138,10 +138,10 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* Raphael Pierzina
|
* Raphael Pierzina
|
||||||
* Joel Torstensson
|
* Joel Torstensson
|
||||||
* Claude
|
* Claude
|
||||||
|
* Martin Tournoij
|
||||||
* Artur Shaik
|
* Artur Shaik
|
||||||
* Antoni Boucher
|
* Antoni Boucher
|
||||||
* ZDarian
|
* ZDarian
|
||||||
* Martin Tournoij
|
|
||||||
* Peter Vilim
|
* Peter Vilim
|
||||||
* John ShaggyTwoDope Jenkins
|
* John ShaggyTwoDope Jenkins
|
||||||
* Jimmy
|
* Jimmy
|
||||||
|
@ -642,13 +642,14 @@ Save open pages and quit.
|
|||||||
|
|
||||||
[[yank]]
|
[[yank]]
|
||||||
=== yank
|
=== yank
|
||||||
Syntax: +:yank [*--title*] [*--sel*]+
|
Syntax: +:yank [*--title*] [*--sel*] [*--domain*]+
|
||||||
|
|
||||||
Yank the current URL/title to the clipboard or primary selection.
|
Yank the current URL/title to the clipboard or primary selection.
|
||||||
|
|
||||||
==== optional arguments
|
==== optional arguments
|
||||||
* +*-t*+, +*--title*+: Yank the title instead of the URL.
|
* +*-t*+, +*--title*+: Yank the title instead of the URL.
|
||||||
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
* +*-s*+, +*--sel*+: Use the primary selection instead of the clipboard.
|
||||||
|
* +*-d*+, +*--domain*+: Yank only the scheme, domain, and port number.
|
||||||
|
|
||||||
[[zoom]]
|
[[zoom]]
|
||||||
=== zoom
|
=== zoom
|
||||||
@ -684,6 +685,7 @@ How many steps to zoom out.
|
|||||||
[options="header",width="75%",cols="25%,75%"]
|
[options="header",width="75%",cols="25%,75%"]
|
||||||
|==============
|
|==============
|
||||||
|Command|Description
|
|Command|Description
|
||||||
|
|<<clear-keychain,clear-keychain>>|Clear the currently entered key chain.
|
||||||
|<<command-accept,command-accept>>|Execute the command currently in the commandline.
|
|<<command-accept,command-accept>>|Execute the command currently in the commandline.
|
||||||
|<<command-history-next,command-history-next>>|Go forward in the commandline history.
|
|<<command-history-next,command-history-next>>|Go forward in the commandline history.
|
||||||
|<<command-history-prev,command-history-prev>>|Go back in the commandline history.
|
|<<command-history-prev,command-history-prev>>|Go back in the commandline history.
|
||||||
@ -738,6 +740,10 @@ How many steps to zoom out.
|
|||||||
|<<toggle-selection,toggle-selection>>|Toggle caret selection mode.
|
|<<toggle-selection,toggle-selection>>|Toggle caret selection mode.
|
||||||
|<<yank-selected,yank-selected>>|Yank the selected text to the clipboard or primary selection.
|
|<<yank-selected,yank-selected>>|Yank the selected text to the clipboard or primary selection.
|
||||||
|==============
|
|==============
|
||||||
|
[[clear-keychain]]
|
||||||
|
=== clear-keychain
|
||||||
|
Clear the currently entered key chain.
|
||||||
|
|
||||||
[[command-accept]]
|
[[command-accept]]
|
||||||
=== command-accept
|
=== command-accept
|
||||||
Execute the command currently in the commandline.
|
Execute the command currently in the commandline.
|
||||||
|
@ -99,7 +99,7 @@
|
|||||||
|<<tabs-select-on-remove,select-on-remove>>|Which tab to select when the focused tab is removed.
|
|<<tabs-select-on-remove,select-on-remove>>|Which tab to select when the focused tab is removed.
|
||||||
|<<tabs-new-tab-position,new-tab-position>>|How new tabs are positioned.
|
|<<tabs-new-tab-position,new-tab-position>>|How new tabs are positioned.
|
||||||
|<<tabs-new-tab-position-explicit,new-tab-position-explicit>>|How new tabs opened explicitly are positioned.
|
|<<tabs-new-tab-position-explicit,new-tab-position-explicit>>|How new tabs opened explicitly are positioned.
|
||||||
|<<tabs-last-close,last-close>>|Behaviour when the last tab is closed.
|
|<<tabs-last-close,last-close>>|Behavior when the last tab is closed.
|
||||||
|<<tabs-hide-auto,hide-auto>>|Hide the tab bar if only one tab is open.
|
|<<tabs-hide-auto,hide-auto>>|Hide the tab bar if only one tab is open.
|
||||||
|<<tabs-hide-always,hide-always>>|Always hide the tab bar.
|
|<<tabs-hide-always,hide-always>>|Always hide the tab bar.
|
||||||
|<<tabs-wrap,wrap>>|Whether to wrap when changing tabs.
|
|<<tabs-wrap,wrap>>|Whether to wrap when changing tabs.
|
||||||
@ -221,6 +221,7 @@
|
|||||||
|<<colors-downloads.bg.stop,downloads.bg.stop>>|Color gradient end for downloads.
|
|<<colors-downloads.bg.stop,downloads.bg.stop>>|Color gradient end for downloads.
|
||||||
|<<colors-downloads.bg.system,downloads.bg.system>>|Color gradient interpolation system for downloads.
|
|<<colors-downloads.bg.system,downloads.bg.system>>|Color gradient interpolation system for downloads.
|
||||||
|<<colors-downloads.bg.error,downloads.bg.error>>|Background color for downloads with errors.
|
|<<colors-downloads.bg.error,downloads.bg.error>>|Background color for downloads with errors.
|
||||||
|
|<<colors-webpage.bg,webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color)
|
||||||
|==============
|
|==============
|
||||||
|
|
||||||
.Quick reference for section ``fonts''
|
.Quick reference for section ``fonts''
|
||||||
@ -910,7 +911,7 @@ Default: +pass:[last]+
|
|||||||
|
|
||||||
[[tabs-last-close]]
|
[[tabs-last-close]]
|
||||||
=== last-close
|
=== last-close
|
||||||
Behaviour when the last tab is closed.
|
Behavior when the last tab is closed.
|
||||||
|
|
||||||
Valid values:
|
Valid values:
|
||||||
|
|
||||||
@ -1433,7 +1434,7 @@ Default: +pass:[true]+
|
|||||||
=== next-regexes
|
=== next-regexes
|
||||||
A comma-separated list of regexes to use for 'next' links.
|
A comma-separated list of regexes to use for 'next' links.
|
||||||
|
|
||||||
Default: +pass:[\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b]+
|
Default: +pass:[\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,\bcontinue\b]+
|
||||||
|
|
||||||
[[hints-prev-regexes]]
|
[[hints-prev-regexes]]
|
||||||
=== prev-regexes
|
=== prev-regexes
|
||||||
@ -1752,6 +1753,12 @@ Background color for downloads with errors.
|
|||||||
|
|
||||||
Default: +pass:[red]+
|
Default: +pass:[red]+
|
||||||
|
|
||||||
|
[[colors-webpage.bg]]
|
||||||
|
=== webpage.bg
|
||||||
|
Background color for webpages if unset (or empty to use the theme's color)
|
||||||
|
|
||||||
|
Default: +pass:[white]+
|
||||||
|
|
||||||
== fonts
|
== fonts
|
||||||
Fonts used for the UI, with optional style/weight/size.
|
Fonts used for the UI, with optional style/weight/size.
|
||||||
|
|
||||||
|
@ -610,7 +610,7 @@ class Quitter:
|
|||||||
# event loop, so we can shut down immediately.
|
# event loop, so we can shut down immediately.
|
||||||
self._shutdown(status)
|
self._shutdown(status)
|
||||||
|
|
||||||
def _shutdown(self, status): # noqa
|
def _shutdown(self, status):
|
||||||
"""Second stage of shutdown."""
|
"""Second stage of shutdown."""
|
||||||
log.destroy.debug("Stage 2 of shutting down...")
|
log.destroy.debug("Stage 2 of shutting down...")
|
||||||
if qApp is None:
|
if qApp is None:
|
||||||
|
@ -700,19 +700,28 @@ class CommandDispatcher:
|
|||||||
frame.scroll(dx, dy)
|
frame.scroll(dx, dy)
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||||
def yank(self, title=False, sel=False):
|
def yank(self, title=False, sel=False, domain=False):
|
||||||
"""Yank the current URL/title to the clipboard or primary selection.
|
"""Yank the current URL/title to the clipboard or primary selection.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sel: Use the primary selection instead of the clipboard.
|
sel: Use the primary selection instead of the clipboard.
|
||||||
title: Yank the title instead of the URL.
|
title: Yank the title instead of the URL.
|
||||||
|
domain: Yank only the scheme, domain, and port number.
|
||||||
"""
|
"""
|
||||||
clipboard = QApplication.clipboard()
|
clipboard = QApplication.clipboard()
|
||||||
if title:
|
if title:
|
||||||
s = self._tabbed_browser.page_title(self._current_index())
|
s = self._tabbed_browser.page_title(self._current_index())
|
||||||
|
what = 'title'
|
||||||
|
elif domain:
|
||||||
|
port = self._current_url().port()
|
||||||
|
s = '{}://{}{}'.format(self._current_url().scheme(),
|
||||||
|
self._current_url().host(),
|
||||||
|
':' + str(port) if port > -1 else '')
|
||||||
|
what = 'domain'
|
||||||
else:
|
else:
|
||||||
s = self._current_url().toString(
|
s = self._current_url().toString(
|
||||||
QUrl.FullyEncoded | QUrl.RemovePassword)
|
QUrl.FullyEncoded | QUrl.RemovePassword)
|
||||||
|
what = 'URL'
|
||||||
if sel and clipboard.supportsSelection():
|
if sel and clipboard.supportsSelection():
|
||||||
mode = QClipboard.Selection
|
mode = QClipboard.Selection
|
||||||
target = "primary selection"
|
target = "primary selection"
|
||||||
@ -721,8 +730,8 @@ class CommandDispatcher:
|
|||||||
target = "clipboard"
|
target = "clipboard"
|
||||||
log.misc.debug("Yanking to {}: '{}'".format(target, s))
|
log.misc.debug("Yanking to {}: '{}'".format(target, s))
|
||||||
clipboard.setText(s, mode)
|
clipboard.setText(s, mode)
|
||||||
what = 'Title' if title else 'URL'
|
message.info(self._win_id, "Yanked {} to {}: {}".format(
|
||||||
message.info(self._win_id, "{} yanked to {}".format(what, target))
|
what, target, s))
|
||||||
|
|
||||||
@cmdutils.register(instance='command-dispatcher', scope='window',
|
@cmdutils.register(instance='command-dispatcher', scope='window',
|
||||||
count='count')
|
count='count')
|
||||||
|
@ -23,14 +23,8 @@ import collections
|
|||||||
|
|
||||||
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
from PyQt5.QtCore import (pyqtSlot, pyqtSignal, PYQT_VERSION, QCoreApplication,
|
||||||
QUrl)
|
QUrl)
|
||||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QSslError
|
from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkReply, QSslError,
|
||||||
|
QSslSocket)
|
||||||
try:
|
|
||||||
from PyQt5.QtNetwork import QSslSocket
|
|
||||||
except ImportError:
|
|
||||||
SSL_AVAILABLE = False
|
|
||||||
else:
|
|
||||||
SSL_AVAILABLE = QSslSocket.supportsSsl()
|
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
|
from qutebrowser.utils import (message, log, usertypes, utils, objreg, qtutils,
|
||||||
@ -46,13 +40,12 @@ _proxy_auth_cache = {}
|
|||||||
|
|
||||||
def init():
|
def init():
|
||||||
"""Disable insecure SSL ciphers on old Qt versions."""
|
"""Disable insecure SSL ciphers on old Qt versions."""
|
||||||
if SSL_AVAILABLE:
|
if not qtutils.version_check('5.3.0'):
|
||||||
if not qtutils.version_check('5.3.0'):
|
# Disable weak SSL ciphers.
|
||||||
# Disable weak SSL ciphers.
|
# See https://codereview.qt-project.org/#/c/75943/
|
||||||
# See https://codereview.qt-project.org/#/c/75943/
|
good_ciphers = [c for c in QSslSocket.supportedCiphers()
|
||||||
good_ciphers = [c for c in QSslSocket.supportedCiphers()
|
if c.usedBits() >= 128]
|
||||||
if c.usedBits() >= 128]
|
QSslSocket.setDefaultCiphers(good_ciphers)
|
||||||
QSslSocket.setDefaultCiphers(good_ciphers)
|
|
||||||
|
|
||||||
|
|
||||||
class SslError(QSslError):
|
class SslError(QSslError):
|
||||||
@ -107,10 +100,9 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
}
|
}
|
||||||
self._set_cookiejar()
|
self._set_cookiejar()
|
||||||
self._set_cache()
|
self._set_cache()
|
||||||
if SSL_AVAILABLE:
|
self.sslErrors.connect(self.on_ssl_errors)
|
||||||
self.sslErrors.connect(self.on_ssl_errors)
|
self._rejected_ssl_errors = collections.defaultdict(list)
|
||||||
self._rejected_ssl_errors = collections.defaultdict(list)
|
self._accepted_ssl_errors = collections.defaultdict(list)
|
||||||
self._accepted_ssl_errors = collections.defaultdict(list)
|
|
||||||
self.authenticationRequired.connect(self.on_authentication_required)
|
self.authenticationRequired.connect(self.on_authentication_required)
|
||||||
self.proxyAuthenticationRequired.connect(
|
self.proxyAuthenticationRequired.connect(
|
||||||
self.on_proxy_authentication_required)
|
self.on_proxy_authentication_required)
|
||||||
@ -181,76 +173,67 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
request.deleteLater()
|
request.deleteLater()
|
||||||
self.shutting_down.emit()
|
self.shutting_down.emit()
|
||||||
|
|
||||||
if SSL_AVAILABLE: # noqa
|
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
|
||||||
@pyqtSlot('QNetworkReply*', 'QList<QSslError>')
|
def on_ssl_errors(self, reply, errors): # pragma: no mccabe
|
||||||
def on_ssl_errors(self, reply, errors):
|
"""Decide if SSL errors should be ignored or not.
|
||||||
"""Decide if SSL errors should be ignored or not.
|
|
||||||
|
|
||||||
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
This slot is called on SSL/TLS errors by the self.sslErrors signal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
reply: The QNetworkReply that is encountering the errors.
|
reply: The QNetworkReply that is encountering the errors.
|
||||||
errors: A list of errors.
|
errors: A list of errors.
|
||||||
"""
|
"""
|
||||||
errors = [SslError(e) for e in errors]
|
errors = [SslError(e) for e in errors]
|
||||||
ssl_strict = config.get('network', 'ssl-strict')
|
ssl_strict = config.get('network', 'ssl-strict')
|
||||||
if ssl_strict == 'ask':
|
if ssl_strict == 'ask':
|
||||||
try:
|
try:
|
||||||
host_tpl = urlutils.host_tuple(reply.url())
|
host_tpl = urlutils.host_tuple(reply.url())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
host_tpl = None
|
host_tpl = None
|
||||||
is_accepted = False
|
is_accepted = False
|
||||||
is_rejected = False
|
is_rejected = False
|
||||||
else:
|
else:
|
||||||
is_accepted = set(errors).issubset(
|
is_accepted = set(errors).issubset(
|
||||||
self._accepted_ssl_errors[host_tpl])
|
self._accepted_ssl_errors[host_tpl])
|
||||||
is_rejected = set(errors).issubset(
|
is_rejected = set(errors).issubset(
|
||||||
self._rejected_ssl_errors[host_tpl])
|
self._rejected_ssl_errors[host_tpl])
|
||||||
if is_accepted:
|
if is_accepted:
|
||||||
reply.ignoreSslErrors()
|
reply.ignoreSslErrors()
|
||||||
elif is_rejected:
|
elif is_rejected:
|
||||||
pass
|
|
||||||
else:
|
|
||||||
err_string = '\n'.join('- ' + err.errorString() for err in
|
|
||||||
errors)
|
|
||||||
answer = self._ask('SSL errors - continue?\n{}'.format(
|
|
||||||
err_string), mode=usertypes.PromptMode.yesno,
|
|
||||||
owner=reply)
|
|
||||||
if answer:
|
|
||||||
reply.ignoreSslErrors()
|
|
||||||
d = self._accepted_ssl_errors
|
|
||||||
else:
|
|
||||||
d = self._rejected_ssl_errors
|
|
||||||
if host_tpl is not None:
|
|
||||||
d[host_tpl] += errors
|
|
||||||
elif ssl_strict:
|
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for err in errors:
|
err_string = '\n'.join('- ' + err.errorString() for err in
|
||||||
# FIXME we might want to use warn here (non-fatal error)
|
errors)
|
||||||
# https://github.com/The-Compiler/qutebrowser/issues/114
|
answer = self._ask('SSL errors - continue?\n{}'.format(
|
||||||
message.error(self._win_id,
|
err_string), mode=usertypes.PromptMode.yesno,
|
||||||
'SSL error: {}'.format(err.errorString()))
|
owner=reply)
|
||||||
reply.ignoreSslErrors()
|
if answer:
|
||||||
|
reply.ignoreSslErrors()
|
||||||
|
d = self._accepted_ssl_errors
|
||||||
|
else:
|
||||||
|
d = self._rejected_ssl_errors
|
||||||
|
if host_tpl is not None:
|
||||||
|
d[host_tpl] += errors
|
||||||
|
elif ssl_strict:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for err in errors:
|
||||||
|
# FIXME we might want to use warn here (non-fatal error)
|
||||||
|
# https://github.com/The-Compiler/qutebrowser/issues/114
|
||||||
|
message.error(self._win_id,
|
||||||
|
'SSL error: {}'.format(err.errorString()))
|
||||||
|
reply.ignoreSslErrors()
|
||||||
|
|
||||||
@pyqtSlot(QUrl)
|
@pyqtSlot(QUrl)
|
||||||
def clear_rejected_ssl_errors(self, url):
|
def clear_rejected_ssl_errors(self, url):
|
||||||
"""Clear the rejected SSL errors on a reload.
|
"""Clear the rejected SSL errors on a reload.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The URL to remove.
|
url: The URL to remove.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
del self._rejected_ssl_errors[url]
|
del self._rejected_ssl_errors[url]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
|
||||||
else:
|
|
||||||
@pyqtSlot(QUrl)
|
|
||||||
def clear_rejected_ssl_errors(self, _url):
|
|
||||||
"""Clear the rejected SSL errors on a reload.
|
|
||||||
|
|
||||||
Does nothing because SSL is unavailable.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
@pyqtSlot('QNetworkReply', 'QAuthenticator')
|
||||||
@ -334,11 +317,7 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
A QNetworkReply.
|
A QNetworkReply.
|
||||||
"""
|
"""
|
||||||
scheme = req.url().scheme()
|
scheme = req.url().scheme()
|
||||||
if scheme == 'https' and not SSL_AVAILABLE:
|
if scheme in self._scheme_handlers:
|
||||||
return networkreply.ErrorNetworkReply(
|
|
||||||
req, "SSL is not supported by the installed Qt library!",
|
|
||||||
QNetworkReply.ProtocolUnknownError, self)
|
|
||||||
elif scheme in self._scheme_handlers:
|
|
||||||
return self._scheme_handlers[scheme].createRequest(
|
return self._scheme_handlers[scheme].createRequest(
|
||||||
op, req, outgoing_data)
|
op, req, outgoing_data)
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import configparser
|
|||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, QObject
|
from PyQt5.QtCore import pyqtSlot, QObject
|
||||||
from PyQt5.QtNetwork import QNetworkReply
|
from PyQt5.QtNetwork import QNetworkReply
|
||||||
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.browser.network import schemehandler, networkreply
|
from qutebrowser.browser.network import schemehandler, networkreply
|
||||||
@ -96,6 +97,12 @@ class JSBridge(QObject):
|
|||||||
@pyqtSlot(int, str, str, str)
|
@pyqtSlot(int, str, str, str)
|
||||||
def set(self, win_id, sectname, optname, value):
|
def set(self, win_id, sectname, optname, value):
|
||||||
"""Slot to set a setting from qute:settings."""
|
"""Slot to set a setting from qute:settings."""
|
||||||
|
# https://github.com/The-Compiler/qutebrowser/issues/727
|
||||||
|
if (sectname, optname == 'content', 'allow-javascript' and
|
||||||
|
value == 'false'):
|
||||||
|
message.error(win_id, "Refusing to disable javascript via "
|
||||||
|
"qute:settings as it needs javascript support.")
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
objreg.get('config').set('conf', sectname, optname, value)
|
objreg.get('config').set('conf', sectname, optname, value)
|
||||||
except (configexc.Error, configparser.Error) as e:
|
except (configexc.Error, configparser.Error) as e:
|
||||||
@ -172,10 +179,18 @@ def qute_help(win_id, request):
|
|||||||
|
|
||||||
def qute_settings(win_id, _request):
|
def qute_settings(win_id, _request):
|
||||||
"""Handler for qute:settings. View/change qute configuration."""
|
"""Handler for qute:settings. View/change qute configuration."""
|
||||||
config_getter = functools.partial(objreg.get('config').get, raw=True)
|
if not QWebSettings.globalSettings().testAttribute(
|
||||||
html = jinja.env.get_template('settings.html').render(
|
QWebSettings.JavascriptEnabled):
|
||||||
win_id=win_id, title='settings', config=configdata,
|
# https://github.com/The-Compiler/qutebrowser/issues/727
|
||||||
confget=config_getter)
|
template = jinja.env.get_template('pre.html')
|
||||||
|
html = template.render(
|
||||||
|
title='Failed to open qute:settings.',
|
||||||
|
content="qute:settings needs javascript enabled to work.")
|
||||||
|
else:
|
||||||
|
config_getter = functools.partial(objreg.get('config').get, raw=True)
|
||||||
|
html = jinja.env.get_template('settings.html').render(
|
||||||
|
win_id=win_id, title='settings', config=configdata,
|
||||||
|
confget=config_getter)
|
||||||
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
return html.encode('UTF-8', errors='xmlcharrefreplace')
|
||||||
|
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@ def javascript_escape(text):
|
|||||||
def get_child_frames(startframe):
|
def get_child_frames(startframe):
|
||||||
"""Get all children recursively of a given QWebFrame.
|
"""Get all children recursively of a given QWebFrame.
|
||||||
|
|
||||||
Loosly based on http://blog.nextgenetics.net/?e=64
|
Loosely based on http://blog.nextgenetics.net/?e=64
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
startframe: The QWebFrame to start with.
|
startframe: The QWebFrame to start with.
|
||||||
|
@ -109,7 +109,7 @@ class BrowserPage(QWebPage):
|
|||||||
def _handle_errorpage(self, info, errpage):
|
def _handle_errorpage(self, info, errpage):
|
||||||
"""Display an error page if needed.
|
"""Display an error page if needed.
|
||||||
|
|
||||||
Loosly based on Helpviewer/HelpBrowserWV.py from eric5
|
Loosely based on Helpviewer/HelpBrowserWV.py from eric5
|
||||||
(line 260 @ 5d937eb378dd)
|
(line 260 @ 5d937eb378dd)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -178,7 +178,7 @@ class BrowserPage(QWebPage):
|
|||||||
def _handle_multiple_files(self, info, files):
|
def _handle_multiple_files(self, info, files):
|
||||||
"""Handle uploading of multiple files.
|
"""Handle uploading of multiple files.
|
||||||
|
|
||||||
Loosly based on Helpviewer/HelpBrowserWV.py from eric5.
|
Loosely based on Helpviewer/HelpBrowserWV.py from eric5.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
info: The ChooseMultipleFilesExtensionOption instance.
|
info: The ChooseMultipleFilesExtensionOption instance.
|
||||||
|
@ -24,6 +24,7 @@ import itertools
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl
|
||||||
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtWidgets import QApplication, QStyleFactory
|
from PyQt5.QtWidgets import QApplication, QStyleFactory
|
||||||
from PyQt5.QtWebKit import QWebSettings
|
from PyQt5.QtWebKit import QWebSettings
|
||||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||||
@ -108,6 +109,7 @@ class WebView(QWebView):
|
|||||||
self.search_flags = 0
|
self.search_flags = 0
|
||||||
self.selection_enabled = False
|
self.selection_enabled = False
|
||||||
self.init_neighborlist()
|
self.init_neighborlist()
|
||||||
|
self._set_bg_color()
|
||||||
cfg = objreg.get('config')
|
cfg = objreg.get('config')
|
||||||
cfg.changed.connect(self.init_neighborlist)
|
cfg.changed.connect(self.init_neighborlist)
|
||||||
# For some reason, this signal doesn't get disconnected automatically
|
# For some reason, this signal doesn't get disconnected automatically
|
||||||
@ -161,7 +163,7 @@ class WebView(QWebView):
|
|||||||
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
return utils.get_repr(self, tab_id=self.tab_id, url=url)
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
# Explicitely releasing the page here seems to prevent some segfaults
|
# Explicitly releasing the page here seems to prevent some segfaults
|
||||||
# when quitting.
|
# when quitting.
|
||||||
# Copied from:
|
# Copied from:
|
||||||
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
|
# https://code.google.com/p/webscraping/source/browse/webkit.py#325
|
||||||
@ -181,6 +183,15 @@ class WebView(QWebView):
|
|||||||
self.load_status = val
|
self.load_status = val
|
||||||
self.load_status_changed.emit(val.name)
|
self.load_status_changed.emit(val.name)
|
||||||
|
|
||||||
|
def _set_bg_color(self):
|
||||||
|
"""Set the webpage background color as configured."""
|
||||||
|
col = config.get('colors', 'webpage.bg')
|
||||||
|
palette = self.palette()
|
||||||
|
if col is None:
|
||||||
|
col = self.style().standardPalette().color(QPalette.Base)
|
||||||
|
palette.setColor(QPalette.Base, col)
|
||||||
|
self.setPalette(palette)
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
@pyqtSlot(str, str)
|
||||||
def on_config_changed(self, section, option):
|
def on_config_changed(self, section, option):
|
||||||
"""Reinitialize the zoom neighborlist if related config changed."""
|
"""Reinitialize the zoom neighborlist if related config changed."""
|
||||||
@ -195,6 +206,8 @@ class WebView(QWebView):
|
|||||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||||
else:
|
else:
|
||||||
self.setContextMenuPolicy(Qt.DefaultContextMenu)
|
self.setContextMenuPolicy(Qt.DefaultContextMenu)
|
||||||
|
elif section == 'colors' and option == 'webpage.bg':
|
||||||
|
self._set_bg_color()
|
||||||
|
|
||||||
def init_neighborlist(self):
|
def init_neighborlist(self):
|
||||||
"""Initialize the _zoom neighborlist."""
|
"""Initialize the _zoom neighborlist."""
|
||||||
@ -607,6 +620,7 @@ class WebView(QWebView):
|
|||||||
"""Save a reference to the context menu so we can close it."""
|
"""Save a reference to the context menu so we can close it."""
|
||||||
menu = self.page().createStandardContextMenu()
|
menu = self.page().createStandardContextMenu()
|
||||||
self.shutting_down.connect(menu.close)
|
self.shutting_down.connect(menu.close)
|
||||||
|
modeman.instance(self.win_id).entered.connect(menu.close)
|
||||||
menu.exec_(e.globalPos())
|
menu.exec_(e.globalPos())
|
||||||
|
|
||||||
def wheelEvent(self, e):
|
def wheelEvent(self, e):
|
||||||
|
@ -420,7 +420,7 @@ class Command:
|
|||||||
value = self._type_conv[param.name](value)
|
value = self._type_conv[param.name](value)
|
||||||
return name, value
|
return name, value
|
||||||
|
|
||||||
def _get_call_args(self, win_id): # noqa
|
def _get_call_args(self, win_id):
|
||||||
"""Get arguments for a function call.
|
"""Get arguments for a function call.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -148,7 +148,7 @@ class _BaseUserscriptRunner(QObject):
|
|||||||
def run(self, cmd, *args, env=None):
|
def run(self, cmd, *args, env=None):
|
||||||
"""Run the userscript given.
|
"""Run the userscript given.
|
||||||
|
|
||||||
Needs to be overridden by superclasses.
|
Needs to be overridden by subclasses.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmd: The command to be started.
|
cmd: The command to be started.
|
||||||
@ -160,7 +160,7 @@ class _BaseUserscriptRunner(QObject):
|
|||||||
def on_proc_finished(self):
|
def on_proc_finished(self):
|
||||||
"""Called when the process has finished.
|
"""Called when the process has finished.
|
||||||
|
|
||||||
Needs to be overridden by superclasses.
|
Needs to be overridden by subclasses.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -272,7 +272,7 @@ class Completer(QObject):
|
|||||||
pattern = parts[self._cursor_part].strip()
|
pattern = parts[self._cursor_part].strip()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
pattern = ''
|
pattern = ''
|
||||||
self._model().set_pattern(pattern)
|
completion.set_pattern(pattern)
|
||||||
|
|
||||||
log.completion.debug(
|
log.completion.debug(
|
||||||
"New completion for {}: {}, with pattern '{}'".format(
|
"New completion for {}: {}, with pattern '{}'".format(
|
||||||
|
@ -201,8 +201,17 @@ class CompletionView(QTreeView):
|
|||||||
for i in range(model.rowCount()):
|
for i in range(model.rowCount()):
|
||||||
self.expand(model.index(i, 0))
|
self.expand(model.index(i, 0))
|
||||||
self._resize_columns()
|
self._resize_columns()
|
||||||
model.rowsRemoved.connect(self.maybe_resize_completion)
|
self.maybe_resize_completion()
|
||||||
model.rowsInserted.connect(self.maybe_resize_completion)
|
|
||||||
|
def set_pattern(self, pattern):
|
||||||
|
"""Set the completion pattern for the current model.
|
||||||
|
|
||||||
|
Called from on_update_completion().
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pattern: The filter pattern to set (what the user entered).
|
||||||
|
"""
|
||||||
|
self.model().set_pattern(pattern)
|
||||||
self.maybe_resize_completion()
|
self.maybe_resize_completion()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
@ -464,7 +464,7 @@ def data(readonly=False):
|
|||||||
|
|
||||||
('last-close',
|
('last-close',
|
||||||
SettingValue(typ.LastClose(), 'ignore'),
|
SettingValue(typ.LastClose(), 'ignore'),
|
||||||
"Behaviour when the last tab is closed."),
|
"Behavior when the last tab is closed."),
|
||||||
|
|
||||||
('hide-auto',
|
('hide-auto',
|
||||||
SettingValue(typ.Bool(), 'false'),
|
SettingValue(typ.Bool(), 'false'),
|
||||||
@ -740,7 +740,8 @@ def data(readonly=False):
|
|||||||
|
|
||||||
('next-regexes',
|
('next-regexes',
|
||||||
SettingValue(typ.RegexList(flags=re.IGNORECASE),
|
SettingValue(typ.RegexList(flags=re.IGNORECASE),
|
||||||
r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b'),
|
r'\bnext\b,\bmore\b,\bnewer\b,\b[>→≫]\b,\b(>>|»)\b,'
|
||||||
|
r'\bcontinue\b'),
|
||||||
"A comma-separated list of regexes to use for 'next' links."),
|
"A comma-separated list of regexes to use for 'next' links."),
|
||||||
|
|
||||||
('prev-regexes',
|
('prev-regexes',
|
||||||
@ -959,6 +960,11 @@ def data(readonly=False):
|
|||||||
SettingValue(typ.QtColor(), 'red'),
|
SettingValue(typ.QtColor(), 'red'),
|
||||||
"Background color for downloads with errors."),
|
"Background color for downloads with errors."),
|
||||||
|
|
||||||
|
('webpage.bg',
|
||||||
|
SettingValue(typ.QtColor(none_ok=True), 'white'),
|
||||||
|
"Background color for webpages if unset (or empty to use the "
|
||||||
|
"theme's color)"),
|
||||||
|
|
||||||
readonly=readonly
|
readonly=readonly
|
||||||
)),
|
)),
|
||||||
|
|
||||||
@ -1125,6 +1131,12 @@ KEY_SECTION_DESC = {
|
|||||||
""),
|
""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Keys which are similar to Return and should be bound by default where Return
|
||||||
|
# is bound.
|
||||||
|
|
||||||
|
RETURN_KEYS = ['<Return>', '<Ctrl-M>', '<Ctrl-J>', '<Shift-Return>', '<Enter>',
|
||||||
|
'<Shift-Enter>']
|
||||||
|
|
||||||
|
|
||||||
KEY_DATA = collections.OrderedDict([
|
KEY_DATA = collections.OrderedDict([
|
||||||
('!normal', collections.OrderedDict([
|
('!normal', collections.OrderedDict([
|
||||||
@ -1132,7 +1144,7 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
])),
|
])),
|
||||||
|
|
||||||
('normal', collections.OrderedDict([
|
('normal', collections.OrderedDict([
|
||||||
('search', ['<Escape>']),
|
('search ;; clear-keychain', ['<Escape>']),
|
||||||
('set-cmd-text -s :open', ['o']),
|
('set-cmd-text -s :open', ['o']),
|
||||||
('set-cmd-text :open {url}', ['go']),
|
('set-cmd-text :open {url}', ['go']),
|
||||||
('set-cmd-text -s :open -t', ['O']),
|
('set-cmd-text -s :open -t', ['O']),
|
||||||
@ -1193,6 +1205,8 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('yank -s', ['yY']),
|
('yank -s', ['yY']),
|
||||||
('yank -t', ['yt']),
|
('yank -t', ['yt']),
|
||||||
('yank -ts', ['yT']),
|
('yank -ts', ['yT']),
|
||||||
|
('yank -d', ['yd']),
|
||||||
|
('yank -ds', ['yD']),
|
||||||
('paste', ['pp']),
|
('paste', ['pp']),
|
||||||
('paste -s', ['pP']),
|
('paste -s', ['pP']),
|
||||||
('paste -t', ['Pp']),
|
('paste -t', ['Pp']),
|
||||||
@ -1244,8 +1258,8 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('stop', ['<Ctrl-s>']),
|
('stop', ['<Ctrl-s>']),
|
||||||
('print', ['<Ctrl-Alt-p>']),
|
('print', ['<Ctrl-Alt-p>']),
|
||||||
('open qute:settings', ['Ss']),
|
('open qute:settings', ['Ss']),
|
||||||
('follow-selected', ['<Return>']),
|
('follow-selected', RETURN_KEYS),
|
||||||
('follow-selected -t', ['<Ctrl-Return>']),
|
('follow-selected -t', ['<Ctrl-Return>', '<Ctrl-Enter>']),
|
||||||
])),
|
])),
|
||||||
|
|
||||||
('insert', collections.OrderedDict([
|
('insert', collections.OrderedDict([
|
||||||
@ -1253,7 +1267,7 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
])),
|
])),
|
||||||
|
|
||||||
('hint', collections.OrderedDict([
|
('hint', collections.OrderedDict([
|
||||||
('follow-hint', ['<Return>', '<Ctrl-M>', '<Ctrl-J>']),
|
('follow-hint', RETURN_KEYS),
|
||||||
('hint --rapid links tab-bg', ['<Ctrl-R>']),
|
('hint --rapid links tab-bg', ['<Ctrl-R>']),
|
||||||
('hint links', ['<Ctrl-F>']),
|
('hint links', ['<Ctrl-F>']),
|
||||||
('hint all tab-bg', ['<Ctrl-B>']),
|
('hint all tab-bg', ['<Ctrl-B>']),
|
||||||
@ -1266,13 +1280,11 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('command-history-next', ['<Ctrl-N>']),
|
('command-history-next', ['<Ctrl-N>']),
|
||||||
('completion-item-prev', ['<Shift-Tab>', '<Up>']),
|
('completion-item-prev', ['<Shift-Tab>', '<Up>']),
|
||||||
('completion-item-next', ['<Tab>', '<Down>']),
|
('completion-item-next', ['<Tab>', '<Down>']),
|
||||||
('command-accept', ['<Return>', '<Ctrl-J>', '<Shift-Return>',
|
('command-accept', RETURN_KEYS),
|
||||||
'<Ctrl-M>']),
|
|
||||||
])),
|
])),
|
||||||
|
|
||||||
('prompt', collections.OrderedDict([
|
('prompt', collections.OrderedDict([
|
||||||
('prompt-accept', ['<Return>', '<Ctrl-J>', '<Shift-Return>',
|
('prompt-accept', RETURN_KEYS),
|
||||||
'<Ctrl-M>']),
|
|
||||||
('prompt-yes', ['y']),
|
('prompt-yes', ['y']),
|
||||||
('prompt-no', ['n']),
|
('prompt-no', ['n']),
|
||||||
])),
|
])),
|
||||||
@ -1313,7 +1325,7 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('move-to-start-of-document', ['gg']),
|
('move-to-start-of-document', ['gg']),
|
||||||
('move-to-end-of-document', ['G']),
|
('move-to-end-of-document', ['G']),
|
||||||
('yank-selected -p', ['Y']),
|
('yank-selected -p', ['Y']),
|
||||||
('yank-selected', ['y', '<Return>', '<Ctrl-J>']),
|
('yank-selected', ['y'] + RETURN_KEYS),
|
||||||
('scroll left', ['H']),
|
('scroll left', ['H']),
|
||||||
('scroll down', ['J']),
|
('scroll down', ['J']),
|
||||||
('scroll up', ['K']),
|
('scroll up', ['K']),
|
||||||
@ -1330,8 +1342,8 @@ CHANGED_KEY_COMMANDS = [
|
|||||||
(re.compile(r'^download-page$'), r'download'),
|
(re.compile(r'^download-page$'), r'download'),
|
||||||
(re.compile(r'^cancel-download$'), r'download-cancel'),
|
(re.compile(r'^cancel-download$'), r'download-cancel'),
|
||||||
|
|
||||||
(re.compile(r'^search ""$'), r'search'),
|
(re.compile(r"""^search (''|"")$"""), r'search ;; clear-keychain'),
|
||||||
(re.compile(r"^search ''$"), r'search'),
|
(re.compile(r'^search$'), r'search ;; clear-keychain'),
|
||||||
|
|
||||||
(re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'),
|
(re.compile(r"""^set-cmd-text ['"](.*) ['"]$"""), r'set-cmd-text -s \1'),
|
||||||
(re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'),
|
(re.compile(r"""^set-cmd-text ['"](.*)['"]$"""), r'set-cmd-text \1'),
|
||||||
|
@ -693,7 +693,7 @@ class FontFamily(Font):
|
|||||||
|
|
||||||
class QtFont(Font):
|
class QtFont(Font):
|
||||||
|
|
||||||
"""A Font which gets converted to q QFont."""
|
"""A Font which gets converted to a QFont."""
|
||||||
|
|
||||||
def transform(self, value):
|
def transform(self, value):
|
||||||
if not value:
|
if not value:
|
||||||
@ -1312,7 +1312,7 @@ class SelectOnRemove(BaseType):
|
|||||||
|
|
||||||
class LastClose(BaseType):
|
class LastClose(BaseType):
|
||||||
|
|
||||||
"""Behaviour when the last tab is closed."""
|
"""Behavior when the last tab is closed."""
|
||||||
|
|
||||||
valid_values = ValidValues(('ignore', "Don't do anything."),
|
valid_values = ValidValues(('ignore', "Don't do anything."),
|
||||||
('blank', "Load a blank page."),
|
('blank', "Load a blank page."),
|
||||||
|
@ -23,7 +23,7 @@ import re
|
|||||||
import functools
|
import functools
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
|
||||||
|
|
||||||
from qutebrowser.config import config
|
from qutebrowser.config import config
|
||||||
from qutebrowser.utils import usertypes, log, utils, objreg
|
from qutebrowser.utils import usertypes, log, utils, objreg
|
||||||
@ -49,6 +49,8 @@ class BaseKeyParser(QObject):
|
|||||||
special: execute() was called via a special key binding
|
special: execute() was called via a special key binding
|
||||||
|
|
||||||
do_log: Whether to log keypresses or not.
|
do_log: Whether to log keypresses or not.
|
||||||
|
passthrough: Whether unbound keys should be passed through with this
|
||||||
|
handler.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
bindings: Bound key bindings
|
bindings: Bound key bindings
|
||||||
@ -69,6 +71,7 @@ class BaseKeyParser(QObject):
|
|||||||
|
|
||||||
keystring_updated = pyqtSignal(str)
|
keystring_updated = pyqtSignal(str)
|
||||||
do_log = True
|
do_log = True
|
||||||
|
passthrough = False
|
||||||
|
|
||||||
Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous',
|
Match = usertypes.enum('Match', ['partial', 'definitive', 'ambiguous',
|
||||||
'other', 'none'])
|
'other', 'none'])
|
||||||
@ -162,12 +165,6 @@ class BaseKeyParser(QObject):
|
|||||||
key = e.key()
|
key = e.key()
|
||||||
self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt))
|
self._debug_log("Got key: 0x{:x} / text: '{}'".format(key, txt))
|
||||||
|
|
||||||
if key == Qt.Key_Escape:
|
|
||||||
self._debug_log("Escape pressed, discarding '{}'.".format(
|
|
||||||
self._keystring))
|
|
||||||
self._keystring = ''
|
|
||||||
return self.Match.none
|
|
||||||
|
|
||||||
if len(txt) == 1:
|
if len(txt) == 1:
|
||||||
category = unicodedata.category(txt)
|
category = unicodedata.category(txt)
|
||||||
is_control_char = (category == 'Cc')
|
is_control_char = (category == 'Cc')
|
||||||
@ -198,7 +195,7 @@ class BaseKeyParser(QObject):
|
|||||||
self._keystring = ''
|
self._keystring = ''
|
||||||
self.execute(binding, self.Type.chain, count)
|
self.execute(binding, self.Type.chain, count)
|
||||||
elif match == self.Match.ambiguous:
|
elif match == self.Match.ambiguous:
|
||||||
self._debug_log("Ambigious match for '{}'.".format(
|
self._debug_log("Ambiguous match for '{}'.".format(
|
||||||
self._keystring))
|
self._keystring))
|
||||||
self._handle_ambiguous_match(binding, count)
|
self._handle_ambiguous_match(binding, count)
|
||||||
elif match == self.Match.partial:
|
elif match == self.Match.partial:
|
||||||
@ -303,6 +300,7 @@ class BaseKeyParser(QObject):
|
|||||||
True if the event was handled, False otherwise.
|
True if the event was handled, False otherwise.
|
||||||
"""
|
"""
|
||||||
handled = self._handle_special_key(e)
|
handled = self._handle_special_key(e)
|
||||||
|
|
||||||
if handled or not self._supports_chains:
|
if handled or not self._supports_chains:
|
||||||
return handled
|
return handled
|
||||||
match = self._handle_single_key(e)
|
match = self._handle_single_key(e)
|
||||||
@ -359,3 +357,9 @@ class BaseKeyParser(QObject):
|
|||||||
"defined!")
|
"defined!")
|
||||||
if mode == self._modename:
|
if mode == self._modename:
|
||||||
self.read_config()
|
self.read_config()
|
||||||
|
|
||||||
|
def clear_keystring(self):
|
||||||
|
"""Clear the currently entered key sequence."""
|
||||||
|
self._debug_log("discarding keystring '{}'.".format(self._keystring))
|
||||||
|
self._keystring = ''
|
||||||
|
self.keystring_updated.emit(self._keystring)
|
||||||
|
@ -55,6 +55,7 @@ class PassthroughKeyParser(CommandKeyParser):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
do_log = False
|
do_log = False
|
||||||
|
passthrough = True
|
||||||
|
|
||||||
def __init__(self, win_id, mode, parent=None, warn=True):
|
def __init__(self, win_id, mode, parent=None, warn=True):
|
||||||
"""Constructor.
|
"""Constructor.
|
||||||
|
@ -84,38 +84,30 @@ def init(win_id, parent):
|
|||||||
modeman.destroyed.connect(
|
modeman.destroyed.connect(
|
||||||
functools.partial(objreg.delete, 'keyparsers', scope='window',
|
functools.partial(objreg.delete, 'keyparsers', scope='window',
|
||||||
window=win_id))
|
window=win_id))
|
||||||
modeman.register(KM.normal, keyparsers[KM.normal].handle)
|
for mode, parser in keyparsers.items():
|
||||||
modeman.register(KM.hint, keyparsers[KM.hint].handle)
|
modeman.register(mode, parser)
|
||||||
modeman.register(KM.insert, keyparsers[KM.insert].handle, passthrough=True)
|
|
||||||
modeman.register(KM.passthrough, keyparsers[KM.passthrough].handle,
|
|
||||||
passthrough=True)
|
|
||||||
modeman.register(KM.command, keyparsers[KM.command].handle,
|
|
||||||
passthrough=True)
|
|
||||||
modeman.register(KM.prompt, keyparsers[KM.prompt].handle, passthrough=True)
|
|
||||||
modeman.register(KM.yesno, keyparsers[KM.yesno].handle)
|
|
||||||
modeman.register(KM.caret, keyparsers[KM.caret].handle, passthrough=True)
|
|
||||||
return modeman
|
return modeman
|
||||||
|
|
||||||
|
|
||||||
def _get_modeman(win_id):
|
def instance(win_id):
|
||||||
"""Get a modemanager object."""
|
"""Get a modemanager object."""
|
||||||
return objreg.get('mode-manager', scope='window', window=win_id)
|
return objreg.get('mode-manager', scope='window', window=win_id)
|
||||||
|
|
||||||
|
|
||||||
def enter(win_id, mode, reason=None, only_if_normal=False):
|
def enter(win_id, mode, reason=None, only_if_normal=False):
|
||||||
"""Enter the mode 'mode'."""
|
"""Enter the mode 'mode'."""
|
||||||
_get_modeman(win_id).enter(mode, reason, only_if_normal)
|
instance(win_id).enter(mode, reason, only_if_normal)
|
||||||
|
|
||||||
|
|
||||||
def leave(win_id, mode, reason=None):
|
def leave(win_id, mode, reason=None):
|
||||||
"""Leave the mode 'mode'."""
|
"""Leave the mode 'mode'."""
|
||||||
_get_modeman(win_id).leave(mode, reason)
|
instance(win_id).leave(mode, reason)
|
||||||
|
|
||||||
|
|
||||||
def maybe_leave(win_id, mode, reason=None):
|
def maybe_leave(win_id, mode, reason=None):
|
||||||
"""Convenience method to leave 'mode' without exceptions."""
|
"""Convenience method to leave 'mode' without exceptions."""
|
||||||
try:
|
try:
|
||||||
_get_modeman(win_id).leave(mode, reason)
|
instance(win_id).leave(mode, reason)
|
||||||
except NotInModeError as e:
|
except NotInModeError as e:
|
||||||
# This is rather likely to happen, so we only log to debug log.
|
# This is rather likely to happen, so we only log to debug log.
|
||||||
log.modes.debug("{} (leave reason: {})".format(e, reason))
|
log.modes.debug("{} (leave reason: {})".format(e, reason))
|
||||||
@ -126,10 +118,9 @@ class ModeManager(QObject):
|
|||||||
"""Manager for keyboard modes.
|
"""Manager for keyboard modes.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
passthrough: A list of modes in which to pass through events.
|
|
||||||
mode: The mode we're currently in.
|
mode: The mode we're currently in.
|
||||||
_win_id: The window ID of this ModeManager
|
_win_id: The window ID of this ModeManager
|
||||||
_handlers: A dictionary of modes and their handlers.
|
_parsers: A dictionary of modes and their keyparsers.
|
||||||
_forward_unbound_keys: If we should forward unbound keys.
|
_forward_unbound_keys: If we should forward unbound keys.
|
||||||
_releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was
|
_releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was
|
||||||
passed through, so the release event should as
|
passed through, so the release event should as
|
||||||
@ -151,8 +142,7 @@ class ModeManager(QObject):
|
|||||||
def __init__(self, win_id, parent=None):
|
def __init__(self, win_id, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._win_id = win_id
|
self._win_id = win_id
|
||||||
self._handlers = {}
|
self._parsers = {}
|
||||||
self.passthrough = []
|
|
||||||
self.mode = usertypes.KeyMode.normal
|
self.mode = usertypes.KeyMode.normal
|
||||||
self._releaseevents_to_pass = set()
|
self._releaseevents_to_pass = set()
|
||||||
self._forward_unbound_keys = config.get(
|
self._forward_unbound_keys = config.get(
|
||||||
@ -160,8 +150,7 @@ class ModeManager(QObject):
|
|||||||
objreg.get('config').changed.connect(self.set_forward_unbound_keys)
|
objreg.get('config').changed.connect(self.set_forward_unbound_keys)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return utils.get_repr(self, mode=self.mode,
|
return utils.get_repr(self, mode=self.mode)
|
||||||
passthrough=self.passthrough)
|
|
||||||
|
|
||||||
def _eventFilter_keypress(self, event):
|
def _eventFilter_keypress(self, event):
|
||||||
"""Handle filtering of KeyPress events.
|
"""Handle filtering of KeyPress events.
|
||||||
@ -173,11 +162,11 @@ class ModeManager(QObject):
|
|||||||
True if event should be filtered, False otherwise.
|
True if event should be filtered, False otherwise.
|
||||||
"""
|
"""
|
||||||
curmode = self.mode
|
curmode = self.mode
|
||||||
handler = self._handlers[curmode]
|
parser = self._parsers[curmode]
|
||||||
if curmode != usertypes.KeyMode.insert:
|
if curmode != usertypes.KeyMode.insert:
|
||||||
log.modes.debug("got keypress in mode {} - calling handler "
|
log.modes.debug("got keypress in mode {} - delegating to "
|
||||||
"{}".format(curmode, utils.qualname(handler)))
|
"{}".format(curmode, utils.qualname(parser)))
|
||||||
handled = handler(event) if handler is not None else False
|
handled = parser.handle(event)
|
||||||
|
|
||||||
is_non_alnum = bool(event.modifiers()) or not event.text().strip()
|
is_non_alnum = bool(event.modifiers()) or not event.text().strip()
|
||||||
focus_widget = QApplication.instance().focusWidget()
|
focus_widget = QApplication.instance().focusWidget()
|
||||||
@ -187,7 +176,7 @@ class ModeManager(QObject):
|
|||||||
filter_this = True
|
filter_this = True
|
||||||
elif is_tab and not isinstance(focus_widget, QWebView):
|
elif is_tab and not isinstance(focus_widget, QWebView):
|
||||||
filter_this = True
|
filter_this = True
|
||||||
elif (curmode in self.passthrough or
|
elif (parser.passthrough or
|
||||||
self._forward_unbound_keys == 'all' or
|
self._forward_unbound_keys == 'all' or
|
||||||
(self._forward_unbound_keys == 'auto' and is_non_alnum)):
|
(self._forward_unbound_keys == 'auto' and is_non_alnum)):
|
||||||
filter_this = False
|
filter_this = False
|
||||||
@ -202,8 +191,8 @@ class ModeManager(QObject):
|
|||||||
"passthrough: {}, is_non_alnum: {}, is_tab {} --> "
|
"passthrough: {}, is_non_alnum: {}, is_tab {} --> "
|
||||||
"filter: {} (focused: {!r})".format(
|
"filter: {} (focused: {!r})".format(
|
||||||
handled, self._forward_unbound_keys,
|
handled, self._forward_unbound_keys,
|
||||||
curmode in self.passthrough, is_non_alnum,
|
parser.passthrough, is_non_alnum, is_tab,
|
||||||
is_tab, filter_this, focus_widget))
|
filter_this, focus_widget))
|
||||||
return filter_this
|
return filter_this
|
||||||
|
|
||||||
def _eventFilter_keyrelease(self, event):
|
def _eventFilter_keyrelease(self, event):
|
||||||
@ -226,20 +215,16 @@ class ModeManager(QObject):
|
|||||||
log.modes.debug("filter: {}".format(filter_this))
|
log.modes.debug("filter: {}".format(filter_this))
|
||||||
return filter_this
|
return filter_this
|
||||||
|
|
||||||
def register(self, mode, handler, passthrough=False):
|
def register(self, mode, parser):
|
||||||
"""Register a new mode.
|
"""Register a new mode.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mode: The name of the mode.
|
mode: The name of the mode.
|
||||||
handler: Handler for keyPressEvents.
|
parser: The KeyParser which should be used.
|
||||||
passthrough: Whether to pass key bindings in this mode through to
|
|
||||||
the widgets.
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(mode, usertypes.KeyMode):
|
assert isinstance(mode, usertypes.KeyMode)
|
||||||
raise TypeError("Mode {} is no KeyMode member!".format(mode))
|
assert parser is not None
|
||||||
self._handlers[mode] = handler
|
self._parsers[mode] = parser
|
||||||
if passthrough:
|
|
||||||
self.passthrough.append(mode)
|
|
||||||
|
|
||||||
def enter(self, mode, reason=None, only_if_normal=False):
|
def enter(self, mode, reason=None, only_if_normal=False):
|
||||||
"""Enter a new mode.
|
"""Enter a new mode.
|
||||||
@ -253,8 +238,8 @@ class ModeManager(QObject):
|
|||||||
raise TypeError("Mode {} is no KeyMode member!".format(mode))
|
raise TypeError("Mode {} is no KeyMode member!".format(mode))
|
||||||
log.modes.debug("Entering mode {}{}".format(
|
log.modes.debug("Entering mode {}{}".format(
|
||||||
mode, '' if reason is None else ' (reason: {})'.format(reason)))
|
mode, '' if reason is None else ' (reason: {})'.format(reason)))
|
||||||
if mode not in self._handlers:
|
if mode not in self._parsers:
|
||||||
raise ValueError("No handler for mode {}".format(mode))
|
raise ValueError("No keyparser for mode {}".format(mode))
|
||||||
prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno)
|
prompt_modes = (usertypes.KeyMode.prompt, usertypes.KeyMode.yesno)
|
||||||
if self.mode == mode or (self.mode in prompt_modes and
|
if self.mode == mode or (self.mode in prompt_modes and
|
||||||
mode in prompt_modes):
|
mode in prompt_modes):
|
||||||
@ -332,3 +317,8 @@ class ModeManager(QObject):
|
|||||||
return self._eventFilter_keypress(event)
|
return self._eventFilter_keypress(event)
|
||||||
else:
|
else:
|
||||||
return self._eventFilter_keyrelease(event)
|
return self._eventFilter_keyrelease(event)
|
||||||
|
|
||||||
|
@cmdutils.register(instance='mode-manager', scope='window', hide=True)
|
||||||
|
def clear_keychain(self):
|
||||||
|
"""Clear the currently entered key chain."""
|
||||||
|
self._parsers[self.mode].clear_keystring()
|
||||||
|
@ -224,6 +224,8 @@ class CaretKeyParser(keyparser.CommandKeyParser):
|
|||||||
|
|
||||||
"""KeyParser for caret mode."""
|
"""KeyParser for caret mode."""
|
||||||
|
|
||||||
|
passthrough = True
|
||||||
|
|
||||||
def __init__(self, win_id, parent=None):
|
def __init__(self, win_id, parent=None):
|
||||||
super().__init__(win_id, parent, supports_count=True,
|
super().__init__(win_id, parent, supports_count=True,
|
||||||
supports_chains=True)
|
supports_chains=True)
|
||||||
|
@ -469,9 +469,9 @@ class StatusBar(QWidget):
|
|||||||
@pyqtSlot(usertypes.KeyMode)
|
@pyqtSlot(usertypes.KeyMode)
|
||||||
def on_mode_entered(self, mode):
|
def on_mode_entered(self, mode):
|
||||||
"""Mark certain modes in the commandline."""
|
"""Mark certain modes in the commandline."""
|
||||||
mode_manager = objreg.get('mode-manager', scope='window',
|
keyparsers = objreg.get('keyparsers', scope='window',
|
||||||
window=self._win_id)
|
window=self._win_id)
|
||||||
if mode in mode_manager.passthrough:
|
if keyparsers[mode].passthrough:
|
||||||
self._set_mode_text(mode.name)
|
self._set_mode_text(mode.name)
|
||||||
if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret):
|
if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.caret):
|
||||||
self.set_mode_active(mode, True)
|
self.set_mode_active(mode, True)
|
||||||
@ -479,10 +479,10 @@ class StatusBar(QWidget):
|
|||||||
@pyqtSlot(usertypes.KeyMode, usertypes.KeyMode)
|
@pyqtSlot(usertypes.KeyMode, usertypes.KeyMode)
|
||||||
def on_mode_left(self, old_mode, new_mode):
|
def on_mode_left(self, old_mode, new_mode):
|
||||||
"""Clear marked mode."""
|
"""Clear marked mode."""
|
||||||
mode_manager = objreg.get('mode-manager', scope='window',
|
keyparsers = objreg.get('keyparsers', scope='window',
|
||||||
window=self._win_id)
|
window=self._win_id)
|
||||||
if old_mode in mode_manager.passthrough:
|
if keyparsers[old_mode].passthrough:
|
||||||
if new_mode in mode_manager.passthrough:
|
if keyparsers[new_mode].passthrough:
|
||||||
self._set_mode_text(new_mode.name)
|
self._set_mode_text(new_mode.name)
|
||||||
else:
|
else:
|
||||||
self.txt.set_text(self.txt.Text.normal, '')
|
self.txt.set_text(self.txt.Text.normal, '')
|
||||||
|
@ -296,7 +296,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
newtab: True to open URL in a new tab, False otherwise.
|
newtab: True to open URL in a new tab, False otherwise.
|
||||||
"""
|
"""
|
||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
if newtab:
|
if newtab or self.currentWidget() is None:
|
||||||
self.tabopen(url, background=False)
|
self.tabopen(url, background=False)
|
||||||
else:
|
else:
|
||||||
self.currentWidget().openurl(url)
|
self.currentWidget().openurl(url)
|
||||||
@ -332,7 +332,7 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
the default settings we handle it like Chromium does:
|
the default settings we handle it like Chromium does:
|
||||||
- Tabs from clicked links etc. are to the right of
|
- Tabs from clicked links etc. are to the right of
|
||||||
the current.
|
the current.
|
||||||
- Explicitely opened tabs are at the very right.
|
- Explicitly opened tabs are at the very right.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
The opened WebView instance.
|
The opened WebView instance.
|
||||||
|
@ -183,7 +183,7 @@ class _CrashDialog(QDialog):
|
|||||||
def _init_text(self):
|
def _init_text(self):
|
||||||
"""Initialize the main text to be displayed on an exception.
|
"""Initialize the main text to be displayed on an exception.
|
||||||
|
|
||||||
Should be extended by superclass to set the actual text."""
|
Should be extended by subclasses to set the actual text."""
|
||||||
self._lbl = QLabel(wordWrap=True, openExternalLinks=True,
|
self._lbl = QLabel(wordWrap=True, openExternalLinks=True,
|
||||||
textInteractionFlags=Qt.LinksAccessibleByMouse)
|
textInteractionFlags=Qt.LinksAccessibleByMouse)
|
||||||
self._vbox.addWidget(self._lbl)
|
self._vbox.addWidget(self._lbl)
|
||||||
|
@ -190,7 +190,7 @@ class CrashHandler(QObject):
|
|||||||
objects = ""
|
objects = ""
|
||||||
return ExceptionInfo(pages, cmd_history, objects)
|
return ExceptionInfo(pages, cmd_history, objects)
|
||||||
|
|
||||||
def exception_hook(self, exctype, excvalue, tb): # noqa
|
def exception_hook(self, exctype, excvalue, tb):
|
||||||
"""Handle uncaught python exceptions.
|
"""Handle uncaught python exceptions.
|
||||||
|
|
||||||
It'll try very hard to write all open tabs to a file, and then exit
|
It'll try very hard to write all open tabs to a file, and then exit
|
||||||
|
@ -213,6 +213,19 @@ def check_qt_version():
|
|||||||
_die(text)
|
_die(text)
|
||||||
|
|
||||||
|
|
||||||
|
def check_ssl_support():
|
||||||
|
"""Check if SSL support is available."""
|
||||||
|
try:
|
||||||
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
|
except ImportError:
|
||||||
|
ok = False
|
||||||
|
else:
|
||||||
|
ok = QSslSocket.supportsSsl()
|
||||||
|
if not ok:
|
||||||
|
text = "Fatal error: Your Qt is built without SSL support."
|
||||||
|
_die(text)
|
||||||
|
|
||||||
|
|
||||||
def check_libraries():
|
def check_libraries():
|
||||||
"""Check if all needed Python libraries are installed."""
|
"""Check if all needed Python libraries are installed."""
|
||||||
modules = {
|
modules = {
|
||||||
@ -288,6 +301,7 @@ def earlyinit(args):
|
|||||||
# Now we can be sure QtCore is available, so we can print dialogs on
|
# Now we can be sure QtCore is available, so we can print dialogs on
|
||||||
# errors, so people only using the GUI notice them as well.
|
# errors, so people only using the GUI notice them as well.
|
||||||
check_qt_version()
|
check_qt_version()
|
||||||
|
check_ssl_support()
|
||||||
remove_inputhook()
|
remove_inputhook()
|
||||||
check_libraries()
|
check_libraries()
|
||||||
init_log(args)
|
init_log(args)
|
||||||
|
@ -77,7 +77,7 @@ class CommandLineEdit(QLineEdit):
|
|||||||
def __on_cursor_position_changed(self, _old, new):
|
def __on_cursor_position_changed(self, _old, new):
|
||||||
"""Prevent the cursor moving to the prompt.
|
"""Prevent the cursor moving to the prompt.
|
||||||
|
|
||||||
We use __ here to avoid accidentally overriding it in superclasses.
|
We use __ here to avoid accidentally overriding it in subclasses.
|
||||||
"""
|
"""
|
||||||
if new < self._promptlen:
|
if new < self._promptlen:
|
||||||
self.setCursorPosition(self._promptlen)
|
self.setCursorPosition(self._promptlen)
|
||||||
|
@ -55,7 +55,7 @@ class ShellLexer:
|
|||||||
self.token = ''
|
self.token = ''
|
||||||
self.state = ' '
|
self.state = ' '
|
||||||
|
|
||||||
def __iter__(self): # noqa
|
def __iter__(self): # pragma: no mccabe
|
||||||
"""Read a raw token from the input stream."""
|
"""Read a raw token from the input stream."""
|
||||||
# pylint: disable=too-many-branches,too-many-statements
|
# pylint: disable=too-many-branches,too-many-statements
|
||||||
self.reset()
|
self.reset()
|
||||||
|
@ -377,7 +377,7 @@ class RAMHandler(logging.Handler):
|
|||||||
|
|
||||||
"""Logging handler which keeps the messages in a deque in RAM.
|
"""Logging handler which keeps the messages in a deque in RAM.
|
||||||
|
|
||||||
Loosly based on logging.BufferingHandler which is unsuitable because it
|
Loosely based on logging.BufferingHandler which is unsuitable because it
|
||||||
uses a simple list rather than a deque.
|
uses a simple list rather than a deque.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -73,7 +73,7 @@ class NeighborList(collections.abc.Sequence):
|
|||||||
Args:
|
Args:
|
||||||
items: The list of items to iterate in.
|
items: The list of items to iterate in.
|
||||||
_default: The initially selected value.
|
_default: The initially selected value.
|
||||||
_mode: Behaviour when the first/last item is reached.
|
_mode: Behavior when the first/last item is reached.
|
||||||
Modes.block: Stay on the selected item
|
Modes.block: Stay on the selected item
|
||||||
Modes.wrap: Wrap around to the other end
|
Modes.wrap: Wrap around to the other end
|
||||||
Modes.exception: Raise an IndexError.
|
Modes.exception: Raise an IndexError.
|
||||||
@ -243,7 +243,7 @@ Completion = enum('Completion', ['command', 'section', 'option', 'value',
|
|||||||
|
|
||||||
# Exit statuses for errors. Needs to be an int for sys.exit.
|
# Exit statuses for errors. Needs to be an int for sys.exit.
|
||||||
Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init',
|
Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init',
|
||||||
'err_config', 'err_key_config'], is_int=True)
|
'err_config', 'err_key_config'], is_int=True, start=0)
|
||||||
|
|
||||||
|
|
||||||
class Question(QObject):
|
class Question(QObject):
|
||||||
|
@ -29,10 +29,7 @@ import collections
|
|||||||
|
|
||||||
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion
|
from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, qVersion
|
||||||
from PyQt5.QtWebKit import qWebKitVersion
|
from PyQt5.QtWebKit import qWebKitVersion
|
||||||
try:
|
from PyQt5.QtNetwork import QSslSocket
|
||||||
from PyQt5.QtNetwork import QSslSocket
|
|
||||||
except ImportError:
|
|
||||||
QSslSocket = None
|
|
||||||
|
|
||||||
import qutebrowser
|
import qutebrowser
|
||||||
from qutebrowser.utils import log, utils
|
from qutebrowser.utils import log, utils
|
||||||
@ -127,18 +124,8 @@ def _module_versions():
|
|||||||
A list of lines with version info.
|
A list of lines with version info.
|
||||||
"""
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
try:
|
|
||||||
import sipconfig # pylint: disable=import-error,unused-variable
|
|
||||||
except ImportError:
|
|
||||||
lines.append('SIP: ?')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
lines.append('SIP: {}'.format(
|
|
||||||
sipconfig.Configuration().sip_version_str))
|
|
||||||
except (AttributeError, TypeError):
|
|
||||||
log.misc.exception("Error while getting SIP version")
|
|
||||||
lines.append('SIP: ?')
|
|
||||||
modules = collections.OrderedDict([
|
modules = collections.OrderedDict([
|
||||||
|
('sip', ['SIP_VERSION_STR']),
|
||||||
('colorlog', []),
|
('colorlog', []),
|
||||||
('colorama', ['VERSION', '__version__']),
|
('colorama', ['VERSION', '__version__']),
|
||||||
('pypeg2', ['__version__']),
|
('pypeg2', ['__version__']),
|
||||||
@ -209,16 +196,13 @@ def version():
|
|||||||
'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()),
|
'Qt: {}, runtime: {}'.format(QT_VERSION_STR, qVersion()),
|
||||||
'PyQt: {}'.format(PYQT_VERSION_STR),
|
'PyQt: {}'.format(PYQT_VERSION_STR),
|
||||||
]
|
]
|
||||||
|
|
||||||
lines += _module_versions()
|
lines += _module_versions()
|
||||||
|
|
||||||
if QSslSocket is not None and QSslSocket.supportsSsl():
|
|
||||||
ssl_version = QSslSocket.sslLibraryVersionString()
|
|
||||||
else:
|
|
||||||
ssl_version = 'unavailable'
|
|
||||||
lines += [
|
lines += [
|
||||||
'Webkit: {}'.format(qWebKitVersion()),
|
'Webkit: {}'.format(qWebKitVersion()),
|
||||||
'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')),
|
'Harfbuzz: {}'.format(os.environ.get('QT_HARFBUZZ', 'system')),
|
||||||
'SSL: {}'.format(ssl_version),
|
'SSL: {}'.format(QSslSocket.sslLibraryVersionString()),
|
||||||
'',
|
'',
|
||||||
'Frozen: {}'.format(hasattr(sys, 'frozen')),
|
'Frozen: {}'.format(hasattr(sys, 'frozen')),
|
||||||
'Platform: {}, {}'.format(platform.platform(),
|
'Platform: {}, {}'.format(platform.platform(),
|
||||||
|
@ -35,9 +35,13 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir))
|
|||||||
from scripts import utils
|
from scripts import utils
|
||||||
|
|
||||||
|
|
||||||
def _py_files(target):
|
def _py_files():
|
||||||
"""Iterate over all python files and yield filenames."""
|
"""Iterate over all python files and yield filenames."""
|
||||||
for (dirpath, _dirnames, filenames) in os.walk(target):
|
for (dirpath, _dirnames, filenames) in os.walk('.'):
|
||||||
|
parts = dirpath.split(os.sep)
|
||||||
|
if len(parts) >= 2 and parts[1].startswith('.'):
|
||||||
|
# ignore hidden dirs
|
||||||
|
continue
|
||||||
for name in (e for e in filenames if e.endswith('.py')):
|
for name in (e for e in filenames if e.endswith('.py')):
|
||||||
yield os.path.join(dirpath, name)
|
yield os.path.join(dirpath, name)
|
||||||
|
|
||||||
@ -64,31 +68,32 @@ def check_git():
|
|||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
def check_spelling(target):
|
def check_spelling():
|
||||||
"""Check commonly misspelled words."""
|
"""Check commonly misspelled words."""
|
||||||
# Words which I often misspell
|
# Words which I often misspell
|
||||||
words = {'behaviour', 'quitted', 'likelyhood', 'sucessfully',
|
words = {'[Bb]ehaviour', '[Qq]uitted', 'Ll]ikelyhood', '[Ss]ucessfully',
|
||||||
'occur[^r .]', 'seperator', 'explicitely', 'resetted',
|
'[Oo]ccur[^r .]', '[Ss]eperator', '[Ee]xplicitely', '[Rr]esetted',
|
||||||
'auxillary', 'accidentaly', 'ambigious', 'loosly',
|
'[Aa]uxillary', '[Aa]ccidentaly', '[Aa]mbigious', '[Ll]oosly',
|
||||||
'initialis', 'convienence', 'similiar', 'uncommited',
|
'[Ii]nitialis', '[Cc]onvienence', '[Ss]imiliar', '[Uu]ncommited',
|
||||||
'reproducable'}
|
'[Rr]eproducable'}
|
||||||
|
|
||||||
# Words which look better when splitted, but might need some fine tuning.
|
# Words which look better when splitted, but might need some fine tuning.
|
||||||
words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence',
|
words |= {'[Kk]eystrings', '[Ww]ebelements', '[Mm]ouseevent',
|
||||||
'normalmode', 'eventloops', 'sizehint', 'statemachine',
|
'[Kk]eysequence', '[Nn]ormalmode', '[Ee]ventloops',
|
||||||
'metaobject', 'logrecord', 'filetype'}
|
'[Ss]izehint', '[Ss]tatemachine', '[Mm]etaobject',
|
||||||
|
'[Ll]ogrecord', '[Ff]iletype'}
|
||||||
|
|
||||||
seen = collections.defaultdict(list)
|
seen = collections.defaultdict(list)
|
||||||
try:
|
try:
|
||||||
ok = True
|
ok = True
|
||||||
for fn in _py_files(target):
|
for fn in _py_files():
|
||||||
with tokenize.open(fn) as f:
|
with tokenize.open(fn) as f:
|
||||||
if fn == os.path.join('scripts', 'misc_checks.py'):
|
if fn == os.path.join('.', 'scripts', 'misc_checks.py'):
|
||||||
continue
|
continue
|
||||||
for line in f:
|
for line in f:
|
||||||
for w in words:
|
for w in words:
|
||||||
if re.search(w, line) and fn not in seen[w]:
|
if re.search(w, line) and fn not in seen[w]:
|
||||||
print("Found '{}' in {}!".format(w, fn))
|
print('Found "{}" in {}!'.format(w, fn))
|
||||||
seen[w].append(fn)
|
seen[w].append(fn)
|
||||||
ok = False
|
ok = False
|
||||||
print()
|
print()
|
||||||
@ -98,11 +103,11 @@ def check_spelling(target):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_vcs_conflict(target):
|
def check_vcs_conflict():
|
||||||
"""Check VCS conflict markers."""
|
"""Check VCS conflict markers."""
|
||||||
try:
|
try:
|
||||||
ok = True
|
ok = True
|
||||||
for fn in _py_files(target):
|
for fn in _py_files():
|
||||||
with tokenize.open(fn) as f:
|
with tokenize.open(fn) as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if any(line.startswith(c * 7) for c in '<>=|'):
|
if any(line.startswith(c * 7) for c in '<>=|'):
|
||||||
@ -120,25 +125,14 @@ def main():
|
|||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('checker', choices=('git', 'vcs', 'spelling'),
|
parser.add_argument('checker', choices=('git', 'vcs', 'spelling'),
|
||||||
help="Which checker to run.")
|
help="Which checker to run.")
|
||||||
parser.add_argument('target', help="What to check", nargs='*')
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.checker == 'git':
|
if args.checker == 'git':
|
||||||
ok = check_git()
|
ok = check_git()
|
||||||
return 0 if ok else 1
|
|
||||||
elif args.checker == 'vcs':
|
elif args.checker == 'vcs':
|
||||||
is_ok = True
|
ok = check_vcs_conflict()
|
||||||
for target in args.target:
|
|
||||||
ok = check_vcs_conflict(target)
|
|
||||||
if not ok:
|
|
||||||
is_ok = False
|
|
||||||
return 0 if is_ok else 1
|
|
||||||
elif args.checker == 'spelling':
|
elif args.checker == 'spelling':
|
||||||
is_ok = True
|
ok = check_spelling()
|
||||||
for target in args.target:
|
return 0 if ok else 1
|
||||||
ok = check_spelling(target)
|
|
||||||
if not ok:
|
|
||||||
is_ok = False
|
|
||||||
return 0 if is_ok else 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
# 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 <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
"""Checker for CRLF in files."""
|
|
||||||
|
|
||||||
from pylint import interfaces, checkers
|
|
||||||
|
|
||||||
|
|
||||||
class CrlfChecker(checkers.BaseChecker):
|
|
||||||
|
|
||||||
"""Check for CRLF in files."""
|
|
||||||
|
|
||||||
__implements__ = interfaces.IRawChecker
|
|
||||||
|
|
||||||
name = 'crlf'
|
|
||||||
msgs = {'W9001': ('Uses CRLFs', 'crlf', None)}
|
|
||||||
options = ()
|
|
||||||
priority = -1
|
|
||||||
|
|
||||||
def process_module(self, node):
|
|
||||||
"""Process the module."""
|
|
||||||
for (lineno, line) in enumerate(node.file_stream):
|
|
||||||
if b'\r\n' in line:
|
|
||||||
self.add_message('crlf', line=lineno)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def register(linter):
|
|
||||||
"""Register the checker."""
|
|
||||||
linter.register_checker(CrlfChecker(linter))
|
|
@ -197,8 +197,10 @@ class TestKeyConfigParser:
|
|||||||
('download-page', 'download'),
|
('download-page', 'download'),
|
||||||
('cancel-download', 'download-cancel'),
|
('cancel-download', 'download-cancel'),
|
||||||
|
|
||||||
('search ""', 'search'),
|
('search ""', 'search ;; clear-keychain'),
|
||||||
("search ''", 'search'),
|
("search ''", 'search ;; clear-keychain'),
|
||||||
|
("search", 'search ;; clear-keychain'),
|
||||||
|
("search ;; foobar", None),
|
||||||
('search "foo"', None),
|
('search "foo"', None),
|
||||||
|
|
||||||
('set-cmd-text "foo bar"', 'set-cmd-text foo bar'),
|
('set-cmd-text "foo bar"', 'set-cmd-text foo bar'),
|
||||||
|
@ -80,8 +80,10 @@ class JSTester:
|
|||||||
def scroll_anchor(self, name):
|
def scroll_anchor(self, name):
|
||||||
"""Scroll the main frame to the given anchor."""
|
"""Scroll the main frame to the given anchor."""
|
||||||
page = self.webview.page()
|
page = self.webview.page()
|
||||||
with self._qtbot.waitSignal(page.scrollRequested):
|
old_pos = page.mainFrame().scrollPosition()
|
||||||
page.mainFrame().scrollToAnchor(name)
|
page.mainFrame().scrollToAnchor(name)
|
||||||
|
new_pos = page.mainFrame().scrollPosition()
|
||||||
|
assert old_pos != new_pos
|
||||||
|
|
||||||
def load(self, path, **kwargs):
|
def load(self, path, **kwargs):
|
||||||
"""Load and display the given test data.
|
"""Load and display the given test data.
|
||||||
@ -92,7 +94,7 @@ class JSTester:
|
|||||||
**kwargs: Passed to jinja's template.render().
|
**kwargs: Passed to jinja's template.render().
|
||||||
"""
|
"""
|
||||||
template = self._jinja_env.get_template(path)
|
template = self._jinja_env.get_template(path)
|
||||||
with self._qtbot.waitSignal(self.webview.loadFinished):
|
with self._qtbot.waitSignal(self.webview.loadFinished, raising=True):
|
||||||
self.webview.setHtml(template.render(**kwargs))
|
self.webview.setHtml(template.render(**kwargs))
|
||||||
|
|
||||||
def run_file(self, filename):
|
def run_file(self, filename):
|
||||||
|
@ -39,6 +39,7 @@ def progress_widget(qtbot, monkeypatch, config_stub):
|
|||||||
'qutebrowser.mainwindow.statusbar.progress.style.config', config_stub)
|
'qutebrowser.mainwindow.statusbar.progress.style.config', config_stub)
|
||||||
widget = Progress()
|
widget = Progress()
|
||||||
qtbot.add_widget(widget)
|
qtbot.add_widget(widget)
|
||||||
|
widget.setGeometry(200, 200, 200, 200)
|
||||||
assert not widget.isVisible()
|
assert not widget.isVisible()
|
||||||
assert not widget.isTextVisible()
|
assert not widget.isTextVisible()
|
||||||
return widget
|
return widget
|
||||||
|
@ -21,144 +21,256 @@
|
|||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
|
import re
|
||||||
import inspect
|
import inspect
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QLineEdit
|
from PyQt5.QtWidgets import QLineEdit, QApplication
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from qutebrowser.misc import readline
|
from qutebrowser.misc import readline
|
||||||
|
|
||||||
|
|
||||||
|
# Some functions aren't 100% readline compatible:
|
||||||
|
# https://github.com/The-Compiler/qutebrowser/issues/678
|
||||||
|
# Those are marked with fixme and have another value marked with '# wrong'
|
||||||
|
# which marks the current behavior.
|
||||||
|
|
||||||
|
fixme = pytest.mark.xfail(reason='readline compatibility - see #678')
|
||||||
|
|
||||||
|
|
||||||
|
class LineEdit(QLineEdit):
|
||||||
|
|
||||||
|
"""QLineEdit with some methods to make testing easier."""
|
||||||
|
|
||||||
|
def _get_index(self, haystack, needle):
|
||||||
|
"""Get the index of a char (needle) in a string (haystack).
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The position where needle was found, or None if it wasn't found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return haystack.index(needle)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_aug_text(self, text):
|
||||||
|
"""Set a text with </> markers for selected text and | as cursor."""
|
||||||
|
real_text = re.sub('[<>|]', '', text)
|
||||||
|
self.setText(real_text)
|
||||||
|
|
||||||
|
cursor_pos = self._get_index(text, '|')
|
||||||
|
sel_start_pos = self._get_index(text, '<')
|
||||||
|
sel_end_pos = self._get_index(text, '>')
|
||||||
|
|
||||||
|
if sel_start_pos is not None and sel_end_pos is None:
|
||||||
|
raise ValueError("< given without >!")
|
||||||
|
if sel_start_pos is None and sel_end_pos is not None:
|
||||||
|
raise ValueError("> given without <!")
|
||||||
|
|
||||||
|
if cursor_pos is not None:
|
||||||
|
if sel_start_pos is not None or sel_end_pos is not None:
|
||||||
|
raise ValueError("Can't mix | and </>!")
|
||||||
|
self.setCursorPosition(cursor_pos)
|
||||||
|
elif sel_start_pos is not None:
|
||||||
|
if sel_start_pos > sel_end_pos:
|
||||||
|
raise ValueError("< given after >!")
|
||||||
|
sel_len = sel_end_pos - sel_start_pos - 1
|
||||||
|
self.setSelection(sel_start_pos, sel_len)
|
||||||
|
|
||||||
|
def aug_text(self):
|
||||||
|
"""Get a text with </> markers for selected text and | as cursor."""
|
||||||
|
text = self.text()
|
||||||
|
chars = list(text)
|
||||||
|
cur_pos = self.cursorPosition()
|
||||||
|
assert cur_pos >= 0
|
||||||
|
chars.insert(cur_pos, '|')
|
||||||
|
if self.hasSelectedText():
|
||||||
|
selected_text = self.selectedText()
|
||||||
|
sel_start = self.selectionStart()
|
||||||
|
sel_end = sel_start + len(selected_text)
|
||||||
|
assert sel_start > 0
|
||||||
|
assert sel_end > 0
|
||||||
|
assert sel_end > sel_start
|
||||||
|
assert cur_pos == sel_end
|
||||||
|
assert text[sel_start:sel_end] == selected_text
|
||||||
|
chars.insert(sel_start, '<')
|
||||||
|
chars.insert(sel_end + 1, '>')
|
||||||
|
return ''.join(chars)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mocked_qapp(monkeypatch, stubs):
|
def lineedit(qtbot, monkeypatch):
|
||||||
"""Fixture that mocks readline.QApplication and returns it."""
|
"""Fixture providing a LineEdit."""
|
||||||
stub = stubs.FakeQApplication()
|
le = LineEdit()
|
||||||
monkeypatch.setattr('qutebrowser.misc.readline.QApplication', stub)
|
qtbot.add_widget(le)
|
||||||
return stub
|
monkeypatch.setattr(QApplication.instance(), 'focusWidget', lambda: le)
|
||||||
|
return le
|
||||||
|
|
||||||
|
|
||||||
class TestNoneWidget:
|
@pytest.fixture
|
||||||
|
def bridge():
|
||||||
"""Test if there are no exceptions when the widget is None."""
|
"""Fixture providing a ReadlineBridge."""
|
||||||
|
return readline.ReadlineBridge()
|
||||||
def test_none(self, mocked_qapp):
|
|
||||||
"""Call each rl_* method with a None focusWidget."""
|
|
||||||
self.bridge = readline.ReadlineBridge()
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=None)
|
|
||||||
for name, method in inspect.getmembers(self.bridge, inspect.ismethod):
|
|
||||||
if name.startswith('rl_'):
|
|
||||||
method()
|
|
||||||
|
|
||||||
|
|
||||||
class TestReadlineBridgeTest:
|
def test_none(bridge, qtbot):
|
||||||
|
"""Call each rl_* method with a None focusWidget."""
|
||||||
|
assert QApplication.instance().focusWidget() is None
|
||||||
|
for name, method in inspect.getmembers(bridge, inspect.ismethod):
|
||||||
|
if name.startswith('rl_'):
|
||||||
|
method()
|
||||||
|
|
||||||
"""Tests for readline bridge."""
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.mark.parametrize('text, expected', [('f<oo>bar', 'fo|obar'),
|
||||||
def setup(self):
|
('|foobar', '|foobar')])
|
||||||
self.qle = mock.Mock()
|
def test_rl_backward_char(text, expected, lineedit, bridge):
|
||||||
self.qle.__class__ = QLineEdit
|
"""Test rl_backward_char."""
|
||||||
self.bridge = readline.ReadlineBridge()
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_backward_char()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def _set_selected_text(self, text):
|
|
||||||
"""Set the value the fake QLineEdit should return for selectedText."""
|
|
||||||
self.qle.configure_mock(**{'selectedText.return_value': text})
|
|
||||||
|
|
||||||
def test_rl_backward_char(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('f<oo>bar', 'foob|ar'),
|
||||||
"""Test rl_backward_char."""
|
('foobar|', 'foobar|')])
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
def test_rl_forward_char(text, expected, lineedit, bridge):
|
||||||
self.bridge.rl_backward_char()
|
"""Test rl_forward_char."""
|
||||||
self.qle.cursorBackward.assert_called_with(False)
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_forward_char()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def test_rl_forward_char(self, mocked_qapp):
|
|
||||||
"""Test rl_forward_char."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_forward_char()
|
|
||||||
self.qle.cursorForward.assert_called_with(False)
|
|
||||||
|
|
||||||
def test_rl_backward_word(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('one <tw>o', 'one |two'),
|
||||||
"""Test rl_backward_word."""
|
('<one >two', '|one two'),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('|one two', '|one two')])
|
||||||
self.bridge.rl_backward_word()
|
def test_rl_backward_word(text, expected, lineedit, bridge):
|
||||||
self.qle.cursorWordBackward.assert_called_with(False)
|
"""Test rl_backward_word."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_backward_word()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def test_rl_forward_word(self, mocked_qapp):
|
|
||||||
"""Test rl_forward_word."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_forward_word()
|
|
||||||
self.qle.cursorWordForward.assert_called_with(False)
|
|
||||||
|
|
||||||
def test_rl_beginning_of_line(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [
|
||||||
"""Test rl_beginning_of_line."""
|
fixme(('<o>ne two', 'one| two')),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('<o>ne two', 'one |two'), # wrong
|
||||||
self.bridge.rl_beginning_of_line()
|
fixme(('<one> two', 'one two|')),
|
||||||
self.qle.home.assert_called_with(False)
|
('<one> two', 'one |two'), # wrong
|
||||||
|
('one t<wo>', 'one two|')
|
||||||
|
])
|
||||||
|
def test_rl_forward_word(text, expected, lineedit, bridge):
|
||||||
|
"""Test rl_forward_word."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_forward_word()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
def test_rl_end_of_line(self, mocked_qapp):
|
|
||||||
"""Test rl_end_of_line."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_end_of_line()
|
|
||||||
self.qle.end.assert_called_with(False)
|
|
||||||
|
|
||||||
def test_rl_delete_char(self, mocked_qapp):
|
def test_rl_beginning_of_line(lineedit, bridge):
|
||||||
"""Test rl_delete_char."""
|
"""Test rl_beginning_of_line."""
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
lineedit.set_aug_text('f<oo>bar')
|
||||||
self.bridge.rl_delete_char()
|
bridge.rl_beginning_of_line()
|
||||||
self.qle.del_.assert_called_with()
|
assert lineedit.aug_text() == '|foobar'
|
||||||
|
|
||||||
def test_rl_backward_delete_char(self, mocked_qapp):
|
|
||||||
"""Test rl_backward_delete_char."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self.bridge.rl_backward_delete_char()
|
|
||||||
self.qle.backspace.assert_called_with()
|
|
||||||
|
|
||||||
def test_rl_unix_line_discard(self, mocked_qapp):
|
def test_rl_end_of_line(lineedit, bridge):
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
"""Test rl_end_of_line."""
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
lineedit.set_aug_text('f<oo>bar')
|
||||||
self._set_selected_text("delete test")
|
bridge.rl_end_of_line()
|
||||||
self.bridge.rl_unix_line_discard()
|
assert lineedit.aug_text() == 'foobar|'
|
||||||
self.qle.home.assert_called_with(True)
|
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
|
||||||
self.qle.del_.assert_called_with()
|
|
||||||
self.bridge.rl_yank()
|
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_kill_line(self, mocked_qapp):
|
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self._set_selected_text("delete test")
|
|
||||||
self.bridge.rl_kill_line()
|
|
||||||
self.qle.end.assert_called_with(True)
|
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
|
||||||
self.qle.del_.assert_called_with()
|
|
||||||
self.bridge.rl_yank()
|
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_unix_word_rubout(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('foo|bar', 'foo|ar'),
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
('foobar|', 'foobar|'),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('|foobar', '|oobar'),
|
||||||
self._set_selected_text("delete test")
|
('f<oo>bar', 'f|bar')])
|
||||||
self.bridge.rl_unix_word_rubout()
|
def test_rl_delete_char(text, expected, lineedit, bridge):
|
||||||
self.qle.cursorWordBackward.assert_called_with(True)
|
"""Test rl_delete_char."""
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
lineedit.set_aug_text(text)
|
||||||
self.qle.del_.assert_called_with()
|
bridge.rl_delete_char()
|
||||||
self.bridge.rl_yank()
|
assert lineedit.aug_text() == expected
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_kill_word(self, mocked_qapp):
|
|
||||||
"""Set a selected text, delete it, see if it comes back with yank."""
|
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
|
||||||
self._set_selected_text("delete test")
|
|
||||||
self.bridge.rl_kill_word()
|
|
||||||
self.qle.cursorWordForward.assert_called_with(True)
|
|
||||||
assert self.bridge._deleted[self.qle] == "delete test"
|
|
||||||
self.qle.del_.assert_called_with()
|
|
||||||
self.bridge.rl_yank()
|
|
||||||
self.qle.insert.assert_called_with("delete test")
|
|
||||||
|
|
||||||
def test_rl_yank_no_text(self, mocked_qapp):
|
@pytest.mark.parametrize('text, expected', [('foo|bar', 'fo|bar'),
|
||||||
"""Test yank without having deleted anything."""
|
('foobar|', 'fooba|'),
|
||||||
mocked_qapp.focusWidget = mock.Mock(return_value=self.qle)
|
('|foobar', '|foobar'),
|
||||||
self.bridge.rl_yank()
|
('f<oo>bar', 'f|bar')])
|
||||||
assert not self.qle.insert.called
|
def test_rl_backward_delete_char(text, expected, lineedit, bridge):
|
||||||
|
"""Test rl_backward_delete_char."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_backward_delete_char()
|
||||||
|
assert lineedit.aug_text() == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
('delete this| test', 'delete this', '| test'),
|
||||||
|
fixme(('delete <this> test', 'delete this', '| test')),
|
||||||
|
('delete <this> test', 'delete ', '|this test'), # wrong
|
||||||
|
fixme(('f<oo>bar', 'foo', '|bar')),
|
||||||
|
('f<oo>bar', 'f', '|oobar'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_unix_line_discard(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete from the cursor to the beginning of the line and yank back."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_unix_line_discard()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
('test |delete this', 'delete this', 'test |'),
|
||||||
|
fixme(('<test >delete this', 'test delete this', 'test |')),
|
||||||
|
('<test >delete this', 'test delete this', '|'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_kill_line(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete from the cursor to the end of line and yank back."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_kill_line()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
('test delete|foobar', 'delete', 'test |foobar'),
|
||||||
|
('test delete |foobar', 'delete ', 'test |foobar'),
|
||||||
|
fixme(('test del<ete>foobar', 'delete', 'test |foobar')),
|
||||||
|
('test del<ete >foobar', 'del', 'test |ete foobar'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_unix_word_rubout(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete to word beginning and see if it comes back with yank."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_unix_word_rubout()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('text, deleted, rest', [
|
||||||
|
fixme(('test foobar| delete', ' delete', 'test foobar|')),
|
||||||
|
('test foobar| delete', ' ', 'test foobar|delete'), # wrong
|
||||||
|
fixme(('test foo|delete bar', 'delete', 'test foo| bar')),
|
||||||
|
('test foo|delete bar', 'delete ', 'test foo|bar'), # wrong
|
||||||
|
fixme(('test foo<bar> delete', ' delete', 'test foobar|')),
|
||||||
|
('test foo<bar>delete', 'bardelete', 'test foo|'), # wrong
|
||||||
|
])
|
||||||
|
def test_rl_kill_word(lineedit, bridge, text, deleted, rest):
|
||||||
|
"""Delete to word end and see if it comes back with yank."""
|
||||||
|
lineedit.set_aug_text(text)
|
||||||
|
bridge.rl_kill_word()
|
||||||
|
assert bridge._deleted[lineedit] == deleted
|
||||||
|
assert lineedit.aug_text() == rest
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == deleted + '|'
|
||||||
|
|
||||||
|
|
||||||
|
def test_rl_yank_no_text(lineedit, bridge):
|
||||||
|
"""Test yank without having deleted anything."""
|
||||||
|
lineedit.clear()
|
||||||
|
bridge.rl_yank()
|
||||||
|
assert lineedit.aug_text() == '|'
|
||||||
|
@ -27,7 +27,6 @@ import itertools
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from PyQt5.QtCore import qWarning
|
|
||||||
|
|
||||||
from qutebrowser.utils import log
|
from qutebrowser.utils import log
|
||||||
|
|
||||||
@ -214,7 +213,7 @@ class TestInitLog:
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def args(self):
|
def args(self):
|
||||||
"""Fixture providing an argparse namespace."""
|
"""Fixture providing an argparse namespace for init_log."""
|
||||||
return argparse.Namespace(debug=True, loglevel=logging.DEBUG,
|
return argparse.Namespace(debug=True, loglevel=logging.DEBUG,
|
||||||
color=True, loglines=10, logfilter="")
|
color=True, loglines=10, logfilter="")
|
||||||
|
|
||||||
@ -230,33 +229,37 @@ class TestHideQtWarning:
|
|||||||
|
|
||||||
"""Tests for hide_qt_warning/QtWarningFilter."""
|
"""Tests for hide_qt_warning/QtWarningFilter."""
|
||||||
|
|
||||||
def test_unfiltered(self, caplog):
|
@pytest.fixture()
|
||||||
|
def logger(self):
|
||||||
|
return logging.getLogger('qt-tests')
|
||||||
|
|
||||||
|
def test_unfiltered(self, logger, caplog):
|
||||||
"""Test a message which is not filtered."""
|
"""Test a message which is not filtered."""
|
||||||
with log.hide_qt_warning("World", logger='qt-tests'):
|
with log.hide_qt_warning("World", logger='qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
||||||
qWarning("Hello World")
|
logger.warning("Hello World")
|
||||||
assert len(caplog.records()) == 1
|
assert len(caplog.records()) == 1
|
||||||
record = caplog.records()[0]
|
record = caplog.records()[0]
|
||||||
assert record.levelname == 'WARNING'
|
assert record.levelname == 'WARNING'
|
||||||
assert record.message == "Hello World"
|
assert record.message == "Hello World"
|
||||||
|
|
||||||
def test_filtered_exact(self, caplog):
|
def test_filtered_exact(self, logger, caplog):
|
||||||
"""Test a message which is filtered (exact match)."""
|
"""Test a message which is filtered (exact match)."""
|
||||||
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
||||||
qWarning("Hello")
|
logger.warning("Hello")
|
||||||
assert not caplog.records()
|
assert not caplog.records()
|
||||||
|
|
||||||
def test_filtered_start(self, caplog):
|
def test_filtered_start(self, logger, caplog):
|
||||||
"""Test a message which is filtered (match at line start)."""
|
"""Test a message which is filtered (match at line start)."""
|
||||||
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
||||||
qWarning("Hello World")
|
logger.warning("Hello World")
|
||||||
assert not caplog.records()
|
assert not caplog.records()
|
||||||
|
|
||||||
def test_filtered_whitespace(self, caplog):
|
def test_filtered_whitespace(self, logger, caplog):
|
||||||
"""Test a message which is filtered (match with whitespace)."""
|
"""Test a message which is filtered (match with whitespace)."""
|
||||||
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
||||||
qWarning(" Hello World ")
|
logger.warning(" Hello World ")
|
||||||
assert not caplog.records()
|
assert not caplog.records()
|
||||||
|
@ -54,3 +54,9 @@ def test_start():
|
|||||||
e = usertypes.enum('Enum', ['three', 'four'], start=3)
|
e = usertypes.enum('Enum', ['three', 'four'], start=3)
|
||||||
assert e.three.value == 3
|
assert e.three.value == 3
|
||||||
assert e.four.value == 4
|
assert e.four.value == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_exit():
|
||||||
|
"""Make sure the exit status enum is correct."""
|
||||||
|
assert usertypes.Exit.ok == 0
|
||||||
|
assert usertypes.Exit.reserved == 1
|
||||||
|
66
tox.ini
66
tox.ini
@ -4,7 +4,7 @@
|
|||||||
# and then run "tox" from this directory.
|
# and then run "tox" from this directory.
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = unittests,misc,pep257,flake8,pylint,pyroma,check-manifest
|
envlist = unittests,misc,pep257,pyflakes,pep8,mccabe,pylint,pyroma,check-manifest
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
basepython = python3
|
basepython = python3
|
||||||
@ -20,11 +20,11 @@ setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/pl
|
|||||||
passenv = DISPLAY XAUTHORITY HOME
|
passenv = DISPLAY XAUTHORITY HOME
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
py==1.4.27
|
py==1.4.28
|
||||||
pytest==2.7.1
|
pytest==2.7.1
|
||||||
pytest-capturelog==0.7
|
pytest-capturelog==0.7
|
||||||
pytest-qt==1.3.0
|
pytest-qt==1.4.0
|
||||||
pytest-mock==0.5
|
pytest-mock==0.6.0
|
||||||
pytest-html==1.3.1
|
pytest-html==1.3.1
|
||||||
# We don't use {[testenv:mkvenv]commands} here because that seems to be broken
|
# We don't use {[testenv:mkvenv]commands} here because that seems to be broken
|
||||||
# on Ubuntu Trusty.
|
# on Ubuntu Trusty.
|
||||||
@ -46,8 +46,8 @@ commands =
|
|||||||
[testenv:misc]
|
[testenv:misc]
|
||||||
commands =
|
commands =
|
||||||
{envpython} scripts/misc_checks.py git
|
{envpython} scripts/misc_checks.py git
|
||||||
{envpython} scripts/misc_checks.py vcs qutebrowser scripts tests
|
{envpython} scripts/misc_checks.py vcs
|
||||||
{envpython} scripts/misc_checks.py spelling qutebrowser scripts tests
|
{envpython} scripts/misc_checks.py spelling
|
||||||
|
|
||||||
[testenv:pylint]
|
[testenv:pylint]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
@ -61,8 +61,8 @@ deps =
|
|||||||
six==1.9.0
|
six==1.9.0
|
||||||
commands =
|
commands =
|
||||||
{[testenv:mkvenv]commands}
|
{[testenv:mkvenv]commands}
|
||||||
{envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no
|
{envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF
|
||||||
{envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no
|
{envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF
|
||||||
|
|
||||||
[testenv:pep257]
|
[testenv:pep257]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
@ -74,16 +74,40 @@ passenv = LANG
|
|||||||
# D402: First line should not be function's signature (false-positives)
|
# D402: First line should not be function's signature (false-positives)
|
||||||
commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_content_disposition).*\.py'
|
commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_content_disposition).*\.py'
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:pyflakes]
|
||||||
skip_install = true
|
# https://github.com/fschulze/pytest-flakes/issues/6
|
||||||
|
setenv = LANG=en_US.UTF-8
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
pyflakes==0.8.1
|
py==1.4.28
|
||||||
pep8==1.5.7 # rq.filter: <1.6.0
|
pytest==2.7.1
|
||||||
flake8==2.4.0
|
pyflakes==0.9.0
|
||||||
|
pytest-flakes==1.0.0
|
||||||
commands =
|
commands =
|
||||||
{[testenv:mkvenv]commands}
|
{[testenv:mkvenv]commands}
|
||||||
{envdir}/bin/flake8 scripts tests qutebrowser --config=.flake8
|
{envpython} -m py.test -q --flakes -m flakes
|
||||||
|
|
||||||
|
[testenv:pep8]
|
||||||
|
deps =
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
py==1.4.28
|
||||||
|
pytest==2.7.1
|
||||||
|
pep8==1.6.2
|
||||||
|
pytest-pep8==1.0.6
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envpython} -m py.test -q --pep8 -m pep8
|
||||||
|
|
||||||
|
[testenv:mccabe]
|
||||||
|
deps =
|
||||||
|
-r{toxinidir}/requirements.txt
|
||||||
|
py==1.4.28
|
||||||
|
pytest==2.7.1
|
||||||
|
mccabe==0.3
|
||||||
|
pytest-mccabe==0.1
|
||||||
|
commands =
|
||||||
|
{[testenv:mkvenv]commands}
|
||||||
|
{envpython} -m py.test -q --mccabe -m mccabe
|
||||||
|
|
||||||
[testenv:pyroma]
|
[testenv:pyroma]
|
||||||
skip_install = true
|
skip_install = true
|
||||||
@ -129,3 +153,17 @@ commands =
|
|||||||
norecursedirs = .tox .venv
|
norecursedirs = .tox .venv
|
||||||
markers =
|
markers =
|
||||||
gui: Tests using the GUI (e.g. spawning widgets)
|
gui: Tests using the GUI (e.g. spawning widgets)
|
||||||
|
flakes-ignore =
|
||||||
|
UnusedImport
|
||||||
|
UnusedVariable
|
||||||
|
resources.py ALL
|
||||||
|
pep8ignore =
|
||||||
|
E265 # Block comment should start with '#'
|
||||||
|
E501 # Line too long
|
||||||
|
E402 # module level import not at top of file
|
||||||
|
E266 # too many leading '#' for block comment
|
||||||
|
W503 # line break before binary operator
|
||||||
|
resources.py ALL
|
||||||
|
mccabe-complexity = 12
|
||||||
|
qt_log_level_fail = WARNING
|
||||||
|
qt_log_ignore = ^SpellCheck: .*
|
||||||
|
Loading…
Reference in New Issue
Block a user