commit
0611dc0cb4
@ -28,6 +28,8 @@ Breaking changes
|
|||||||
- New dependency on ruamel.yaml; dropped PyYAML dependency.
|
- New dependency on ruamel.yaml; dropped PyYAML dependency.
|
||||||
- The QtWebEngine backend is now used by default if available.
|
- The QtWebEngine backend is now used by default if available.
|
||||||
- New config system which ignores the old config file.
|
- New config system which ignores the old config file.
|
||||||
|
- The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
|
||||||
|
that PyQt5.QtOpenGL is still a dependency.
|
||||||
|
|
||||||
Major changes
|
Major changes
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
@ -45,8 +45,8 @@ pointers:
|
|||||||
|
|
||||||
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
|
* https://github.com/qutebrowser/qutebrowser/labels/easy[Issues which should
|
||||||
be easy to solve]
|
be easy to solve]
|
||||||
* https://github.com/qutebrowser/qutebrowser/labels/not%20code[Issues which
|
* https://github.com/qutebrowser/qutebrowser/labels/component%3A%20docs[Documentation
|
||||||
require little/no coding]
|
* issues which require little/no coding]
|
||||||
|
|
||||||
If you prefer C++ or Javascript to Python, see the relevant issues which involve
|
If you prefer C++ or Javascript to Python, see the relevant issues which involve
|
||||||
work in those languages:
|
work in those languages:
|
||||||
|
@ -118,7 +118,6 @@ The following software and libraries are required to run qutebrowser:
|
|||||||
* http://jinja.pocoo.org/[jinja2]
|
* http://jinja.pocoo.org/[jinja2]
|
||||||
* http://pygments.org/[pygments]
|
* http://pygments.org/[pygments]
|
||||||
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
* http://pyyaml.org/wiki/PyYAML[PyYAML]
|
||||||
* http://pyopengl.sourceforge.net/[PyOpenGL] when using QtWebEngine
|
|
||||||
|
|
||||||
The following libraries are optional:
|
The following libraries are optional:
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
certifi==2017.4.17
|
certifi==2017.7.27.1
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
codecov==2.0.9
|
codecov==2.0.9
|
||||||
coverage==4.4.1
|
coverage==4.4.1
|
||||||
idna==2.5
|
idna==2.5
|
||||||
requests==2.18.1
|
requests==2.18.2
|
||||||
urllib3==1.21.1
|
urllib3==1.22
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
flake8==2.6.2 # rq.filter: < 3.0.0
|
flake8==2.6.2 # rq.filter: < 3.0.0
|
||||||
flake8-copyright==0.2.0
|
flake8-copyright==0.2.0
|
||||||
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
|
flake8-debugger==1.4.0 # rq.filter: != 2.0.0
|
||||||
flake8-deprecated==1.2
|
flake8-deprecated==1.2.1
|
||||||
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
|
flake8-docstrings==1.0.3 # rq.filter: < 1.1.0
|
||||||
flake8-future-import==0.4.3
|
flake8-future-import==0.4.3
|
||||||
flake8-mock==0.3
|
flake8-mock==0.3
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
appdirs==1.4.3
|
appdirs==1.4.3
|
||||||
packaging==16.8
|
packaging==16.8
|
||||||
pyparsing==2.2.0
|
pyparsing==2.2.0
|
||||||
setuptools==36.2.0
|
setuptools==36.2.5
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
wheel==0.29.0
|
wheel==0.29.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
-e git+https://github.com/PyCQA/astroid.git#egg=astroid
|
||||||
certifi==2017.4.17
|
certifi==2017.7.27.1
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
github3.py==0.9.6
|
github3.py==0.9.6
|
||||||
idna==2.5
|
idna==2.5
|
||||||
@ -10,9 +10,9 @@ lazy-object-proxy==1.3.1
|
|||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
-e git+https://github.com/PyCQA/pylint.git#egg=pylint
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.1
|
requests==2.18.2
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
uritemplate==3.0.0
|
uritemplate==3.0.0
|
||||||
uritemplate.py==3.0.2
|
uritemplate.py==3.0.2
|
||||||
urllib3==1.21.1
|
urllib3==1.22
|
||||||
wrapt==1.10.10
|
wrapt==1.10.10
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
astroid==1.5.3
|
astroid==1.5.3
|
||||||
certifi==2017.4.17
|
certifi==2017.7.27.1
|
||||||
chardet==3.0.4
|
chardet==3.0.4
|
||||||
github3.py==0.9.6
|
github3.py==0.9.6
|
||||||
idna==2.5
|
idna==2.5
|
||||||
@ -10,9 +10,9 @@ lazy-object-proxy==1.3.1
|
|||||||
mccabe==0.6.1
|
mccabe==0.6.1
|
||||||
pylint==1.7.2
|
pylint==1.7.2
|
||||||
./scripts/dev/pylint_checkers
|
./scripts/dev/pylint_checkers
|
||||||
requests==2.18.1
|
requests==2.18.2
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
uritemplate==3.0.0
|
uritemplate==3.0.0
|
||||||
uritemplate.py==3.0.2
|
uritemplate.py==3.0.2
|
||||||
urllib3==1.21.1
|
urllib3==1.22
|
||||||
wrapt==1.10.10
|
wrapt==1.10.10
|
||||||
|
@ -5,14 +5,14 @@ cheroot==5.7.0
|
|||||||
click==6.7
|
click==6.7
|
||||||
# colorama==0.3.9
|
# colorama==0.3.9
|
||||||
coverage==4.4.1
|
coverage==4.4.1
|
||||||
decorator==4.1.1
|
decorator==4.1.2
|
||||||
EasyProcess==0.2.3
|
EasyProcess==0.2.3
|
||||||
fields==5.0.0
|
fields==5.0.0
|
||||||
Flask==0.12.2
|
Flask==0.12.2
|
||||||
glob2==0.5
|
glob2==0.5
|
||||||
httpbin==0.5.0
|
httpbin==0.5.0
|
||||||
hunter==1.4.1
|
hunter==1.4.1
|
||||||
hypothesis==3.13.0
|
hypothesis==3.14.0
|
||||||
itsdangerous==0.24
|
itsdangerous==0.24
|
||||||
# Jinja2==2.9.6
|
# Jinja2==2.9.6
|
||||||
Mako==1.0.7
|
Mako==1.0.7
|
||||||
@ -22,12 +22,12 @@ parse-type==0.3.4
|
|||||||
py==1.4.34
|
py==1.4.34
|
||||||
pytest==3.1.3
|
pytest==3.1.3
|
||||||
pytest-bdd==2.18.2
|
pytest-bdd==2.18.2
|
||||||
pytest-benchmark==3.0.0
|
pytest-benchmark==3.1.1
|
||||||
pytest-catchlog==1.2.2
|
pytest-catchlog==1.2.2
|
||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
pytest-faulthandler==1.3.1
|
pytest-faulthandler==1.3.1
|
||||||
pytest-instafail==0.3.0
|
pytest-instafail==0.3.0
|
||||||
pytest-mock==1.6.0
|
pytest-mock==1.6.2
|
||||||
pytest-qt==2.1.2
|
pytest-qt==2.1.2
|
||||||
pytest-repeat==0.4.1
|
pytest-repeat==0.4.1
|
||||||
pytest-rerunfailures==2.2
|
pytest-rerunfailures==2.2
|
||||||
@ -35,5 +35,5 @@ pytest-travis-fold==1.2.0
|
|||||||
pytest-xvfb==1.0.0
|
pytest-xvfb==1.0.0
|
||||||
PyVirtualDisplay==0.2.1
|
PyVirtualDisplay==0.2.1
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
vulture==0.16
|
vulture==0.21
|
||||||
Werkzeug==0.12.2
|
Werkzeug==0.12.2
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
# This file is automatically generated by scripts/dev/recompile_requirements.py
|
||||||
|
|
||||||
vulture==0.16
|
vulture==0.21
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[pytest]
|
[pytest]
|
||||||
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail --pythonwarnings error
|
addopts = --strict -rfEw --faulthandler-timeout=70 --instafail --pythonwarnings error --benchmark-columns=Min,Max,Median
|
||||||
testpaths = tests
|
testpaths = tests
|
||||||
markers =
|
markers =
|
||||||
gui: Tests using the GUI (e.g. spawning widgets)
|
gui: Tests using the GUI (e.g. spawning widgets)
|
||||||
|
@ -77,13 +77,9 @@ class UrlMarkManager(QObject):
|
|||||||
|
|
||||||
Signals:
|
Signals:
|
||||||
changed: Emitted when anything changed.
|
changed: Emitted when anything changed.
|
||||||
added: Emitted when a new quickmark/bookmark was added.
|
|
||||||
removed: Emitted when an existing quickmark/bookmark was removed.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
changed = pyqtSignal()
|
changed = pyqtSignal()
|
||||||
added = pyqtSignal(str, str)
|
|
||||||
removed = pyqtSignal(str)
|
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
"""Initialize and read quickmarks."""
|
"""Initialize and read quickmarks."""
|
||||||
@ -121,7 +117,6 @@ class UrlMarkManager(QObject):
|
|||||||
"""
|
"""
|
||||||
del self.marks[key]
|
del self.marks[key]
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
self.removed.emit(key)
|
|
||||||
|
|
||||||
|
|
||||||
class QuickmarkManager(UrlMarkManager):
|
class QuickmarkManager(UrlMarkManager):
|
||||||
@ -133,7 +128,6 @@ class QuickmarkManager(UrlMarkManager):
|
|||||||
- self.marks maps names to URLs.
|
- self.marks maps names to URLs.
|
||||||
- changed gets emitted with the name as first argument and the URL as
|
- changed gets emitted with the name as first argument and the URL as
|
||||||
second argument.
|
second argument.
|
||||||
- removed gets emitted with the name as argument.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _init_lineparser(self):
|
def _init_lineparser(self):
|
||||||
@ -193,7 +187,6 @@ class QuickmarkManager(UrlMarkManager):
|
|||||||
"""Really set the quickmark."""
|
"""Really set the quickmark."""
|
||||||
self.marks[name] = url
|
self.marks[name] = url
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
self.added.emit(name, url)
|
|
||||||
log.misc.debug("Added quickmark {} for {}".format(name, url))
|
log.misc.debug("Added quickmark {} for {}".format(name, url))
|
||||||
|
|
||||||
if name in self.marks:
|
if name in self.marks:
|
||||||
@ -243,7 +236,6 @@ class BookmarkManager(UrlMarkManager):
|
|||||||
- self.marks maps URLs to titles.
|
- self.marks maps URLs to titles.
|
||||||
- changed gets emitted with the URL as first argument and the title as
|
- changed gets emitted with the URL as first argument and the title as
|
||||||
second argument.
|
second argument.
|
||||||
- removed gets emitted with the URL as argument.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _init_lineparser(self):
|
def _init_lineparser(self):
|
||||||
@ -295,5 +287,4 @@ class BookmarkManager(UrlMarkManager):
|
|||||||
else:
|
else:
|
||||||
self.marks[urlstr] = title
|
self.marks[urlstr] = title
|
||||||
self.changed.emit()
|
self.changed.emit()
|
||||||
self.added.emit(title, urlstr)
|
|
||||||
return True
|
return True
|
||||||
|
@ -28,6 +28,9 @@ Module attributes:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
from PyQt5.QtWebEngineWidgets import (QWebEngineSettings, QWebEngineProfile,
|
||||||
@ -199,6 +202,11 @@ def init(args):
|
|||||||
if args.enable_webengine_inspector:
|
if args.enable_webengine_inspector:
|
||||||
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = str(utils.random_port())
|
||||||
|
|
||||||
|
# WORKAROUND for
|
||||||
|
# https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
|
||||||
|
if sys.platform == 'linux':
|
||||||
|
ctypes.CDLL(ctypes.util.find_library("GL"), mode=ctypes.RTLD_GLOBAL)
|
||||||
|
|
||||||
_init_profiles()
|
_init_profiles()
|
||||||
|
|
||||||
# We need to do this here as a WORKAROUND for
|
# We need to do this here as a WORKAROUND for
|
||||||
|
@ -148,6 +148,8 @@ class CompletionView(QTreeView):
|
|||||||
|
|
||||||
def _resize_columns(self):
|
def _resize_columns(self):
|
||||||
"""Resize the completion columns based on column_widths."""
|
"""Resize the completion columns based on column_widths."""
|
||||||
|
if self.model() is None:
|
||||||
|
return
|
||||||
width = self.size().width()
|
width = self.size().width()
|
||||||
column_widths = self.model().column_widths
|
column_widths = self.model().column_widths
|
||||||
pixel_widths = [(width * perc // 100) for perc in column_widths]
|
pixel_widths = [(width * perc // 100) for perc in column_widths]
|
||||||
@ -253,6 +255,10 @@ class CompletionView(QTreeView):
|
|||||||
selmodel.setCurrentIndex(
|
selmodel.setCurrentIndex(
|
||||||
idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
|
idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
|
||||||
|
|
||||||
|
# if the last item is focused, try to fetch more
|
||||||
|
if idx.row() == self.model().rowCount(idx.parent()) - 1:
|
||||||
|
self.expandAll()
|
||||||
|
|
||||||
count = self.model().count()
|
count = self.model().count()
|
||||||
if count == 0:
|
if count == 0:
|
||||||
self.hide()
|
self.hide()
|
||||||
|
@ -93,10 +93,12 @@ class HistoryCategory(QSqlQueryModel):
|
|||||||
self._query.run(pat=pattern)
|
self._query.run(pat=pattern)
|
||||||
self.setQuery(self._query)
|
self.setQuery(self._query)
|
||||||
|
|
||||||
def removeRows(self, _row, _count, _parent=None):
|
def removeRows(self, row, _count, _parent=None):
|
||||||
"""Override QAbstractItemModel::removeRows to re-run sql query."""
|
"""Override QAbstractItemModel::removeRows to re-run sql query."""
|
||||||
# re-run query to reload updated table
|
# re-run query to reload updated table
|
||||||
with debug.log_time('sql', 'Re-running completion query post-delete'):
|
with debug.log_time('sql', 'Re-running completion query post-delete'):
|
||||||
self._query.run()
|
self._query.run()
|
||||||
self.setQuery(self._query)
|
self.setQuery(self._query)
|
||||||
|
while self.rowCount() < row:
|
||||||
|
self.fetchMore()
|
||||||
return True
|
return True
|
||||||
|
@ -60,17 +60,33 @@ def helptopic():
|
|||||||
|
|
||||||
def quickmark():
|
def quickmark():
|
||||||
"""A CompletionModel filled with all quickmarks."""
|
"""A CompletionModel filled with all quickmarks."""
|
||||||
|
def delete(data):
|
||||||
|
"""Delete a quickmark from the completion menu."""
|
||||||
|
name = data[0]
|
||||||
|
quickmark_manager = objreg.get('quickmark-manager')
|
||||||
|
log.completion.debug('Deleting quickmark {}'.format(name))
|
||||||
|
quickmark_manager.delete(name)
|
||||||
|
|
||||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||||
marks = objreg.get('quickmark-manager').marks.items()
|
marks = objreg.get('quickmark-manager').marks.items()
|
||||||
model.add_category(listcategory.ListCategory('Quickmarks', marks))
|
model.add_category(listcategory.ListCategory('Quickmarks', marks,
|
||||||
|
delete_func=delete))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
def bookmark():
|
def bookmark():
|
||||||
"""A CompletionModel filled with all bookmarks."""
|
"""A CompletionModel filled with all bookmarks."""
|
||||||
|
def delete(data):
|
||||||
|
"""Delete a bookmark from the completion menu."""
|
||||||
|
urlstr = data[0]
|
||||||
|
log.completion.debug('Deleting bookmark {}'.format(urlstr))
|
||||||
|
bookmark_manager = objreg.get('bookmark-manager')
|
||||||
|
bookmark_manager.delete(urlstr)
|
||||||
|
|
||||||
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
model = completionmodel.CompletionModel(column_widths=(30, 70, 0))
|
||||||
marks = objreg.get('bookmark-manager').marks.items()
|
marks = objreg.get('bookmark-manager').marks.items()
|
||||||
model.add_category(listcategory.ListCategory('Bookmarks', marks))
|
model.add_category(listcategory.ListCategory('Bookmarks', marks,
|
||||||
|
delete_func=delete))
|
||||||
return model
|
return model
|
||||||
|
|
||||||
|
|
||||||
@ -126,11 +142,12 @@ def bind(key):
|
|||||||
key: the key being bound.
|
key: the key being bound.
|
||||||
"""
|
"""
|
||||||
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
|
model = completionmodel.CompletionModel(column_widths=(20, 60, 20))
|
||||||
cmd_name = objreg.get('key-config').get_bindings_for('normal').get(key)
|
cmd_text = objreg.get('key-config').get_bindings_for('normal').get(key)
|
||||||
|
|
||||||
if cmd_name:
|
if cmd_text:
|
||||||
|
cmd_name = cmd_text.split(' ')[0]
|
||||||
cmd = cmdutils.cmd_dict.get(cmd_name)
|
cmd = cmdutils.cmd_dict.get(cmd_name)
|
||||||
data = [(cmd_name, cmd.desc, key)]
|
data = [(cmd_text, cmd.desc, key)]
|
||||||
model.add_category(listcategory.ListCategory("Current", data))
|
model.add_category(listcategory.ListCategory("Current", data))
|
||||||
|
|
||||||
cmdlist = _get_cmd_completions(include_hidden=True, include_aliases=True)
|
cmdlist = _get_cmd_completions(include_hidden=True, include_aliases=True)
|
||||||
|
@ -708,13 +708,16 @@ class TabbedBrowser(tabwidget.TabWidget):
|
|||||||
}
|
}
|
||||||
msg = messages[status]
|
msg = messages[status]
|
||||||
|
|
||||||
|
def show_error_page(html):
|
||||||
|
tab.set_html(html)
|
||||||
|
log.webview.error(msg)
|
||||||
|
|
||||||
if qtutils.version_check('5.9'):
|
if qtutils.version_check('5.9'):
|
||||||
url_string = tab.url(requested=True).toDisplayString()
|
url_string = tab.url(requested=True).toDisplayString()
|
||||||
error_page = jinja.render(
|
error_page = jinja.render(
|
||||||
'error.html', title="Error loading {}".format(url_string),
|
'error.html', title="Error loading {}".format(url_string),
|
||||||
url=url_string, error=msg, icon='')
|
url=url_string, error=msg, icon='')
|
||||||
QTimer.singleShot(0, lambda: tab.set_html(error_page))
|
QTimer.singleShot(100, lambda: show_error_page(error_page))
|
||||||
log.webview.error(msg)
|
|
||||||
else:
|
else:
|
||||||
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698
|
# WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698
|
||||||
message.error(msg)
|
message.error(msg)
|
||||||
|
@ -36,7 +36,6 @@ import traceback
|
|||||||
import signal
|
import signal
|
||||||
import importlib
|
import importlib
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
|
||||||
try:
|
try:
|
||||||
import tkinter
|
import tkinter
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -344,12 +343,6 @@ def check_libraries(backend):
|
|||||||
modules['PyQt5.QtWebEngineWidgets'] = _missing_str("QtWebEngine",
|
modules['PyQt5.QtWebEngineWidgets'] = _missing_str("QtWebEngine",
|
||||||
webengine=True)
|
webengine=True)
|
||||||
modules['PyQt5.QtOpenGL'] = _missing_str("PyQt5.QtOpenGL")
|
modules['PyQt5.QtOpenGL'] = _missing_str("PyQt5.QtOpenGL")
|
||||||
# Workaround for a black screen with some setups
|
|
||||||
# https://github.com/spyder-ide/spyder/issues/3226
|
|
||||||
if not os.environ.get('QUTE_NO_OPENGL_WORKAROUND'):
|
|
||||||
# Hide "No OpenGL_accelerate module loaded: ..." message
|
|
||||||
logging.getLogger('OpenGL.acceleratesupport').propagate = False
|
|
||||||
modules['OpenGL.GL'] = _missing_str("PyOpenGL")
|
|
||||||
else:
|
else:
|
||||||
assert backend == 'webkit'
|
assert backend == 'webkit'
|
||||||
modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit")
|
modules['PyQt5.QtWebKit'] = _missing_str("PyQt5.QtWebKit")
|
||||||
|
@ -23,7 +23,7 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import sip
|
import sip
|
||||||
from PyQt5.QtCore import pyqtSignal, QUrl, QObject, QPoint, QTimer
|
from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
import yaml
|
import yaml
|
||||||
try:
|
try:
|
||||||
@ -106,14 +106,8 @@ class SessionManager(QObject):
|
|||||||
closed.
|
closed.
|
||||||
_current: The name of the currently loaded session, or None.
|
_current: The name of the currently loaded session, or None.
|
||||||
did_load: Set when a session was loaded.
|
did_load: Set when a session was loaded.
|
||||||
|
|
||||||
Signals:
|
|
||||||
update_completion: Emitted when the session completion should get
|
|
||||||
updated.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
update_completion = pyqtSignal()
|
|
||||||
|
|
||||||
def __init__(self, base_path, parent=None):
|
def __init__(self, base_path, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._current = None
|
self._current = None
|
||||||
@ -303,8 +297,7 @@ class SessionManager(QObject):
|
|||||||
encoding='utf-8', allow_unicode=True)
|
encoding='utf-8', allow_unicode=True)
|
||||||
except (OSError, UnicodeEncodeError, yaml.YAMLError) as e:
|
except (OSError, UnicodeEncodeError, yaml.YAMLError) as e:
|
||||||
raise SessionError(e)
|
raise SessionError(e)
|
||||||
else:
|
|
||||||
self.update_completion.emit()
|
|
||||||
if load_next_time:
|
if load_next_time:
|
||||||
state_config = objreg.get('state-config')
|
state_config = objreg.get('state-config')
|
||||||
state_config['general']['session'] = name
|
state_config['general']['session'] = name
|
||||||
@ -425,7 +418,6 @@ class SessionManager(QObject):
|
|||||||
os.remove(path)
|
os.remove(path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise SessionError(e)
|
raise SessionError(e)
|
||||||
self.update_completion.emit()
|
|
||||||
|
|
||||||
def list_sessions(self):
|
def list_sessions(self):
|
||||||
"""Get a list of all session names."""
|
"""Get a list of all session names."""
|
||||||
|
@ -186,7 +186,6 @@ def _module_versions():
|
|||||||
('yaml', ['__version__']),
|
('yaml', ['__version__']),
|
||||||
('cssutils', ['__version__']),
|
('cssutils', ['__version__']),
|
||||||
('typing', []),
|
('typing', []),
|
||||||
('OpenGL', ['__version__']),
|
|
||||||
('PyQt5.QtWebEngineWidgets', []),
|
('PyQt5.QtWebEngineWidgets', []),
|
||||||
('PyQt5.QtWebKitWidgets', []),
|
('PyQt5.QtWebKitWidgets', []),
|
||||||
])
|
])
|
||||||
|
@ -7,4 +7,3 @@ MarkupSafe==1.0
|
|||||||
Pygments==2.2.0
|
Pygments==2.2.0
|
||||||
pyPEG2==2.15.2
|
pyPEG2==2.15.2
|
||||||
PyYAML==3.12
|
PyYAML==3.12
|
||||||
PyOpenGL==3.1.0
|
|
||||||
|
@ -89,6 +89,12 @@ def whitelist_generator():
|
|||||||
# vulture doesn't notice the hasattr() and thus thinks netrc_used is unused
|
# vulture doesn't notice the hasattr() and thus thinks netrc_used is unused
|
||||||
# in NetworkManager.on_authentication_required
|
# in NetworkManager.on_authentication_required
|
||||||
yield 'PyQt5.QtNetwork.QNetworkReply.netrc_used'
|
yield 'PyQt5.QtNetwork.QNetworkReply.netrc_used'
|
||||||
|
yield 'qutebrowser.browser.downloads.last_used_directory'
|
||||||
|
yield 'PaintContext.clip' # from completiondelegate.py
|
||||||
|
yield 'logging.LogRecord.log_color' # from logging.py
|
||||||
|
yield 'scripts.utils.use_color' # from asciidoc2html.py
|
||||||
|
for attr in ['pyeval_output', 'log_clipboard', 'fake_clipboard']:
|
||||||
|
yield 'qutebrowser.misc.utilcmds.' + attr
|
||||||
|
|
||||||
for attr in ['fileno', 'truncate', 'closed', 'readable']:
|
for attr in ['fileno', 'truncate', 'closed', 'readable']:
|
||||||
yield 'qutebrowser.utils.qtutils.PyQIODevice.' + attr
|
yield 'qutebrowser.utils.qtutils.PyQIODevice.' + attr
|
||||||
@ -111,7 +117,7 @@ def filter_func(item):
|
|||||||
True if the missing function should be filtered/ignored, False
|
True if the missing function should be filtered/ignored, False
|
||||||
otherwise.
|
otherwise.
|
||||||
"""
|
"""
|
||||||
return bool(re.match(r'[a-z]+[A-Z][a-zA-Z]+', str(item)))
|
return bool(re.match(r'[a-z]+[A-Z][a-zA-Z]+', item.name))
|
||||||
|
|
||||||
|
|
||||||
def report(items):
|
def report(items):
|
||||||
@ -125,7 +131,7 @@ def report(items):
|
|||||||
relpath = os.path.relpath(item.filename)
|
relpath = os.path.relpath(item.filename)
|
||||||
path = relpath if not relpath.startswith('..') else item.filename
|
path = relpath if not relpath.startswith('..') else item.filename
|
||||||
output.append("{}:{}: Unused {} '{}'".format(path, item.lineno,
|
output.append("{}:{}: Unused {} '{}'".format(path, item.lineno,
|
||||||
item.typ, item))
|
item.typ, item.name))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
@ -65,6 +65,9 @@ def completionview(qtbot, status_command_stub, config_stub, win_registry,
|
|||||||
}
|
}
|
||||||
# mock the Completer that the widget creates in its constructor
|
# mock the Completer that the widget creates in its constructor
|
||||||
mocker.patch('qutebrowser.completion.completer.Completer', autospec=True)
|
mocker.patch('qutebrowser.completion.completer.Completer', autospec=True)
|
||||||
|
mocker.patch(
|
||||||
|
'qutebrowser.completion.completiondelegate.CompletionItemDelegate',
|
||||||
|
new=lambda *_: None)
|
||||||
view = completionwidget.CompletionView(win_id=0)
|
view = completionwidget.CompletionView(win_id=0)
|
||||||
qtbot.addWidget(view)
|
qtbot.addWidget(view)
|
||||||
return view
|
return view
|
||||||
@ -186,6 +189,37 @@ def test_completion_item_focus_no_model(which, completionview, qtbot):
|
|||||||
completionview.completion_item_focus(which)
|
completionview.completion_item_focus(which)
|
||||||
|
|
||||||
|
|
||||||
|
def test_completion_item_focus_fetch(completionview, qtbot):
|
||||||
|
"""Test that on_next_prev_item moves the selection properly.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
which: the direction in which to move the selection.
|
||||||
|
tree: Each list represents a completion category, with each string
|
||||||
|
being an item under that category.
|
||||||
|
expected: expected argument from on_selection_changed for each
|
||||||
|
successive movement. None implies no signal should be
|
||||||
|
emitted.
|
||||||
|
"""
|
||||||
|
model = completionmodel.CompletionModel()
|
||||||
|
cat = mock.Mock(spec=['layoutChanged', 'layoutAboutToBeChanged',
|
||||||
|
'canFetchMore', 'fetchMore', 'rowCount', 'index', 'data'])
|
||||||
|
cat.canFetchMore = lambda *_: True
|
||||||
|
cat.rowCount = lambda *_: 2
|
||||||
|
cat.fetchMore = mock.Mock()
|
||||||
|
model.add_category(cat)
|
||||||
|
completionview.set_model(model)
|
||||||
|
# clear the fetchMore call that happens on set_model
|
||||||
|
cat.reset_mock()
|
||||||
|
|
||||||
|
# not at end, fetchMore shouldn't be called
|
||||||
|
completionview.completion_item_focus('next')
|
||||||
|
assert not cat.fetchMore.called
|
||||||
|
|
||||||
|
# at end, fetchMore should be called
|
||||||
|
completionview.completion_item_focus('next')
|
||||||
|
assert cat.fetchMore.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('show', ['always', 'auto', 'never'])
|
@pytest.mark.parametrize('show', ['always', 'auto', 'never'])
|
||||||
@pytest.mark.parametrize('rows', [[], ['Aa'], ['Aa', 'Bb']])
|
@pytest.mark.parametrize('rows', [[], ['Aa'], ['Aa', 'Bb']])
|
||||||
@pytest.mark.parametrize('quick_complete', [True, False])
|
@pytest.mark.parametrize('quick_complete', [True, False])
|
||||||
@ -240,3 +274,8 @@ def test_completion_item_del_no_selection(completionview):
|
|||||||
with pytest.raises(cmdexc.CommandError, match='No item selected!'):
|
with pytest.raises(cmdexc.CommandError, match='No item selected!'):
|
||||||
completionview.completion_item_del()
|
completionview.completion_item_del()
|
||||||
assert not func.called
|
assert not func.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_resize_no_model(completionview, qtbot):
|
||||||
|
"""Ensure no crash if resizeEvent is triggered with no model (#2854)."""
|
||||||
|
completionview.resizeEvent(None)
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from PyQt5.QtCore import QModelIndex
|
|
||||||
|
|
||||||
from qutebrowser.misc import sql
|
from qutebrowser.misc import sql
|
||||||
from qutebrowser.completion.models import histcategory
|
from qutebrowser.completion.models import histcategory
|
||||||
@ -147,6 +146,22 @@ def test_remove_rows(hist, model_validator):
|
|||||||
model_validator.set_model(cat)
|
model_validator.set_model(cat)
|
||||||
cat.set_pattern('')
|
cat.set_pattern('')
|
||||||
hist.delete('url', 'foo')
|
hist.delete('url', 'foo')
|
||||||
# histcategory does not care which index was removed, it just regenerates
|
cat.removeRows(0, 1)
|
||||||
cat.removeRows(QModelIndex(), 1)
|
|
||||||
model_validator.validate([('bar', 'Bar', '')])
|
model_validator.validate([('bar', 'Bar', '')])
|
||||||
|
|
||||||
|
|
||||||
|
def test_remove_rows_fetch(hist):
|
||||||
|
"""removeRows should fetch enough data to make the current index valid."""
|
||||||
|
# we cannot use model_validator as it will fetch everything up front
|
||||||
|
hist.insert_batch({'url': [str(i) for i in range(300)]})
|
||||||
|
cat = histcategory.HistoryCategory()
|
||||||
|
cat.set_pattern('')
|
||||||
|
|
||||||
|
# sanity check that we didn't fetch everything up front
|
||||||
|
assert cat.rowCount() < 300
|
||||||
|
cat.fetchMore()
|
||||||
|
assert cat.rowCount() == 300
|
||||||
|
|
||||||
|
hist.delete('url', '298')
|
||||||
|
cat.removeRows(297, 1)
|
||||||
|
assert cat.rowCount() == 299
|
||||||
|
@ -252,6 +252,27 @@ def test_quickmark_completion(qtmodeltester, quickmarks):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('row, removed', [
|
||||||
|
(0, 'aw'),
|
||||||
|
(1, 'ddg'),
|
||||||
|
(2, 'wiki'),
|
||||||
|
])
|
||||||
|
def test_quickmark_completion_delete(qtmodeltester, quickmarks, row, removed):
|
||||||
|
"""Test deleting a quickmark from the quickmark completion model."""
|
||||||
|
model = miscmodels.quickmark()
|
||||||
|
model.set_pattern('')
|
||||||
|
qtmodeltester.data_display_may_return_none = True
|
||||||
|
qtmodeltester.check(model)
|
||||||
|
|
||||||
|
parent = model.index(0, 0)
|
||||||
|
idx = model.index(row, 0, parent)
|
||||||
|
|
||||||
|
before = set(quickmarks.marks.keys())
|
||||||
|
model.delete_cur_item(idx)
|
||||||
|
after = set(quickmarks.marks.keys())
|
||||||
|
assert before.difference(after) == {removed}
|
||||||
|
|
||||||
|
|
||||||
def test_bookmark_completion(qtmodeltester, bookmarks):
|
def test_bookmark_completion(qtmodeltester, bookmarks):
|
||||||
"""Test the results of bookmark completion."""
|
"""Test the results of bookmark completion."""
|
||||||
model = miscmodels.bookmark()
|
model = miscmodels.bookmark()
|
||||||
@ -268,6 +289,27 @@ def test_bookmark_completion(qtmodeltester, bookmarks):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('row, removed', [
|
||||||
|
(0, 'http://qutebrowser.org'),
|
||||||
|
(1, 'https://github.com'),
|
||||||
|
(2, 'https://python.org'),
|
||||||
|
])
|
||||||
|
def test_bookmark_completion_delete(qtmodeltester, bookmarks, row, removed):
|
||||||
|
"""Test deleting a quickmark from the quickmark completion model."""
|
||||||
|
model = miscmodels.bookmark()
|
||||||
|
model.set_pattern('')
|
||||||
|
qtmodeltester.data_display_may_return_none = True
|
||||||
|
qtmodeltester.check(model)
|
||||||
|
|
||||||
|
parent = model.index(0, 0)
|
||||||
|
idx = model.index(row, 0, parent)
|
||||||
|
|
||||||
|
before = set(bookmarks.marks.keys())
|
||||||
|
model.delete_cur_item(idx)
|
||||||
|
after = set(bookmarks.marks.keys())
|
||||||
|
assert before.difference(after) == {removed}
|
||||||
|
|
||||||
|
|
||||||
def test_url_completion(qtmodeltester, web_history_populated,
|
def test_url_completion(qtmodeltester, web_history_populated,
|
||||||
quickmarks, bookmarks):
|
quickmarks, bookmarks):
|
||||||
"""Test the results of url completion.
|
"""Test the results of url completion.
|
||||||
@ -583,7 +625,7 @@ def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub,
|
|||||||
_patch_cmdutils(monkeypatch, stubs,
|
_patch_cmdutils(monkeypatch, stubs,
|
||||||
'qutebrowser.completion.models.miscmodels.cmdutils')
|
'qutebrowser.completion.models.miscmodels.cmdutils')
|
||||||
config_stub.data['aliases'] = {'rock': 'roll'}
|
config_stub.data['aliases'] = {'rock': 'roll'}
|
||||||
key_config_stub.set_bindings_for('normal', {'s': 'stop',
|
key_config_stub.set_bindings_for('normal', {'s': 'stop now',
|
||||||
'rr': 'roll',
|
'rr': 'roll',
|
||||||
'ro': 'rock'})
|
'ro': 'rock'})
|
||||||
model = miscmodels.bind('s')
|
model = miscmodels.bind('s')
|
||||||
@ -593,14 +635,14 @@ def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub,
|
|||||||
|
|
||||||
_check_completions(model, {
|
_check_completions(model, {
|
||||||
"Current": [
|
"Current": [
|
||||||
('stop', 'stop qutebrowser', 's'),
|
('stop now', 'stop qutebrowser', 's'),
|
||||||
],
|
],
|
||||||
"Commands": [
|
"Commands": [
|
||||||
('drop', 'drop all user data', ''),
|
('drop', 'drop all user data', ''),
|
||||||
('hide', '', ''),
|
('hide', '', ''),
|
||||||
('rock', "Alias for 'roll'", 'ro'),
|
('rock', "Alias for 'roll'", 'ro'),
|
||||||
('roll', 'never gonna give you up', 'rr'),
|
('roll', 'never gonna give you up', 'rr'),
|
||||||
('stop', 'stop qutebrowser', 's'),
|
('stop', 'stop qutebrowser', ''),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -215,11 +215,6 @@ class TestSave:
|
|||||||
objreg.delete('main-window', scope='window', window=0)
|
objreg.delete('main-window', scope='window', window=0)
|
||||||
objreg.delete('tabbed-browser', scope='window', window=0)
|
objreg.delete('tabbed-browser', scope='window', window=0)
|
||||||
|
|
||||||
def test_update_completion_signal(self, sess_man, tmpdir, qtbot):
|
|
||||||
session_path = tmpdir / 'foo.yml'
|
|
||||||
with qtbot.waitSignal(sess_man.update_completion):
|
|
||||||
sess_man.save(str(session_path))
|
|
||||||
|
|
||||||
def test_no_state_config(self, sess_man, tmpdir, state_config):
|
def test_no_state_config(self, sess_man, tmpdir, state_config):
|
||||||
session_path = tmpdir / 'foo.yml'
|
session_path = tmpdir / 'foo.yml'
|
||||||
sess_man.save(str(session_path))
|
sess_man.save(str(session_path))
|
||||||
@ -367,14 +362,6 @@ class TestLoadTab:
|
|||||||
assert loaded_item.original_url == expected
|
assert loaded_item.original_url == expected
|
||||||
|
|
||||||
|
|
||||||
def test_delete_update_completion_signal(sess_man, qtbot, tmpdir):
|
|
||||||
sess = tmpdir / 'foo.yml'
|
|
||||||
sess.ensure()
|
|
||||||
|
|
||||||
with qtbot.waitSignal(sess_man.update_completion):
|
|
||||||
sess_man.delete(str(sess))
|
|
||||||
|
|
||||||
|
|
||||||
class TestListSessions:
|
class TestListSessions:
|
||||||
|
|
||||||
def test_no_sessions(self, tmpdir):
|
def test_no_sessions(self, tmpdir):
|
||||||
|
@ -495,7 +495,6 @@ class ImportFake:
|
|||||||
('yaml', True),
|
('yaml', True),
|
||||||
('cssutils', True),
|
('cssutils', True),
|
||||||
('typing', True),
|
('typing', True),
|
||||||
('OpenGL', True),
|
|
||||||
('PyQt5.QtWebEngineWidgets', True),
|
('PyQt5.QtWebEngineWidgets', True),
|
||||||
('PyQt5.QtWebKitWidgets', True),
|
('PyQt5.QtWebKitWidgets', True),
|
||||||
])
|
])
|
||||||
|
Loading…
Reference in New Issue
Block a user