File indexing completed on 2024-04-28 15:30:47

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