Merge remote-tracking branch 'origin/pr/4220'

This commit is contained in:
Florian Bruhin 2018-10-04 19:28:01 +02:00
commit 923b726e38
6 changed files with 179 additions and 113 deletions

View File

@ -59,7 +59,6 @@ except ImportError:
import qutebrowser
import qutebrowser.resources
from qutebrowser.completion import completiondelegate
from qutebrowser.completion.models import miscmodels
from qutebrowser.commands import cmdutils, runners, cmdexc
from qutebrowser.config import config, websettings, configfiles, configinit
@ -453,9 +452,6 @@ def _init_modules(args, crash_handler):
pre_text='Error initializing SQL')
sys.exit(usertypes.Exit.err_init)
log.init.debug("Initializing completion...")
completiondelegate.init()
log.init.debug("Initializing command history...")
cmdhistory.init()

View File

@ -28,13 +28,27 @@ import html
from PyQt5.QtWidgets import QStyle, QStyleOptionViewItem, QStyledItemDelegate
from PyQt5.QtCore import QRectF, QSize, Qt
from PyQt5.QtGui import (QIcon, QPalette, QTextDocument, QTextOption,
QAbstractTextDocumentLayout)
QAbstractTextDocumentLayout, QSyntaxHighlighter,
QTextCharFormat)
from qutebrowser.config import config
from qutebrowser.utils import qtutils, jinja
from qutebrowser.utils import qtutils
_cached_stylesheet = None
class _Highlighter(QSyntaxHighlighter):
def __init__(self, doc, pattern, color):
super().__init__(doc)
self._format = QTextCharFormat()
self._format.setForeground(color)
self._pattern = pattern
def highlightBlock(self, text):
"""Override highlightBlock for custom highlighting."""
for match in re.finditer(self._pattern, text):
start, end = match.span()
length = end - start
self.setFormat(start, length, self._format)
class CompletionItemDelegate(QStyledItemDelegate):
@ -194,21 +208,15 @@ class CompletionItemDelegate(QStyledItemDelegate):
self._doc.setDefaultTextOption(text_option)
self._doc.setDocumentMargin(2)
assert _cached_stylesheet is not None
self._doc.setDefaultStyleSheet(_cached_stylesheet)
if index.parent().isValid():
view = self.parent()
pattern = view.pattern
columns_to_filter = index.model().columns_to_filter(index)
self._doc.setPlainText(self._opt.text)
if index.column() in columns_to_filter and pattern:
repl = r'<span class="highlight">\g<0></span>'
pat = html.escape(re.escape(pattern)).replace(r'\ ', r'|')
txt = html.escape(self._opt.text)
text = re.sub(pat, repl, txt, flags=re.IGNORECASE)
self._doc.setHtml(text)
else:
self._doc.setPlainText(self._opt.text)
pat = re.escape(pattern).replace(r'\ ', r'|')
_Highlighter(self._doc, pat,
config.val.colors.completion.match.fg)
else:
self._doc.setHtml(
'<span style="font: {};">{}</span>'.format(
@ -283,24 +291,3 @@ class CompletionItemDelegate(QStyledItemDelegate):
self._draw_focus_rect()
self._painter.restore()
@config.change_filter('colors.completion.match.fg', function=True)
def _update_stylesheet():
"""Update the cached stylesheet."""
stylesheet = """
.highlight {
color: {{ conf.colors.completion.match.fg }};
}
"""
with jinja.environment.no_autoescape():
template = jinja.environment.from_string(stylesheet)
global _cached_stylesheet
_cached_stylesheet = template.render(conf=config.val)
def init():
"""Initialize the cached stylesheet."""
_update_stylesheet()
config.instance.changed.connect(_update_stylesheet)

View File

@ -1896,7 +1896,7 @@ colors.completion.item.selected.border.bottom:
colors.completion.match.fg:
default: '#ff4444'
type: QssColor
type: QtColor
desc: Foreground color of the matched text in the completion.
colors.completion.scrollbar.fg:

View File

@ -922,6 +922,23 @@ class QtColor(BaseType):
* transparent (no color)
"""
def _parse_value(self, val):
try:
return int(val)
except ValueError:
pass
mult = 255.0
if val.endswith('%'):
val = val[:-1]
mult = 255.0 / 100
return int(float(val) * 255.0 / 100.0)
try:
return int(float(val) * mult)
except ValueError:
raise configexc.ValidationError(val, "must be a valid color value")
def to_py(self, value):
self._basic_py_validation(value, str)
if value is configutils.UNSET:
@ -929,6 +946,20 @@ class QtColor(BaseType):
elif not value:
return None
if value.endswith(')'):
openparen = value.index('(')
kind = value[:openparen]
vals = value[openparen+1:-1].split(',')
vals = [self._parse_value(v) for v in vals]
if kind == 'rgba' and len(vals) == 4:
return QColor.fromRgb(*vals)
if kind == 'rgb' and len(vals) == 3:
return QColor.fromRgb(*vals)
if kind == 'hsva' and len(vals) == 4:
return QColor.fromHsv(*vals)
if kind == 'hsv' and len(vals) == 3:
return QColor.fromHsv(*vals)
color = QColor(value)
if color.isValid():
return color

View File

@ -0,0 +1,51 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2018 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
from unittest import mock
import pytest
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QTextDocument
from qutebrowser.completion import completiondelegate
@pytest.mark.parametrize('pat,txt,segments', [
('foo', 'foo', [(0, 3)]),
('foo', 'foobar', [(0, 3)]),
('foo', 'barfoo', [(3, 3)]),
('foo', 'barfoobaz', [(3, 3)]),
('foo', 'barfoobazfoo', [(3, 3), (9, 3)]),
('foo', 'foofoo', [(0, 3), (3, 3)]),
('a|b', 'cadb', [(1, 1), (3, 1)]),
('foo', '<foo>', [(1, 3)]),
('<a>', "<a>bc", [(0, 3)]),
# https://github.com/qutebrowser/qutebrowser/issues/4199
('foo', "'foo'", [(1, 3)]),
('x', "'x'", [(1, 1)]),
('lt', "<lt", [(1, 2)]),
])
def test_highlight(pat, txt, segments):
doc = QTextDocument(txt)
highlighter = completiondelegate._Highlighter(doc, pat, Qt.red)
highlighter.setFormat = mock.Mock()
highlighter.highlightBlock(txt)
highlighter.setFormat.assert_has_calls([
mock.call(s[0], s[1], mock.ANY) for s in segments
])

View File

@ -1227,87 +1227,88 @@ class TestCommand:
assert ('cmd2', "desc 2") in items
class ColorTests:
class TestQtColor:
"""Generator for tests for TestColors."""
"""Test QtColor."""
TYPES = [configtypes.QtColor, configtypes.QssColor]
@pytest.mark.parametrize('val, expected', [
('#123', QColor('#123')),
('#112233', QColor('#112233')),
('#111222333', QColor('#111222333')),
('#111122223333', QColor('#111122223333')),
('red', QColor('red')),
TESTS = [
('#123', TYPES),
('#112233', TYPES),
('#111222333', TYPES),
('#111122223333', TYPES),
('red', TYPES),
('rgb(0, 0, 0)', QColor.fromRgb(0, 0, 0)),
('rgb(0,0,0)', QColor.fromRgb(0, 0, 0)),
('#00000G', []),
('#123456789ABCD', []),
('#12', []),
('foobar', []),
('42', []),
('foo(1, 2, 3)', []),
('rgb(1, 2, 3', []),
('rgba(255, 255, 255, 1.0)', QColor.fromRgb(255, 255, 255, 255)),
('rgb(0, 0, 0)', [configtypes.QssColor]),
('rgb(0,0,0)', [configtypes.QssColor]),
# this should be (36, 25, 25) as hue goes to 359
# however this is consistent with Qt's CSS parser
# https://bugreports.qt.io/browse/QTBUG-70897
('hsv(10%,10%,10%)', QColor.fromHsv(25, 25, 25)),
])
def test_valid(self, val, expected):
act = configtypes.QtColor().to_py(val)
print(expected.hue(), expected.saturation(), expected.value(), expected.alpha())
print(act.hue(), act.saturation(), act.value(), act.alpha())
assert configtypes.QtColor().to_py(val) == expected
('rgba(255, 255, 255, 1.0)', [configtypes.QssColor]),
('hsv(10%,10%,10%)', [configtypes.QssColor]),
('qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, '
'stop: 0.4 gray, stop:1 green)', [configtypes.QssColor]),
('qconicalgradient(cx:0.5, cy:0.5, angle:30, stop:0 white, '
'stop:1 #00FF00)', [configtypes.QssColor]),
('qradialgradient(cx:0, cy:0, radius: 1, fx:0.5, fy:0.5, '
'stop:0 white, stop:1 green)', [configtypes.QssColor]),
]
COMBINATIONS = list(itertools.product(TESTS, TYPES))
def __init__(self):
self.valid = list(self._generate_valid())
self.invalid = list(self._generate_invalid())
def _generate_valid(self):
for (val, valid_classes), klass in self.COMBINATIONS:
if klass in valid_classes:
yield klass, val
def _generate_invalid(self):
for (val, valid_classes), klass in self.COMBINATIONS:
if klass not in valid_classes:
yield klass, val
class TestColors:
"""Test QtColor/QssColor."""
TESTS = ColorTests()
@pytest.fixture(params=ColorTests.TYPES)
def klass_fixt(self, request):
"""Fixture which provides all ColorTests classes.
Named klass_fix so it has a different name from the parametrized klass,
see https://github.com/pytest-dev/pytest/issues/979.
"""
return request.param
def test_test_generator(self):
"""Some sanity checks for ColorTests."""
assert self.TESTS.valid
assert self.TESTS.invalid
@pytest.mark.parametrize('klass, val', TESTS.valid)
def test_to_py_valid(self, klass, val):
expected = QColor(val) if klass is configtypes.QtColor else val
assert klass().to_py(val) == expected
@pytest.mark.parametrize('klass, val', TESTS.invalid)
def test_to_py_invalid(self, klass, val):
@pytest.mark.parametrize('val', [
'#00000G',
'#123456789ABCD',
'#12',
'foobar',
'42',
'foo(1, 2, 3)',
'rgb(1, 2, 3',
])
def test_invalid(self, val):
with pytest.raises(configexc.ValidationError):
klass().to_py(val)
configtypes.QtColor().to_py(val)
class TestQssColor:
"""Test QssColor."""
@pytest.mark.parametrize('val', [
'#123',
'#112233',
'#111222333',
'#111122223333',
'red',
'rgb(0, 0, 0)',
'rgb(0,0,0)',
'rgba(255, 255, 255, 1.0)',
'hsv(10%,10%,10%)',
'qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 white, '
'stop: 0.4 gray, stop:1 green)',
'qconicalgradient(cx:0.5, cy:0.5, angle:30, stop:0 white, '
'stop:1 #00FF00)',
'qradialgradient(cx:0, cy:0, radius: 1, fx:0.5, fy:0.5, '
'stop:0 white, stop:1 green)',
])
def test_valid(self, val):
assert configtypes.QssColor().to_py(val) == val
@pytest.mark.parametrize('val', [
'#00000G',
'#123456789ABCD',
'#12',
'foobar',
'42',
'foo(1, 2, 3)',
'rgb(1, 2, 3',
])
def test_invalid(self, val):
with pytest.raises(configexc.ValidationError):
configtypes.QssColor().to_py(val)
@attr.s