forked from qt-creator/qt-creator
This change involves the relocation of SyntaxHighlighter processing to another thread. The core idea is to create a duplicate of the original TextDocument using SyntaxHighlighterRunnerPrivate::cloneDocument. A new SyntaxHighlighter is then instantiated by SyntaxHighLighterCreator for the cloned document. The entire SyntaxHighLighterCreator class is moved to a new thread, where it performs highlighting on the cloned document. Upon completion of the highlighting process, the resultsReady signal is emitted, and the updated highlighting data is applied to the original document. This shift of SyntaxHighlighter to another thread enhances the user experience by preventing UI slowdowns during the highlighting process. - Introduction of BaseSyntaxHighlighterRunner as an interface class for future *SyntaxHighlighterRunner. - Inclusion of DirectSyntaxHighlighterRunner class for performing highlighting in the main thread, suitable for syntax highlighters that cannot be moved to another thread. - Introduction of ThreadedSyntaxHighlighterRunner class for highlighting in a separate thread, preventing UI blocking during the process. - Addition of Result data to the SyntaxHighlighter class to facilitate data exchange between threads. Task-number: QTCREATORBUG-28727 Change-Id: I4b6a38d15f5ec9b8828055d38d2a0c6f21a657b4 Reviewed-by: Jarek Kobus <jaroslaw.kobus@qt.io> Reviewed-by: David Schulz <david.schulz@qt.io>
222 lines
8.5 KiB
C++
222 lines
8.5 KiB
C++
// Copyright (C) 2023 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include "syntaxhighlighterrunner.h"
|
|
|
|
#include "highlighter_test.h"
|
|
|
|
#include "fontsettings.h"
|
|
#include "textdocument.h"
|
|
#include "texteditor.h"
|
|
#include "texteditorsettings.h"
|
|
|
|
#include <coreplugin/coreconstants.h>
|
|
#include <coreplugin/editormanager/editormanager.h>
|
|
#include <utils/mimeutils.h>
|
|
|
|
#include <QtTest/QtTest>
|
|
|
|
namespace TextEditor::Internal {
|
|
|
|
constexpr auto json = R"(
|
|
{
|
|
"name": "Test",
|
|
"scope": "source.test",
|
|
"uuid": "test",
|
|
"patterns": [
|
|
{
|
|
"match": "a",
|
|
"name": "keyword.test"
|
|
}
|
|
]
|
|
}
|
|
)";
|
|
|
|
void GenerigHighlighterTests::initTestCase()
|
|
{
|
|
QString title = "test.json";
|
|
|
|
Core::IEditor *editor = Core::EditorManager::openEditorWithContents(
|
|
Core::Constants::K_DEFAULT_TEXT_EDITOR_ID, &title, json);
|
|
QVERIFY(editor);
|
|
m_editor = qobject_cast<BaseTextEditor *>(editor);
|
|
m_editor->editorWidget()->configureGenericHighlighter(Utils::mimeTypeForName("application/json"));
|
|
QVERIFY(m_editor);
|
|
m_editor->textDocument()->syntaxHighlighterRunner()->rehighlight();
|
|
}
|
|
|
|
using FormatRanges = QList<QTextLayout::FormatRange>;
|
|
|
|
QTextCharFormat toFormat(const TextStyle &style)
|
|
{
|
|
const static FontSettings &fontSettings = TextEditorSettings::fontSettings();
|
|
auto format = fontSettings.toTextCharFormat(style);
|
|
if (style == C_FUNCTION)
|
|
format.setFontWeight(QFont::Bold); // is explicitly set by the ksyntax format definition
|
|
if (style == C_TEXT) {
|
|
format = QTextCharFormat();
|
|
format.setFontWeight(QFont::Bold); // is explicitly set by the ksyntax format definition
|
|
}
|
|
return format;
|
|
};
|
|
|
|
void GenerigHighlighterTests::testHighlight_data()
|
|
{
|
|
QTest::addColumn<int>("blockNumber");
|
|
QTest::addColumn<QList<QTextLayout::FormatRange>>("formatRanges");
|
|
|
|
// clang-format off
|
|
QTest::addRow("0:<empty block>")
|
|
<< 0
|
|
<< FormatRanges();
|
|
QTest::addRow("1:{")
|
|
<< 1
|
|
<< FormatRanges{{0, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("2: \"name\": \"Test\",")
|
|
<< 2
|
|
<< FormatRanges{{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 6, toFormat(C_TYPE)},
|
|
{10, 1, toFormat(C_FUNCTION)},
|
|
{11, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{12, 6, toFormat(C_STRING)},
|
|
{18, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("3: \"scope\": \"source.test\",")
|
|
<< 3
|
|
<< FormatRanges{{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 7, toFormat(C_TYPE)},
|
|
{11, 1, toFormat(C_FUNCTION)},
|
|
{12, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{13, 13, toFormat(C_STRING)},
|
|
{26, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("4: \"uuid\": \"test\",")
|
|
<< 4
|
|
<< FormatRanges{{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 6, toFormat(C_TYPE)},
|
|
{10, 1, toFormat(C_FUNCTION)},
|
|
{11, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{12, 6, toFormat(C_STRING)},
|
|
{18, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("5: \"patterns\": [")
|
|
<< 5
|
|
<< FormatRanges{{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 10, toFormat(C_TYPE)},
|
|
{14, 1, toFormat(C_FUNCTION)},
|
|
{15, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{16, 1, toFormat(C_TEXT)}};
|
|
QTest::addRow("6: {")
|
|
<< 6
|
|
<< FormatRanges{{0, 8, toFormat(C_VISUAL_WHITESPACE)},
|
|
{8, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("7: \"match\": \"a\",")
|
|
<< 7
|
|
<< FormatRanges{{0, 12, toFormat(C_VISUAL_WHITESPACE)},
|
|
{12, 7, toFormat(C_TYPE)},
|
|
{19, 1, toFormat(C_FUNCTION)},
|
|
{20, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{21, 3, toFormat(C_STRING)},
|
|
{24, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("8: \"name\": \"keyword.test\"")
|
|
<< 8
|
|
<< FormatRanges{{0, 12, toFormat(C_VISUAL_WHITESPACE)},
|
|
{12, 6, toFormat(C_TYPE)},
|
|
{18, 1, toFormat(C_FUNCTION)},
|
|
{19, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{20, 14, toFormat(C_STRING)}};
|
|
QTest::addRow("9: }")
|
|
<< 9
|
|
<< FormatRanges{{0, 8, toFormat(C_VISUAL_WHITESPACE)},
|
|
{8, 1, toFormat(C_FUNCTION)}};
|
|
QTest::addRow("10: ]")
|
|
<< 10
|
|
<< FormatRanges{{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 1, toFormat(C_TEXT)}};
|
|
QTest::addRow("11:}")
|
|
<< 11
|
|
<< FormatRanges{{0, 1, toFormat(C_FUNCTION)}};
|
|
// clang-format on
|
|
}
|
|
|
|
void compareFormats(const QTextLayout::FormatRange &actual, const QTextLayout::FormatRange &expected)
|
|
{
|
|
QCOMPARE(actual.start, expected.start);
|
|
QCOMPARE(actual.length, expected.length);
|
|
QCOMPARE(actual.format.foreground(), expected.format.foreground());
|
|
QCOMPARE(actual.format.background(), expected.format.background());
|
|
QCOMPARE(actual.format.fontWeight(), expected.format.fontWeight());
|
|
QCOMPARE(actual.format.fontItalic(), expected.format.fontItalic());
|
|
QCOMPARE(actual.format.underlineStyle(), expected.format.underlineStyle());
|
|
QCOMPARE(actual.format.underlineColor(), expected.format.underlineColor());
|
|
}
|
|
|
|
void GenerigHighlighterTests::testHighlight()
|
|
{
|
|
QFETCH(int, blockNumber);
|
|
QFETCH(FormatRanges, formatRanges);
|
|
|
|
QTextBlock block = m_editor->textDocument()->document()->findBlockByNumber(blockNumber);
|
|
QVERIFY(block.isValid());
|
|
|
|
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
|
|
// full hash calculation for QTextCharFormat fails so just check the important entries of format
|
|
QCOMPARE(actualFormats.size(), formatRanges.size());
|
|
for (int i = 0; i < formatRanges.size(); ++i)
|
|
compareFormats(actualFormats.at(i), formatRanges.at(i));
|
|
}
|
|
|
|
void GenerigHighlighterTests::testChange()
|
|
{
|
|
QTextBlock block = m_editor->textDocument()->document()->findBlockByNumber(10);
|
|
QVERIFY(block.isValid());
|
|
|
|
QTextCursor c(block);
|
|
c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor);
|
|
c.removeSelectedText();
|
|
m_editor->textDocument()->document()->undo();
|
|
|
|
block = m_editor->textDocument()->document()->findBlockByNumber(10);
|
|
QVERIFY(block.isValid());
|
|
|
|
const FormatRanges formatRanges = {{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 1, toFormat(C_TEXT)}};
|
|
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
|
|
// full hash calculation for QTextCharFormat fails so just check the important entries of format
|
|
QCOMPARE(actualFormats.size(), formatRanges.size());
|
|
for (int i = 0; i < formatRanges.size(); ++i)
|
|
compareFormats(actualFormats.at(i), formatRanges.at(i));
|
|
}
|
|
|
|
void GenerigHighlighterTests::testPreeditText()
|
|
{
|
|
QTextBlock block = m_editor->textDocument()->document()->findBlockByNumber(2);
|
|
QVERIFY(block.isValid());
|
|
|
|
QTextCursor c(block);
|
|
c.beginEditBlock();
|
|
block.layout()->setPreeditArea(7, "uaf");
|
|
c.endEditBlock();
|
|
|
|
m_editor->textDocument()->syntaxHighlighterRunner()->rehighlight();
|
|
|
|
const FormatRanges formatRanges = {{0, 4, toFormat(C_VISUAL_WHITESPACE)},
|
|
{4, 3, toFormat(C_TYPE)},
|
|
{10, 3, toFormat(C_TYPE)},
|
|
{13, 1, toFormat(C_FUNCTION)},
|
|
{14, 1, toFormat(C_VISUAL_WHITESPACE)},
|
|
{15, 6, toFormat(C_STRING)},
|
|
{21, 1, toFormat(C_FUNCTION)}};
|
|
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
|
|
// full hash calculation for QTextCharFormat fails so just check the important entries of format
|
|
QCOMPARE(actualFormats.size(), formatRanges.size());
|
|
for (int i = 0; i < formatRanges.size(); ++i)
|
|
compareFormats(actualFormats.at(i), formatRanges.at(i));
|
|
}
|
|
|
|
void GenerigHighlighterTests::cleanupTestCase()
|
|
{
|
|
if (m_editor)
|
|
Core::EditorManager::closeEditors({m_editor});
|
|
QVERIFY(Core::EditorManager::currentEditor() == nullptr);
|
|
}
|
|
|
|
} // namespace TextEditor::Internal
|