File indexing completed on 2024-04-28 11:45:26
0001 /* 0002 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch> 0003 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> 0004 SPDX-FileCopyrightText: 2003, 2004 Anders Lund <anders@alweb.dk> 0005 SPDX-FileCopyrightText: 2003 Hamish Rodda <rodda@kde.org> 0006 SPDX-FileCopyrightText: 2001, 2002 Joseph Wenninger <jowenn@kde.org> 0007 SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org> 0008 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> 0009 0010 SPDX-License-Identifier: LGPL-2.0-or-later 0011 */ 0012 0013 // BEGIN INCLUDES 0014 #include "katehighlight.h" 0015 0016 #include "katedocument.h" 0017 #include "katesyntaxmanager.h" 0018 // END 0019 0020 // BEGIN KateHighlighting 0021 KateHighlighting::KateHighlighting(const KSyntaxHighlighting::Definition &def) 0022 { 0023 // get name and section, always works 0024 iName = def.name(); 0025 iSection = def.translatedSection(); 0026 0027 // get all included definitions, e.g. PHP for HTML highlighting 0028 auto definitions = def.includedDefinitions(); 0029 0030 // handle the "no highlighting" case 0031 // it's possible to not have any definitions with malformed file 0032 if (!def.isValid() || (definitions.isEmpty() && def.formats().isEmpty())) { 0033 // dummy properties + formats 0034 m_properties.resize(1); 0035 m_propertiesForFormat.push_back(&m_properties[0]); 0036 m_formats.resize(1); 0037 m_formatsIdToIndex.insert(std::make_pair(m_formats[0].id(), 0)); 0038 0039 // be done, all below is just for the real highlighting variants 0040 return; 0041 } 0042 0043 // handle the real highlighting case 0044 noHl = false; 0045 iHidden = def.isHidden(); 0046 identifier = def.filePath(); 0047 iStyle = def.style(); 0048 m_indentation = def.indenter(); 0049 folding = def.foldingEnabled(); 0050 m_foldingIndentationSensitive = def.indentationBasedFoldingEnabled(); 0051 0052 // tell the AbstractHighlighter the definition it shall use 0053 setDefinition(def); 0054 0055 embeddedHighlightingModes.reserve(definitions.size()); 0056 // first: handle only really included definitions 0057 for (const auto &includedDefinition : std::as_const(definitions)) { 0058 embeddedHighlightingModes.push_back(includedDefinition.name()); 0059 } 0060 0061 // now: handle all, including this definition itself 0062 // create the format => attributes mapping 0063 // collect embedded highlightings, too 0064 // 0065 // we start with our definition as we want to have the default format 0066 // of the initial definition as attribute with index == 0 0067 // 0068 // we collect additional properties in the m_properties and 0069 // map the formats to the right properties in m_propertiesForFormat 0070 definitions.push_front(definition()); 0071 m_properties.resize(definitions.size()); 0072 size_t propertiesIndex = 0; 0073 for (const auto &includedDefinition : std::as_const(definitions)) { 0074 auto &properties = m_properties[propertiesIndex]; 0075 properties.definition = includedDefinition; 0076 properties.emptyLines.reserve(includedDefinition.foldingIgnoreList().size()); 0077 const auto foldingIgnoreList = includedDefinition.foldingIgnoreList(); 0078 for (const auto &emptyLine : foldingIgnoreList) { 0079 properties.emptyLines.push_back(QRegularExpression(emptyLine, QRegularExpression::UseUnicodePropertiesOption)); 0080 } 0081 properties.singleLineCommentMarker = includedDefinition.singleLineCommentMarker(); 0082 properties.singleLineCommentPosition = includedDefinition.singleLineCommentPosition(); 0083 const auto multiLineComment = includedDefinition.multiLineCommentMarker(); 0084 properties.multiLineCommentStart = multiLineComment.first; 0085 properties.multiLineCommentEnd = multiLineComment.second; 0086 0087 // collect character characters 0088 const auto encodings = includedDefinition.characterEncodings(); 0089 for (const auto &enc : encodings) { 0090 properties.characterEncodingsPrefixStore.addPrefix(enc.second); 0091 properties.characterEncodings[enc.second] = enc.first; 0092 properties.reverseCharacterEncodings[enc.first] = enc.second; 0093 } 0094 0095 // collect formats 0096 const auto formats = includedDefinition.formats(); 0097 for (const auto &format : formats) { 0098 // register format id => internal attributes, we want no clashs 0099 const auto nextId = m_formats.size(); 0100 m_formatsIdToIndex.insert(std::make_pair(format.id(), nextId)); 0101 m_formats.push_back(format); 0102 m_propertiesForFormat.push_back(&properties); 0103 } 0104 0105 // advance to next properties 0106 ++propertiesIndex; 0107 } 0108 } 0109 0110 void KateHighlighting::doHighlight(const Kate::TextLineData *prevLine, Kate::TextLineData *textLine, bool &ctxChanged) 0111 { 0112 // default: no context change 0113 ctxChanged = false; 0114 0115 // no text line => nothing to do 0116 if (!textLine) { 0117 return; 0118 } 0119 0120 // in all cases, remove old hl, or we will grow to infinite ;) 0121 textLine->clearAttributesAndFoldings(); 0122 0123 // reset folding start 0124 textLine->clearMarkedAsFoldingStart(); 0125 0126 // no hl set, nothing to do more than the above cleaning ;) 0127 if (noHl) { 0128 return; 0129 } 0130 0131 // ensure we arrive in clean state 0132 Q_ASSERT(!m_textLineToHighlight); 0133 Q_ASSERT(m_foldingStartToCount.isEmpty()); 0134 0135 // highlight the given line via the abstract highlighter 0136 // a bit ugly: we set the line to highlight as member to be able to update its stats in the applyFormat and applyFolding member functions 0137 m_textLineToHighlight = textLine; 0138 const KSyntaxHighlighting::State initialState(!prevLine ? KSyntaxHighlighting::State() : prevLine->highlightingState()); 0139 const KSyntaxHighlighting::State endOfLineState = highlightLine(textLine->text(), initialState); 0140 m_textLineToHighlight = nullptr; 0141 0142 // update highlighting state if needed 0143 if (textLine->highlightingState() != endOfLineState) { 0144 textLine->setHighlightingState(endOfLineState); 0145 ctxChanged = true; 0146 } 0147 0148 // handle folding info computed and cleanup hash again, if there 0149 // check if folding is not balanced and we have more starts then ends 0150 // then this line is a possible folding start! 0151 if (!m_foldingStartToCount.isEmpty()) { 0152 // possible folding start, if imbalanced, aka hash not empty! 0153 textLine->markAsFoldingStartAttribute(); 0154 0155 // clear hash for next doHighlight 0156 m_foldingStartToCount.clear(); 0157 } 0158 } 0159 0160 void KateHighlighting::applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) 0161 { 0162 // WE ATM assume ascending offset order 0163 Q_ASSERT(m_textLineToHighlight); 0164 if (!format.isValid()) { 0165 return; 0166 } 0167 0168 // get internal attribute, must exist 0169 const auto it = m_formatsIdToIndex.find(format.id()); 0170 Q_ASSERT(it != m_formatsIdToIndex.end()); 0171 0172 // remember highlighting info in our textline 0173 m_textLineToHighlight->addAttribute(Kate::TextLineData::Attribute(offset, length, it->second)); 0174 } 0175 0176 void KateHighlighting::applyFolding(int offset, int length, KSyntaxHighlighting::FoldingRegion region) 0177 { 0178 // WE ATM assume ascending offset order, we add the length to the offset for the folding ends to have ranges spanning the full folding region 0179 Q_ASSERT(m_textLineToHighlight); 0180 Q_ASSERT(region.isValid()); 0181 const int foldingValue = (region.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? int(region.id()) : -int(region.id()); 0182 m_textLineToHighlight->addFolding(offset + ((region.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 0 : length), length, foldingValue); 0183 0184 // for each end region, decrement counter for that type, erase if count reaches 0! 0185 if (foldingValue < 0) { 0186 QHash<int, int>::iterator end = m_foldingStartToCount.find(-foldingValue); 0187 if (end != m_foldingStartToCount.end()) { 0188 if (end.value() > 1) { 0189 --(end.value()); 0190 } else { 0191 m_foldingStartToCount.erase(end); 0192 } 0193 } 0194 } 0195 0196 // increment counter for each begin region! 0197 if (foldingValue > 0) { 0198 ++m_foldingStartToCount[foldingValue]; 0199 } 0200 } 0201 0202 int KateHighlighting::sanitizeFormatIndex(int attrib) const 0203 { 0204 // sanitize, e.g. one could have old hl info with now invalid attribs 0205 if (attrib < 0 || size_t(attrib) >= m_formats.size()) { 0206 return 0; 0207 } 0208 return attrib; 0209 } 0210 0211 const QHash<QString, QChar> &KateHighlighting::getCharacterEncodings(int attrib) const 0212 { 0213 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->characterEncodings; 0214 } 0215 0216 const KatePrefixStore &KateHighlighting::getCharacterEncodingsPrefixStore(int attrib) const 0217 { 0218 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->characterEncodingsPrefixStore; 0219 } 0220 0221 const QHash<QChar, QString> &KateHighlighting::getReverseCharacterEncodings(int attrib) const 0222 { 0223 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->reverseCharacterEncodings; 0224 } 0225 0226 bool KateHighlighting::attributeRequiresSpellchecking(int attr) 0227 { 0228 return m_formats[sanitizeFormatIndex(attr)].spellCheck(); 0229 } 0230 0231 KTextEditor::DefaultStyle KateHighlighting::defaultStyleForAttribute(int attr) const 0232 { 0233 return textStyleToDefaultStyle(m_formats[sanitizeFormatIndex(attr)].textStyle()); 0234 } 0235 0236 QString KateHighlighting::nameForAttrib(int attrib) const 0237 { 0238 const auto &format = m_formats.at(sanitizeFormatIndex(attrib)); 0239 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->definition.name() + QLatin1Char(':') 0240 + QString(format.isValid() ? format.name() : QStringLiteral("Normal")); 0241 } 0242 0243 bool KateHighlighting::isInWord(QChar c, int attrib) const 0244 { 0245 return !m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->definition.isWordDelimiter(c) && !c.isSpace() && c != QLatin1Char('"') 0246 && c != QLatin1Char('\'') && c != QLatin1Char('`'); 0247 } 0248 0249 bool KateHighlighting::canBreakAt(QChar c, int attrib) const 0250 { 0251 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->definition.isWordWrapDelimiter(c) && c != QLatin1Char('"') && c != QLatin1Char('\''); 0252 } 0253 0254 const QVector<QRegularExpression> &KateHighlighting::emptyLines(int attrib) const 0255 { 0256 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->emptyLines; 0257 } 0258 0259 bool KateHighlighting::canComment(int startAttrib, int endAttrib) const 0260 { 0261 const auto startProperties = m_propertiesForFormat.at(sanitizeFormatIndex(startAttrib)); 0262 const auto endProperties = m_propertiesForFormat.at(sanitizeFormatIndex(endAttrib)); 0263 return (startProperties == endProperties 0264 && ((!startProperties->multiLineCommentStart.isEmpty() && !startProperties->multiLineCommentEnd.isEmpty()) 0265 || !startProperties->singleLineCommentMarker.isEmpty())); 0266 } 0267 0268 QString KateHighlighting::getCommentStart(int attrib) const 0269 { 0270 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->multiLineCommentStart; 0271 } 0272 0273 QString KateHighlighting::getCommentEnd(int attrib) const 0274 { 0275 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->multiLineCommentEnd; 0276 } 0277 0278 QString KateHighlighting::getCommentSingleLineStart(int attrib) const 0279 { 0280 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->singleLineCommentMarker; 0281 } 0282 0283 KSyntaxHighlighting::CommentPosition KateHighlighting::getCommentSingleLinePosition(int attrib) const 0284 { 0285 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->singleLineCommentPosition; 0286 } 0287 0288 const QHash<QString, QChar> &KateHighlighting::characterEncodings(int attrib) const 0289 { 0290 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->characterEncodings; 0291 } 0292 0293 void KateHighlighting::clearAttributeArrays() 0294 { 0295 // just clear the hashed attributes, we create them lazy again 0296 m_attributeArrays.clear(); 0297 } 0298 0299 QVector<KTextEditor::Attribute::Ptr> KateHighlighting::attributesForDefinition(const QString &schema) const 0300 { 0301 // create list of known attributes based on highlighting format & wanted theme 0302 QVector<KTextEditor::Attribute::Ptr> array; 0303 array.reserve(m_formats.size()); 0304 const auto currentTheme = KateHlManager::self()->repository().theme(schema); 0305 for (const auto &format : m_formats) { 0306 // create a KTextEditor attribute matching the given format 0307 KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(nameForAttrib(array.size()), textStyleToDefaultStyle(format.textStyle()))); 0308 0309 if (const auto color = format.textColor(currentTheme).rgba()) { 0310 newAttribute->setForeground(QColor::fromRgba(color)); 0311 } 0312 0313 if (const auto color = format.selectedTextColor(currentTheme).rgba()) { 0314 newAttribute->setSelectedForeground(QColor::fromRgba(color)); 0315 } 0316 0317 if (const auto color = format.backgroundColor(currentTheme).rgba()) { 0318 newAttribute->setBackground(QColor::fromRgba(color)); 0319 } else { 0320 newAttribute->clearBackground(); 0321 } 0322 0323 if (const auto color = format.selectedBackgroundColor(currentTheme).rgba()) { 0324 newAttribute->setSelectedBackground(QColor::fromRgba(color)); 0325 } else { 0326 newAttribute->clearProperty(SelectedBackground); 0327 } 0328 0329 // Only set attributes if true, otherwise we waste memory 0330 if (format.isBold(currentTheme)) { 0331 newAttribute->setFontBold(true); 0332 } 0333 if (format.isItalic(currentTheme)) { 0334 newAttribute->setFontItalic(true); 0335 } 0336 if (format.isUnderline(currentTheme)) { 0337 newAttribute->setFontUnderline(true); 0338 } 0339 if (format.isStrikeThrough(currentTheme)) { 0340 newAttribute->setFontStrikeOut(true); 0341 } 0342 if (format.spellCheck()) { 0343 newAttribute->setSkipSpellChecking(true); 0344 } 0345 array.append(newAttribute); 0346 } 0347 return array; 0348 } 0349 0350 QVector<KTextEditor::Attribute::Ptr> KateHighlighting::attributes(const QString &schema) 0351 { 0352 // query cache first 0353 if (m_attributeArrays.contains(schema)) { 0354 return m_attributeArrays[schema]; 0355 } 0356 0357 // create new attributes array for wanted theme and cache it 0358 const auto array = attributesForDefinition(schema); 0359 m_attributeArrays.insert(schema, array); 0360 return array; 0361 } 0362 0363 QStringList KateHighlighting::getEmbeddedHighlightingModes() const 0364 { 0365 return embeddedHighlightingModes; 0366 } 0367 0368 bool KateHighlighting::isEmptyLine(const Kate::TextLineData *textline) const 0369 { 0370 const QString &txt = textline->text(); 0371 if (txt.isEmpty()) { 0372 return true; 0373 } 0374 0375 const auto &l = emptyLines(textline->attribute(0)); 0376 if (l.isEmpty()) { 0377 return false; 0378 } 0379 0380 for (const QRegularExpression &re : l) { 0381 const QRegularExpressionMatch match = re.match(txt, 0, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption); 0382 if (match.hasMatch() && match.capturedLength() == txt.length()) { 0383 return true; 0384 } 0385 } 0386 0387 return false; 0388 } 0389 0390 int KateHighlighting::attributeForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor) 0391 { 0392 // Validate parameters to prevent out of range access 0393 if (cursor.line() < 0 || cursor.line() >= doc->lines() || cursor.column() < 0) { 0394 return 0; 0395 } 0396 0397 // get highlighted line 0398 Kate::TextLine tl = doc->kateTextLine(cursor.line()); 0399 0400 // make sure the textline is a valid pointer 0401 if (!tl) { 0402 return 0; 0403 } 0404 0405 // either get char attribute or attribute of context still active at end of line 0406 if (cursor.column() < tl->length()) { 0407 return sanitizeFormatIndex(tl->attribute(cursor.column())); 0408 } else if (cursor.column() >= tl->length()) { 0409 if (!tl->attributesList().isEmpty()) { 0410 return sanitizeFormatIndex(tl->attributesList().back().attributeValue); 0411 } 0412 } 0413 return 0; 0414 } 0415 0416 QStringList KateHighlighting::keywordsForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor) 0417 { 0418 // FIXME-SYNTAX: was before more precise, aka context level 0419 const auto &def = m_propertiesForFormat.at(attributeForLocation(doc, cursor))->definition; 0420 QStringList keywords; 0421 keywords.reserve(def.keywordLists().size()); 0422 const auto keyWordLists = def.keywordLists(); 0423 for (const auto &keylist : keyWordLists) { 0424 keywords += def.keywordList(keylist); 0425 } 0426 return keywords; 0427 } 0428 0429 bool KateHighlighting::spellCheckingRequiredForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor) 0430 { 0431 return m_formats.at(attributeForLocation(doc, cursor)).spellCheck(); 0432 } 0433 0434 QString KateHighlighting::higlightingModeForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor) 0435 { 0436 return m_propertiesForFormat.at(attributeForLocation(doc, cursor))->definition.name(); 0437 } 0438 0439 // END