Merge remote-tracking branch 'upstream/master' into really_complete

This commit is contained in:
Ryan Roden-Corrent 2017-07-07 20:42:21 -04:00
commit 515e82262d
11 changed files with 185 additions and 85 deletions

10
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,10 @@
* @The-Compiler
qutebrowser/browser/history.py @The-Compiler @rcorre
qutebrowser/completion/* @The-Compiler @rcorre
qutebrowser/misc/sql.py @The-Compiler @rcorre
tests/end2end/features/completion.feature @The-Compiler @rcorre
tests/end2end/features/test_completion_bdd.py @The-Compiler @rcorre
tests/unit/browser/test_history.py @The-Compiler @rcorre
tests/unit/completion/* @The-Compiler @rcorre
tests/unit/misc/test_sql.py @The-Compiler @rcorre

View File

@ -14,6 +14,42 @@ This project adheres to http://semver.org/[Semantic Versioning].
// `Fixed` for any bug fixes.
// `Security` to invite users to upgrade in case of vulnerabilities.
v0.1.0 (unreleased)
-------------------
Breaking changes
~~~~~~~~~~~~~~~~
- Support for legacy QtWebKit (before 5.212 which is distributed
independently from Qt) is dropped.
- Support for Python 3.4 is dropped.
- Support for Qt before 5.7 is dropped.
- New dependency on the QtSql module and Qt sqlite support.
- New dependency on ruamel.yaml; dropped PyYAML dependency.
- The QtWebEngine backend is now used by default if available.
- New config system which ignores the old config file.
Major changes
~~~~~~~~~~~~~
- New completion engine based on sqlite, which allows to complete
the entire browsing history.
- Completely rewritten configuration system.
Fixes
~~~~~
- Exiting fullscreen via `:fullscreen` or buttons on a page now
restores the correct previous window state (maximized/fullscreen).
v0.11.1 (unreleased)
--------------------
Fixes
~~~~~
- Fixed empty space being shown after tabs in the tabbar in some cases.
v0.11.0
-------

View File

@ -163,9 +163,9 @@ Contributors, sorted by the number of commits in descending order:
* Kevin Velghe
* Raphael Pierzina
* Joel Torstensson
* Jay Kamat
* Patric Schmitz
* Tarcisio Fedrizzi
* Jay Kamat
* Claude
* Philipp Hansch
* Fritz Reichwald
@ -193,6 +193,7 @@ Contributors, sorted by the number of commits in descending order:
* knaggita
* Oliver Caldwell
* Nikolay Amiantov
* Marius
* Julian Weigt
* Tomasz Kramkowski
* Sebastian Frysztak
@ -210,6 +211,7 @@ Contributors, sorted by the number of commits in descending order:
* Halfwit
* David Vogt
* Claire Cavanaugh
* Christian Helbling
* rikn00
* kanikaa1234
* haitaka
@ -217,7 +219,6 @@ Contributors, sorted by the number of commits in descending order:
* Michał Góral
* Michael Ilsaas
* Martin Zimmermann
* Marius
* Link
* Jussi Timperi
* Cosmin Popescu

View File

@ -38,7 +38,7 @@ from qutebrowser.browser import (urlmarks, browsertab, inspector, navigate,
webelem, downloads)
from qutebrowser.keyinput import modeman
from qutebrowser.utils import (message, usertypes, log, qtutils, urlutils,
objreg, utils, typing)
objreg, utils, typing, debug)
from qutebrowser.utils.usertypes import KeyMode
from qutebrowser.misc import editor, guiprocess
from qutebrowser.completion.models import urlmodel, miscmodels
@ -2166,6 +2166,10 @@ class CommandDispatcher:
window = self._tabbed_browser.window()
if window.isFullScreen():
window.showNormal()
window.setWindowState(
window.state_before_fullscreen & ~Qt.WindowFullScreen)
else:
window.state_before_fullscreen = window.windowState()
window.showFullScreen()
log.misc.debug('state before fullscreen: {}'.format(
debug.qflags_key(Qt, window.state_before_fullscreen)))

View File

@ -615,6 +615,7 @@ class WebEngineTab(browsertab.AbstractTab):
def shutdown(self):
self.shutting_down.emit()
self.action.exit_fullscreen()
if qtutils.version_check('5.8', exact=True):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-58563
@ -715,7 +716,8 @@ class WebEngineTab(browsertab.AbstractTab):
@pyqtSlot()
def _on_load_started(self):
"""Clear search when a new load is started if needed."""
if qtutils.version_check('5.9'):
if (qtutils.version_check('5.9') and
not qtutils.version_check('5.9.2')):
# WORKAROUND for
# https://bugreports.qt.io/browse/QTBUG-61506
self.search.clear()

View File

@ -70,6 +70,8 @@ the <span class="mono">qute://settings</span> page or caret browsing).</span>
{{ install_webengine('qt5-qtwebengine') }}
{% elif distribution.parsed == Distribution.opensuse %}
{{ install_webengine('libqt5-qtwebengine') }}
{% elif distribution.parsed == Distribution.gentoo %}
{{ install_webengine('dev-qt/qtwebengine') }}
{% else %}
{{ unknown_system() }}
{% endif %}

View File

@ -30,7 +30,8 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
from qutebrowser.commands import runners, cmdutils
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes, qtutils, objreg, utils
from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils,
debug)
from qutebrowser.mainwindow import tabbedbrowser, messageview, prompt
from qutebrowser.mainwindow.statusbar import bar
from qutebrowser.completion import completionwidget, completer
@ -123,6 +124,7 @@ class MainWindow(QWidget):
Attributes:
status: The StatusBar widget.
tabbed_browser: The TabbedBrowser widget.
state_before_fullscreen: window state before activation of fullscreen.
_downloadview: The DownloadView widget.
_vbox: The main QVBoxLayout.
_commandrunner: The main CommandRunner instance.
@ -217,6 +219,8 @@ class MainWindow(QWidget):
objreg.get("app").new_window.emit(self)
self.state_before_fullscreen = self.windowState()
def _init_geometry(self, geometry):
"""Initialize the window geometry or load it from disk."""
if geometry is not None:
@ -483,9 +487,12 @@ class MainWindow(QWidget):
@pyqtSlot(bool)
def _on_fullscreen_requested(self, on):
if on:
self.state_before_fullscreen = self.windowState()
self.showFullScreen()
else:
self.showNormal()
elif self.isFullScreen():
self.setWindowState(self.state_before_fullscreen)
log.misc.debug('on: {}, state before fullscreen: {}'.format(
on, debug.qflags_key(Qt, self.state_before_fullscreen)))
@cmdutils.register(instance='main-window', scope='window')
@pyqtSlot()

View File

@ -761,6 +761,17 @@ class TabBarStyle(QCommonStyle):
rct = super().subElementRect(sr, opt, widget)
return rct
else:
try:
# We need this so the left scroll button is aligned properly.
# Otherwise, empty space will be shown after the last tab even
# though the button width is set to 0
#
# QStyle.SE_TabBarScrollLeftButton was added in Qt 5.7
if sr == QStyle.SE_TabBarScrollLeftButton:
return super().subElementRect(sr, opt, widget)
except AttributeError:
pass
return self._style.subElementRect(sr, opt, widget)
def _tab_layout(self, opt):

View File

@ -326,12 +326,11 @@ def version():
lines += _module_versions()
lines += ['pdf.js: {}'.format(_pdfjs_version())]
lines += ['sqlite: {}'.format(sql.version())]
lines += [
'SSL: {}'.format(QSslSocket.sslLibraryVersionString()),
'',
'pdf.js: {}'.format(_pdfjs_version()),
'sqlite: {}'.format(sql.version()),
'QtNetwork SSL: {}\n'.format(QSslSocket.sslLibraryVersionString()
if QSslSocket.supportsSsl() else 'no'),
]
qapp = QApplication.instance()

View File

@ -292,6 +292,14 @@ def build_sdist():
return artifacts
def read_github_token():
"""Read the GitHub API token from disk."""
token_file = os.path.join(os.path.expanduser('~'), '.gh_token')
with open(token_file, encoding='ascii') as f:
token = f.read().strip()
return token
def github_upload(artifacts, tag):
"""Upload the given artifacts to GitHub.
@ -302,9 +310,7 @@ def github_upload(artifacts, tag):
import github3
utils.print_title("Uploading to github...")
token_file = os.path.join(os.path.expanduser('~'), '.gh_token')
with open(token_file, encoding='ascii') as f:
token = f.read().strip()
token = read_github_token()
gh = github3.login(token=token)
repo = gh.repository('qutebrowser', 'qutebrowser')
@ -341,6 +347,12 @@ def main():
upload_to_pypi = False
if args.upload is not None:
# Fail early when trying to upload without github3 installed
# or without API token
import github3 # pylint: disable=unused-variable
read_github_token()
if os.name == 'nt':
if sys.maxsize > 2**32:
# WORKAROUND

View File

@ -21,6 +21,7 @@
import io
import sys
import collections
import os.path
import subprocess
import contextlib
@ -475,29 +476,32 @@ class ImportFake:
"""A fake for __import__ which is used by the import_fake fixture.
Attributes:
exists: A dict mapping module names to bools. If True, the import will
success. Otherwise, it'll fail with ImportError.
modules: A dict mapping module names to bools. If True, the import will
success. Otherwise, it'll fail with ImportError.
version_attribute: The name to use in the fake modules for the version
attribute.
version: The version to use for the modules.
_real_import: Saving the real __import__ builtin so the imports can be
done normally for modules not in self.exists.
done normally for modules not in self. modules.
"""
def __init__(self):
self.exists = {
'sip': True,
'colorama': True,
'pypeg2': True,
'jinja2': True,
'pygments': True,
'yaml': True,
'cssutils': True,
'typing': True,
'PyQt5.QtWebEngineWidgets': True,
'PyQt5.QtWebKitWidgets': True,
'OpenGL': True,
}
self.modules = collections.OrderedDict([
('sip', True),
('colorama', True),
('pypeg2', True),
('jinja2', True),
('pygments', True),
('yaml', True),
('cssutils', True),
('typing', True),
('OpenGL', True),
('PyQt5.QtWebEngineWidgets', True),
('PyQt5.QtWebKitWidgets', True),
])
self.no_version_attribute = ['sip', 'typing',
'PyQt5.QtWebEngineWidgets',
'PyQt5.QtWebKitWidgets']
self.version_attribute = '__version__'
self.version = '1.2.3'
self._real_import = builtins.__import__
@ -509,10 +513,10 @@ class ImportFake:
The imported fake module, or None if normal importing should be
used.
"""
if name not in self.exists:
if name not in self.modules:
# Not one of the modules to test -> use real import
return None
elif self.exists[name]:
elif self.modules[name]:
ns = types.SimpleNamespace()
if self.version_attribute is not None:
setattr(ns, self.version_attribute, self.version)
@ -551,14 +555,14 @@ class TestModuleVersions:
"""Tests for _module_versions()."""
@pytest.mark.usefixtures('import_fake')
def test_all_present(self):
def test_all_present(self, import_fake):
"""Test with all modules present in version 1.2.3."""
expected = ['sip: yes', 'colorama: 1.2.3', 'pypeg2: 1.2.3',
'jinja2: 1.2.3', 'pygments: 1.2.3', 'yaml: 1.2.3',
'cssutils: 1.2.3', 'typing: yes', 'OpenGL: 1.2.3',
'PyQt5.QtWebEngineWidgets: yes',
'PyQt5.QtWebKitWidgets: yes']
expected = []
for name in import_fake.modules:
if name in import_fake.no_version_attribute:
expected.append('{}: yes'.format(name))
else:
expected.append('{}: 1.2.3'.format(name))
assert version._module_versions() == expected
@pytest.mark.parametrize('module, idx, expected', [
@ -574,36 +578,31 @@ class TestModuleVersions:
idx: The index where the given text is expected.
expected: The expected text.
"""
import_fake.exists[module] = False
import_fake.modules[module] = False
assert version._module_versions()[idx] == expected
@pytest.mark.parametrize('value, expected', [
('VERSION', ['sip: yes', 'colorama: 1.2.3', 'pypeg2: yes',
'jinja2: yes', 'pygments: yes', 'yaml: yes',
'cssutils: yes', 'typing: yes', 'OpenGL: yes',
'PyQt5.QtWebEngineWidgets: yes',
'PyQt5.QtWebKitWidgets: yes']),
('SIP_VERSION_STR', ['sip: 1.2.3', 'colorama: yes', 'pypeg2: yes',
'jinja2: yes', 'pygments: yes', 'yaml: yes',
'cssutils: yes', 'typing: yes', 'OpenGL: yes',
'PyQt5.QtWebEngineWidgets: yes',
'PyQt5.QtWebKitWidgets: yes']),
(None, ['sip: yes', 'colorama: yes', 'pypeg2: yes', 'jinja2: yes',
'pygments: yes', 'yaml: yes', 'cssutils: yes', 'typing: yes',
'OpenGL: yes', 'PyQt5.QtWebEngineWidgets: yes',
'PyQt5.QtWebKitWidgets: yes']),
@pytest.mark.parametrize('attribute, expected_modules', [
('VERSION', ['colorama']),
('SIP_VERSION_STR', ['sip']),
(None, []),
])
def test_version_attribute(self, value, expected, import_fake):
def test_version_attribute(self, attribute, expected_modules, import_fake):
"""Test with a different version attribute.
VERSION is tested for old colorama versions, and None to make sure
things still work if some package suddenly doesn't have __version__.
Args:
value: The name of the version attribute.
attribute: The name of the version attribute.
expected: The expected return value.
"""
import_fake.version_attribute = value
import_fake.version_attribute = attribute
expected = []
for name in import_fake.modules:
if name in expected_modules:
expected.append('{}: 1.2.3'.format(name))
else:
expected.append('{}: yes'.format(name))
assert version._module_versions() == expected
@pytest.mark.parametrize('name, has_version', [
@ -759,14 +758,16 @@ class FakeQSslSocket:
Attributes:
_version: What QSslSocket::sslLibraryVersionString() should return.
_support: Whether SSL is supported.
"""
def __init__(self, version=None):
def __init__(self, version=None, support=True):
self._version = version
self._support = support
def supportsSsl(self):
"""Fake for QSslSocket::supportsSsl()."""
return True
return self._support
def sslLibraryVersionString(self):
"""Fake for QSslSocket::sslLibraryVersionString()."""
@ -799,18 +800,30 @@ def test_chromium_version_unpatched(qapp):
assert version._chromium_version() not in ['', 'unknown', 'unavailable']
@pytest.mark.parametrize(['git_commit', 'frozen', 'style', 'with_webkit',
'known_distribution'], [
(True, False, True, True, True), # normal
(False, False, True, True, True), # no git commit
(True, True, True, True, True), # frozen
(True, True, False, True, True), # no style
(True, False, True, False, True), # no webkit
(True, False, True, 'ng', True), # QtWebKit-NG
(True, False, True, True, False), # unknown Linux distribution
]) # pylint: disable=too-many-locals
def test_version_output(git_commit, frozen, style, with_webkit,
known_distribution, stubs, monkeypatch, init_sql):
class VersionParams:
def __init__(self, name, git_commit=True, frozen=False, style=True,
with_webkit=True, known_distribution=True, ssl_support=True):
self.name = name
self.git_commit = git_commit
self.frozen = frozen
self.style = style
self.with_webkit = with_webkit
self.known_distribution = known_distribution
self.ssl_support = ssl_support
@pytest.mark.parametrize('params', [
VersionParams('normal'),
VersionParams('no-git-commit', git_commit=False),
VersionParams('frozen', frozen=True),
VersionParams('no-style', style=False),
VersionParams('no-webkit', with_webkit=False),
VersionParams('webkit-ng', with_webkit='ng'),
VersionParams('unknown-dist', known_distribution=False),
VersionParams('no-ssl', ssl_support=False),
], ids=lambda param: param.name)
def test_version_output(params, stubs, monkeypatch):
"""Test version.version()."""
class FakeWebEngineProfile:
def httpUserAgent(self):
@ -820,37 +833,38 @@ def test_version_output(git_commit, frozen, style, with_webkit,
patches = {
'qutebrowser.__file__': os.path.join(import_path, '__init__.py'),
'qutebrowser.__version__': 'VERSION',
'_git_str': lambda: ('GIT COMMIT' if git_commit else None),
'_git_str': lambda: ('GIT COMMIT' if params.git_commit else None),
'platform.python_implementation': lambda: 'PYTHON IMPLEMENTATION',
'platform.python_version': lambda: 'PYTHON VERSION',
'PYQT_VERSION_STR': 'PYQT VERSION',
'earlyinit.qt_version': lambda: 'QT VERSION',
'_module_versions': lambda: ['MODULE VERSION 1', 'MODULE VERSION 2'],
'_pdfjs_version': lambda: 'PDFJS VERSION',
'QSslSocket': FakeQSslSocket('SSL VERSION'),
'QSslSocket': FakeQSslSocket('SSL VERSION', params.ssl_support),
'platform.platform': lambda: 'PLATFORM',
'platform.architecture': lambda: ('ARCHITECTURE', ''),
'_os_info': lambda: ['OS INFO 1', 'OS INFO 2'],
'_path_info': lambda: {'PATH DESC': 'PATH NAME'},
'QApplication': (stubs.FakeQApplication(style='STYLE') if style else
'QApplication': (stubs.FakeQApplication(style='STYLE')
if params.style else
stubs.FakeQApplication(instance=None)),
'QLibraryInfo.location': (lambda _loc: 'QT PATH'),
'sql.version': lambda: 'SQLITE VERSION',
}
substitutions = {
'git_commit': '\nGit commit: GIT COMMIT' if git_commit else '',
'style': '\nStyle: STYLE' if style else '',
'git_commit': '\nGit commit: GIT COMMIT' if params.git_commit else '',
'style': '\nStyle: STYLE' if params.style else '',
'qt': 'QT VERSION',
'frozen': str(frozen),
'frozen': str(params.frozen),
'import_path': import_path,
}
if with_webkit:
if params.with_webkit:
patches['qWebKitVersion'] = lambda: 'WEBKIT VERSION'
patches['objects.backend'] = usertypes.Backend.QtWebKit
patches['QWebEngineProfile'] = None
if with_webkit == 'ng':
if params.with_webkit == 'ng':
backend = 'QtWebKit-NG'
patches['qtutils.is_qtwebkit_ng'] = lambda: True
else:
@ -863,7 +877,7 @@ def test_version_output(git_commit, frozen, style, with_webkit,
patches['QWebEngineProfile'] = FakeWebEngineProfile
substitutions['backend'] = 'QtWebEngine (Chromium CHROMIUMVERSION)'
if known_distribution:
if params.known_distribution:
patches['distribution'] = lambda: version.DistributionInfo(
parsed=version.Distribution.arch, version=None,
pretty='LINUX DISTRIBUTION', id='arch')
@ -875,10 +889,12 @@ def test_version_output(git_commit, frozen, style, with_webkit,
substitutions['linuxdist'] = ''
substitutions['osinfo'] = 'OS INFO 1\nOS INFO 2\n'
substitutions['ssl'] = 'SSL VERSION' if params.ssl_support else 'no'
for attr, val in patches.items():
monkeypatch.setattr('qutebrowser.utils.version.' + attr, val)
if frozen:
if params.frozen:
monkeypatch.setattr(sys, 'frozen', True, raising=False)
else:
monkeypatch.delattr(sys, 'frozen', raising=False)
@ -895,7 +911,7 @@ def test_version_output(git_commit, frozen, style, with_webkit,
MODULE VERSION 2
pdf.js: PDFJS VERSION
sqlite: SQLITE VERSION
SSL: SSL VERSION
QtNetwork SSL: {ssl}
{style}
Platform: PLATFORM, ARCHITECTURE{linuxdist}
Frozen: {frozen}