diff --git a/qutebrowser/app.py b/qutebrowser/app.py
index 594764c37..3359d07dc 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
@@ -452,9 +451,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 bc856d1a6..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,27 +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:
- pat = '({})'.format(re.escape(pattern).replace(r'\ ', r'|'))
- parts = re.split(pat, self._opt.text, flags=re.IGNORECASE)
- fmt = '{}'
- escape = lambda s: html.escape(s, quote=False)
- highlight = lambda s: fmt.format(escape(s))
- # matches are at every odd index
- text = ''.join([
- highlight(s) if i % 2 == 1 else escape(s)
- for i, s in enumerate(parts)
- ])
- 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(
@@ -289,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/tests/unit/completion/test_completiondelegate.py b/tests/unit/completion/test_completiondelegate.py
index 626085d30..f9f6dc492 100644
--- a/tests/unit/completion/test_completiondelegate.py
+++ b/tests/unit/completion/test_completiondelegate.py
@@ -19,82 +19,33 @@
from unittest import mock
import pytest
-from PyQt5.QtCore import QModelIndex, QObject
-from PyQt5.QtGui import QPainter
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QTextDocument
from qutebrowser.completion import completiondelegate
-@pytest.fixture
-def painter():
- return mock.Mock(spec=QPainter)
-
-
-def _qt_mock(klass, mocker):
- m = mocker.patch.object(completiondelegate, klass, autospec=True)
- return m
-
-
-@pytest.fixture
-def mock_style_option(mocker):
- return _qt_mock('QStyleOptionViewItem', mocker)
-
-
-@pytest.fixture
-def mock_text_document(mocker):
- return _qt_mock('QTextDocument', mocker)
-
-
-@pytest.fixture
-def view():
- class FakeView(QObject):
- def __init__(self):
- super().__init__()
- self.pattern = None
-
- return FakeView()
-
-
-@pytest.fixture
-def delegate(mock_style_option, mock_text_document, config_stub, mocker, view):
- _qt_mock('QStyle', mocker)
- _qt_mock('QTextOption', mocker)
- _qt_mock('QAbstractTextDocumentLayout', mocker)
- completiondelegate._cached_stylesheet = ''
- delegate = completiondelegate.CompletionItemDelegate(parent=view)
- delegate.initStyleOption = mock.Mock()
- return delegate
-
-
-@pytest.mark.parametrize('pat,txt_in,txt_out', [
- # { and } represent the open/close html tags for highlighting
- ('foo', 'foo', '{foo}'),
- ('foo', 'foobar', '{foo}bar'),
- ('foo', 'barfoo', 'bar{foo}'),
- ('foo', 'barfoobaz', 'bar{foo}baz'),
- ('foo', 'barfoobazfoo', 'bar{foo}baz{foo}'),
- ('foo', 'foofoo', '{foo}{foo}'),
- ('a b', 'cadb', 'c{a}d{b}'),
- ('foo', '', '<{foo}>'),
- ('', "bc", '{<a>}bc'),
+@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'", "'{foo}'"),
- ('x', "'x'", "'{x}'"),
- ('lt', "'
- ).replace(
- '}', ''
- )
- mock_text_document().setHtml.assert_called_once_with(expected)
+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
+ ])