2023-05-08 17:11:54 +02:00
|
|
|
// 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"
|
|
|
|
|
|
2023-11-28 14:58:58 +01:00
|
|
|
#include "fontsettings.h"
|
|
|
|
|
#include "textdocumentlayout.h"
|
2023-12-13 14:52:44 +01:00
|
|
|
#include "texteditorsettings.h"
|
2023-05-08 17:11:54 +02:00
|
|
|
|
|
|
|
|
#include <utils/textutils.h>
|
|
|
|
|
|
|
|
|
|
#include <QMetaObject>
|
|
|
|
|
#include <QTextCursor>
|
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
#include <QThread>
|
|
|
|
|
|
|
|
|
|
namespace TextEditor {
|
|
|
|
|
|
|
|
|
|
class SyntaxHighlighterRunnerPrivate : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
public:
|
2023-12-13 14:52:44 +01:00
|
|
|
void createHighlighter()
|
|
|
|
|
{
|
|
|
|
|
m_highlighter.reset(m_creator());
|
|
|
|
|
m_highlighter->setFontSettings(m_fontSettings);
|
|
|
|
|
m_highlighter->setDocument(m_document);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-08 17:11:54 +02:00
|
|
|
SyntaxHighlighterRunnerPrivate(BaseSyntaxHighlighterRunner::SyntaxHighLighterCreator creator,
|
2023-12-14 16:04:07 +01:00
|
|
|
QTextDocument *document, FontSettings fontSettings)
|
2023-05-08 17:11:54 +02:00
|
|
|
: m_creator(creator)
|
2023-12-13 14:52:44 +01:00
|
|
|
, m_document(document)
|
2023-12-14 16:04:07 +01:00
|
|
|
, m_fontSettings(fontSettings)
|
2023-05-08 17:11:54 +02:00
|
|
|
{
|
|
|
|
|
m_highlighter.reset(m_creator());
|
2023-12-13 14:52:44 +01:00
|
|
|
createHighlighter();
|
2023-05-08 17:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void create()
|
|
|
|
|
{
|
|
|
|
|
if (m_document != nullptr)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_document = new QTextDocument(this);
|
|
|
|
|
m_document->setDocumentLayout(new TextDocumentLayout(m_document));
|
|
|
|
|
|
2023-12-13 14:52:44 +01:00
|
|
|
createHighlighter();
|
2023-05-08 17:11:54 +02:00
|
|
|
|
|
|
|
|
connect(m_highlighter.get(),
|
|
|
|
|
&SyntaxHighlighter::resultsReady,
|
|
|
|
|
this,
|
|
|
|
|
&SyntaxHighlighterRunnerPrivate::resultsReady);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void cloneDocument(int from,
|
|
|
|
|
int charsRemoved,
|
|
|
|
|
const QString textAdded,
|
|
|
|
|
const QMap<int, BaseSyntaxHighlighterRunner::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->findBlock(it.key());
|
|
|
|
|
block.layout()->setPreeditArea(it.value().position, it.value().text);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void setExtraFormats(const QMap<int, QList<QTextLayout::FormatRange>> &formatMap)
|
|
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-28 14:58:58 +01:00
|
|
|
void setDefinitionName(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
return m_highlighter->setDefinitionName(name);
|
|
|
|
|
}
|
2023-05-08 17:11:54 +02:00
|
|
|
|
|
|
|
|
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<SyntaxHighlighter::Result> &result);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
BaseSyntaxHighlighterRunner::SyntaxHighLighterCreator m_creator;
|
|
|
|
|
std::unique_ptr<SyntaxHighlighter> m_highlighter;
|
|
|
|
|
QTextDocument *m_document = nullptr;
|
2023-12-14 16:04:07 +01:00
|
|
|
FontSettings m_fontSettings;
|
2023-05-08 17:11:54 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ----------------------------- BaseSyntaxHighlighterRunner --------------------------------------
|
|
|
|
|
|
|
|
|
|
BaseSyntaxHighlighterRunner::BaseSyntaxHighlighterRunner(
|
2023-12-14 16:04:07 +01:00
|
|
|
BaseSyntaxHighlighterRunner::SyntaxHighLighterCreator creator,
|
|
|
|
|
QTextDocument *document,
|
|
|
|
|
const TextEditor::FontSettings &fontSettings)
|
|
|
|
|
: d(new SyntaxHighlighterRunnerPrivate(creator, document, fontSettings))
|
2023-05-08 17:11:54 +02:00
|
|
|
{
|
|
|
|
|
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<TextDocumentLayout *>(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<int, BaseSyntaxHighlighterRunner::BlockPreeditData> 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<int, BlockPreeditData> &blocksPreedit)
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(d.get(), [this, from, charsRemoved, textAdded, blocksPreedit] {
|
|
|
|
|
d->cloneDocument(from, charsRemoved, textAdded, blocksPreedit);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseSyntaxHighlighterRunner::setExtraFormats(
|
|
|
|
|
const QMap<int, QList<QTextLayout::FormatRange>> &formatMap)
|
|
|
|
|
{
|
|
|
|
|
QMetaObject::invokeMethod(d.get(), [this, formatMap] { d->setExtraFormats(formatMap); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseSyntaxHighlighterRunner::clearExtraFormats(const QList<int> &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(); });
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-28 14:58:58 +01:00
|
|
|
QString BaseSyntaxHighlighterRunner::definitionName()
|
|
|
|
|
{
|
|
|
|
|
return m_definitionName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BaseSyntaxHighlighterRunner::setDefinitionName(const QString &name)
|
2023-05-08 17:11:54 +02:00
|
|
|
{
|
2023-11-28 14:58:58 +01:00
|
|
|
if (name.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_definitionName = name;
|
|
|
|
|
QMetaObject::invokeMethod(d.get(), [this, name] { d->setDefinitionName(name); });
|
2023-05-08 17:11:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --------------------------- 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<SyntaxHighlighter::Result> &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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace TextEditor
|
|
|
|
|
|
|
|
|
|
#include "syntaxhighlighterrunner.moc"
|