File indexing completed on 2024-05-12 04:02:20

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "syntaxhighlighter.h"
0008 #include "abstracthighlighter_p.h"
0009 #include "definition.h"
0010 #include "definition_p.h"
0011 #include "foldingregion.h"
0012 #include "format.h"
0013 #include "format_p.h"
0014 #include "state.h"
0015 #include "theme.h"
0016 #include "themedata_p.h"
0017 
0018 Q_DECLARE_METATYPE(QTextBlock)
0019 
0020 using namespace KSyntaxHighlighting;
0021 
0022 namespace KSyntaxHighlighting
0023 {
0024 class TextBlockUserData : public QTextBlockUserData
0025 {
0026 public:
0027     State state;
0028     QList<FoldingRegion> foldingRegions;
0029 };
0030 
0031 class SyntaxHighlighterPrivate : public AbstractHighlighterPrivate
0032 {
0033 public:
0034     static FoldingRegion foldingRegion(const QTextBlock &startBlock);
0035     void initTextFormat(QTextCharFormat &tf, const Format &format);
0036     void computeTextFormats();
0037 
0038     struct TextFormat {
0039         QTextCharFormat tf;
0040         /**
0041          * id to check that the format belongs to the definition
0042          */
0043         std::intptr_t ptrId;
0044     };
0045 
0046     QList<FoldingRegion> foldingRegions;
0047     std::vector<TextFormat> tfs;
0048 };
0049 
0050 }
0051 
0052 FoldingRegion SyntaxHighlighterPrivate::foldingRegion(const QTextBlock &startBlock)
0053 {
0054     const auto data = dynamic_cast<TextBlockUserData *>(startBlock.userData());
0055     if (!data) {
0056         return FoldingRegion();
0057     }
0058     for (int i = data->foldingRegions.size() - 1; i >= 0; --i) {
0059         if (data->foldingRegions.at(i).type() == FoldingRegion::Begin) {
0060             return data->foldingRegions.at(i);
0061         }
0062     }
0063     return FoldingRegion();
0064 }
0065 
0066 void SyntaxHighlighterPrivate::initTextFormat(QTextCharFormat &tf, const Format &format)
0067 {
0068     // always set the foreground color to avoid palette issues
0069     tf.setForeground(format.textColor(m_theme));
0070 
0071     if (format.hasBackgroundColor(m_theme)) {
0072         tf.setBackground(format.backgroundColor(m_theme));
0073     }
0074     if (format.isBold(m_theme)) {
0075         tf.setFontWeight(QFont::Bold);
0076     }
0077     if (format.isItalic(m_theme)) {
0078         tf.setFontItalic(true);
0079     }
0080     if (format.isUnderline(m_theme)) {
0081         tf.setFontUnderline(true);
0082     }
0083     if (format.isStrikeThrough(m_theme)) {
0084         tf.setFontStrikeOut(true);
0085     }
0086 }
0087 
0088 void SyntaxHighlighterPrivate::computeTextFormats()
0089 {
0090     auto definitions = m_definition.includedDefinitions();
0091     definitions.append(m_definition);
0092 
0093     int maxId = 0;
0094     for (const auto &definition : std::as_const(definitions)) {
0095         for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
0096             maxId = qMax(maxId, format.id());
0097         }
0098     }
0099     tfs.clear();
0100     tfs.resize(maxId + 1);
0101 
0102     // initialize tfs
0103     for (const auto &definition : std::as_const(definitions)) {
0104         for (const auto &format : std::as_const(DefinitionData::get(definition)->formats)) {
0105             auto &tf = tfs[format.id()];
0106             tf.ptrId = FormatPrivate::ptrId(format);
0107             initTextFormat(tf.tf, format);
0108         }
0109     }
0110 }
0111 
0112 SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
0113     : QSyntaxHighlighter(parent)
0114     , AbstractHighlighter(new SyntaxHighlighterPrivate)
0115 {
0116     qRegisterMetaType<QTextBlock>();
0117 }
0118 
0119 SyntaxHighlighter::SyntaxHighlighter(QTextDocument *document)
0120     : QSyntaxHighlighter(document)
0121     , AbstractHighlighter(new SyntaxHighlighterPrivate)
0122 {
0123     qRegisterMetaType<QTextBlock>();
0124 }
0125 
0126 SyntaxHighlighter::~SyntaxHighlighter()
0127 {
0128 }
0129 
0130 void SyntaxHighlighter::setDefinition(const Definition &def)
0131 {
0132     Q_D(SyntaxHighlighter);
0133 
0134     const auto needsRehighlight = d->m_definition != def;
0135     if (DefinitionData::get(d->m_definition) != DefinitionData::get(def)) {
0136         d->m_definition = def;
0137         d->tfs.clear();
0138     }
0139     if (needsRehighlight) {
0140         rehighlight();
0141     }
0142 }
0143 
0144 void SyntaxHighlighter::setTheme(const Theme &theme)
0145 {
0146     Q_D(SyntaxHighlighter);
0147     if (ThemeData::get(d->m_theme) != ThemeData::get(theme)) {
0148         d->m_theme = theme;
0149         d->tfs.clear();
0150     }
0151 }
0152 
0153 bool SyntaxHighlighter::startsFoldingRegion(const QTextBlock &startBlock) const
0154 {
0155     return SyntaxHighlighterPrivate::foldingRegion(startBlock).type() == FoldingRegion::Begin;
0156 }
0157 
0158 QTextBlock SyntaxHighlighter::findFoldingRegionEnd(const QTextBlock &startBlock) const
0159 {
0160     const auto region = SyntaxHighlighterPrivate::foldingRegion(startBlock);
0161 
0162     auto block = startBlock;
0163     int depth = 1;
0164     while (block.isValid()) {
0165         block = block.next();
0166         const auto data = dynamic_cast<TextBlockUserData *>(block.userData());
0167         if (!data) {
0168             continue;
0169         }
0170         for (const auto &foldingRegion : std::as_const(data->foldingRegions)) {
0171             if (foldingRegion.id() != region.id()) {
0172                 continue;
0173             }
0174             if (foldingRegion.type() == FoldingRegion::End) {
0175                 --depth;
0176             } else if (foldingRegion.type() == FoldingRegion::Begin) {
0177                 ++depth;
0178             }
0179             if (depth == 0) {
0180                 return block;
0181             }
0182         }
0183     }
0184 
0185     return QTextBlock();
0186 }
0187 
0188 void SyntaxHighlighter::highlightBlock(const QString &text)
0189 {
0190     Q_D(SyntaxHighlighter);
0191 
0192     static const State emptyState;
0193     const State *previousState = &emptyState;
0194     if (currentBlock().position() > 0) {
0195         const auto prevBlock = currentBlock().previous();
0196         const auto prevData = dynamic_cast<TextBlockUserData *>(prevBlock.userData());
0197         if (prevData) {
0198             previousState = &prevData->state;
0199         }
0200     }
0201     d->foldingRegions.clear();
0202     auto newState = highlightLine(text, *previousState);
0203 
0204     auto data = dynamic_cast<TextBlockUserData *>(currentBlockUserData());
0205     if (!data) { // first time we highlight this
0206         data = new TextBlockUserData;
0207         data->state = std::move(newState);
0208         data->foldingRegions = d->foldingRegions;
0209         setCurrentBlockUserData(data);
0210         return;
0211     }
0212 
0213     if (data->state == newState && data->foldingRegions == d->foldingRegions) { // we ended up in the same state, so we are done here
0214         return;
0215     }
0216     data->state = std::move(newState);
0217     data->foldingRegions = d->foldingRegions;
0218 
0219     const auto nextBlock = currentBlock().next();
0220     if (nextBlock.isValid()) {
0221         QMetaObject::invokeMethod(this, "rehighlightBlock", Qt::QueuedConnection, Q_ARG(QTextBlock, nextBlock));
0222     }
0223 }
0224 
0225 void SyntaxHighlighter::applyFormat(int offset, int length, const Format &format)
0226 {
0227     if (length == 0) {
0228         return;
0229     }
0230 
0231     Q_D(SyntaxHighlighter);
0232 
0233     if (Q_UNLIKELY(d->tfs.empty())) {
0234         d->computeTextFormats();
0235     }
0236 
0237     const auto id = static_cast<std::size_t>(format.id());
0238     // This doesn't happen when format comes from the definition.
0239     // But as the user can override the function to pass any format, this is a possible scenario.
0240     if (id < d->tfs.size() && d->tfs[id].ptrId == FormatPrivate::ptrId(format)) {
0241         QSyntaxHighlighter::setFormat(offset, length, d->tfs[id].tf);
0242     } else {
0243         QTextCharFormat tf;
0244         d->initTextFormat(tf, format);
0245         QSyntaxHighlighter::setFormat(offset, length, tf);
0246     }
0247 }
0248 
0249 void SyntaxHighlighter::applyFolding(int offset, int length, FoldingRegion region)
0250 {
0251     Q_UNUSED(offset);
0252     Q_UNUSED(length);
0253     Q_D(SyntaxHighlighter);
0254 
0255     if (region.type() == FoldingRegion::Begin) {
0256         d->foldingRegions.push_back(region);
0257     }
0258 
0259     if (region.type() == FoldingRegion::End) {
0260         for (int i = d->foldingRegions.size() - 1; i >= 0; --i) {
0261             if (d->foldingRegions.at(i).id() != region.id() || d->foldingRegions.at(i).type() != FoldingRegion::Begin) {
0262                 continue;
0263             }
0264             d->foldingRegions.remove(i);
0265             return;
0266         }
0267         d->foldingRegions.push_back(region);
0268     }
0269 }
0270 
0271 #include "moc_syntaxhighlighter.cpp"