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