Files
qt-creator/src/plugins/texteditor/syntaxhighlighterrunner.cpp
David Schulz 2428db2a30 TextEditor: Fix crash in SyntaxHighlighter
The SyntaxHighlighterRunner is deleted by deleteLater in the
TextDocument destructor. This makes it possible that the
SyntaxHighlighter inside the runner lives longer than the document
itself. Avoid this by making the document the parent of the
SyntaxHighlighter like before the async highlighter patch series.

Fixes: QTCREATORBUG-30494
Change-Id: I6ce9c35ab400b17f2a1a6f3c3bd98df23f41c71e
Reviewed-by: Artem Sokolovskii <artem.sokolovskii@qt.io>
2024-03-07 08:20:07 +00:00

386 lines
12 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 "fontsettings.h"
#include "textdocumentlayout.h"
#include "texteditorsettings.h"
#include "highlighter.h"
#include <utils/algorithm.h>
#include <utils/textutils.h>
#include <QMetaObject>
#include <QTextCursor>
#include <QTextDocument>
#include <QThread>
namespace TextEditor {
struct BlockPreeditData {
int position;
QString text;
};
class SyntaxHighlighterRunnerPrivate : public QObject
{
Q_OBJECT
public:
SyntaxHighlighterRunnerPrivate(SyntaxHighlighter *highlighter,
QTextDocument *document,
bool async)
: m_highlighter(highlighter)
{
if (async) {
m_document = new QTextDocument(this);
m_document->setDocumentLayout(new TextDocumentLayout(m_document));
} else {
m_document = document;
}
m_highlighter->setParent(m_document);
m_highlighter->setDocument(m_document);
connect(m_highlighter,
&SyntaxHighlighter::resultsReady,
this,
&SyntaxHighlighterRunnerPrivate::resultsReady);
}
void changeDocument(int from,
int charsRemoved,
const QString textAdded,
const QMap<int, BlockPreeditData> &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->findBlockByNumber(it.key());
block.layout()->setPreeditArea(it.value().position, it.value().text);
}
}
void setExtraFormats(const QMap<int, QList<QTextLayout::FormatRange>> &formatMap)
{
QTC_ASSERT(m_highlighter, return);
for (auto it = formatMap.cbegin(); it != formatMap.cend(); ++it)
m_highlighter->setExtraFormats(m_document->findBlockByNumber(it.key()), it.value());
}
void clearExtraFormats(const QList<int> &blockNumbers)
{
QTC_ASSERT(m_highlighter, return);
for (auto it = blockNumbers.cbegin(); it != blockNumbers.cend(); ++it)
m_highlighter->clearExtraFormats(m_document->findBlockByNumber(*it));
}
void clearAllExtraFormats()
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->clearAllExtraFormats();
}
void setFontSettings(const TextEditor::FontSettings &fontSettings)
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->setFontSettings(fontSettings);
}
void setDefinitionName(const QString &name)
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->setDefinitionName(name);
}
void setLanguageFeaturesFlags(unsigned int flags)
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->setLanguageFeaturesFlags(flags);
}
void setEnabled(bool enabled)
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->setEnabled(enabled);
}
void rehighlight()
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->rehighlight();
}
void reformatBlocks(int from, int charsRemoved, int charsAdded)
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->reformatBlocks(from, charsRemoved, charsAdded);
}
void setInterrupted(bool interrupted)
{
QTC_ASSERT(m_highlighter, return);
m_highlighter->setInterrupted(interrupted);
}
QPointer<SyntaxHighlighter> m_highlighter = nullptr;
QTextDocument *m_document = nullptr;
signals:
void resultsReady(const QList<SyntaxHighlighter::Result> &result);
};
void SyntaxHighlighterRunner::HighlightingStatus::notInterrupted(int from,
int charsRemoved,
int charsAdded)
{
m_from = from;
m_addedChars = charsAdded;
m_removedChars = charsRemoved;
m_current = from;
m_newFrom = from + m_addedChars;
m_interruptionRequested = false;
}
void SyntaxHighlighterRunner::HighlightingStatus::interrupted(int from,
int charsRemoved,
int charsAdded)
{
m_newFrom = std::min(m_newFrom, from);
m_newFrom = std::min(m_current, m_newFrom);
m_removedChars += charsRemoved;
m_addedChars += charsAdded;
m_interruptionRequested = true;
}
void SyntaxHighlighterRunner::HighlightingStatus::applyNewFrom()
{
m_from = m_newFrom;
m_current = m_newFrom;
m_interruptionRequested = false;
}
SyntaxHighlighterRunner::SyntaxHighlighterRunner(SyntaxHighlighter *highlighter,
QTextDocument *document,
bool async)
: d(new SyntaxHighlighterRunnerPrivate(highlighter, document, async))
, m_document(document)
{
m_useGenericHighlighter = qobject_cast<Highlighter *>(d->m_highlighter);
if (async) {
m_thread.emplace();
d->moveToThread(&*m_thread);
connect(&*m_thread, &QThread::finished, d, &QObject::deleteLater);
m_thread->start();
connect(d,
&SyntaxHighlighterRunnerPrivate::resultsReady,
this,
&SyntaxHighlighterRunner::applyFormatRanges);
changeDocument(0, 0, m_document->characterCount());
connect(m_document,
&QTextDocument::contentsChange,
this,
&SyntaxHighlighterRunner::changeDocument);
m_foldValidator.setup(qobject_cast<TextDocumentLayout *>(document->documentLayout()));
} else {
connect(d,
&SyntaxHighlighterRunnerPrivate::resultsReady,
this,
[this](const QList<SyntaxHighlighter::Result> &result) {
if (result.size() == 1
&& result.at(0).m_state == SyntaxHighlighter::State::Extras)
return;
auto done = std::find_if(result.cbegin(),
result.cend(),
[](const SyntaxHighlighter::Result &res) {
return res.m_state
== SyntaxHighlighter::State::Done;
});
if (done != result.cend()) {
m_syntaxInfoUpdated = SyntaxHighlighter::State::Done;
emit highlightingFinished();
return;
}
m_syntaxInfoUpdated = SyntaxHighlighter::State::InProgress;
});
}
}
SyntaxHighlighterRunner::~SyntaxHighlighterRunner()
{
if (m_thread) {
m_thread->requestInterruption();
m_thread->quit();
m_thread->wait();
} else {
delete d;
}
}
void SyntaxHighlighterRunner::applyFormatRanges(const QList<SyntaxHighlighter::Result> &results)
{
if (m_document == nullptr)
return;
if (m_highlightingStatus.m_interruptionRequested) {
d->setInterrupted(false);
m_highlightingStatus.applyNewFrom();
reformatBlocks(m_highlightingStatus.m_newFrom,
m_highlightingStatus.m_removedChars,
m_highlightingStatus.m_addedChars);
return;
}
auto processResult = [this](SyntaxHighlighter::Result result, QTextBlock docBlock) {
if (!docBlock.isValid())
return;
result.copyToBlock(docBlock);
m_highlightingStatus.m_current = docBlock.position() + docBlock.length() - 1;
if (result.m_formatRanges != docBlock.layout()->formats()) {
docBlock.layout()->setFormats(result.m_formatRanges);
m_document->markContentsDirty(docBlock.position(), docBlock.length());
}
};
if (results.size() == 1 && results.at(0).m_state == SyntaxHighlighter::State::Extras) {
QTextBlock docBlock = m_document->findBlockByNumber(results.at(0).m_blockNumber);
processResult(results.at(0), docBlock);
return;
}
for (const SyntaxHighlighter::Result &result : results) {
m_syntaxInfoUpdated = result.m_state;
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::Start) {
m_foldValidator.reset();
continue;
}
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::Done) {
m_foldValidator.finalize();
emit highlightingFinished();
return;
}
QTextBlock docBlock = m_document->findBlockByNumber(result.m_blockNumber);
processResult(result, docBlock);
m_foldValidator.process(docBlock);
}
}
void SyntaxHighlighterRunner::changeDocument(int from, int charsRemoved, int charsAdded)
{
QTC_ASSERT(m_document, return);
SyntaxHighlighter::State prevSyntaxInfoUpdated = m_syntaxInfoUpdated;
m_syntaxInfoUpdated = SyntaxHighlighter::State::InProgress;
QMap<int, BlockPreeditData> blocksPreedit;
QTextBlock block = m_document->findBlock(from);
const QTextBlock endBlock = m_document->findBlock(from + charsAdded);
while (block.isValid() && block != endBlock) {
if (QTextLayout *layout = block.layout()) {
if (const int pos = layout->preeditAreaPosition(); pos != -1)
blocksPreedit[block.blockNumber()] = {pos, layout->preeditAreaText()};
}
block = block.next();
}
const QString text = Utils::Text::textAt(QTextCursor(m_document), from, charsAdded);
QMetaObject::invokeMethod(d, [this, from, charsRemoved, text, blocksPreedit] {
d->changeDocument(from, charsRemoved, text, blocksPreedit);
});
if (prevSyntaxInfoUpdated == SyntaxHighlighter::State::InProgress) {
m_highlightingStatus.interrupted(from, charsRemoved, charsAdded);
d->setInterrupted(true);
} else {
m_highlightingStatus.notInterrupted(from, charsRemoved, charsAdded);
d->setInterrupted(false);
}
}
bool SyntaxHighlighterRunner::useGenericHighlighter() const
{
return m_useGenericHighlighter;
}
void SyntaxHighlighterRunner::setExtraFormats(
const QMap<int, QList<QTextLayout::FormatRange>> &formatMap)
{
QMetaObject::invokeMethod(d, [this, formatMap] { d->setExtraFormats(formatMap); });
}
void SyntaxHighlighterRunner::clearExtraFormats(const QList<int> &blockNumbers)
{
QMetaObject::invokeMethod(d, [this, blockNumbers] { d->clearExtraFormats(blockNumbers); });
}
void SyntaxHighlighterRunner::clearAllExtraFormats()
{
QMetaObject::invokeMethod(d, [this] { d->clearAllExtraFormats(); });
}
void SyntaxHighlighterRunner::setFontSettings(const TextEditor::FontSettings &fontSettings)
{
QMetaObject::invokeMethod(d, [this, fontSettings] { d->setFontSettings(fontSettings); });
rehighlight();
}
void SyntaxHighlighterRunner::setLanguageFeaturesFlags(unsigned int flags)
{
QMetaObject::invokeMethod(d, [this, flags] { d->setLanguageFeaturesFlags(flags); });
}
void SyntaxHighlighterRunner::setEnabled(bool enabled)
{
QMetaObject::invokeMethod(d, [this, enabled] { d->setEnabled(enabled); });
}
void SyntaxHighlighterRunner::rehighlight()
{
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::InProgress) {
m_highlightingStatus.interrupted(0, 0, m_document->characterCount());
d->setInterrupted(true);
} else {
m_highlightingStatus.notInterrupted(0, 0, m_document->characterCount());
d->setInterrupted(false);
QMetaObject::invokeMethod(d, [this] { d->rehighlight(); });
}
}
void SyntaxHighlighterRunner::reformatBlocks(int from, int charsRemoved, int charsAdded)
{
QMetaObject::invokeMethod(
d,
[this, from, charsRemoved, charsAdded] {
d->reformatBlocks(from, charsRemoved, charsAdded);
});
}
QString SyntaxHighlighterRunner::definitionName()
{
return m_definitionName;
}
void SyntaxHighlighterRunner::setDefinitionName(const QString &name)
{
if (name.isEmpty())
return;
m_definitionName = name;
QMetaObject::invokeMethod(d, [this, name] { d->setDefinitionName(name); });
}
} // namespace TextEditor
#include "syntaxhighlighterrunner.moc"