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', "