diff --git a/src/syntax/katehighlight.cpp b/src/syntax/katehighlight.cpp index 937ec5df..a26c322d 100644 --- a/src/syntax/katehighlight.cpp +++ b/src/syntax/katehighlight.cpp @@ -1,728 +1,735 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2007 Matthew Woehlke Copyright (C) 2003, 2004 Anders Lund Copyright (C) 2003 Hamish Rodda Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN INCLUDES #include "katehighlight.h" #include "katetextline.h" #include "katedocument.h" #include "katerenderer.h" #include "kateglobal.h" #include "kateschema.h" #include "kateconfig.h" #include "kateextendedattribute.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include #include #include #include #include #include #include #include #include //END //BEGIN STATICS namespace { /** * convert from KSyntaxHighlighting => KTextEditor type * special handle non-1:1 things */ inline KTextEditor::DefaultStyle textStyleToDefaultStyle(const KSyntaxHighlighting::Theme::TextStyle textStyle) { // handle deviations if (textStyle == KSyntaxHighlighting::Theme::Error) { return KTextEditor::dsError; } if (textStyle == KSyntaxHighlighting::Theme::Others) { return KTextEditor::dsOthers; } // else: simple cast return static_cast(textStyle); } } //END //BEGIN KateHighlighting KateHighlighting::KateHighlighting(const KSyntaxHighlighting::Definition &def) { /** * handle the "no highlighting" case */ if (!def.isValid()) { // some default values iName = QStringLiteral("None"); // not translated internal name (for config and more) iNameTranslated = i18nc("Syntax highlighting", "None"); // user visible name iSection = QString(); // dummy hl info m_additionalData[QStringLiteral("None")]; m_hlIndex[0] = QStringLiteral("None"); // be done, all below is just for the real highlighting variants return; } /** * handle the real highlighting case */ noHl = false; iName = def.name(); iNameTranslated = def.translatedName(); iSection = def.translatedSection(); iHidden = def.isHidden(); identifier = def.filePath(); iVersion = QString::number(def.version()); iStyle = def.style(); iAuthor = def.author(); iLicense = def.license(); m_indentation = def.indenter(); // FIXME folding = true; m_foldingIndentationSensitive = def.indentationBasedFoldingEnabled(); /** * tell the AbstractHighlighter the definition it shall use */ setDefinition(def); /** * get all included definitions, e.g. PHP for HTML highlighting */ auto definitions = definition().includedDefinitions(); /** * first: handle only really included definitions */ for (const auto &includedDefinition : definitions) embeddedHighlightingModes.push_back(includedDefinition.name()); /** * now: handle all, including this definition itself * create the format => attributes mapping * collect embedded highlightings, too * * we start with our definition as we want to have the default format * of the initial definition as attribute with index == 0 */ definitions.push_front(definition()); for (const auto & includedDefinition : definitions) { // FIXME: right values auto &properties = m_additionalData[includedDefinition.name()]; properties.definition = includedDefinition; for (const auto &emptyLine : includedDefinition.foldingIgnoreList()) properties.emptyLines.push_back(QRegularExpression(emptyLine)); + properties.singleLineCommentMarker = includedDefinition.singleLineCommentMarker(); + properties.singleLineCommentPosition = includedDefinition.singleLineCommentPosition() == KSyntaxHighlighting::CommentPosition::StartOfLine ? + CSLPosColumn0 : CSLPosAfterWhitespace; + const auto multiLineComment = includedDefinition.multiLineCommentMarker(); + properties.multiLineCommentStart = multiLineComment.first; + properties.multiLineCommentEnd = multiLineComment.second; +// properties.multiLineRegion; // collect formats for (const auto & format : includedDefinition.formats()) { // register format id => internal attributes, we want no clashs const auto nextId = m_formats.size(); m_formatsIdToIndex.insert(std::make_pair(format.id(), nextId)).second; m_formats.push_back(format); m_hlIndex[nextId] = includedDefinition.name(); } } } KateHighlighting::~KateHighlighting() { // cleanup ;) cleanup(); } void KateHighlighting::cleanup() { m_attributeArrays.clear(); } void KateHighlighting::doHighlight(const Kate::TextLineData *prevLine, Kate::TextLineData *textLine, const Kate::TextLineData *nextLine, bool &ctxChanged, int tabWidth) { // default: no context change ctxChanged = false; // no text line => nothing to do if (!textLine) { return; } // in all cases, remove old hl, or we will grow to infinite ;) textLine->clearAttributesAndFoldings(); // reset folding start textLine->clearMarkedAsFoldingStart(); // no hl set, nothing to do more than the above cleaning ;) if (noHl) { return; } /** * ensure we arrive in clean state */ Q_ASSERT(!m_textLineToHighlight); Q_ASSERT(!m_foldingStartToCount); /** * highlight the given line via the abstract highlighter * 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 */ m_textLineToHighlight = textLine; const KSyntaxHighlighting::State initialState (!prevLine ? KSyntaxHighlighting::State() : prevLine->highlightingState()); const KSyntaxHighlighting::State endOfLineState = highlightLine(textLine->string(), initialState); m_textLineToHighlight = nullptr; /** * update highlighting state if needed */ if (textLine->highlightingState() != endOfLineState) { textLine->setHighlightingState(endOfLineState); ctxChanged = true; } /** * handle folding info computed and cleanup hash again, if there * check if folding is not balanced and we have more starts then ends * then this line is a possible folding start! */ if (m_foldingStartToCount) { /** * possible folding start, if imbalanced, aka hash not empty! */ if (!m_foldingStartToCount->isEmpty()) { textLine->markAsFoldingStartAttribute(); } /** * kill hash */ delete m_foldingStartToCount; m_foldingStartToCount = nullptr; } /** * check for indentation based folding */ if (m_foldingIndentationSensitive && (tabWidth > 0) && !textLine->markedAsFoldingStartAttribute()) { /** * compute if we increase indentation in next line */ if (endOfLineState.indentationBasedFoldingEnabled() && !isEmptyLine(textLine) && !isEmptyLine(nextLine) && (textLine->indentDepth(tabWidth) < nextLine->indentDepth(tabWidth))) { textLine->markAsFoldingStartIndentation(); } } } void KateHighlighting::applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) { // WE ATM assume ascending offset order Q_ASSERT(m_textLineToHighlight); Q_ASSERT(format.isValid()); // get internal attribute, must exist const auto it = m_formatsIdToIndex.find(format.id()); Q_ASSERT(it != m_formatsIdToIndex.end()); // remember highlighting info in our textline m_textLineToHighlight->addAttribute(Kate::TextLineData::Attribute(offset, length, it->second)); } void KateHighlighting::applyFolding(int offset, int, KSyntaxHighlighting::FoldingRegion region) { // WE ATM assume ascending offset order and don't care for length Q_ASSERT(m_textLineToHighlight); Q_ASSERT(region.isValid()); const int foldingValue = (region.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? int(region.id()) : -int(region.id()); m_textLineToHighlight->addFolding(offset, foldingValue); /** * for each end region, decrement counter for that type, erase if count reaches 0! */ if ((foldingValue < 0) && m_foldingStartToCount) { QHash::iterator end = m_foldingStartToCount->find(foldingValue); if (end != m_foldingStartToCount->end()) { if (end.value() > 1) { --(end.value()); } else { m_foldingStartToCount->erase(end); } } } /** * increment counter for each begin region! */ if (foldingValue > 0) { // construct on demand! if (!m_foldingStartToCount) { m_foldingStartToCount = new QHash (); } ++(*m_foldingStartToCount)[foldingValue]; } } void KateHighlighting::getKateExtendedAttributeList(const QString &schema, QList &list, KConfig *cfg) { KConfigGroup config(cfg ? cfg : KateHlManager::self()->getKConfig(), QLatin1String("Highlighting ") + iName + QLatin1String(" - Schema ") + schema); list = attributesForDefinition(); foreach (KTextEditor::Attribute::Ptr p, list) { Q_ASSERT(p); QStringList s = config.readEntry(p->name(), QStringList()); // qCDebug(LOG_KTE)<name< 0) { while (s.count() < 10) { s << QString(); } QString name = p->name(); bool spellCheck = !p->skipSpellChecking(); p->clear(); p->setName(name); p->setSkipSpellChecking(!spellCheck); QString tmp = s[0]; if (!tmp.isEmpty()) { p->setDefaultStyle(static_cast (tmp.toInt())); } QRgb col; tmp = s[1]; if (!tmp.isEmpty()) { col = tmp.toUInt(nullptr, 16); p->setForeground(QColor(col)); } tmp = s[2]; if (!tmp.isEmpty()) { col = tmp.toUInt(nullptr, 16); p->setSelectedForeground(QColor(col)); } tmp = s[3]; if (!tmp.isEmpty()) { p->setFontBold(tmp != QLatin1String("0")); } tmp = s[4]; if (!tmp.isEmpty()) { p->setFontItalic(tmp != QLatin1String("0")); } tmp = s[5]; if (!tmp.isEmpty()) { p->setFontStrikeOut(tmp != QLatin1String("0")); } tmp = s[6]; if (!tmp.isEmpty()) { p->setFontUnderline(tmp != QLatin1String("0")); } tmp = s[7]; if (!tmp.isEmpty()) { col = tmp.toUInt(nullptr, 16); p->setBackground(QColor(col)); } tmp = s[8]; if (!tmp.isEmpty()) { col = tmp.toUInt(nullptr, 16); p->setSelectedBackground(QColor(col)); } tmp = s[9]; if (!tmp.isEmpty() && tmp != QLatin1String("---")) { p->setFontFamily(tmp); } } } } void KateHighlighting::getKateExtendedAttributeListCopy(const QString &schema, QList< KTextEditor::Attribute::Ptr > &list, KConfig *cfg) { QList attributes; getKateExtendedAttributeList(schema, attributes, cfg); list.clear(); foreach (const KTextEditor::Attribute::Ptr &attribute, attributes) { list.append(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*attribute.data()))); } } void KateHighlighting::setKateExtendedAttributeList(const QString &schema, QList &list, KConfig *cfg, bool writeDefaultsToo) { KConfigGroup config(cfg ? cfg : KateHlManager::self()->getKConfig(), QLatin1String("Highlighting ") + iName + QLatin1String(" - Schema ") + schema); QStringList settings; KateAttributeList defList; KateHlManager::self()->getDefaults(schema, defList); foreach (const KTextEditor::Attribute::Ptr &p, list) { Q_ASSERT(p); settings.clear(); KTextEditor::DefaultStyle defStyle = p->defaultStyle(); KTextEditor::Attribute::Ptr a(defList[defStyle]); settings << QString::number(p->defaultStyle(), 10); settings << (p->hasProperty(QTextFormat::ForegroundBrush) ? QString::number(p->foreground().color().rgb(), 16) : (writeDefaultsToo ? QString::number(a->foreground().color().rgb(), 16) : QString())); settings << (p->hasProperty(SelectedForeground) ? QString::number(p->selectedForeground().color().rgb(), 16) : (writeDefaultsToo ? QString::number(a->selectedForeground().color().rgb(), 16) : QString())); settings << (p->hasProperty(QTextFormat::FontWeight) ? (p->fontBold() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontBold() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::FontItalic) ? (p->fontItalic() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontItalic() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::FontStrikeOut) ? (p->fontStrikeOut() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontStrikeOut() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::FontUnderline) ? (p->fontUnderline() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontUnderline() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::BackgroundBrush) ? QString::number(p->background().color().rgb(), 16) : ((writeDefaultsToo && a->hasProperty(QTextFormat::BackgroundBrush)) ? QString::number(a->background().color().rgb(), 16) : QString())); settings << (p->hasProperty(SelectedBackground) ? QString::number(p->selectedBackground().color().rgb(), 16) : ((writeDefaultsToo && a->hasProperty(SelectedBackground)) ? QString::number(a->selectedBackground().color().rgb(), 16) : QString())); settings << (p->hasProperty(QTextFormat::FontFamily) ? (p->fontFamily()) : (writeDefaultsToo ? a->fontFamily() : QString())); settings << QStringLiteral("---"); config.writeEntry(p->name(), settings); } } const QHash &KateHighlighting::getCharacterEncodings(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).characterEncodings; } const KatePrefixStore &KateHighlighting::getCharacterEncodingsPrefixStore(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).characterEncodingsPrefixStore; } const QHash &KateHighlighting::getReverseCharacterEncodings(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).reverseCharacterEncodings; } int KateHighlighting::getEncodedCharactersInsertionPolicy(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).encodedCharactersInsertionPolicy; } void KateHighlighting::addCharacterEncoding(const QString &key, const QString &encoding, const QChar &c) { m_additionalData[ key ].characterEncodingsPrefixStore.addPrefix(encoding); m_additionalData[ key ].characterEncodings[ encoding ] = c; m_additionalData[ key ].reverseCharacterEncodings[ c ] = encoding; } /** * Increase the usage count, and trigger initialization if needed. */ void KateHighlighting::use() { if (refCount == 0) { init(); } refCount = 1; } /** * Reload the highlighting. */ void KateHighlighting::reload() { // nop if not referenced if (refCount == 0) { return; } cleanup(); init(); } /** * Initialize a context for the first time. */ void KateHighlighting::init() { } bool KateHighlighting::attributeRequiresSpellchecking(int attr) { if (attr >= 0 && attr < m_formats.size()) { return m_formats[attr].spellCheck(); } return true; } KTextEditor::DefaultStyle KateHighlighting::defaultStyleForAttribute(int attr) const { if (attr >= 0 && attr < m_formats.size()) { return textStyleToDefaultStyle(m_formats[attr].textStyle()); } return KTextEditor::dsNormal; } QString KateHighlighting::hlKeyForAttrib(int i) const { Q_ASSERT(i >= 0 && i < m_hlIndex.size()); return m_hlIndex[i]; } QString KateHighlighting::nameForAttrib(int attrib) const { Q_ASSERT(attrib >= 0 && attrib < m_formats.size()); return hlKeyForAttrib(attrib) + QLatin1Char(':') + m_formats[attrib].name(); } bool KateHighlighting::isInWord(QChar c, int attrib) const { return !additionalData(hlKeyForAttrib(attrib)).definition.isWordDelimiter(c) && !c.isSpace() && c != QLatin1Char('"') && c != QLatin1Char('\'') && c != QLatin1Char('`'); } bool KateHighlighting::canBreakAt(QChar c, int attrib) const { return additionalData(hlKeyForAttrib(attrib)).definition.isWordWrapDelimiter(c) && c != QLatin1Char('"') && c != QLatin1Char('\''); } const QVector &KateHighlighting::emptyLines(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).emptyLines; } signed char KateHighlighting::commentRegion(int attr) const { QString commentRegion = additionalData(hlKeyForAttrib(attr)).multiLineRegion; return (commentRegion.isEmpty() ? 0 : (commentRegion.toShort())); } bool KateHighlighting::canComment(int startAttrib, int endAttrib) const { QString k = hlKeyForAttrib(startAttrib); return (k == hlKeyForAttrib(endAttrib) && ((!additionalData(k).multiLineCommentStart.isEmpty() && !additionalData(k).multiLineCommentEnd.isEmpty()) || !additionalData(k).singleLineCommentMarker.isEmpty())); } QString KateHighlighting::getCommentStart(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).multiLineCommentStart; } QString KateHighlighting::getCommentEnd(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).multiLineCommentEnd; } QString KateHighlighting::getCommentSingleLineStart(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).singleLineCommentMarker; } KateHighlighting::CSLPos KateHighlighting::getCommentSingleLinePosition(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).singleLineCommentPosition; } const QHash &KateHighlighting::characterEncodings(int attrib) const { return additionalData(hlKeyForAttrib(attrib)).characterEncodings; } void KateHighlighting::clearAttributeArrays() { // just clear the hashed attributes, we create them lazy again m_attributeArrays.clear(); } QList KateHighlighting::attributesForDefinition() { /** * create list of all known things */ QList array; if (m_formats.empty()) { KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(iName + QLatin1Char(':') + QLatin1String("Normal"), KTextEditor::dsNormal)); array.append(newAttribute); } else { for (const auto &format : m_formats) { /** * FIXME: atm we just set some theme here for later color generation */ setTheme(KateHlManager::self()->repository().defaultTheme(KSyntaxHighlighting::Repository::LightTheme)); /** * create a KTextEditor attribute matching the given format */ KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(nameForAttrib(array.size()), textStyleToDefaultStyle(format.textStyle()))); if (format.hasTextColor(theme())) { newAttribute->setForeground(format.textColor(theme())); newAttribute->setSelectedForeground(format.selectedTextColor(theme())); } if (format.hasBackgroundColor(theme())) { newAttribute->setBackground(format.backgroundColor(theme())); newAttribute->setSelectedBackground(format.selectedBackgroundColor(theme())); } if (format.isBold(theme())) { newAttribute->setFontBold(true); } if (format.isItalic(theme())) { newAttribute->setFontItalic(true); } if (format.isUnderline(theme())) { newAttribute->setFontUnderline(true); } if (format.isStrikeThrough(theme())) { newAttribute->setFontStrikeOut(true); } newAttribute->setSkipSpellChecking(format.spellCheck()); array.append(newAttribute); } } return array; } QList KateHighlighting::attributes(const QString &schema) { // found it, already floating around if (m_attributeArrays.contains(schema)) { return m_attributeArrays[schema]; } // k, schema correct, let create the data QList array; KateAttributeList defaultStyleList; KateHlManager::self()->getDefaults(schema, defaultStyleList); QList itemDataList; getKateExtendedAttributeList(schema, itemDataList); uint nAttribs = itemDataList.count(); for (uint z = 0; z < nAttribs; z++) { KTextEditor::Attribute::Ptr itemData = itemDataList.at(z); KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(*defaultStyleList.at(itemData->defaultStyle()))); if (itemData && itemData->hasAnyProperty()) { *newAttribute += *itemData; } array.append(newAttribute); } m_attributeArrays.insert(schema, array); return array; } QStringList KateHighlighting::getEmbeddedHighlightingModes() const { return embeddedHighlightingModes; } bool KateHighlighting::isEmptyLine(const Kate::TextLineData *textline) const { const QString &txt = textline->string(); if (txt.isEmpty()) { return true; } const auto &l = emptyLines(textline->attribute(0)); if (l.isEmpty()) { return false; } foreach (const QRegularExpression &re, l) { const QRegularExpressionMatch match = re.match (txt, 0, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption); if (match.hasMatch() && match.capturedLength() == txt.length()) { return true; } } return false; } int KateHighlighting::attributeForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor) { // Validate parameters to prevent out of range access if (cursor.line() < 0 || cursor.line() >= doc->lines() || cursor.column() < 0) { return 0; } // get highlighted line Kate::TextLine tl = doc->kateTextLine(cursor.line()); // make sure the textline is a valid pointer if (!tl) { return 0; } /** * either get char attribute or attribute of context still active at end of line */ if (cursor.column() < tl->length()) { return tl->attribute(cursor.column()); } else if (cursor.column() >= tl->length()) { if (!tl->attributesList().isEmpty()) { return tl->attributesList().back().attributeValue; } } return 0; } QStringList KateHighlighting::keywordsForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor) { // FIXME-SYNTAX: was before more precise, aka context level const auto &def = additionalData(m_hlIndex[attributeForLocation(doc, cursor)]).definition; QStringList keywords; for (const auto &keylist : def.keywordLists()) { keywords += def.keywordList(keylist); } return keywords; } bool KateHighlighting::spellCheckingRequiredForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor) { return m_formats[attributeForLocation(doc, cursor)].spellCheck(); } QString KateHighlighting::higlightingModeForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor) { return m_hlIndex[attributeForLocation(doc, cursor)]; } //END