From 62ea85ee6ad15c8e4d9cb5e35b1f10bee3c49ac7 Mon Sep 17 00:00:00 2001 From: Artem Sokolovskii Date: Mon, 8 May 2023 17:11:54 +0200 Subject: [PATCH] SyntaxHighlighter: Move SyntaxHighlighter to separate thread 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 Reviewed-by: David Schulz --- .../clangformat/clangformatconfigwidget.cpp | 2 +- src/plugins/cppeditor/cppeditordocument.cpp | 9 +- src/plugins/cppeditor/cpphighlighter.cpp | 6 +- src/plugins/cppeditor/cpphighlighter.h | 2 +- src/plugins/cppeditor/cpptoolsreuse.cpp | 2 +- src/plugins/cppeditor/semantichighlighter.cpp | 5 +- src/plugins/diffeditor/diffeditor.cpp | 2 +- src/plugins/git/giteditor.cpp | 4 +- .../semantichighlightsupport.cpp | 5 +- src/plugins/nim/editor/nimeditorfactory.cpp | 2 +- .../bindingeditor/bindingeditorwidget.cpp | 2 +- src/plugins/qmljseditor/qmljseditor.cpp | 4 +- .../qmljseditor/qmljseditordocument.cpp | 2 +- .../qmljseditor/qmljssemantichighlighter.cpp | 4 +- src/plugins/texteditor/CMakeLists.txt | 1 + src/plugins/texteditor/highlighter.cpp | 5 + src/plugins/texteditor/highlighter.h | 2 + src/plugins/texteditor/highlighter_test.cpp | 10 +- src/plugins/texteditor/highlighterhelper.cpp | 4 +- .../texteditor/semantichighlighter.cpp | 43 +-- src/plugins/texteditor/semantichighlighter.h | 25 +- src/plugins/texteditor/syntaxhighlighter.cpp | 78 ++++-- src/plugins/texteditor/syntaxhighlighter.h | 76 +++++- .../texteditor/syntaxhighlighterrunner.cpp | 255 ++++++++++++++++++ .../texteditor/syntaxhighlighterrunner.h | 75 ++++++ src/plugins/texteditor/textdocument.cpp | 31 ++- src/plugins/texteditor/textdocument.h | 5 +- src/plugins/texteditor/texteditor.cpp | 27 +- src/plugins/texteditor/texteditor.qbs | 2 + src/plugins/vcsbase/diffandloghighlighter.cpp | 4 +- src/plugins/vcsbase/diffandloghighlighter.h | 4 +- src/plugins/vcsbase/vcsbaseeditor.cpp | 10 +- .../highlighter/tst_highlighter.cpp | 37 +-- 33 files changed, 603 insertions(+), 142 deletions(-) create mode 100644 src/plugins/texteditor/syntaxhighlighterrunner.cpp create mode 100644 src/plugins/texteditor/syntaxhighlighterrunner.h diff --git a/src/plugins/clangformat/clangformatconfigwidget.cpp b/src/plugins/clangformat/clangformatconfigwidget.cpp index 23916bf00f3..0be1c14fc4d 100644 --- a/src/plugins/clangformat/clangformatconfigwidget.cpp +++ b/src/plugins/clangformat/clangformatconfigwidget.cpp @@ -131,7 +131,7 @@ ClangFormatConfigWidget::ClangFormatConfigWidget(TextEditor::ICodeStylePreferenc d->preview->setPlainText(QLatin1String(CppEditor::Constants::DEFAULT_CODE_STYLE_SNIPPETS[0])); d->preview->textDocument()->setIndenter(new ClangFormatIndenter(d->preview->document())); d->preview->textDocument()->setFontSettings(TextEditor::TextEditorSettings::fontSettings()); - d->preview->textDocument()->setSyntaxHighlighterCreator( + d->preview->textDocument()->resetSyntaxHighlighter( [] { return new CppEditor::CppHighlighter(); }); d->preview->textDocument()->indenter()->setFileName(fileName); diff --git a/src/plugins/cppeditor/cppeditordocument.cpp b/src/plugins/cppeditor/cppeditordocument.cpp index 3d62aadcd65..8d4adebf141 100644 --- a/src/plugins/cppeditor/cppeditordocument.cpp +++ b/src/plugins/cppeditor/cppeditordocument.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -79,7 +80,7 @@ private: CppEditorDocument::CppEditorDocument() { setId(CppEditor::Constants::CPPEDITOR_ID); - setSyntaxHighlighterCreator([] { return new CppHighlighter(); }); + resetSyntaxHighlighter([] { return new CppHighlighter(); }); ICodeStylePreferencesFactory *factory = TextEditorSettings::codeStyleFactory(Constants::CPP_SETTINGS_ID); @@ -165,7 +166,7 @@ QByteArray CppEditorDocument::contentsText() const void CppEditorDocument::applyFontSettings() { - if (TextEditor::SyntaxHighlighter *highlighter = syntaxHighlighter()) + if (TextEditor::BaseSyntaxHighlighterRunner *highlighter = syntaxHighlighterRunner()) highlighter->clearAllExtraFormats(); // Clear all additional formats since they may have changed TextDocument::applyFontSettings(); // rehighlights and updates additional formats if (m_processor) @@ -409,8 +410,8 @@ BaseEditorDocumentProcessor *CppEditorDocument::processor() connect(m_processor.data(), &BaseEditorDocumentProcessor::cppDocumentUpdated, this, [this](const CPlusPlus::Document::Ptr document) { // Update syntax highlighter - auto *highlighter = qobject_cast(syntaxHighlighter()); - highlighter->setLanguageFeatures(document->languageFeatures()); + if (BaseSyntaxHighlighterRunner *highlighter = syntaxHighlighterRunner()) + highlighter->setLanguageFeaturesFlags(document->languageFeatures().flags); m_overviewModel.update(usesClangd() ? nullptr : document); diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp index 142b2b296c9..591881c6c5b 100644 --- a/src/plugins/cppeditor/cpphighlighter.cpp +++ b/src/plugins/cppeditor/cpphighlighter.cpp @@ -268,10 +268,10 @@ void CppHighlighter::highlightBlock(const QString &text) tokenize.expectedRawStringSuffix()); } -void CppHighlighter::setLanguageFeatures(const LanguageFeatures &languageFeatures) +void CppHighlighter::setLanguageFeaturesFlags(unsigned int flags) { - if (languageFeatures != m_languageFeatures) { - m_languageFeatures = languageFeatures; + if (flags != m_languageFeatures.flags) { + m_languageFeatures.flags = flags; rehighlight(); } } diff --git a/src/plugins/cppeditor/cpphighlighter.h b/src/plugins/cppeditor/cpphighlighter.h index e2fac20bba6..5a6af63adaf 100644 --- a/src/plugins/cppeditor/cpphighlighter.h +++ b/src/plugins/cppeditor/cpphighlighter.h @@ -22,7 +22,7 @@ class CPPEDITOR_EXPORT CppHighlighter : public TextEditor::SyntaxHighlighter public: CppHighlighter(QTextDocument *document = nullptr); - void setLanguageFeatures(const CPlusPlus::LanguageFeatures &languageFeatures); + void setLanguageFeaturesFlags(unsigned int flags) override; void highlightBlock(const QString &text) override; private: diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp index c8ab16c3228..3c9f6eeec33 100644 --- a/src/plugins/cppeditor/cpptoolsreuse.cpp +++ b/src/plugins/cppeditor/cpptoolsreuse.cpp @@ -854,7 +854,7 @@ namespace Internal { void decorateCppEditor(TextEditor::TextEditorWidget *editor) { - editor->textDocument()->setSyntaxHighlighterCreator([] { return new CppHighlighter(); }); + editor->textDocument()->resetSyntaxHighlighter([] { return new CppHighlighter(); }); editor->textDocument()->setIndenter( new CppQtStyleIndenter(editor->textDocument()->document())); editor->setAutoCompleter(new CppAutoCompleter); diff --git a/src/plugins/cppeditor/semantichighlighter.cpp b/src/plugins/cppeditor/semantichighlighter.cpp index 156a1d40524..499b1eef6c9 100644 --- a/src/plugins/cppeditor/semantichighlighter.cpp +++ b/src/plugins/cppeditor/semantichighlighter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -111,7 +112,7 @@ void SemanticHighlighter::handleHighlighterResults() QElapsedTimer t; t.start(); - SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter(); + BaseSyntaxHighlighterRunner *highlighter = m_baseTextDocument->syntaxHighlighterRunner(); QTC_ASSERT(highlighter, return); incrementalApplyExtraAdditionalFormats(highlighter, m_watcher->future(), from, to, m_formatMap); @@ -199,7 +200,7 @@ void SemanticHighlighter::onHighlighterFinished() t.start(); if (!m_watcher->isCanceled() && documentRevision() == m_revision) { - SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter(); + BaseSyntaxHighlighterRunner *highlighter = m_baseTextDocument->syntaxHighlighterRunner(); if (QTC_GUARD(highlighter)) { qCDebug(log) << "onHighlighterFinished() - clearing formats"; clearExtraAdditionalFormatsUntilEnd(highlighter, m_watcher->future()); diff --git a/src/plugins/diffeditor/diffeditor.cpp b/src/plugins/diffeditor/diffeditor.cpp index 4998dfbd9a0..59baa0c273a 100644 --- a/src/plugins/diffeditor/diffeditor.cpp +++ b/src/plugins/diffeditor/diffeditor.cpp @@ -93,7 +93,7 @@ DescriptionEditorWidget::DescriptionEditorWidget(QWidget *parent) context->setContext(Context(Constants::C_DIFF_EDITOR_DESCRIPTION)); ICore::addContextObject(context); - textDocument()->setSyntaxHighlighterCreator([] { return new SyntaxHighlighter(); }); + textDocument()->resetSyntaxHighlighter([] { return new SyntaxHighlighter(); }); } QSize DescriptionEditorWidget::sizeHint() const diff --git a/src/plugins/git/giteditor.cpp b/src/plugins/git/giteditor.cpp index 2136846be0d..b6223f80207 100644 --- a/src/plugins/git/giteditor.cpp +++ b/src/plugins/git/giteditor.cpp @@ -246,10 +246,10 @@ void GitEditorWidget::init() return; const QChar commentChar = gitClient().commentChar(source()); if (isCommitEditor) - textDocument()->setSyntaxHighlighterCreator( + textDocument()->resetSyntaxHighlighter( [commentChar] { return new GitSubmitHighlighter(commentChar); }); else if (isRebaseEditor) - textDocument()->setSyntaxHighlighterCreator( + textDocument()->resetSyntaxHighlighter( [commentChar] { return new GitRebaseHighlighter(commentChar); }); } diff --git a/src/plugins/languageclient/semantichighlightsupport.cpp b/src/plugins/languageclient/semantichighlightsupport.cpp index 472f55b4976..655026c9c26 100644 --- a/src/plugins/languageclient/semantichighlightsupport.cpp +++ b/src/plugins/languageclient/semantichighlightsupport.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -182,7 +183,7 @@ void SemanticTokenSupport::queueDocumentReload(TextEditor::TextDocument *doc) void SemanticTokenSupport::clearHighlight(TextEditor::TextDocument *doc) { if (m_tokens.contains(doc->filePath())){ - if (TextEditor::SyntaxHighlighter *highlighter = doc->syntaxHighlighter()) + if (TextEditor::BaseSyntaxHighlighterRunner *highlighter = doc->syntaxHighlighterRunner()) highlighter->clearAllExtraFormats(); } } @@ -412,7 +413,7 @@ void SemanticTokenSupport::highlight(const Utils::FilePath &filePath, bool force TextDocument *doc = TextDocument::textDocumentForFilePath(filePath); if (!doc || LanguageClientManager::clientForDocument(doc) != m_client) return; - SyntaxHighlighter *highlighter = doc->syntaxHighlighter(); + BaseSyntaxHighlighterRunner *highlighter = doc->syntaxHighlighterRunner(); if (!highlighter) return; const VersionedTokens versionedTokens = m_tokens.value(filePath); diff --git a/src/plugins/nim/editor/nimeditorfactory.cpp b/src/plugins/nim/editor/nimeditorfactory.cpp index 62e49595385..92d6f05c0f3 100644 --- a/src/plugins/nim/editor/nimeditorfactory.cpp +++ b/src/plugins/nim/editor/nimeditorfactory.cpp @@ -51,7 +51,7 @@ NimEditorFactory::NimEditorFactory() void NimEditorFactory::decorateEditor(TextEditorWidget *editor) { - editor->textDocument()->setSyntaxHighlighterCreator([] { return new NimHighlighter();}); + editor->textDocument()->resetSyntaxHighlighter([] { return new NimHighlighter();}); editor->textDocument()->setIndenter(new NimIndenter(editor->textDocument()->document())); } diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp index 6f6e3ffaa84..58917c0954b 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditorwidget.cpp @@ -167,7 +167,7 @@ BindingEditorFactory::BindingEditorFactory() void BindingEditorFactory::decorateEditor(TextEditor::TextEditorWidget *editor) { - editor->textDocument()->setSyntaxHighlighterCreator( + editor->textDocument()->resetSyntaxHighlighter( [] { return new QmlJSEditor::QmlJSHighlighter(); }); editor->textDocument()->setIndenter(new QmlJSEditor::Internal::Indenter( editor->textDocument()->document())); diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index a8cdb569269..8219243c29d 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -743,7 +743,7 @@ void QmlJSEditorWidget::inspectElementUnderCursor() const widget->setReadOnly(true); widget->textDocument()->setTemporary(true); - widget->textDocument()->setSyntaxHighlighterCreator([] {return new QmlJSHighlighter();}); + widget->textDocument()->resetSyntaxHighlighter([] { return new QmlJSHighlighter(); }); const QString buf = inspectCppComponent(cppValue); widget->textDocument()->setPlainText(buf); @@ -1159,7 +1159,7 @@ QmlJSEditorFactory::QmlJSEditorFactory(Utils::Id _id) void QmlJSEditorFactory::decorateEditor(TextEditorWidget *editor) { - editor->textDocument()->setSyntaxHighlighterCreator([] { return new QmlJSHighlighter(); }); + editor->textDocument()->resetSyntaxHighlighter([] { return new QmlJSHighlighter(); }); editor->textDocument()->setIndenter(new Internal::Indenter(editor->textDocument()->document())); editor->setAutoCompleter(new AutoCompleter); } diff --git a/src/plugins/qmljseditor/qmljseditordocument.cpp b/src/plugins/qmljseditor/qmljseditordocument.cpp index 5923521ab06..2c145f38c7c 100644 --- a/src/plugins/qmljseditor/qmljseditordocument.cpp +++ b/src/plugins/qmljseditor/qmljseditordocument.cpp @@ -820,7 +820,7 @@ QmlJSEditorDocument::QmlJSEditorDocument(Utils::Id id) d, &Internal::QmlJSEditorDocumentPrivate::invalidateFormatterCache); connect(this, &TextEditor::TextDocument::openFinishedSuccessfully, d, &Internal::QmlJSEditorDocumentPrivate::settingsChanged); - setSyntaxHighlighterCreator([] { return new QmlJSHighlighter(); }); + resetSyntaxHighlighter([] { return new QmlJSHighlighter(); }); setCodec(QTextCodec::codecForName("UTF-8")); // qml files are defined to be utf-8 setIndenter(new Internal::Indenter(document())); } diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp index 67e14dfbcd5..c414684df7a 100644 --- a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp +++ b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp @@ -569,7 +569,7 @@ void SemanticHighlighter::applyResults(int from, int to) if (m_enableHighlighting) TextEditor::SemanticHighlighter::incrementalApplyExtraAdditionalFormats( - m_document->syntaxHighlighter(), m_watcher.future(), from, to, m_extraFormats); + m_document->syntaxHighlighterRunner(), m_watcher.future(), from, to, m_extraFormats); } void SemanticHighlighter::finished() @@ -584,7 +584,7 @@ void SemanticHighlighter::finished() if (m_enableHighlighting) TextEditor::SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd( - m_document->syntaxHighlighter(), m_watcher.future()); + m_document->syntaxHighlighterRunner(), m_watcher.future()); } void SemanticHighlighter::run(QPromise &promise, diff --git a/src/plugins/texteditor/CMakeLists.txt b/src/plugins/texteditor/CMakeLists.txt index 3f38c4436fc..939c313712b 100644 --- a/src/plugins/texteditor/CMakeLists.txt +++ b/src/plugins/texteditor/CMakeLists.txt @@ -96,6 +96,7 @@ add_qtc_plugin(TextEditor snippets/snippetssettingspage.cpp snippets/snippetssettingspage.h storagesettings.cpp storagesettings.h syntaxhighlighter.cpp syntaxhighlighter.h + syntaxhighlighterrunner.cpp syntaxhighlighterrunner.h tabsettings.cpp tabsettings.h tabsettingswidget.cpp tabsettingswidget.h textdocument.cpp textdocument.h diff --git a/src/plugins/texteditor/highlighter.cpp b/src/plugins/texteditor/highlighter.cpp index 14e2f1ff168..49276a61c76 100644 --- a/src/plugins/texteditor/highlighter.cpp +++ b/src/plugins/texteditor/highlighter.cpp @@ -74,6 +74,11 @@ Highlighter::Highlighter() &categoryForTextStyle); } +KSyntaxHighlighting::Definition Highlighter::getDefinition() +{ + return definition(); +} + static bool isOpeningParenthesis(QChar c) { return c == QLatin1Char('{') || c == QLatin1Char('[') || c == QLatin1Char('('); diff --git a/src/plugins/texteditor/highlighter.h b/src/plugins/texteditor/highlighter.h index 0b99265aa52..3cb3edce620 100644 --- a/src/plugins/texteditor/highlighter.h +++ b/src/plugins/texteditor/highlighter.h @@ -19,6 +19,8 @@ class Highlighter : public SyntaxHighlighter, public KSyntaxHighlighting::Abstra public: Highlighter(); + KSyntaxHighlighting::Definition getDefinition() override; + protected: void highlightBlock(const QString &text) override; void applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) override; diff --git a/src/plugins/texteditor/highlighter_test.cpp b/src/plugins/texteditor/highlighter_test.cpp index f38e6a4f854..ef0718f65b4 100644 --- a/src/plugins/texteditor/highlighter_test.cpp +++ b/src/plugins/texteditor/highlighter_test.cpp @@ -1,7 +1,7 @@ // 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 "syntaxhighlighter.h" +#include "syntaxhighlighterrunner.h" #include "highlighter_test.h" @@ -42,7 +42,7 @@ void GenerigHighlighterTests::initTestCase() m_editor = qobject_cast(editor); m_editor->editorWidget()->configureGenericHighlighter(Utils::mimeTypeForName("application/json")); QVERIFY(m_editor); - m_editor->textDocument()->syntaxHighlighter()->rehighlight(); + m_editor->textDocument()->syntaxHighlighterRunner()->rehighlight(); } using FormatRanges = QList; @@ -190,8 +190,12 @@ void GenerigHighlighterTests::testPreeditText() QTextBlock block = m_editor->textDocument()->document()->findBlockByNumber(2); QVERIFY(block.isValid()); + QTextCursor c(block); + c.beginEditBlock(); block.layout()->setPreeditArea(7, "uaf"); - m_editor->textDocument()->syntaxHighlighter()->rehighlight(); + c.endEditBlock(); + + m_editor->textDocument()->syntaxHighlighterRunner()->rehighlight(); const FormatRanges formatRanges = {{0, 4, toFormat(C_VISUAL_WHITESPACE)}, {4, 3, toFormat(C_TYPE)}, diff --git a/src/plugins/texteditor/highlighterhelper.cpp b/src/plugins/texteditor/highlighterhelper.cpp index abe8b3ceeca..f27c2f3f394 100644 --- a/src/plugins/texteditor/highlighterhelper.cpp +++ b/src/plugins/texteditor/highlighterhelper.cpp @@ -9,6 +9,7 @@ #include "texteditor.h" #include "texteditorsettings.h" #include "texteditortr.h" +#include "syntaxhighlighterrunner.h" #include #include @@ -209,7 +210,8 @@ void reload() highlightRepository()->reload(); for (auto editor : Core::DocumentModel::editorsForOpenedDocuments()) { if (auto textEditor = qobject_cast(editor)) { - if (qobject_cast(textEditor->textDocument()->syntaxHighlighter())) + auto highlighterCreator = textEditor->textDocument()->syntaxHighlighterRunner()->creator(); + if (highlighterCreator && qobject_cast(highlighterCreator())) textEditor->editorWidget()->configureGenericHighlighter(); } } diff --git a/src/plugins/texteditor/semantichighlighter.cpp b/src/plugins/texteditor/semantichighlighter.cpp index 30cb9cf729a..b4f4245036d 100644 --- a/src/plugins/texteditor/semantichighlighter.cpp +++ b/src/plugins/texteditor/semantichighlighter.cpp @@ -5,6 +5,7 @@ #include "syntaxhighlighter.h" #include "texteditorsettings.h" +#include "syntaxhighlighterrunner.h" #include @@ -73,11 +74,13 @@ const Ranges rangesForResult( } -void SemanticHighlighter::incrementalApplyExtraAdditionalFormats(SyntaxHighlighter *highlighter, - const QFuture &future, - int from, int to, - const QHash &kindToFormat, - const Splitter &splitter) +void SemanticHighlighter::incrementalApplyExtraAdditionalFormats( + BaseSyntaxHighlighterRunner *highlighter, + const QFuture &future, + int from, + int to, + const QHash &kindToFormat, + const Splitter &splitter) { if (to <= from) return; @@ -111,17 +114,21 @@ void SemanticHighlighter::incrementalApplyExtraAdditionalFormats(SyntaxHighlight formatRanges[range.block].append(range.formatRange); } + QList clearBlockNumberVector; + QMap> blockNumberMap; for (auto &[block, ranges] : formatRanges) { while (currentBlock < block) { - highlighter->clearExtraFormats(currentBlock); + clearBlockNumberVector.append(currentBlock.blockNumber()); currentBlock = currentBlock.next(); } - highlighter->setExtraFormats(block, std::move(ranges)); + blockNumberMap[block.blockNumber()] = ranges; currentBlock = block.next(); } + highlighter->clearExtraFormats(clearBlockNumberVector); + highlighter->setExtraFormats(blockNumberMap); } -void SemanticHighlighter::setExtraAdditionalFormats(SyntaxHighlighter *highlighter, +void SemanticHighlighter::setExtraAdditionalFormats(BaseSyntaxHighlighterRunner *highlighter, const QList &results, const QHash &kindToFormat) { @@ -132,20 +139,19 @@ void SemanticHighlighter::setExtraAdditionalFormats(SyntaxHighlighter *highlight QTextDocument *doc = highlighter->document(); QTC_ASSERT(doc, return ); - std::map> formatRanges; + QMap> blockNumberMap; - for (auto result : results) { - for (const Range &range : rangesForResult(result, doc, kindToFormat)) - formatRanges[range.block].append(range.formatRange); + for (const HighlightingResult &result : results) { + const Ranges ranges = rangesForResult(result, doc, kindToFormat); + for (const Range &range : ranges) + blockNumberMap[range.block.blockNumber()].append(range.formatRange); } - for (auto &[block, ranges] : formatRanges) - highlighter->setExtraFormats(block, std::move(ranges)); + highlighter->setExtraFormats(blockNumberMap); } void SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd( - SyntaxHighlighter *highlighter, - const QFuture &future) + BaseSyntaxHighlighterRunner *highlighter, const QFuture &future) { const QTextDocument * const doc = highlighter->document(); QTextBlock firstBlockToClear = doc->begin(); @@ -160,6 +166,9 @@ void SemanticHighlighter::clearExtraAdditionalFormatsUntilEnd( } } + QList clearBlockNumberVector; for (QTextBlock b = firstBlockToClear; b.isValid(); b = b.next()) - highlighter->clearExtraFormats(b); + clearBlockNumberVector.append(b.blockNumber()); + + highlighter->clearExtraFormats(clearBlockNumberVector); } diff --git a/src/plugins/texteditor/semantichighlighter.h b/src/plugins/texteditor/semantichighlighter.h index 7ef35bc4c23..fa841f5b2c6 100644 --- a/src/plugins/texteditor/semantichighlighter.h +++ b/src/plugins/texteditor/semantichighlighter.h @@ -21,6 +21,7 @@ QT_END_NAMESPACE namespace TextEditor { class SyntaxHighlighter; +class BaseSyntaxHighlighterRunner; class TEXTEDITOR_EXPORT HighlightingResult { @@ -72,29 +73,27 @@ using Splitter = std::function &future, - int from, int to, - const QHash &kindToFormat, - const Splitter &splitter = {}); +void TEXTEDITOR_EXPORT +incrementalApplyExtraAdditionalFormats(BaseSyntaxHighlighterRunner *highlighter, + const QFuture &future, + int from, + int to, + const QHash &kindToFormat, + const Splitter &splitter = {}); // Clears all extra highlights and applies the extra formats // indicated by Result::kind and kindToFormat to the correct location using // SyntaxHighlighter::setExtraFormats. In contrast to // incrementalApplyExtraAdditionalFormats the results do not have to be ordered by line. -void TEXTEDITOR_EXPORT setExtraAdditionalFormats( - SyntaxHighlighter *highlighter, - const HighlightingResults &results, - const QHash &kindToFormat); +void TEXTEDITOR_EXPORT setExtraAdditionalFormats(BaseSyntaxHighlighterRunner *highlighter, + const HighlightingResults &results, + const QHash &kindToFormat); // Cleans the extra additional formats after the last result of the Future // until the end of the document. // Requires that results of the Future are ordered by line. void TEXTEDITOR_EXPORT clearExtraAdditionalFormatsUntilEnd( - SyntaxHighlighter *highlighter, - const QFuture &future); - + BaseSyntaxHighlighterRunner *highlighter, const QFuture &future); } // namespace SemanticHighlighter } // namespace TextEditor diff --git a/src/plugins/texteditor/syntaxhighlighter.cpp b/src/plugins/texteditor/syntaxhighlighter.cpp index 36c4549487c..0d7b4d2ede9 100644 --- a/src/plugins/texteditor/syntaxhighlighter.cpp +++ b/src/plugins/texteditor/syntaxhighlighter.cpp @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "syntaxhighlighter.h" -#include "textdocument.h" #include "textdocumentlayout.h" #include "texteditorsettings.h" #include "fontsettings.h" @@ -54,13 +53,13 @@ public: void updateFormats(const FontSettings &fontSettings); FontSettings fontSettings; - QVector formatChanges; + QList formatChanges; QTextBlock currentBlock; bool rehighlightPending = false; bool inReformatBlocks = false; TextDocumentLayout::FoldValidator foldValidator; - QVector formats; - QVector> formatCategories; + QList formats; + QList> formatCategories; QTextCharFormat whitespaceFormat; bool noAutomaticHighlighting = false; }; @@ -102,8 +101,8 @@ void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, in QTextLayout *layout = currentBlock.layout(); - QVector ranges; - QVector oldRanges; + QList ranges; + QList oldRanges; std::tie(oldRanges, ranges) = Utils::partition(layout->formats(), [](const QTextLayout::FormatRange &range) { return range.format.property(SyntaxHighlight).toBool(); @@ -119,7 +118,7 @@ void SyntaxHighlighterPrivate::applyFormatChanges(int from, int charsRemoved, in QTextLayout::FormatRange r; - QVector newRanges; + QList newRanges; int i = 0; while (i < formatChanges.count()) { @@ -179,6 +178,7 @@ void SyntaxHighlighter::reformatBlocks(int from, int charsRemoved, int charsAdde void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int charsAdded) { + Q_Q(SyntaxHighlighter); foldValidator.reset(); rehighlightPending = false; @@ -192,10 +192,12 @@ void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int ch if (lastBlock.isValid()) endPosition = lastBlock.position() + lastBlock.length(); else - endPosition = doc->lastBlock().position() + doc->lastBlock().length(); //doc->docHandle()->length(); + endPosition = doc->lastBlock().position() + doc->lastBlock().length(); bool forceHighlightOfNextBlock = false; + QList vecRes; + while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) { const int stateBeforeHighlight = block.userState(); @@ -203,12 +205,25 @@ void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int ch forceHighlightOfNextBlock = (block.userState() != stateBeforeHighlight); + SyntaxHighlighter::Result res; + res.m_formatRanges = block.layout()->formats(); + res.fillByBlock(block); + vecRes << res; + + // Sending data to the text editor in chunks of 200 blocks is a sensible approach. + // This helps avoid UI slowdowns when applying formatting to the text. + if (vecRes.size() >= 200) { + emit q->resultsReady(vecRes); + vecRes.clear(); + } + block = block.next(); } formatChanges.clear(); foldValidator.finalize(); + emit q->resultsReady(vecRes); } void SyntaxHighlighterPrivate::reformatBlock(const QTextBlock &block, int from, int charsRemoved, int charsAdded) @@ -670,22 +685,22 @@ static bool byStartOfRange(const QTextLayout::FormatRange &range, const QTextLay return range.start < other.start; } -// The formats is passed in by rvalue reference in order to prevent unnecessary copying of its items. -// After this function returns, the list is modified, and should be considered invalidated! void SyntaxHighlighter::setExtraFormats(const QTextBlock &block, - QVector &&formats) + const QList &formats) { Q_D(SyntaxHighlighter); + QList formatsCopy = formats; + const int blockLength = block.length(); if (block.layout() == nullptr || blockLength == 0) return; const QString preeditText = block.layout()->preeditAreaText(); if (!preeditText.isEmpty()) { - QVector additionalRanges; + QList additionalRanges; const int preeditPosition = block.layout()->preeditAreaPosition(); - for (QTextLayout::FormatRange &r : formats) { + for (QTextLayout::FormatRange &r : formatsCopy) { if (r.start >= preeditPosition) { r.start += preeditText.length(); } else if (r.start + r.length > preeditPosition) { @@ -696,33 +711,39 @@ void SyntaxHighlighter::setExtraFormats(const QTextBlock &block, r.length = preeditPosition - r.start; } } - formats << additionalRanges; + formatsCopy << additionalRanges; } - Utils::sort(formats, byStartOfRange); + Utils::sort(formatsCopy, byStartOfRange); - const QVector all = block.layout()->formats(); - QVector previousSemanticFormats; - QVector formatsToApply; + const QList all = block.layout()->formats(); + QList previousSemanticFormats; + QList formatsToApply; std::tie(previousSemanticFormats, formatsToApply) = Utils::partition(all, [](const QTextLayout::FormatRange &r) { return r.format.property(SemanticHighlight).toBool(); }); - for (auto &format : formats) + for (auto &format : formatsCopy) format.format.setProperty(SemanticHighlight, true); - if (formats.size() == previousSemanticFormats.size()) { + if (formatsCopy.size() == previousSemanticFormats.size()) { Utils::sort(previousSemanticFormats, byStartOfRange); if (formats == previousSemanticFormats) return; } - formatsToApply += formats; + formatsToApply += formatsCopy; bool wasInReformatBlocks = d->inReformatBlocks; d->inReformatBlocks = true; block.layout()->setFormats(formatsToApply); + + SyntaxHighlighter::Result res; + res.m_formatRanges = block.layout()->formats(); + res.fillByBlock(block); + emit resultsReady({res}); + document()->markContentsDirty(block.position(), blockLength - 1); d->inReformatBlocks = wasInReformatBlocks; } @@ -735,7 +756,7 @@ void SyntaxHighlighter::clearExtraFormats(const QTextBlock &block) if (block.layout() == nullptr || blockLength == 0) return; - const QVector formatsToApply + const QList formatsToApply = Utils::filtered(block.layout()->formats(), [](const QTextLayout::FormatRange &r) { return !r.format.property(SemanticHighlight).toBool(); }); @@ -743,6 +764,12 @@ void SyntaxHighlighter::clearExtraFormats(const QTextBlock &block) bool wasInReformatBlocks = d->inReformatBlocks; d->inReformatBlocks = true; block.layout()->setFormats(formatsToApply); + + SyntaxHighlighter::Result res; + res.m_formatRanges = block.layout()->formats(); + res.fillByBlock(block); + emit resultsReady({res}); + document()->markContentsDirty(block.position(), blockLength - 1); d->inReformatBlocks = wasInReformatBlocks; } @@ -832,7 +859,7 @@ void SyntaxHighlighter::setDefaultTextFormatCategories() void SyntaxHighlighter::setTextFormatCategories(int count, std::function formatMapping) { - QVector> categories; + QList> categories; categories.reserve(count); for (int i = 0; i < count; ++i) categories.append({i, formatMapping(i)}); @@ -848,12 +875,12 @@ void SyntaxHighlighter::setTextFormatCategories(int count, \sa setDefaultTextFormatCategories() */ -void SyntaxHighlighter::setTextFormatCategories(const QVector> &categories) +void SyntaxHighlighter::setTextFormatCategories(const QList> &categories) { Q_D(SyntaxHighlighter); d->formatCategories = categories; const int maxCategory = Utils::maxElementOr(categories, {-1, C_TEXT}).first; - d->formats = QVector(maxCategory + 1); + d->formats = QList(maxCategory + 1); d->updateFormats(TextEditorSettings::fontSettings()); } @@ -899,4 +926,3 @@ void SyntaxHighlighterPrivate::updateFormats(const FontSettings &fontSettings) } // namespace TextEditor -#include "moc_syntaxhighlighter.cpp" diff --git a/src/plugins/texteditor/syntaxhighlighter.h b/src/plugins/texteditor/syntaxhighlighter.h index 78e0e031849..1e9ffdabffc 100644 --- a/src/plugins/texteditor/syntaxhighlighter.h +++ b/src/plugins/texteditor/syntaxhighlighter.h @@ -6,6 +6,9 @@ #include "texteditor_global.h" #include +#include + +#include #include #include @@ -41,10 +44,6 @@ public: void setDocument(QTextDocument *doc); QTextDocument *document() const; - void setExtraFormats(const QTextBlock &block, QVector &&formats); - void clearExtraFormats(const QTextBlock &block); - void clearAllExtraFormats(); - static QList generateColors(int n, const QColor &background); // Don't call in constructors of derived classes @@ -53,9 +52,73 @@ public: void setNoAutomaticHighlighting(bool noAutomatic); + struct Result + { + void fillByBlock(const QTextBlock &block) + { + m_blockNumber = block.position(); + m_userState = block.userState(); + + TextBlockUserData *userDate = TextDocumentLayout::textUserData(block); + if (!userDate) + return; + + m_hasBlockUserData = true; + m_foldingIndent = userDate->foldingIndent(); + m_folded = userDate->folded(); + m_ifdefedOut = userDate->ifdefedOut(); + m_foldingStartIncluded = userDate->foldingStartIncluded(); + m_foldingEndIncluded = userDate->foldingEndIncluded(); + m_parentheses = userDate->parentheses(); + m_expectedRawStringSuffix = userDate->expectedRawStringSuffix(); + } + + void copyToBlock(QTextBlock &block) const + { + block.setUserState(m_userState); + + if (m_hasBlockUserData) { + TextBlockUserData *data = TextDocumentLayout::userData(block); + data->setExpectedRawStringSuffix(m_expectedRawStringSuffix); + data->setFolded(m_folded); + data->setFoldingIndent(m_foldingIndent); + data->setFoldingStartIncluded(m_foldingStartIncluded); + data->setFoldingEndIncluded(m_foldingEndIncluded); + + if (m_ifdefedOut) + data->setIfdefedOut(); + else + data->clearIfdefedOut(); + + data->setParentheses(m_parentheses); + } + } + + int m_blockNumber; + bool m_hasBlockUserData = false; + + int m_foldingIndent : 16; + uint m_folded : 1; + uint m_ifdefedOut : 1; + uint m_foldingStartIncluded : 1; + uint m_foldingEndIncluded : 1; + + Parentheses m_parentheses; + QByteArray m_expectedRawStringSuffix; + int m_userState = -1; + QList m_formatRanges; + }; + + void setExtraFormats(const QTextBlock &block, const QList &formats); + virtual void setLanguageFeaturesFlags(unsigned int /*flags*/) {}; // needed for CppHighlighting + virtual void setEnabled(bool /*enabled*/) {}; // needed for DiffAndLogHighlighter + virtual KSyntaxHighlighting::Definition getDefinition() { return {}; } + public slots: virtual void rehighlight(); void rehighlightBlock(const QTextBlock &block); + void clearExtraFormats(const QTextBlock &block); + void clearAllExtraFormats(); protected: void setDefaultTextFormatCategories(); @@ -89,8 +152,11 @@ protected: protected: virtual void documentChanged(QTextDocument * /*oldDoc*/, QTextDocument * /*newDoc*/) {}; +signals: + void resultsReady(const QList &result); + private: - void setTextFormatCategories(const QVector> &categories); + void setTextFormatCategories(const QList> &categories); void reformatBlocks(int from, int charsRemoved, int charsAdded); void delayedRehighlight(); diff --git a/src/plugins/texteditor/syntaxhighlighterrunner.cpp b/src/plugins/texteditor/syntaxhighlighterrunner.cpp new file mode 100644 index 00000000000..9ba547da946 --- /dev/null +++ b/src/plugins/texteditor/syntaxhighlighterrunner.cpp @@ -0,0 +1,255 @@ +// 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 +#include + +#include + +#include +#include +#include +#include + +namespace TextEditor { + +class SyntaxHighlighterRunnerPrivate : public QObject +{ + Q_OBJECT +public: + SyntaxHighlighterRunnerPrivate(BaseSyntaxHighlighterRunner::SyntaxHighLighterCreator creator, + QTextDocument *document) + : m_creator(creator) + { + m_highlighter.reset(m_creator()); + m_highlighter->setDocument(document); + m_document = document; + } + + void create() + { + if (m_document != nullptr) + return; + + m_document = new QTextDocument(this); + m_document->setDocumentLayout(new TextDocumentLayout(m_document)); + + m_highlighter.reset(m_creator()); + m_highlighter->setDocument(m_document); + + connect(m_highlighter.get(), + &SyntaxHighlighter::resultsReady, + this, + &SyntaxHighlighterRunnerPrivate::resultsReady); + } + + void cloneDocument(int from, + int charsRemoved, + const QString textAdded, + const QMap &blocksPreedit) + { + QTextCursor cursor(m_document); + cursor.setPosition(qMin(m_document->characterCount() - 1, from + charsRemoved)); + cursor.setPosition(from, QTextCursor::KeepAnchor); + cursor.insertText(textAdded); + + for (auto it = blocksPreedit.cbegin(); it != blocksPreedit.cend(); ++it) { + const QTextBlock block = m_document->findBlock(it.key()); + block.layout()->setPreeditArea(it.value().position, it.value().text); + } + } + + void setExtraFormats(const QMap> &formatMap) + { + for (auto it = formatMap.cbegin(); it != formatMap.cend(); ++it) + m_highlighter->setExtraFormats(m_document->findBlockByNumber(it.key()), it.value()); + } + + void clearExtraFormats(const QList &blockNumbers) + { + for (auto it = blockNumbers.cbegin(); it != blockNumbers.cend(); ++it) + m_highlighter->clearExtraFormats(m_document->findBlockByNumber(*it)); + } + + void clearAllExtraFormats() { m_highlighter->clearAllExtraFormats(); } + + void setFontSettings(const TextEditor::FontSettings &fontSettings) + { + m_highlighter->setFontSettings(fontSettings); + m_highlighter->rehighlight(); + } + + KSyntaxHighlighting::Definition getDefinition() { return m_highlighter->getDefinition(); } + + void setLanguageFeaturesFlags(unsigned int flags) + { + m_highlighter->setLanguageFeaturesFlags(flags); + } + + void setEnabled(bool enabled) { m_highlighter->setEnabled(enabled); } + + void rehighlight() { m_highlighter->rehighlight(); } + +signals: + void resultsReady(const QList &result); + +private: + BaseSyntaxHighlighterRunner::SyntaxHighLighterCreator m_creator; + std::unique_ptr m_highlighter; + QTextDocument *m_document = nullptr; +}; + +// ----------------------------- BaseSyntaxHighlighterRunner -------------------------------------- + +BaseSyntaxHighlighterRunner::BaseSyntaxHighlighterRunner( + BaseSyntaxHighlighterRunner::SyntaxHighLighterCreator creator, QTextDocument *document) + : d(new SyntaxHighlighterRunnerPrivate(creator, document)) +{ + m_document = document; +} + +BaseSyntaxHighlighterRunner::~BaseSyntaxHighlighterRunner() = default; + +void BaseSyntaxHighlighterRunner::applyFormatRanges(const SyntaxHighlighter::Result &result) +{ + QTextBlock docBlock = m_document->findBlock(result.m_blockNumber); + if (!docBlock.isValid()) + return; + + result.copyToBlock(docBlock); + + if (!result.m_formatRanges.empty()) { + TextDocumentLayout::FoldValidator foldValidator; + foldValidator.setup(qobject_cast(m_document->documentLayout())); + docBlock.layout()->setFormats(result.m_formatRanges); + m_document->markContentsDirty(docBlock.position(), docBlock.length()); + foldValidator.process(docBlock); + } +} + +void BaseSyntaxHighlighterRunner::cloneDocumentData(int from, int charsRemoved, int charsAdded) +{ + QMap blocksPreedit; + QTextBlock firstBlock = m_document->findBlock(from); + QTextBlock endBlock = m_document->findBlock(from + charsAdded); + while (firstBlock.isValid() && firstBlock.position() < endBlock.position()) { + const int position = firstBlock.position(); + if (firstBlock.layout()) { + const int preeditAreaPosition = firstBlock.layout()->preeditAreaPosition(); + const QString preeditAreaText = firstBlock.layout()->preeditAreaText(); + if (preeditAreaPosition != -1) + blocksPreedit[position] = {preeditAreaPosition, preeditAreaText}; + } + firstBlock = firstBlock.next(); + } + + const QString text = Utils::Text::textAt(QTextCursor(m_document), from, charsAdded); + cloneDocument(from, charsRemoved, text, blocksPreedit); +} + +void BaseSyntaxHighlighterRunner::cloneDocument(int from, + int charsRemoved, + const QString textAdded, + const QMap &blocksPreedit) +{ + QMetaObject::invokeMethod(d.get(), [this, from, charsRemoved, textAdded, blocksPreedit] { + d->cloneDocument(from, charsRemoved, textAdded, blocksPreedit); + }); +} + +void BaseSyntaxHighlighterRunner::setExtraFormats( + const QMap> &formatMap) +{ + QMetaObject::invokeMethod(d.get(), [this, formatMap] { d->setExtraFormats(formatMap); }); +} + +void BaseSyntaxHighlighterRunner::clearExtraFormats(const QList &blockNumbers) +{ + QMetaObject::invokeMethod(d.get(), [this, blockNumbers] { d->clearExtraFormats(blockNumbers); }); +} + +void BaseSyntaxHighlighterRunner::clearAllExtraFormats() +{ + QMetaObject::invokeMethod(d.get(), [this] { d->clearAllExtraFormats(); }); +} + +void BaseSyntaxHighlighterRunner::setFontSettings(const TextEditor::FontSettings &fontSettings) +{ + QMetaObject::invokeMethod(d.get(), [this, fontSettings] { d->setFontSettings(fontSettings); }); +} + +void BaseSyntaxHighlighterRunner::setLanguageFeaturesFlags(unsigned int flags) +{ + QMetaObject::invokeMethod(d.get(), [this, flags] { d->setLanguageFeaturesFlags(flags); }); +} + +void BaseSyntaxHighlighterRunner::setEnabled(bool enabled) +{ + QMetaObject::invokeMethod(d.get(), [this, enabled] { d->setEnabled(enabled); }); +} + +void BaseSyntaxHighlighterRunner::rehighlight() +{ + QMetaObject::invokeMethod(d.get(), [this] { d->rehighlight(); }); +} + +KSyntaxHighlighting::Definition BaseSyntaxHighlighterRunner::getDefinition() +{ + return d->getDefinition(); +} + +// --------------------------- ThreadedSyntaxHighlighterRunner ------------------------------------ + +ThreadedSyntaxHighlighterRunner::ThreadedSyntaxHighlighterRunner(SyntaxHighLighterCreator creator, + QTextDocument *document) + : BaseSyntaxHighlighterRunner(creator, nullptr) +{ + QTC_ASSERT(document, return); + + d->moveToThread(&m_thread); + connect(&m_thread, &QThread::finished, d.get(), [this] { d.release()->deleteLater(); }); + m_thread.start(); + + QMetaObject::invokeMethod(d.get(), &SyntaxHighlighterRunnerPrivate::create); + + m_document = document; + connect(d.get(), + &SyntaxHighlighterRunnerPrivate::resultsReady, + document, + [this](const QList &result) { + for (const SyntaxHighlighter::Result &res : result) + applyFormatRanges(res); + }); + cloneDocumentData(0, 0, document->characterCount()); + connect(document, + &QTextDocument::contentsChange, + this, + [this](int from, int charsRemoved, int charsAdded) { + if (!this->document()) + return; + + cloneDocumentData(from, charsRemoved, charsAdded); + }); +} + +ThreadedSyntaxHighlighterRunner::~ThreadedSyntaxHighlighterRunner() +{ + m_thread.quit(); + m_thread.wait(); +} + +KSyntaxHighlighting::Definition ThreadedSyntaxHighlighterRunner::getDefinition() +{ + KSyntaxHighlighting::Definition definition; + QMetaObject::invokeMethod( + d.get(), + [this, &definition] { definition = d->getDefinition(); }, + Qt::BlockingQueuedConnection); + return definition; +} + +} // namespace TextEditor + +#include "syntaxhighlighterrunner.moc" diff --git a/src/plugins/texteditor/syntaxhighlighterrunner.h b/src/plugins/texteditor/syntaxhighlighterrunner.h new file mode 100644 index 00000000000..984b227e77b --- /dev/null +++ b/src/plugins/texteditor/syntaxhighlighterrunner.h @@ -0,0 +1,75 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE +class QTextDocument; +QT_END_NAMESPACE + +namespace TextEditor { + +class SyntaxHighlighterRunnerPrivate; + +class TEXTEDITOR_EXPORT BaseSyntaxHighlighterRunner : public QObject +{ + Q_OBJECT +public: + using SyntaxHighLighterCreator = std::function; + struct BlockPreeditData { + int position; + QString text; + }; + + BaseSyntaxHighlighterRunner(SyntaxHighLighterCreator creator, QTextDocument *document); + virtual ~BaseSyntaxHighlighterRunner(); + + void setExtraFormats(const QMap> &formats); + void clearExtraFormats(const QList &blockNumbers); + void clearAllExtraFormats(); + void setFontSettings(const TextEditor::FontSettings &fontSettings); + void setLanguageFeaturesFlags(unsigned int flags); + void setEnabled(bool enabled); + void rehighlight(); + + virtual KSyntaxHighlighting::Definition getDefinition(); + + QTextDocument *document() const { return m_document; } + SyntaxHighLighterCreator creator() const { return m_creator; } + +protected: + std::unique_ptr d; + QPointer m_document = nullptr; + void applyFormatRanges(const SyntaxHighlighter::Result &result); + void cloneDocumentData(int from, int charsRemoved, int charsAdded); + void cloneDocument(int from, + int charsRemoved, + const QString textAdded, + const QMap &blocksPreedit); + +private: + SyntaxHighLighterCreator m_creator; +}; + +class TEXTEDITOR_EXPORT ThreadedSyntaxHighlighterRunner : public BaseSyntaxHighlighterRunner +{ +public: + ThreadedSyntaxHighlighterRunner(SyntaxHighLighterCreator SyntaxHighLighterCreator, + QTextDocument *document); + ~ThreadedSyntaxHighlighterRunner(); + + KSyntaxHighlighting::Definition getDefinition() override; + +private: + QThread m_thread; +}; + +} // namespace TextEditor + diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp index 2a6f4da0a7c..0f19b922048 100644 --- a/src/plugins/texteditor/textdocument.cpp +++ b/src/plugins/texteditor/textdocument.cpp @@ -13,11 +13,13 @@ #include "texteditortr.h" #include "textindenter.h" #include "typingsettings.h" +#include "syntaxhighlighterrunner.h" #include #include #include #include +#include #include #include @@ -73,7 +75,6 @@ public: FontSettings m_fontSettings; bool m_fontSettingsNeedsApply = false; // for applying font settings delayed till an editor becomes visible QTextDocument m_document; - SyntaxHighlighter *m_highlighter = nullptr; CompletionAssistProvider *m_completionAssistProvider = nullptr; CompletionAssistProvider *m_functionHintAssistProvider = nullptr; IAssistProvider *m_quickFixProvider = nullptr; @@ -92,6 +93,8 @@ public: TextMarks m_marksCache; // Marks not owned Utils::Guard m_modificationChangedGuard; + + BaseSyntaxHighlighterRunner *m_highlighterRunner = nullptr; }; MultiTextCursor TextDocumentPrivate::indentOrUnindent(const MultiTextCursor &cursors, @@ -450,10 +453,8 @@ void TextDocument::applyFontSettings() block = block.next(); } updateLayout(); - if (d->m_highlighter) { - d->m_highlighter->setFontSettings(d->m_fontSettings); - d->m_highlighter->rehighlight(); - } + if (d->m_highlighterRunner) + d->m_highlighterRunner->setFontSettings(d->m_fontSettings); } const FontSettings &TextDocument::fontSettings() const @@ -625,9 +626,9 @@ QTextDocument *TextDocument::document() const return &d->m_document; } -SyntaxHighlighter *TextDocument::syntaxHighlighter() const +BaseSyntaxHighlighterRunner *TextDocument::syntaxHighlighterRunner() const { - return d->m_highlighter; + return d->m_highlighterRunner; } /*! @@ -901,14 +902,18 @@ bool TextDocument::reload(QString *errorString, ReloadFlag flag, ChangeType type return reload(errorString); } -void TextDocument::setSyntaxHighlighterCreator(const SyntaxHighLighterCreator &creator) +void TextDocument::resetSyntaxHighlighter(const std::function &creator, + bool threaded) { - if (d->m_highlighter) - delete d->m_highlighter; + if (d->m_highlighterRunner) + delete d->m_highlighterRunner; - d->m_highlighter = creator(); - d->m_highlighter->setParent(this); - d->m_highlighter->setDocument(&d->m_document); + if (threaded) { + d->m_highlighterRunner = new ThreadedSyntaxHighlighterRunner(creator, document()); + return; + } + + d->m_highlighterRunner = new BaseSyntaxHighlighterRunner(creator, document()); } void TextDocument::cleanWhitespace(const QTextCursor &cursor) diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h index e05abbdba39..c7fe881d27e 100644 --- a/src/plugins/texteditor/textdocument.h +++ b/src/plugins/texteditor/textdocument.h @@ -34,6 +34,7 @@ class FontSettings; class IAssistProvider; class StorageSettings; class SyntaxHighlighter; +class BaseSyntaxHighlighterRunner; class TabSettings; class TextDocumentPrivate; class TextMark; @@ -126,8 +127,8 @@ public: QTextDocument *document() const; using SyntaxHighLighterCreator = std::function; - void setSyntaxHighlighterCreator(const SyntaxHighLighterCreator &creator); - SyntaxHighlighter *syntaxHighlighter() const; + void resetSyntaxHighlighter(const SyntaxHighLighterCreator &creator, bool threaded = true); + BaseSyntaxHighlighterRunner *syntaxHighlighterRunner() const; bool reload(QString *errorString, QTextCodec *codec); void cleanWhitespace(const QTextCursor &cursor); diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 80d72a4ded8..0d0f66e6b13 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -34,6 +34,7 @@ #include "texteditorsettings.h" #include "texteditortr.h" #include "typingsettings.h" +#include "syntaxhighlighterrunner.h" #include @@ -1930,9 +1931,11 @@ void TextEditorWidgetPrivate::foldLicenseHeader() const QString trimmedText = text.trimmed(); QStringList commentMarker; QStringList docMarker; - if (auto highlighter = qobject_cast( - q->textDocument()->syntaxHighlighter())) { - const HighlighterHelper::Definition def = highlighter->definition(); + HighlighterHelper::Definition def; + if (BaseSyntaxHighlighterRunner *highlighter = q->textDocument()->syntaxHighlighterRunner()) + def = highlighter->getDefinition(); + + if (def.isValid()) { for (const QString &marker : {def.singleLineCommentMarker(), def.multiLineCommentMarker().first}) { if (!marker.isEmpty()) @@ -3706,12 +3709,12 @@ void TextEditorWidgetPrivate::configureGenericHighlighter( q->setCodeFoldingSupported(false); } - m_document->setSyntaxHighlighterCreator([definition] { + m_document->resetSyntaxHighlighter([definition] { auto highlighter = new Highlighter(); if (definition.isValid()) highlighter->setDefinition(definition); return highlighter; - }); + }, false); m_document->setFontSettings(TextEditorSettings::fontSettings()); } @@ -3736,8 +3739,8 @@ void TextEditorWidgetPrivate::setupFromDefinition(const KSyntaxHighlighting::Def KSyntaxHighlighting::Definition TextEditorWidgetPrivate::currentDefinition() { - if (auto highlighter = qobject_cast(m_document->syntaxHighlighter())) - return highlighter->definition(); + if (BaseSyntaxHighlighterRunner *highlighter = m_document->syntaxHighlighterRunner()) + return highlighter->getDefinition(); return {}; } @@ -8120,7 +8123,7 @@ void TextEditorWidget::setDisplaySettings(const DisplaySettings &ds) optionFlags.setFlag(QTextOption::AddSpaceForLineAndParagraphSeparators); optionFlags.setFlag(QTextOption::ShowTabsAndSpaces, ds.m_visualizeWhitespace); if (optionFlags != currentOptionFlags) { - if (SyntaxHighlighter *highlighter = textDocument()->syntaxHighlighter()) + if (BaseSyntaxHighlighterRunner *highlighter = textDocument()->syntaxHighlighterRunner()) highlighter->rehighlight(); QTextOption option = document()->defaultTextOption(); option.setFlags(optionFlags); @@ -9259,7 +9262,7 @@ QString TextEditorWidget::textAt(int from, int to) const void TextEditorWidget::configureGenericHighlighter() { - HighlighterHelper::Definitions definitions = HighlighterHelper::definitionsForDocument(textDocument()); + const HighlighterHelper::Definitions definitions = HighlighterHelper::definitionsForDocument(textDocument()); d->configureGenericHighlighter(definitions.isEmpty() ? HighlighterHelper::Definition() : definitions.first()); d->updateSyntaxInfoBar(definitions, textDocument()->filePath().fileName()); @@ -9267,7 +9270,7 @@ void TextEditorWidget::configureGenericHighlighter() void TextEditorWidget::configureGenericHighlighter(const Utils::MimeType &mimeType) { - HighlighterHelper::Definitions definitions = HighlighterHelper::definitionsForMimeType(mimeType.name()); + const HighlighterHelper::Definitions definitions = HighlighterHelper::definitionsForMimeType(mimeType.name()); d->configureGenericHighlighter(definitions.isEmpty() ? HighlighterHelper::Definition() : definitions.first()); d->removeSyntaxInfoBar(); @@ -9275,7 +9278,7 @@ void TextEditorWidget::configureGenericHighlighter(const Utils::MimeType &mimeTy expected_str TextEditorWidget::configureGenericHighlighter(const QString &definitionName) { - HighlighterHelper::Definition definition = TextEditor::HighlighterHelper::definitionForName(definitionName); + const HighlighterHelper::Definition definition = TextEditor::HighlighterHelper::definitionForName(definitionName); if (!definition.isValid()) return make_unexpected(Tr::tr("Could not find definition.")); @@ -9453,7 +9456,7 @@ void TextEditorFactory::setEditorCreator(const EditorCreator &creator) doc->setIndenter(d->m_indenterCreator(doc->document())); if (d->m_syntaxHighlighterCreator) - doc->setSyntaxHighlighterCreator(d->m_syntaxHighlighterCreator); + doc->resetSyntaxHighlighter(d->m_syntaxHighlighterCreator); doc->setCompletionAssistProvider(d->m_completionAssistProvider ? d->m_completionAssistProvider : &basicSnippetProvider); diff --git a/src/plugins/texteditor/texteditor.qbs b/src/plugins/texteditor/texteditor.qbs index 77f80a51a58..4f64752c122 100644 --- a/src/plugins/texteditor/texteditor.qbs +++ b/src/plugins/texteditor/texteditor.qbs @@ -124,6 +124,8 @@ QtcPlugin { "storagesettings.h", "syntaxhighlighter.cpp", "syntaxhighlighter.h", + "syntaxhighlighterrunner.cpp", + "syntaxhighlighterrunner.h", "tabsettings.cpp", "tabsettings.h", "tabsettingswidget.cpp", diff --git a/src/plugins/vcsbase/diffandloghighlighter.cpp b/src/plugins/vcsbase/diffandloghighlighter.cpp index 3cb27b9edb9..6b4eb2ba0b3 100644 --- a/src/plugins/vcsbase/diffandloghighlighter.cpp +++ b/src/plugins/vcsbase/diffandloghighlighter.cpp @@ -222,9 +222,9 @@ void DiffAndLogHighlighter::setFontSettings(const TextEditor::FontSettings &font d->updateOtherFormats(); } -void DiffAndLogHighlighter::setEnabled(bool e) +void DiffAndLogHighlighter::setEnabled(bool enabled) { - d->m_enabled = e; + d->m_enabled = enabled; } } // namespace VcsBase diff --git a/src/plugins/vcsbase/diffandloghighlighter.h b/src/plugins/vcsbase/diffandloghighlighter.h index 6318de941f3..7caea12d52f 100644 --- a/src/plugins/vcsbase/diffandloghighlighter.h +++ b/src/plugins/vcsbase/diffandloghighlighter.h @@ -23,11 +23,11 @@ public: ~DiffAndLogHighlighter() override; void highlightBlock(const QString &text) override; + void setEnabled(bool enabled) override; +protected: void setFontSettings(const TextEditor::FontSettings &fontSettings) override; - void setEnabled(bool e); - private: friend class DiffAndLogHighlighterPrivate; DiffAndLogHighlighterPrivate *const d; diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp index 93fb8af7e09..539d10f6aaa 100644 --- a/src/plugins/vcsbase/vcsbaseeditor.cpp +++ b/src/plugins/vcsbase/vcsbaseeditor.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -761,7 +762,7 @@ void VcsBaseEditorWidget::init() } if (hasDiff()) { setCodeFoldingSupported(true); - textDocument()->setSyntaxHighlighterCreator( + textDocument()->resetSyntaxHighlighter( [diffFilePattern = d->m_diffFilePattern, logEntryPattern = d->m_logEntryPattern] { return new DiffAndLogHighlighter(diffFilePattern, logEntryPattern); }); @@ -824,8 +825,7 @@ void VcsBaseEditorWidget::setFileLogAnnotateEnabled(bool e) void VcsBaseEditorWidget::setHighlightingEnabled(bool e) { - auto dh = static_cast(textDocument()->syntaxHighlighter()); - dh->setEnabled(e); + textDocument()->syntaxHighlighterRunner()->setEnabled(e); } FilePath VcsBaseEditorWidget::workingDirectory() const @@ -1100,11 +1100,11 @@ void VcsBaseEditorWidget::slotActivateAnnotation() disconnect(this, &QPlainTextEdit::textChanged, this, &VcsBaseEditorWidget::slotActivateAnnotation); - if (auto ah = qobject_cast(textDocument()->syntaxHighlighter())) { + if (BaseSyntaxHighlighterRunner *ah = textDocument()->syntaxHighlighterRunner()) { ah->rehighlight(); } else { BaseAnnotationHighlighterCreator creator = annotationHighlighterCreator(); - textDocument()->setSyntaxHighlighterCreator( + textDocument()->resetSyntaxHighlighter( [creator, annotation = d->m_annotation] { return creator(annotation); }); } } diff --git a/tests/auto/texteditor/highlighter/tst_highlighter.cpp b/tests/auto/texteditor/highlighter/tst_highlighter.cpp index 7c30d3371ea..f9060afae5b 100644 --- a/tests/auto/texteditor/highlighter/tst_highlighter.cpp +++ b/tests/auto/texteditor/highlighter/tst_highlighter.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -31,7 +32,7 @@ private slots: private: QTextDocument *doc = nullptr; - SyntaxHighlighter *highlighter = nullptr; + BaseSyntaxHighlighterRunner *highlighterRunner = nullptr; FontSettings fontsettings; QHash formatHash; QTextCharFormat whitespaceFormat; @@ -59,7 +60,9 @@ Last)"; doc = new QTextDocument(); doc->setPlainText(text); - highlighter = new SyntaxHighlighter(doc, fontsettings); + highlighterRunner + = new BaseSyntaxHighlighterRunner([this] { return new SyntaxHighlighter(doc, fontsettings); }, + doc); } static const HighlightingResults &highlightingResults() @@ -77,7 +80,7 @@ void tst_highlighter::test_setExtraAdditionalFormats() { QCOMPARE(doc->blockCount(), 4); - SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash); + SemanticHighlighter::setExtraAdditionalFormats(highlighterRunner, highlightingResults(), formatHash); for (auto block = doc->firstBlock(); block.isValid(); block = block.next()) { QVERIFY(block.blockNumber() < 4); @@ -140,17 +143,17 @@ void tst_highlighter::test_clearExtraFormats() { QCOMPARE(doc->blockCount(), 4); - SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash); + SemanticHighlighter::setExtraAdditionalFormats(highlighterRunner, highlightingResults(), formatHash); QTextBlock firstBlock = doc->findBlockByNumber(0); QTextBlock spacesLineBlock = doc->findBlockByNumber(1); QTextBlock emptyLineBlock = doc->findBlockByNumber(2); QTextBlock lastBlock = doc->findBlockByNumber(3); - highlighter->clearExtraFormats(emptyLineBlock); + highlighterRunner->clearExtraFormats({emptyLineBlock.blockNumber()}); QVERIFY(emptyLineBlock.layout()->formats().isEmpty()); - highlighter->clearExtraFormats(spacesLineBlock); + highlighterRunner->clearExtraFormats({spacesLineBlock.blockNumber()}); auto formats = spacesLineBlock.layout()->formats(); // the spaces are not extra formats and should remain when clearing extra formats @@ -185,7 +188,7 @@ void tst_highlighter::test_clearExtraFormats() QCOMPARE(formats.at(1).start, 0); QCOMPARE(formats.at(1).length, 5); - highlighter->clearAllExtraFormats(); + highlighterRunner->clearAllExtraFormats(); QVERIFY(firstBlock.layout()->formats().isEmpty()); QVERIFY(emptyLineBlock.layout()->formats().isEmpty()); @@ -221,7 +224,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() QFutureInterface fiOld; fiOld.reportResults(highlightingResults()); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fiOld.future(), 2, 0, @@ -229,7 +232,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() auto formats = firstBlock.layout()->formats(); QVERIFY(formats.isEmpty()); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fiOld.future(), 0, 2, @@ -254,7 +257,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() QCOMPARE(formats.at(1).start, 11); QCOMPARE(formats.at(1).length, 1); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fiOld.future(), 3, 6, @@ -280,7 +283,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() QFutureInterface fiNew; fiNew.reportResults(newResults); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fiNew.future(), 0, 3, @@ -294,7 +297,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() QCOMPARE(formats.at(0).start, 0); QCOMPARE(formats.at(0).length, 1); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fiNew.future(), 3, 4, @@ -305,14 +308,14 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() QVERIFY(formats.isEmpty()); // QTCREATORBUG-29218 - highlighter->clearAllExtraFormats(); + highlighterRunner->clearAllExtraFormats(); const HighlightingResults bug29218Results{HighlightingResult(1, 1, 2, 0), HighlightingResult(1, 3, 2, 1)}; QFutureInterface fi29218; fi29218.reportResults(bug29218Results); formats = firstBlock.layout()->formats(); QVERIFY(formats.isEmpty()); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fi29218.future(), 0, 1, @@ -323,7 +326,7 @@ void tst_highlighter::test_incrementalApplyAdditionalFormats() QCOMPARE(formats.at(0).format.fontOverline(), false); QCOMPARE(formats.at(0).start, 0); QCOMPARE(formats.at(0).length, 2); - SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighter, + SemanticHighlighter::incrementalApplyExtraAdditionalFormats(highlighterRunner, fi29218.future(), 1, 2, @@ -347,7 +350,7 @@ void tst_highlighter::test_preeditText() QTextBlock firstBlock = doc->findBlockByNumber(0); firstBlock.layout()->setPreeditArea(2, "uaf"); - SemanticHighlighter::setExtraAdditionalFormats(highlighter, highlightingResults(), formatHash); + SemanticHighlighter::setExtraAdditionalFormats(highlighterRunner, highlightingResults(), formatHash); auto formats = firstBlock.layout()->formats(); QCOMPARE(formats.size(), 2); @@ -366,7 +369,7 @@ void tst_highlighter::cleanup() { delete doc; doc = nullptr; - highlighter = nullptr; + highlighterRunner = nullptr; } } // namespace TextEditor