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"