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