diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 3429ed5f5..1cfcc8496 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -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() diff --git a/qutebrowser/completion/completiondelegate.py b/qutebrowser/completion/completiondelegate.py index 779906a83..6b1309378 100644 --- a/qutebrowser/completion/completiondelegate.py +++ b/qutebrowser/completion/completiondelegate.py @@ -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'\g<0>' - 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( '{}'.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) diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 34b2d4923..298ae7efc 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -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: diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index f4857fc36..4eca47521 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -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 diff --git a/tests/unit/completion/test_completiondelegate.py b/tests/unit/completion/test_completiondelegate.py new file mode 100644 index 000000000..f9f6dc492 --- /dev/null +++ b/tests/unit/completion/test_completiondelegate.py @@ -0,0 +1,51 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018 Ryan Roden-Corrent (rcorre) +# +# 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 . +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', '', [(1, 3)]), + ('', "bc", [(0, 3)]), + + # https://github.com/qutebrowser/qutebrowser/issues/4199 + ('foo', "'foo'", [(1, 3)]), + ('x', "'x'", [(1, 1)]), + ('lt', "