Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a90a894177 | ||
|
|
ce305b0fed | ||
|
|
b19b8163e1 | ||
|
|
203664a12c | ||
|
|
cfe7e7883a | ||
|
|
dfe74ff0ee | ||
|
|
18a45bbd5b | ||
|
|
30462b3839 | ||
|
|
1a7669c8a2 | ||
|
|
da5f8269c7 | ||
|
|
39353cdc00 | ||
|
|
6edfd1801d | ||
|
|
21364f135e | ||
|
|
6f6eeae902 | ||
|
|
c551922e2d | ||
|
|
f7585eb60e | ||
|
|
fc63cea917 | ||
|
|
effbe36919 | ||
|
|
db466097e0 | ||
|
|
0446babb3d | ||
|
|
85d643ff49 | ||
|
|
12c35ff836 | ||
|
|
b33bb94593 | ||
|
|
25a11f492b | ||
|
|
d0453542c3 | ||
|
|
4bff63f17b | ||
|
|
a5426ca4ac | ||
|
|
ec9981f0d1 | ||
|
|
5ebca1c309 | ||
|
|
1559965e6b | ||
|
|
9a2d451bc5 | ||
|
|
5a080ebea5 | ||
|
|
2f3e3c3073 | ||
|
|
457bba7e79 | ||
|
|
0045af8738 | ||
|
|
7dc82407e8 | ||
|
|
0becdfa6b0 | ||
|
|
b69b5c81fc | ||
|
|
8607ddce24 | ||
|
|
55a88ceea6 | ||
|
|
d4bf04d2c8 | ||
|
|
cb527913dc | ||
|
|
ddfa82345c | ||
|
|
45c75d5e04 | ||
|
|
9404c61f10 |
@@ -51,7 +51,7 @@ matrix:
|
||||
env: TESTENV=eslint
|
||||
language: node_js
|
||||
python: null
|
||||
node_js: node
|
||||
node_js: "lts/*"
|
||||
fast_finish: true
|
||||
|
||||
cache:
|
||||
|
||||
@@ -109,7 +109,7 @@ The following software and libraries are required to run qutebrowser:
|
||||
link:https://github.com/annulen/webkit/wiki[updated fork] (5.212) is
|
||||
supported
|
||||
* http://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer
|
||||
(5.9 recommended) for Python 3
|
||||
(5.9.2 recommended) for Python 3
|
||||
* https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools]
|
||||
* http://fdik.org/pyPEG/[pyPEG2]
|
||||
* http://jinja.pocoo.org/[jinja2]
|
||||
|
||||
@@ -15,19 +15,63 @@ breaking changes (such as renamed commands) can happen in minor releases.
|
||||
// `Fixed` for any bug fixes.
|
||||
// `Security` to invite users to upgrade in case of vulnerabilities.
|
||||
|
||||
v1.0.2 (unreleased)
|
||||
-------------------
|
||||
v1.0.4
|
||||
------
|
||||
|
||||
Fixes
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed workaround for black screens with Nvidia cards
|
||||
- The `qute://gpl` page now works correctly again.
|
||||
- Trying to bind an empty command now doesn't crash anymore.
|
||||
- Fixed crash when `:config-write-py` fails to write to the given path.
|
||||
- Fixed crash for some users when selecting a file with Qt 5.9.3
|
||||
- Improved handling for various SQL errors
|
||||
- Fix crash when setting content.cache.size to a big value (> 2 GB)
|
||||
|
||||
v1.0.3
|
||||
------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- macOS and Windows builds are now built with PyQt 5.9.1 and Qt 5.9.2, including
|
||||
various bugfixes, as well as security fixes from Chromium up to version
|
||||
61.0.3163.79.
|
||||
- Performance improvements for tab rendering.
|
||||
- The :open-editor command is now not hidden anymore as it's also usable in
|
||||
normal mode.
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Handle accessing a locked sqlite database gracefully
|
||||
- Abort pinned tab dialogs properly when a tab is closed e.g. by closing a
|
||||
window
|
||||
- Unbinding a default keybinding twice now doesn't bind it again
|
||||
- Completions are now sorted correctly again when filtered
|
||||
|
||||
v1.0.2
|
||||
------
|
||||
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fix workaround for black screens or crashes with Nvidia cards
|
||||
- Handle a filesystem going read-only gracefully
|
||||
- Fix crash when setting `fonts.monospace`
|
||||
- Fix list options not being modifyable via `.append()` in `config.py`
|
||||
- Mark the content.notifications setting as QtWebKit only correctly
|
||||
- Fix wrong rendering of keys like `<back>` in the completion
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- Nicer error messages and other minor improvements
|
||||
|
||||
v1.0.1
|
||||
------
|
||||
|
||||
Fixes
|
||||
Fixed
|
||||
~~~~~
|
||||
|
||||
- Fixed starting after customizing `fonts.tabs` or `fonts.debug_console`.
|
||||
@@ -65,6 +109,9 @@ Major changes
|
||||
the entire browsing history. The default for
|
||||
`completion.web_history_max_items` got changed to `-1` (unlimited). If the
|
||||
completion is too slow on your machine, try setting it to a few 1000 items.
|
||||
- Up/Down now navigates through the command history instead of selecting
|
||||
completion items. Either use Tab to cycle through the completion, or
|
||||
https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc#migrating-older-configurations[restore the old behavior].
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
@@ -60,6 +60,7 @@ It is possible to run or bind multiple commands by separating them with `;;`.
|
||||
|<<messages,messages>>|Show a log of past messages.
|
||||
|<<navigate,navigate>>|Open typical prev/next links or navigate using the URL path.
|
||||
|<<open,open>>|Open a URL in the current/[count]th tab.
|
||||
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||
|<<print,print>>|Print the current/[count]th tab.
|
||||
|<<quickmark-add,quickmark-add>>|Add a new quickmark.
|
||||
|<<quickmark-del,quickmark-del>>|Delete a quickmark.
|
||||
@@ -485,7 +486,9 @@ Start hinting.
|
||||
|
||||
|
||||
==== optional arguments
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. This is only possible with targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
* +*-r*+, +*--rapid*+: Whether to do rapid hinting. With rapid hinting, the hint mode isn't left after a hint is followed, so you can easily
|
||||
open multiple links. This is only possible with targets
|
||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
|
||||
* +*-m*+, +*--mode*+: The hinting mode to use.
|
||||
@@ -653,6 +656,12 @@ The tab index to open the URL in.
|
||||
==== note
|
||||
* This command does not split arguments after the last argument and handles quotes literally.
|
||||
|
||||
[[open-editor]]
|
||||
=== open-editor
|
||||
Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
[[print]]
|
||||
=== print
|
||||
Syntax: +:print [*--preview*] [*--pdf* 'file']+
|
||||
@@ -1137,7 +1146,6 @@ How many steps to zoom out.
|
||||
|<<move-to-start-of-next-block,move-to-start-of-next-block>>|Move the cursor or selection to the start of next block.
|
||||
|<<move-to-start-of-prev-block,move-to-start-of-prev-block>>|Move the cursor or selection to the start of previous block.
|
||||
|<<nop,nop>>|Do nothing.
|
||||
|<<open-editor,open-editor>>|Open an external editor with the currently selected form field.
|
||||
|<<prompt-accept,prompt-accept>>|Accept the current prompt.
|
||||
|<<prompt-item-focus,prompt-item-focus>>|Shift the focus of the prompt file completion menu to another item.
|
||||
|<<prompt-open-download,prompt-open-download>>|Immediately open a download.
|
||||
@@ -1388,12 +1396,6 @@ How many blocks to move.
|
||||
=== nop
|
||||
Do nothing.
|
||||
|
||||
[[open-editor]]
|
||||
=== open-editor
|
||||
Open an external editor with the currently selected form field.
|
||||
|
||||
The editor which should be launched can be configured via the `editor.command` config option.
|
||||
|
||||
[[prompt-accept]]
|
||||
=== prompt-accept
|
||||
Syntax: +:prompt-accept ['value']+
|
||||
|
||||
@@ -22,8 +22,8 @@ Other changes in default settings:
|
||||
doing:
|
||||
+
|
||||
----
|
||||
:bind -f -m command <Up> completion-item-focus prev
|
||||
:bind -f -m command <Down> completion-item-focus next
|
||||
:bind -m command <Up> completion-item-focus prev
|
||||
:bind -m command <Down> completion-item-focus next
|
||||
----
|
||||
|
||||
- The default for `completion.web_history_max_items` is now set to `-1`, showing
|
||||
|
||||
@@ -1407,6 +1407,7 @@ This setting is only available with the QtWebKit backend.
|
||||
[[content.cache.size]]
|
||||
=== content.cache.size
|
||||
Size of the HTTP network cache. Null to use the default value.
|
||||
With QtWebEngine, the maximum supported value is 2147483647 (~2 GB).
|
||||
|
||||
Type: <<types,Int>>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||
|
||||
PyQt5==5.9
|
||||
sip==4.19.3
|
||||
PyQt5==5.9.2
|
||||
sip==4.19.6
|
||||
|
||||
@@ -1 +1 @@
|
||||
PyQt5
|
||||
PyQt5
|
||||
|
||||
@@ -55,4 +55,6 @@ qt_log_ignore =
|
||||
^QQuickWidget::invalidateRenderControl could not make context current
|
||||
^libpng warning: iCCP: known incorrect sRGB profile
|
||||
^inotify_add_watch(".*") failed: "No space left on device"
|
||||
^QSettings::value: Empty key passed
|
||||
^Icon theme ".*" not found
|
||||
xfail_strict = true
|
||||
|
||||
@@ -26,7 +26,7 @@ __copyright__ = "Copyright 2014-2017 Florian Bruhin (The Compiler)"
|
||||
__license__ = "GPL"
|
||||
__maintainer__ = __author__
|
||||
__email__ = "mail@qutebrowser.org"
|
||||
__version_info__ = (1, 0, 1)
|
||||
__version_info__ = (1, 0, 4)
|
||||
__version__ = '.'.join(str(e) for e in __version_info__)
|
||||
__description__ = "A keyboard-driven, vim-like browser based on PyQt5."
|
||||
|
||||
|
||||
@@ -1599,8 +1599,7 @@ class CommandDispatcher:
|
||||
self.on_editing_finished, elem))
|
||||
ed.edit(text)
|
||||
|
||||
@cmdutils.register(instance='command-dispatcher', hide=True,
|
||||
scope='window')
|
||||
@cmdutils.register(instance='command-dispatcher', scope='window')
|
||||
def open_editor(self):
|
||||
"""Open an external editor with the currently selected form field.
|
||||
|
||||
|
||||
@@ -616,8 +616,10 @@ class HintManager(QObject):
|
||||
"""Start hinting.
|
||||
|
||||
Args:
|
||||
rapid: Whether to do rapid hinting. This is only possible with
|
||||
targets `tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
rapid: Whether to do rapid hinting. With rapid hinting, the hint
|
||||
mode isn't left after a hint is followed, so you can easily
|
||||
open multiple links. This is only possible with targets
|
||||
`tab` (with `tabs.background_tabs=true`), `tab-bg`,
|
||||
`window`, `run`, `hover`, `userscript` and `spawn`.
|
||||
add_history: Whether to add the spawned or yanked link to the
|
||||
browsing history.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
import os
|
||||
import time
|
||||
import contextlib
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, QUrl, QTimer
|
||||
|
||||
@@ -87,6 +88,16 @@ class WebHistory(sql.SqlTable):
|
||||
def __contains__(self, url):
|
||||
return self._contains_query.run(val=url).value()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _handle_sql_errors(self):
|
||||
try:
|
||||
yield
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
message.error("Failed to write history: {}".format(e.text()))
|
||||
else:
|
||||
raise
|
||||
|
||||
def _rebuild_completion(self):
|
||||
data = {'url': [], 'title': [], 'last_atime': []}
|
||||
# select the latest entry for each url
|
||||
@@ -142,8 +153,9 @@ class WebHistory(sql.SqlTable):
|
||||
"history?")
|
||||
|
||||
def _do_clear(self):
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
with self._handle_sql_errors():
|
||||
self.delete_all()
|
||||
self.completion.delete_all()
|
||||
|
||||
def delete_url(self, url):
|
||||
"""Remove all history entries with the given url.
|
||||
@@ -191,7 +203,7 @@ class WebHistory(sql.SqlTable):
|
||||
|
||||
atime = int(atime) if (atime is not None) else int(time.time())
|
||||
|
||||
try:
|
||||
with self._handle_sql_errors():
|
||||
self.insert({'url': self._format_url(url),
|
||||
'title': title,
|
||||
'atime': atime,
|
||||
@@ -202,11 +214,6 @@ class WebHistory(sql.SqlTable):
|
||||
'title': title,
|
||||
'last_atime': atime
|
||||
}, replace=True)
|
||||
except sql.SqlError as e:
|
||||
if e.environmental:
|
||||
message.error("Failed to write history: {}".format(e.text()))
|
||||
else:
|
||||
raise
|
||||
|
||||
def _parse_entry(self, line):
|
||||
"""Parse a history line like '12345 http://example.com title'."""
|
||||
|
||||
@@ -307,7 +307,7 @@ def qute_log(url):
|
||||
@add_handler('gpl')
|
||||
def qute_gpl(_url):
|
||||
"""Handler for qute://gpl. Return HTML content as string."""
|
||||
return 'text/html', utils.read_file('html/LICENSE.html')
|
||||
return 'text/html', utils.read_file('html/license.html')
|
||||
|
||||
|
||||
@add_handler('help')
|
||||
|
||||
@@ -92,9 +92,10 @@ class DefaultProfileSetter(websettings.Base):
|
||||
|
||||
"""A setting set on the QWebEngineProfile."""
|
||||
|
||||
def __init__(self, setter, default=websettings.UNSET):
|
||||
def __init__(self, setter, converter=None, default=websettings.UNSET):
|
||||
super().__init__(default)
|
||||
self._setter = setter
|
||||
self._converter = converter
|
||||
|
||||
def __repr__(self):
|
||||
return utils.get_repr(self, setter=self._setter, constructor=True)
|
||||
@@ -103,7 +104,11 @@ class DefaultProfileSetter(websettings.Base):
|
||||
if settings is not None:
|
||||
raise ValueError("'settings' may not be set with "
|
||||
"DefaultProfileSetters!")
|
||||
|
||||
setter = getattr(default_profile, self._setter)
|
||||
if self._converter is not None:
|
||||
value = self._converter(value)
|
||||
|
||||
setter(value)
|
||||
|
||||
|
||||
@@ -281,7 +286,9 @@ MAPPINGS = {
|
||||
Attribute(QWebEngineSettings.LocalStorageEnabled),
|
||||
'content.cache.size':
|
||||
# 0: automatically managed by QtWebEngine
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0),
|
||||
DefaultProfileSetter('setHttpCacheMaximumSize', default=0,
|
||||
converter=lambda val:
|
||||
qtutils.check_overflow(val, 'int', fatal=False)),
|
||||
'content.xss_auditing':
|
||||
Attribute(QWebEngineSettings.XSSAuditingEnabled),
|
||||
'content.default_encoding':
|
||||
|
||||
@@ -422,12 +422,13 @@ class WebKitScroller(browsertab.AbstractScroller):
|
||||
else:
|
||||
for val, orientation in [(x, Qt.Horizontal), (y, Qt.Vertical)]:
|
||||
if val is not None:
|
||||
val = qtutils.check_overflow(val, 'int', fatal=False)
|
||||
frame = self._widget.page().mainFrame()
|
||||
m = frame.scrollBarMaximum(orientation)
|
||||
if m == 0:
|
||||
maximum = frame.scrollBarMaximum(orientation)
|
||||
if maximum == 0:
|
||||
continue
|
||||
frame.setScrollBarValue(orientation, int(m * val / 100))
|
||||
pos = int(maximum * val / 100)
|
||||
pos = qtutils.check_overflow(pos, 'int', fatal=False)
|
||||
frame.setScrollBarValue(orientation, pos)
|
||||
|
||||
def _key_press(self, key, count=1, getter_name=None, direction=None):
|
||||
frame = self._widget.page().mainFrame()
|
||||
|
||||
@@ -202,7 +202,8 @@ class CompletionItemDelegate(QStyledItemDelegate):
|
||||
if index.column() in columns_to_filter and pattern:
|
||||
repl = r'<span class="highlight">\g<0></span>'
|
||||
text = re.sub(re.escape(pattern).replace(r'\ ', r'|'),
|
||||
repl, self._opt.text, flags=re.IGNORECASE)
|
||||
repl, html.escape(self._opt.text),
|
||||
flags=re.IGNORECASE)
|
||||
self._doc.setHtml(text)
|
||||
else:
|
||||
self._doc.setPlainText(self._opt.text)
|
||||
|
||||
@@ -29,7 +29,7 @@ def option(*, info):
|
||||
model = completionmodel.CompletionModel(column_widths=(20, 70, 10))
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt in configdata.DATA.values())
|
||||
model.add_category(listcategory.ListCategory("Options", sorted(options)))
|
||||
model.add_category(listcategory.ListCategory("Options", options))
|
||||
return model
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ def customized_option(*, info):
|
||||
options = ((opt.name, opt.description, info.config.get_str(opt.name))
|
||||
for opt, _value in info.config)
|
||||
model.add_category(listcategory.ListCategory("Customized options",
|
||||
sorted(options)))
|
||||
options))
|
||||
return model
|
||||
|
||||
|
||||
@@ -66,8 +66,7 @@ def value(optname, *_values, info):
|
||||
|
||||
vals = opt.typ.complete()
|
||||
if vals is not None:
|
||||
model.add_category(listcategory.ListCategory("Completions",
|
||||
sorted(vals)))
|
||||
model.add_category(listcategory.ListCategory("Completions", vals))
|
||||
return model
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||
|
||||
"""Expose a list of items as a category for the CompletionModel."""
|
||||
|
||||
def __init__(self, name, items, delete_func=None, parent=None):
|
||||
def __init__(self, name, items, sort=True, delete_func=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.name = name
|
||||
self.srcmodel = QStandardItemModel(parent=self)
|
||||
@@ -43,6 +43,7 @@ class ListCategory(QSortFilterProxyModel):
|
||||
self.srcmodel.appendRow([QStandardItem(x) for x in item])
|
||||
self.setSourceModel(self.srcmodel)
|
||||
self.delete_func = delete_func
|
||||
self._sort = sort
|
||||
|
||||
def set_pattern(self, val):
|
||||
"""Setter for pattern.
|
||||
@@ -60,19 +61,33 @@ class ListCategory(QSortFilterProxyModel):
|
||||
sortcol = 0
|
||||
self.sort(sortcol)
|
||||
|
||||
def lessThan(self, _lindex, rindex):
|
||||
def lessThan(self, lindex, rindex):
|
||||
"""Custom sorting implementation.
|
||||
|
||||
Prefers all items which start with self._pattern. Other than that, keep
|
||||
items in their original order.
|
||||
Prefers all items which start with self._pattern. Other than that, uses
|
||||
normal Python string sorting.
|
||||
|
||||
Args:
|
||||
_lindex: The QModelIndex of the left item (*left* < right)
|
||||
lindex: The QModelIndex of the left item (*left* < right)
|
||||
rindex: The QModelIndex of the right item (left < *right*)
|
||||
|
||||
Return:
|
||||
True if left < right, else False
|
||||
"""
|
||||
qtutils.ensure_valid(lindex)
|
||||
qtutils.ensure_valid(rindex)
|
||||
|
||||
left = self.srcmodel.data(lindex)
|
||||
right = self.srcmodel.data(rindex)
|
||||
return not right.startswith(self._pattern)
|
||||
|
||||
leftstart = left.startswith(self._pattern)
|
||||
rightstart = right.startswith(self._pattern)
|
||||
|
||||
if leftstart and not rightstart:
|
||||
return True
|
||||
elif rightstart and not leftstart:
|
||||
return False
|
||||
elif self._sort:
|
||||
return left < right
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -43,7 +43,7 @@ def helptopic(*, info):
|
||||
for opt in configdata.DATA.values())
|
||||
|
||||
model.add_category(listcategory.ListCategory("Commands", cmdlist))
|
||||
model.add_category(listcategory.ListCategory("Settings", sorted(settings)))
|
||||
model.add_category(listcategory.ListCategory("Settings", settings))
|
||||
return model
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ def quickmark(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
marks = objreg.get('quickmark-manager').marks.items()
|
||||
model.add_category(listcategory.ListCategory('Quickmarks', marks,
|
||||
delete_func=delete))
|
||||
delete_func=delete,
|
||||
sort=False))
|
||||
return model
|
||||
|
||||
|
||||
@@ -75,7 +76,8 @@ def bookmark(*, info=None): # pylint: disable=unused-argument
|
||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||
marks = objreg.get('bookmark-manager').marks.items()
|
||||
model.add_category(listcategory.ListCategory('Bookmarks', marks,
|
||||
delete_func=delete))
|
||||
delete_func=delete,
|
||||
sort=False))
|
||||
return model
|
||||
|
||||
|
||||
|
||||
@@ -61,9 +61,9 @@ def url(*, info):
|
||||
bookmarks = objreg.get('bookmark-manager').marks.items()
|
||||
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark))
|
||||
'Quickmarks', quickmarks, delete_func=_delete_quickmark, sort=False))
|
||||
model.add_category(listcategory.ListCategory(
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark))
|
||||
'Bookmarks', bookmarks, delete_func=_delete_bookmark, sort=False))
|
||||
|
||||
if info.config.get('completion.web_history_max_items') != 0:
|
||||
hist_cat = histcategory.HistoryCategory(delete_func=_delete_history)
|
||||
|
||||
@@ -170,6 +170,11 @@ class KeyConfig:
|
||||
|
||||
def bind(self, key, command, *, mode, save_yaml=False):
|
||||
"""Add a new binding from key to command."""
|
||||
if command is not None and not command.strip():
|
||||
raise configexc.KeybindingError(
|
||||
"Can't add binding '{}' with empty command in {} "
|
||||
'mode'.format(key, mode))
|
||||
|
||||
key = self._prepare(key, mode)
|
||||
log.keyboard.vdebug("Adding binding {} -> {} in mode {}.".format(
|
||||
key, command, mode))
|
||||
@@ -198,7 +203,7 @@ class KeyConfig:
|
||||
|
||||
bindings_commands = self._config.get_obj('bindings.commands')
|
||||
|
||||
if key in val.bindings.commands[mode]:
|
||||
if val.bindings.commands[mode].get(key, None) is not None:
|
||||
# In custom bindings -> remove it
|
||||
del bindings_commands[mode][key]
|
||||
elif key in val.bindings.default[mode]:
|
||||
|
||||
@@ -75,6 +75,10 @@ class ConfigCommands:
|
||||
tabbed_browser.openurl(QUrl('qute://settings'), newtab=False)
|
||||
return
|
||||
|
||||
if option.endswith('!'):
|
||||
raise cmdexc.CommandError("Toggling values was moved to the "
|
||||
":config-cycle command")
|
||||
|
||||
if option.endswith('?') and option != '?':
|
||||
self._print_value(option[:-1])
|
||||
return
|
||||
@@ -283,4 +287,7 @@ class ConfigCommands:
|
||||
|
||||
writer = configfiles.ConfigPyWriter(options, bindings,
|
||||
commented=commented)
|
||||
writer.write(filename)
|
||||
try:
|
||||
writer.write(filename)
|
||||
except OSError as e:
|
||||
raise cmdexc.CommandError(str(e))
|
||||
|
||||
@@ -181,7 +181,10 @@ content.cache.size:
|
||||
none_ok: true
|
||||
minval: 0
|
||||
maxval: maxint64
|
||||
desc: Size of the HTTP network cache. Null to use the default value.
|
||||
desc: >-
|
||||
Size of the HTTP network cache. Null to use the default value.
|
||||
|
||||
With QtWebEngine, the maximum supported value is 2147483647 (~2 GB).
|
||||
|
||||
# Defaults from QWebSettings::QWebSettings() in
|
||||
# qtwebkit/Source/WebKit/qt/Api/qwebsettings.cpp
|
||||
|
||||
@@ -755,6 +755,6 @@ def get_diff():
|
||||
lexer = pygments.lexers.DiffLexer()
|
||||
formatter = pygments.formatters.HtmlFormatter(
|
||||
full=True, linenos='table',
|
||||
title='Config diff')
|
||||
title='Diffing pre-1.0 default config with pre-1.0 modified config')
|
||||
# pylint: enable=no-member
|
||||
return pygments.highlight(conf_diff + key_diff, lexer, formatter)
|
||||
|
||||
@@ -104,7 +104,9 @@ def _update_monospace_fonts():
|
||||
continue
|
||||
elif not isinstance(opt.typ, configtypes.Font):
|
||||
continue
|
||||
elif not config.instance.get_obj(name).endswith(' monospace'):
|
||||
|
||||
value = config.instance.get_obj(name)
|
||||
if value is None or not value.endswith(' monospace'):
|
||||
continue
|
||||
|
||||
config.instance.changed.emit(name)
|
||||
|
||||
@@ -249,13 +249,14 @@ class TabbedBrowser(tabwidget.TabWidget):
|
||||
def tab_close_prompt_if_pinned(self, tab, force, yes_action):
|
||||
"""Helper method for tab_close.
|
||||
|
||||
If tab is pinned, prompt. If everything is good, run yes_action.
|
||||
If tab is pinned, prompt. If not, run yes_action.
|
||||
If tab is destroyed, abort question.
|
||||
"""
|
||||
if tab.data.pinned and not force:
|
||||
message.confirm_async(
|
||||
title='Pinned Tab',
|
||||
text="Are you sure you want to close a pinned tab?",
|
||||
yes_action=yes_action, default=False)
|
||||
yes_action=yes_action, default=False, abort_on=[tab.destroyed])
|
||||
else:
|
||||
yes_action()
|
||||
|
||||
|
||||
@@ -310,7 +310,7 @@ class TabBar(QTabBar):
|
||||
return self.parent().currentWidget()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _on_config_changed(self, option):
|
||||
def _on_config_changed(self, option: str):
|
||||
if option == 'fonts.tabs':
|
||||
self._set_font()
|
||||
elif option == 'tabs.favicons.scale':
|
||||
@@ -325,6 +325,12 @@ class TabBar(QTabBar):
|
||||
if option.startswith('colors.tabs.'):
|
||||
self.update()
|
||||
|
||||
# Clear _minimum_tab_size_hint_helper cache when appropriate
|
||||
if option in ["tabs.indicator_padding",
|
||||
"tabs.padding",
|
||||
"tabs.width.indicator"]:
|
||||
self._minimum_tab_size_hint_helper.cache_clear()
|
||||
|
||||
def _on_show_switching_delay_changed(self):
|
||||
"""Set timer interval when tabs.show_switching_delay got changed."""
|
||||
self._auto_hide_timer.setInterval(config.val.tabs.show_switching_delay)
|
||||
@@ -424,7 +430,7 @@ class TabBar(QTabBar):
|
||||
return
|
||||
super().mousePressEvent(e)
|
||||
|
||||
def minimumTabSizeHint(self, index, ellipsis: bool = True):
|
||||
def minimumTabSizeHint(self, index, ellipsis: bool = True) -> QSize:
|
||||
"""Set the minimum tab size to indicator/icon/... text.
|
||||
|
||||
Args:
|
||||
@@ -434,38 +440,47 @@ class TabBar(QTabBar):
|
||||
Return:
|
||||
A QSize of the smallest tab size we can make.
|
||||
"""
|
||||
text = '\u2026' if ellipsis else self.tabText(index)
|
||||
icon = self.tabIcon(index)
|
||||
extent = self.style().pixelMetric(QStyle.PM_TabBarIconSize, None, self)
|
||||
if icon.isNull():
|
||||
icon_width = 0
|
||||
else:
|
||||
icon_width = icon.actualSize(QSize(extent, extent)).width()
|
||||
return self._minimum_tab_size_hint_helper(self.tabText(index),
|
||||
icon_width,
|
||||
ellipsis)
|
||||
|
||||
@functools.lru_cache(maxsize=2**9)
|
||||
def _minimum_tab_size_hint_helper(self, tab_text: str,
|
||||
icon_width: int,
|
||||
ellipsis: bool) -> QSize:
|
||||
"""Helper function to cache tab results.
|
||||
|
||||
Config values accessed in here should be added to _on_config_changed to
|
||||
ensure cache is flushed when needed.
|
||||
"""
|
||||
text = '\u2026' if ellipsis else tab_text
|
||||
# Don't ever shorten if text is shorter than the ellipsis
|
||||
text_width = min(self.fontMetrics().width(text),
|
||||
self.fontMetrics().width(self.tabText(index)))
|
||||
icon = self.tabIcon(index)
|
||||
self.fontMetrics().width(tab_text))
|
||||
padding = config.val.tabs.padding
|
||||
indicator_padding = config.val.tabs.indicator_padding
|
||||
padding_h = padding.left + padding.right
|
||||
padding_h += indicator_padding.left + indicator_padding.right
|
||||
padding_v = padding.top + padding.bottom
|
||||
if icon.isNull():
|
||||
icon_size = QSize(0, 0)
|
||||
else:
|
||||
extent = self.style().pixelMetric(QStyle.PM_TabBarIconSize, None,
|
||||
self)
|
||||
icon_size = icon.actualSize(QSize(extent, extent))
|
||||
height = self.fontMetrics().height() + padding_v
|
||||
width = (text_width + icon_size.width() +
|
||||
width = (text_width + icon_width +
|
||||
padding_h + config.val.tabs.width.indicator)
|
||||
return QSize(width, height)
|
||||
|
||||
def _tab_total_width_pinned(self):
|
||||
"""Get the current total width of pinned tabs.
|
||||
|
||||
This width is calculated assuming no shortening due to ellipsis."""
|
||||
return sum(self.minimumTabSizeHint(idx, ellipsis=False).width()
|
||||
for idx in range(self.count())
|
||||
if self._tab_pinned(idx))
|
||||
|
||||
def _pinnedCount(self) -> int:
|
||||
"""Get the number of pinned tabs."""
|
||||
return sum(self._tab_pinned(idx) for idx in range(self.count()))
|
||||
def _pinned_statistics(self) -> (int, int):
|
||||
"""Get the number of pinned tabs and the total width of pinned tabs."""
|
||||
pinned_list = [idx for idx in range(self.count())
|
||||
if self._tab_pinned(idx)]
|
||||
pinned_count = len(pinned_list)
|
||||
pinned_width = sum(self.minimumTabSizeHint(idx, ellipsis=False).width()
|
||||
for idx in pinned_list)
|
||||
return (pinned_count, pinned_width)
|
||||
|
||||
def _tab_pinned(self, index: int) -> bool:
|
||||
"""Return True if tab is pinned."""
|
||||
@@ -504,8 +519,8 @@ class TabBar(QTabBar):
|
||||
return QSize()
|
||||
else:
|
||||
pinned = self._tab_pinned(index)
|
||||
no_pinned_count = self.count() - self._pinnedCount()
|
||||
pinned_width = self._tab_total_width_pinned()
|
||||
pinned_count, pinned_width = self._pinned_statistics()
|
||||
no_pinned_count = self.count() - pinned_count
|
||||
no_pinned_width = self.width() - pinned_width
|
||||
|
||||
if pinned:
|
||||
|
||||
@@ -65,15 +65,26 @@ class SqliteError(SqlError):
|
||||
log.sql.debug("error code: {}".format(error.nativeErrorCode()))
|
||||
|
||||
# https://sqlite.org/rescode.html
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2930
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3004
|
||||
environmental_errors = [
|
||||
# SQLITE_LOCKED,
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/2930
|
||||
'9',
|
||||
# SQLITE_FULL,
|
||||
# https://github.com/qutebrowser/qutebrowser/issues/3004
|
||||
'13',
|
||||
'5', # SQLITE_BUSY ("database is locked")
|
||||
'8', # SQLITE_READONLY
|
||||
'11', # SQLITE_CORRUPT
|
||||
'13', # SQLITE_FULL
|
||||
]
|
||||
self.environmental = error.nativeErrorCode() in environmental_errors
|
||||
# At least in init(), we can get errors like this:
|
||||
# type: ConnectionError
|
||||
# database text: out of memory
|
||||
# driver text: Error opening database
|
||||
# error code: -1
|
||||
environmental_strings = [
|
||||
"out of memory",
|
||||
]
|
||||
errcode = error.nativeErrorCode()
|
||||
self.environmental = (
|
||||
errcode in environmental_errors or
|
||||
(errcode == -1 and error.databaseText() in environmental_strings))
|
||||
|
||||
def text(self):
|
||||
return self.error.databaseText()
|
||||
|
||||
@@ -171,6 +171,7 @@ def debug_cache_stats():
|
||||
prefix_info = configdata.is_valid_prefix.cache_info()
|
||||
# pylint: disable=protected-access
|
||||
render_stylesheet_info = config._render_stylesheet.cache_info()
|
||||
# pylint: enable=protected-access
|
||||
|
||||
history_info = None
|
||||
try:
|
||||
@@ -181,9 +182,17 @@ def debug_cache_stats():
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
tabbed_browser = objreg.get('tabbed-browser', scope='window',
|
||||
window='last-focused')
|
||||
# pylint: disable=protected-access
|
||||
tab_bar = tabbed_browser.tabBar()
|
||||
tabbed_browser_info = tab_bar._minimum_tab_size_hint_helper.cache_info()
|
||||
# pylint: enable=protected-access
|
||||
|
||||
log.misc.debug('is_valid_prefix: {}'.format(prefix_info))
|
||||
log.misc.debug('_render_stylesheet: {}'.format(render_stylesheet_info))
|
||||
log.misc.debug('history: {}'.format(history_info))
|
||||
log.misc.debug('tab width cache: {}'.format(tabbed_browser_info))
|
||||
|
||||
|
||||
@cmdutils.register(debug=True)
|
||||
|
||||
@@ -327,7 +327,8 @@ def change_console_formatter(level):
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
|
||||
def qt_message_handler(msg_type, context, msg):
|
||||
def qt_message_handler(msg_type, context, # pylint: disable=too-many-branches
|
||||
msg):
|
||||
"""Qt message handler to redirect qWarning etc. to the logging system.
|
||||
|
||||
Args:
|
||||
@@ -421,6 +422,9 @@ def qt_message_handler(msg_type, context, msg):
|
||||
'with: -9805', # flake8: disable=E131
|
||||
]
|
||||
|
||||
if not msg:
|
||||
msg = "Logged empty message!"
|
||||
|
||||
if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs):
|
||||
level = logging.DEBUG
|
||||
else:
|
||||
|
||||
@@ -882,7 +882,7 @@ def yaml_load(f):
|
||||
end = datetime.datetime.now()
|
||||
|
||||
delta = (end - start).total_seconds()
|
||||
deadline = 3 if 'CI' in os.environ else 1
|
||||
deadline = 3 if 'CI' in os.environ else 2
|
||||
if delta > deadline: # pragma: no cover
|
||||
log.misc.warning(
|
||||
"YAML load took unusually long, please report this at "
|
||||
|
||||
@@ -209,3 +209,9 @@ Feature: Special qute:// pages
|
||||
Scenario: Open qute://version
|
||||
When I open qute://version
|
||||
Then the page should contain the plaintext "Version info"
|
||||
|
||||
# qute://gpl
|
||||
|
||||
Scenario: Open qute://gpl
|
||||
When I open qute://gpl
|
||||
Then the page should contain the plaintext "GNU GENERAL PUBLIC LICENSE"
|
||||
|
||||
@@ -141,6 +141,12 @@ def is_ignored_chromium_message(line):
|
||||
# channel message
|
||||
'Invalid node channel message',
|
||||
|
||||
# Qt 5.9.3
|
||||
# [30217:30229:1124/141512.682110:ERROR:
|
||||
# cert_verify_proc_openssl.cc(212)]
|
||||
# X509 Verification error self signed certificate : 18 : 0 : 4
|
||||
'X509 Verification error self signed certificate : 18 : 0 : 4',
|
||||
|
||||
# Not reproducible anymore?
|
||||
|
||||
'Running without the SUID sandbox! *',
|
||||
|
||||
@@ -147,7 +147,7 @@ def test_remove_rows(hist, model_validator):
|
||||
cat.set_pattern('')
|
||||
hist.delete('url', 'foo')
|
||||
cat.removeRows(0, 1)
|
||||
model_validator.validate([('bar', 'Bar', '1970-01-01')])
|
||||
model_validator.validate([('bar', 'Bar')])
|
||||
|
||||
|
||||
def test_remove_rows_fetch(hist):
|
||||
|
||||
@@ -24,27 +24,36 @@ import pytest
|
||||
from qutebrowser.completion.models import listcategory
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pattern, before, after', [
|
||||
@pytest.mark.parametrize('pattern, before, after, after_nosort', [
|
||||
('foo',
|
||||
[('foo', ''), ('bar', '')],
|
||||
[('foo', '')],
|
||||
[('foo', '')]),
|
||||
|
||||
('foo',
|
||||
[('foob', ''), ('fooc', ''), ('fooa', '')],
|
||||
[('fooa', ''), ('foob', ''), ('fooc', '')],
|
||||
[('foob', ''), ('fooc', ''), ('fooa', '')]),
|
||||
|
||||
# prefer foobar as it starts with the pattern
|
||||
('foo',
|
||||
[('barfoo', ''), ('foobar', '')],
|
||||
[('foobar', ''), ('barfoo', '')]),
|
||||
[('barfoo', ''), ('foobaz', ''), ('foobar', '')],
|
||||
[('foobar', ''), ('foobaz', ''), ('barfoo', '')],
|
||||
[('foobaz', ''), ('foobar', ''), ('barfoo', '')]),
|
||||
|
||||
('foo',
|
||||
[('foo', 'bar'), ('bar', 'foo'), ('bar', 'bar')],
|
||||
[('foo', 'bar'), ('bar', 'foo')],
|
||||
[('foo', 'bar'), ('bar', 'foo')]),
|
||||
])
|
||||
def test_set_pattern(pattern, before, after, model_validator):
|
||||
def test_set_pattern(pattern, before, after, after_nosort, model_validator):
|
||||
"""Validate the filtering and sorting results of set_pattern."""
|
||||
cat = listcategory.ListCategory('Foo', before)
|
||||
model_validator.set_model(cat)
|
||||
cat.set_pattern(pattern)
|
||||
model_validator.validate(after)
|
||||
|
||||
cat = listcategory.ListCategory('Foo', before, sort=False)
|
||||
model_validator.set_model(cat)
|
||||
cat.set_pattern(pattern)
|
||||
model_validator.validate(after_nosort)
|
||||
|
||||
@@ -466,9 +466,9 @@ def test_session_completion(qtmodeltester, session_manager_stub):
|
||||
qtmodeltester.check(model)
|
||||
|
||||
_check_completions(model, {
|
||||
"Sessions": [('default', None, None),
|
||||
('1', None, None),
|
||||
('2', None, None)]
|
||||
"Sessions": [('1', None, None),
|
||||
('2', None, None),
|
||||
('default', None, None)]
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -269,6 +269,28 @@ class TestKeyConfig:
|
||||
match="Can't find binding 'foobar' in normal mode"):
|
||||
key_config_stub.unbind('foobar', mode='normal')
|
||||
|
||||
def test_unbound_twice(self, key_config_stub, config_stub, no_bindings):
|
||||
"""Try unbinding an already-unbound default key.
|
||||
|
||||
For custom-bound keys (in bindings.commands), it's okay to display an
|
||||
error, as this isn't something you'd do in e.g a config.py anyways.
|
||||
|
||||
https://github.com/qutebrowser/qutebrowser/issues/3162
|
||||
"""
|
||||
config_stub.val.bindings.default = {'normal': {'a': 'nop'}}
|
||||
config_stub.val.bindings.commands = no_bindings
|
||||
|
||||
key_config_stub.unbind('a')
|
||||
assert key_config_stub.get_command('a', mode='normal') is None
|
||||
key_config_stub.unbind('a')
|
||||
assert key_config_stub.get_command('a', mode='normal') is None
|
||||
|
||||
def test_empty_command(self, key_config_stub):
|
||||
"""Try binding a key to an empty command."""
|
||||
message = "Can't add binding 'x' with empty command in normal mode"
|
||||
with pytest.raises(configexc.KeybindingError, match=message):
|
||||
key_config_stub.bind('x', ' ', mode='normal')
|
||||
|
||||
|
||||
class TestConfig:
|
||||
|
||||
|
||||
@@ -133,9 +133,9 @@ class TestSet:
|
||||
"QtWebEngine backend!"):
|
||||
commands.set(0, 'content.cookies.accept', 'all')
|
||||
|
||||
@pytest.mark.parametrize('option', ['?', '!', 'url.auto_search'])
|
||||
@pytest.mark.parametrize('option', ['?', 'url.auto_search'])
|
||||
def test_empty(self, commands, option):
|
||||
"""Run ':set ?' / ':set !' / ':set url.auto_search'.
|
||||
"""Run ':set ?' / ':set url.auto_search'.
|
||||
|
||||
Should show an error.
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/1109
|
||||
@@ -145,6 +145,16 @@ class TestSet:
|
||||
"value"):
|
||||
commands.set(win_id=0, option=option)
|
||||
|
||||
def test_toggle(self, commands):
|
||||
"""Try toggling a value.
|
||||
|
||||
Should show an nicer error.
|
||||
"""
|
||||
with pytest.raises(cmdexc.CommandError,
|
||||
match="Toggling values was moved to the "
|
||||
":config-cycle command"):
|
||||
commands.set(win_id=0, option='javascript.enabled!')
|
||||
|
||||
def test_invalid(self, commands):
|
||||
"""Run ':set foo?'.
|
||||
|
||||
@@ -396,6 +406,11 @@ class TestWritePy:
|
||||
lines = confpy.read_text('utf-8').splitlines()
|
||||
assert '# Autogenerated config.py' in lines
|
||||
|
||||
def test_oserror(self, commands, tmpdir):
|
||||
"""Test writing to a directory which does not exist."""
|
||||
with pytest.raises(cmdexc.CommandError):
|
||||
commands.config_write_py(str(tmpdir / 'foo' / 'config.py'))
|
||||
|
||||
|
||||
class TestBind:
|
||||
|
||||
|
||||
@@ -258,6 +258,15 @@ class TestEarlyInit:
|
||||
# Font subclass, but doesn't end with "monospace"
|
||||
assert 'fonts.web.family.standard' not in changed_options
|
||||
|
||||
def test_setting_monospace_fonts_family(self, init_patch, args):
|
||||
"""Make sure setting fonts.monospace after a family works.
|
||||
|
||||
See https://github.com/qutebrowser/qutebrowser/issues/3130
|
||||
"""
|
||||
configinit.early_init(args)
|
||||
config.instance.set_str('fonts.web.family.standard', '')
|
||||
config.instance.set_str('fonts.monospace', 'Terminus')
|
||||
|
||||
def test_force_software_rendering(self, monkeypatch, config_stub):
|
||||
"""Setting force_software_rendering should set the environment var."""
|
||||
envvar = 'QT_XCB_FORCE_SOFTWARE_OPENGL'
|
||||
|
||||
@@ -52,3 +52,16 @@ class TestTabWidget:
|
||||
|
||||
with qtbot.waitExposed(widget):
|
||||
widget.show()
|
||||
|
||||
def test_update_tab_titles_benchmark(self, benchmark, widget,
|
||||
qtbot, fake_web_tab):
|
||||
"""Benchmark for update_tab_titles."""
|
||||
widget.addTab(fake_web_tab(), 'foobar')
|
||||
widget.addTab(fake_web_tab(), 'foobar2')
|
||||
widget.addTab(fake_web_tab(), 'foobar3')
|
||||
widget.addTab(fake_web_tab(), 'foobar4')
|
||||
|
||||
with qtbot.waitExposed(widget):
|
||||
widget.show()
|
||||
|
||||
benchmark(widget._update_tab_titles)
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_sqlerror():
|
||||
class TestSqliteError:
|
||||
|
||||
@pytest.mark.parametrize('error_code, environmental', [
|
||||
('9', True), # SQLITE_LOCKED
|
||||
('5', True), # SQLITE_BUSY
|
||||
('19', False), # SQLITE_CONSTRAINT
|
||||
])
|
||||
def test_environmental(self, error_code, environmental):
|
||||
|
||||
@@ -25,12 +25,15 @@ import itertools
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import attr
|
||||
import pytest
|
||||
import pytest_catchlog
|
||||
|
||||
from qutebrowser.utils import log
|
||||
from qutebrowser.misc import utilcmds
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def restore_loggers():
|
||||
@@ -252,3 +255,22 @@ def test_ignore_py_warnings(caplog):
|
||||
assert len(caplog.records) == 1
|
||||
msg = caplog.records[0].message.splitlines()[0]
|
||||
assert msg.endswith("UserWarning: not hidden")
|
||||
|
||||
|
||||
class TestQtMessageHandler:
|
||||
|
||||
@attr.s
|
||||
class Context:
|
||||
|
||||
"""Fake QMessageLogContext."""
|
||||
|
||||
function = attr.ib(default=None)
|
||||
category = attr.ib(default=None)
|
||||
file = attr.ib(default=None)
|
||||
line = attr.ib(default=None)
|
||||
|
||||
def test_empty_message(self, caplog):
|
||||
"""Make sure there's no crash with an empty message."""
|
||||
log.qt_message_handler(QtCore.QtDebugMsg, self.Context(), "")
|
||||
assert len(caplog.records) == 1
|
||||
assert caplog.records[0].msg == "Logged empty message!"
|
||||
|
||||
8
tox.ini
8
tox.ini
@@ -86,7 +86,7 @@ setenv =
|
||||
passenv = {[testenv]passenv}
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
PyQt5==5.9
|
||||
PyQt5==5.9.2
|
||||
commands = {envpython} -bb -m pytest {posargs:tests}
|
||||
|
||||
[testenv:py36-pyqt59]
|
||||
@@ -97,7 +97,7 @@ setenv =
|
||||
passenv = {[testenv]passenv}
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
PyQt5==5.9
|
||||
PyQt5==5.9.2
|
||||
commands = {envpython} -bb -m pytest {posargs:tests}
|
||||
|
||||
# test envs with coverage
|
||||
@@ -110,7 +110,7 @@ setenv =
|
||||
passenv = {[testenv]passenv}
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
PyQt5==5.9
|
||||
PyQt5==5.9.2
|
||||
commands =
|
||||
{envpython} -bb -m pytest --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests}
|
||||
{envpython} scripts/dev/check_coverage.py {posargs}
|
||||
@@ -123,7 +123,7 @@ setenv =
|
||||
passenv = {[testenv]passenv}
|
||||
deps =
|
||||
{[testenv]deps}
|
||||
PyQt5==5.9
|
||||
PyQt5==5.9.2
|
||||
commands =
|
||||
{envpython} -bb -m pytest --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests}
|
||||
{envpython} scripts/dev/check_coverage.py {posargs}
|
||||
|
||||
Reference in New Issue
Block a user