Files
qt-creator/src/plugins/texteditor/highlighter_test.cpp
David Schulz d4e775ebc6 TextEditor: handle syntax highlight in batches
Use an elapsed timer in the syntax highlighter to periodically return
from the highlight and push a continue highlight to the end of the event
loop.
This allows the user to interact with the editor in between those
batches. If the user modifies the document in between highlighting
batches, the area that still needs a rehighlight is increased if needed.

This also reverts 62ea85ee6a and the
related changes.

Task-number: QTCREATORBUG-28727
Change-Id: I7c394dbdff658330bb72f3b68b9928980947db75
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
2024-03-25 08:57:37 +00:00

247 lines
9.0 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 "highlighter_test.h"
#include "fontsettings.h"
#include "syntaxhighlighter.h"
#include "textdocument.h"
#include "texteditor.h"
#include "texteditorsettings.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <utils/mimeconstants.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"
}
]
}
)";
class GenerigHighlighterTests final : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void testHighlight_data();
void testHighlight();
void testChange();
void testPreeditText();
void cleanupTestCase();
private:
BaseTextEditor *m_editor = nullptr;
};
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(Utils::Constants::JSON_MIMETYPE));
QVERIFY(m_editor);
m_editor->textDocument()->syntaxHighlighter()->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());
QTRY_COMPARE(block.layout()->formats().size(), formatRanges.size());
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
// full hash calculation for QTextCharFormat fails so just check the important entries of format
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)}};
QTRY_COMPARE(block.layout()->formats().size(), formatRanges.size());
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
// full hash calculation for QTextCharFormat fails so just check the important entries of format
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()->syntaxHighlighter()->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)}};
QTRY_COMPARE(block.layout()->formats().size(), formatRanges.size());
const QList<QTextLayout::FormatRange> actualFormats = block.layout()->formats();
// full hash calculation for QTextCharFormat fails so just check the important entries of format
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);
}
QObject *createGenericHighlighterTests()
{
return new GenerigHighlighterTests;
}
} // namespace TextEditor::Internal
#include "highlighter_test.moc"