aboutsummaryrefslogtreecommitdiffstats
path: root/src/libs/3rdparty/syntax-highlighting/src
diff options
context:
space:
mode:
authorDavid Schulz <david.schulz@qt.io>2021-03-16 17:57:55 +0100
committerDavid Schulz <david.schulz@qt.io>2021-03-17 10:35:36 +0000
commitd4c4f8c00753abaa37517f6d5c36dcbc008bb658 (patch)
tree99f3cc965a67f61b357728defaea2f19ebb80617 /src/libs/3rdparty/syntax-highlighting/src
parentd187022892bad0591dd8d8e2a2d44bcddac1995d (diff)
Highlighting: update KSyntaxHighlighting to v5.80.0
Task-number: QTCREATORBUG-22558 Change-Id: I57d782397f88842edbd08b1008b2d88706c6ab52 Reviewed-by: Eike Ziller <eike.ziller@qt.io>
Diffstat (limited to 'src/libs/3rdparty/syntax-highlighting/src')
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/cli/CMakeLists.txt2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/cli/kate-syntax-highlighter.cpp62
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/indexer/CMakeLists.txt2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/indexer/katehighlightingindexer.cpp2701
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/CMakeLists.txt4
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp6
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter_p.h4
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.cpp1265
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.h21
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/context.cpp6
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch.cpp2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch_p.h2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/definition.cpp54
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/definition.h2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/definition_p.h2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/definitiondownloader.cpp24
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/format.cpp25
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/htmlhighlighter.cpp50
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist.cpp10
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist_p.h9
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/repository.cpp56
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/repository.h26
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/rule.cpp139
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/rule_p.h20
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/state.cpp2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/state_p.h4
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/theme.cpp2
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/themedata.cpp4
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters.cpp26
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters_p.h8
-rw-r--r--src/libs/3rdparty/syntax-highlighting/src/lib/xml_p.h4
31 files changed, 3270 insertions, 1274 deletions
diff --git a/src/libs/3rdparty/syntax-highlighting/src/cli/CMakeLists.txt b/src/libs/3rdparty/syntax-highlighting/src/cli/CMakeLists.txt
index 113115359e..1a4d24d828 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/cli/CMakeLists.txt
+++ b/src/libs/3rdparty/syntax-highlighting/src/cli/CMakeLists.txt
@@ -2,4 +2,4 @@ add_executable(kate-syntax-highlighter kate-syntax-highlighter.cpp)
ecm_mark_nongui_executable(kate-syntax-highlighter)
target_link_libraries(kate-syntax-highlighter KF5SyntaxHighlighting)
-install(TARGETS kate-syntax-highlighter ${INSTALL_TARGETS_DEFAULT_ARGS})
+install(TARGETS kate-syntax-highlighter ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
diff --git a/src/libs/3rdparty/syntax-highlighting/src/cli/kate-syntax-highlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/cli/kate-syntax-highlighter.cpp
index a178c1ba27..a009c4f259 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/cli/kate-syntax-highlighter.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/cli/kate-syntax-highlighter.cpp
@@ -6,10 +6,10 @@
#include "ksyntaxhighlighting_version.h"
+#include <ansihighlighter.h>
#include <definition.h>
#include <definitiondownloader.h>
#include <htmlhighlighter.h>
-#include <ansihighlighter.h>
#include <repository.h>
#include <theme.h>
@@ -21,8 +21,14 @@
using namespace KSyntaxHighlighting;
-template<class Highlighter, class ...Ts>
-static void applyHighlighter(Highlighter &highlighter, QCommandLineParser &parser, bool fromFileName, const QString &inFileName, const QCommandLineOption &stdinOption, const QCommandLineOption &outputName, const Ts &...highlightParams)
+template<class Highlighter, class... Ts>
+static void applyHighlighter(Highlighter &highlighter,
+ QCommandLineParser &parser,
+ bool fromFileName,
+ const QString &inFileName,
+ const QCommandLineOption &stdinOption,
+ const QCommandLineOption &outputName,
+ const Ts &...highlightParams)
{
if (parser.isSet(outputName))
highlighter.setOutputFile(parser.value(outputName));
@@ -56,16 +62,19 @@ int main(int argc, char **argv)
parser.addVersionOption();
parser.addPositionalArgument(app.translate("SyntaxHighlightingCLI", "source"), app.translate("SyntaxHighlightingCLI", "The source file to highlight."));
- QCommandLineOption listDefs(QStringList() << QStringLiteral("l") << QStringLiteral("list"), app.translate("SyntaxHighlightingCLI", "List all available syntax definitions."));
+ QCommandLineOption listDefs(QStringList() << QStringLiteral("l") << QStringLiteral("list"),
+ app.translate("SyntaxHighlightingCLI", "List all available syntax definitions."));
parser.addOption(listDefs);
QCommandLineOption listThemes(QStringList() << QStringLiteral("list-themes"), app.translate("SyntaxHighlightingCLI", "List all available themes."));
parser.addOption(listThemes);
- QCommandLineOption updateDefs(QStringList() << QStringLiteral("u") << QStringLiteral("update"), app.translate("SyntaxHighlightingCLI", "Download new/updated syntax definitions."));
+ QCommandLineOption updateDefs(QStringList() << QStringLiteral("u") << QStringLiteral("update"),
+ app.translate("SyntaxHighlightingCLI", "Download new/updated syntax definitions."));
parser.addOption(updateDefs);
- QCommandLineOption outputName(
- QStringList() << QStringLiteral("o") << QStringLiteral("output"), app.translate("SyntaxHighlightingCLI", "File to write HTML output to (default: stdout)."), app.translate("SyntaxHighlightingCLI", "output"));
+ QCommandLineOption outputName(QStringList() << QStringLiteral("o") << QStringLiteral("output"),
+ app.translate("SyntaxHighlightingCLI", "File to write HTML output to (default: stdout)."),
+ app.translate("SyntaxHighlightingCLI", "output"));
parser.addOption(outputName);
QCommandLineOption syntaxName(QStringList() << QStringLiteral("s") << QStringLiteral("syntax"),
@@ -73,18 +82,23 @@ int main(int argc, char **argv)
app.translate("SyntaxHighlightingCLI", "syntax"));
parser.addOption(syntaxName);
- QCommandLineOption themeName(
- QStringList() << QStringLiteral("t") << QStringLiteral("theme"), app.translate("SyntaxHighlightingCLI", "Color theme to use for highlighting."), app.translate("SyntaxHighlightingCLI", "theme"), repo.defaultTheme(Repository::LightTheme).name());
+ QCommandLineOption themeName(QStringList() << QStringLiteral("t") << QStringLiteral("theme"),
+ app.translate("SyntaxHighlightingCLI", "Color theme to use for highlighting."),
+ app.translate("SyntaxHighlightingCLI", "theme"),
+ repo.defaultTheme(Repository::LightTheme).name());
parser.addOption(themeName);
- QCommandLineOption outputFormatOption(QStringList() << QStringLiteral("f") << QStringLiteral("output-format"),
- app.translate("SyntaxHighlightingCLI", "Use the specified format instead of html. Must be html, ansi or ansi256Colors."),
- app.translate("SyntaxHighlightingCLI", "format"),
- QStringLiteral("html"));
+ QCommandLineOption outputFormatOption(
+ QStringList() << QStringLiteral("f") << QStringLiteral("output-format"),
+ app.translate("SyntaxHighlightingCLI", "Use the specified format instead of html. Must be html, ansi or ansi256Colors."),
+ app.translate("SyntaxHighlightingCLI", "format"),
+ QStringLiteral("html"));
parser.addOption(outputFormatOption);
QCommandLineOption traceOption(QStringList() << QStringLiteral("syntax-trace"),
- app.translate("SyntaxHighlightingCLI", "Add information to debug a syntax file. Only works with --output-format=ansi or ansi256Colors. Possible values are format, region and context."),
+ app.translate("SyntaxHighlightingCLI",
+ "Add information to debug a syntax file. Only works with --output-format=ansi or ansi256Colors. Possible "
+ "values are format, region, context and stackSize."),
app.translate("SyntaxHighlightingCLI", "type"));
parser.addOption(traceOption);
@@ -92,12 +106,14 @@ int main(int argc, char **argv)
app.translate("SyntaxHighlightingCLI", "Disable ANSI background for the default color."));
parser.addOption(noAnsiEditorBg);
- QCommandLineOption titleOption(QStringList() << QStringLiteral("T") << QStringLiteral("title"),
- app.translate("SyntaxHighlightingCLI", "Set HTML page's title\n(default: the filename or \"Kate Syntax Highlighter\" if reading from stdin)."),
- app.translate("SyntaxHighlightingCLI", "title"));
+ QCommandLineOption titleOption(
+ QStringList() << QStringLiteral("T") << QStringLiteral("title"),
+ app.translate("SyntaxHighlightingCLI", "Set HTML page's title\n(default: the filename or \"Kate Syntax Highlighter\" if reading from stdin)."),
+ app.translate("SyntaxHighlightingCLI", "title"));
parser.addOption(titleOption);
- QCommandLineOption stdinOption(QStringList() << QStringLiteral("stdin"), app.translate("SyntaxHighlightingCLI", "Read file from stdin. The -s option must also be used."));
+ QCommandLineOption stdinOption(QStringList() << QStringLiteral("stdin"),
+ app.translate("SyntaxHighlightingCLI", "Read file from stdin. The -s option must also be used."));
parser.addOption(stdinOption);
parser.process(app);
@@ -117,7 +133,9 @@ int main(int argc, char **argv)
if (parser.isSet(updateDefs)) {
DefinitionDownloader downloader(&repo);
- QObject::connect(&downloader, &DefinitionDownloader::informationMessage, [](const QString &msg) { std::cout << qPrintable(msg) << std::endl; });
+ QObject::connect(&downloader, &DefinitionDownloader::informationMessage, [](const QString &msg) {
+ std::cout << qPrintable(msg) << std::endl;
+ });
QObject::connect(&downloader, &DefinitionDownloader::done, &app, &QCoreApplication::quit);
downloader.start();
return app.exec();
@@ -139,7 +157,7 @@ int main(int argc, char **argv)
def = repo.definitionForMimeType(syntax);
if (!def.isValid()) {
/* see if it's a extension instead */
- def = repo.definitionForFileName(QLatin1String("f.")+syntax);
+ def = repo.definitionForFileName(QLatin1String("f.") + syntax);
if (!def.isValid())
/* see if it's a filename instead */
def = repo.definitionForFileName(syntax);
@@ -178,13 +196,15 @@ int main(int argc, char **argv)
auto debugOptions = AnsiHighlighter::TraceOptions();
if (parser.isSet(traceOption)) {
const auto options = parser.values(traceOption);
- for (auto const& option : options) {
+ for (auto const &option : options) {
if (option == QStringLiteral("format")) {
debugOptions |= AnsiHighlighter::TraceOption::Format;
} else if (option == QStringLiteral("region")) {
debugOptions |= AnsiHighlighter::TraceOption::Region;
} else if (option == QStringLiteral("context")) {
debugOptions |= AnsiHighlighter::TraceOption::Context;
+ } else if (option == QStringLiteral("stackSize")) {
+ debugOptions |= AnsiHighlighter::TraceOption::StackSize;
} else {
std::cerr << "Unknown trace name." << std::endl;
return 2;
diff --git a/src/libs/3rdparty/syntax-highlighting/src/indexer/CMakeLists.txt b/src/libs/3rdparty/syntax-highlighting/src/indexer/CMakeLists.txt
index 9fa26b27ce..508cd276cc 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/indexer/CMakeLists.txt
+++ b/src/libs/3rdparty/syntax-highlighting/src/indexer/CMakeLists.txt
@@ -27,7 +27,7 @@ elseif(CMAKE_CROSSCOMPILING)
${CMAKE_CURRENT_BINARY_DIR}/native_katehighlightingindexer-prefix/src/native_katehighlightingindexer-build/bin/katehighlightingindexer)
else()
# host build
- add_executable(katehighlightingindexer katehighlightingindexer.cpp)
+ add_executable(katehighlightingindexer katehighlightingindexer.cpp ../lib/worddelimiters.cpp)
if(Qt5XmlPatterns_FOUND)
target_link_libraries(katehighlightingindexer Qt5::XmlPatterns)
else()
diff --git a/src/libs/3rdparty/syntax-highlighting/src/indexer/katehighlightingindexer.cpp b/src/libs/3rdparty/syntax-highlighting/src/indexer/katehighlightingindexer.cpp
index b5d04934e6..86b3a38b12 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/indexer/katehighlightingindexer.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/indexer/katehighlightingindexer.cpp
@@ -1,14 +1,16 @@
/*
SPDX-FileCopyrightText: 2014 Christoph Cullmann <cullmann@kde.org>
+ SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
SPDX-License-Identifier: MIT
*/
+#include <QCborValue>
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
-#include <QCborValue>
+#include <QMutableMapIterator>
#include <QRegularExpression>
#include <QVariant>
#include <QXmlStreamReader>
@@ -18,426 +20,2228 @@
#include <QXmlSchemaValidator>
#endif
+#include "../lib/worddelimiters_p.h"
#include "../lib/xml_p.h"
+#include <array>
+
+using KSyntaxHighlighting::WordDelimiters;
using KSyntaxHighlighting::Xml::attrToBool;
-namespace
+class HlFilesChecker
{
-QStringList readListing(const QString &fileName)
-{
- QFile file(fileName);
- if (!file.open(QIODevice::ReadOnly)) {
- return QStringList();
- }
-
- QXmlStreamReader xml(&file);
- QStringList listing;
- while (!xml.atEnd()) {
- xml.readNext();
+public:
+ void setDefinition(const QStringRef &verStr, const QString &filename, const QString &name)
+ {
+ m_currentDefinition = &*m_definitions.insert(name, Definition{});
+ m_currentDefinition->languageName = name;
+ m_currentDefinition->filename = filename;
+ m_currentDefinition->kateVersionStr = verStr.toString();
+ m_currentKeywords = nullptr;
+ m_currentContext = nullptr;
- // add only .xml files, no .json or stuff
- if (xml.isCharacters() && xml.text().toString().contains(QLatin1String(".xml"))) {
- listing.append(xml.text().toString());
+ const auto idx = verStr.indexOf(QLatin1Char('.'));
+ if (idx <= 0) {
+ qWarning() << filename << "invalid kateversion" << verStr;
+ m_success = false;
+ } else {
+ m_currentDefinition->kateVersion = {verStr.left(idx).toInt(), verStr.mid(idx + 1).toInt()};
}
}
- if (xml.hasError()) {
- qWarning() << "XML error while reading" << fileName << " - " << qPrintable(xml.errorString()) << "@ offset" << xml.characterOffset();
- listing.clear();
+ void processElement(QXmlStreamReader &xml)
+ {
+ if (xml.isStartElement()) {
+ if (m_currentContext) {
+ m_currentContext->rules.append(Context::Rule{});
+ auto &rule = m_currentContext->rules.back();
+ m_success = rule.parseElement(m_currentDefinition->filename, xml) && m_success;
+ } else if (m_currentKeywords) {
+ m_success = m_currentKeywords->items.parseElement(m_currentDefinition->filename, xml) && m_success;
+ } else if (xml.name() == QStringLiteral("context")) {
+ processContextElement(xml);
+ } else if (xml.name() == QStringLiteral("list")) {
+ processListElement(xml);
+ } else if (xml.name() == QStringLiteral("keywords")) {
+ m_success = m_currentDefinition->parseKeywords(xml) && m_success;
+ } else if (xml.name() == QStringLiteral("emptyLine")) {
+ m_success = parseEmptyLine(m_currentDefinition->filename, xml) && m_success;
+ } else if (xml.name() == QStringLiteral("itemData")) {
+ m_success = m_currentDefinition->itemDatas.parseElement(m_currentDefinition->filename, xml) && m_success;
+ }
+ } else if (xml.isEndElement()) {
+ if (m_currentContext && xml.name() == QStringLiteral("context")) {
+ m_currentContext = nullptr;
+ } else if (m_currentKeywords && xml.name() == QStringLiteral("list")) {
+ m_currentKeywords = nullptr;
+ }
+ }
}
- return listing;
-}
+ //! Resolve context attribute and include tag
+ void resolveContexts()
+ {
+ QMutableMapIterator<QString, Definition> def(m_definitions);
+ while (def.hasNext()) {
+ def.next();
+ auto &definition = def.value();
+ auto &contexts = definition.contexts;
+
+ if (contexts.isEmpty()) {
+ qWarning() << definition.filename << "has no context";
+ m_success = false;
+ continue;
+ }
-/**
- * check if the "extensions" attribute have valid wildcards
- * @param extensions extensions string to check
- * @return valid?
- */
-bool checkExtensions(const QString &extensions)
-{
- // get list of extensions
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- const QStringList extensionParts = extensions.split(QLatin1Char(';'), QString::SkipEmptyParts);
-#else
- const QStringList extensionParts = extensions.split(QLatin1Char(';'), Qt::SkipEmptyParts);
-#endif
+ QMutableMapIterator<QString, Context> contextIt(contexts);
+ while (contextIt.hasNext()) {
+ contextIt.next();
+ auto &context = contextIt.value();
+ resolveContextName(definition, context, context.lineEndContext, context.line);
+ resolveContextName(definition, context, context.lineEmptyContext, context.line);
+ resolveContextName(definition, context, context.fallthroughContext, context.line);
+ for (auto &rule : context.rules) {
+ resolveContextName(definition, context, rule.context, rule.line);
+ }
+ }
- // ok if empty
- if (extensionParts.isEmpty()) {
- return true;
+ definition.firstContext = &*definition.contexts.find(definition.firstContextName);
+ }
+
+ resolveIncludeRules();
}
- // check that only valid wildcard things are inside the parts
- for (const auto &extension : extensionParts) {
- for (const auto c : extension) {
- // eat normal things
- if (c.isDigit() || c.isLetter()) {
- continue;
+ bool check() const
+ {
+ bool success = m_success;
+
+ const auto usedContexts = extractUsedContexts();
+
+ QMap<const Definition *, const Definition *> maxVersionByDefinitions;
+
+ QMapIterator<QString, Definition> def(m_definitions);
+ while (def.hasNext()) {
+ def.next();
+ const auto &definition = def.value();
+ const auto &filename = definition.filename;
+
+ auto *maxDef = maxKateVersionDefinition(definition, maxVersionByDefinitions);
+ if (maxDef != &definition) {
+ qWarning() << definition.filename << "depends on a language" << maxDef->languageName << "in version" << maxDef->kateVersionStr
+ << ". Please, increase kateversion.";
+ success = false;
}
- // allow some special characters
- if (c == QLatin1Char('.') || c == QLatin1Char('-') || c == QLatin1Char('_') || c == QLatin1Char('+')) {
- continue;
+ QSet<const Keywords *> referencedKeywords;
+ QSet<ItemDatas::Style> usedAttributeNames;
+ success = checkKeywordsList(definition, referencedKeywords) && success;
+ success = checkContexts(definition, referencedKeywords, usedAttributeNames, usedContexts) && success;
+
+ // search for non-existing or unreferenced keyword lists.
+ for (const auto &keywords : definition.keywordsList) {
+ if (!referencedKeywords.contains(&keywords)) {
+ qWarning() << filename << "line" << keywords.line << "unused keyword:" << keywords.name;
+ }
}
- // only allowed wildcard things: '?' and '*'
- if (c == QLatin1Char('?') || c == QLatin1Char('*')) {
- continue;
+ // search for non-existing itemDatas.
+ const auto invalidNames = usedAttributeNames - definition.itemDatas.styleNames;
+ for (const auto &styleName : invalidNames) {
+ qWarning() << filename << "line" << styleName.line << "reference of non-existing itemData attributes:" << styleName.name;
+ success = false;
}
- qWarning() << "invalid character" << c << " seen in extensions wildcard";
- return false;
+ // search for unused itemDatas.
+ const auto unusedNames = definition.itemDatas.styleNames - usedAttributeNames;
+ for (const auto &styleName : unusedNames) {
+ qWarning() << filename << "line" << styleName.line << "unused itemData:" << styleName.name;
+ success = false;
+ }
}
+
+ return success;
}
- // all checks passed
- return true;
-}
+private:
+ enum class XmlBool {
+ Unspecified,
+ False,
+ True,
+ };
-//! Check that a regular expression in a RegExpr rule:
-//! - is not empty
-//! - isValid()
-//! - character ranges such as [A-Z] are valid and not accidentally e.g. [A-z].
-//! - dynamic=true but no place holder used?
-bool checkRegularExpression(const QString &hlFilename, QXmlStreamReader &xml)
-{
- if (xml.name() == QLatin1String("RegExpr") || xml.name() == QLatin1String("emptyLine")) {
- // get right attribute
- const QString string(xml.attributes().value((xml.name() == QLatin1String("RegExpr")) ? QLatin1String("String") : QLatin1String("regexpr")).toString());
+ struct Context;
- // validate regexp
- const QRegularExpression regexp(string);
- if (!regexp.isValid()) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "broken regex:" << string << "problem:" << regexp.errorString() << "at offset" << regexp.patternErrorOffset();
- return false;
- } else if (string.isEmpty()) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "empty regex not allowed.";
- return false;
+ struct ContextName {
+ QString name;
+ int popCount = 0;
+ bool stay = false;
+
+ const Context *context = nullptr;
+ };
+
+ struct Parser {
+ const QString &filename;
+ QXmlStreamReader &xml;
+ QXmlStreamAttribute &attr;
+ bool success;
+
+ //! Read a string type attribute, \c sucess = \c false when \p str is not empty
+ //! \return \c true when attr.name() == attrName, otherwise false
+ bool extractString(QString &str, const QString &attrName)
+ {
+ if (attr.name() != attrName)
+ return false;
+
+ str = attr.value().toString();
+ if (str.isEmpty()) {
+ qWarning() << filename << "line" << xml.lineNumber() << attrName << "attribute is empty";
+ success = false;
+ }
+
+ return true;
}
- // catch possible case typos: [A-z] or [a-Z]
- const int azOffset = std::max(string.indexOf(QStringLiteral("A-z")), string.indexOf(QStringLiteral("a-Z")));
- if (azOffset >= 0) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "broken regex:" << string << "problem: [a-Z] or [A-z] at offset" << azOffset;
- return false;
+ //! Read a bool type attribute, \c sucess = \c false when \p xmlBool is not \c XmlBool::Unspecified.
+ //! \return \c true when attr.name() == attrName, otherwise false
+ bool extractXmlBool(XmlBool &xmlBool, const QString &attrName)
+ {
+ if (attr.name() != attrName)
+ return false;
+
+ xmlBool = attr.value().isNull() ? XmlBool::Unspecified : attrToBool(attr.value()) ? XmlBool::True : XmlBool::False;
+
+ return true;
}
- // dynamic == true and no place holder?
- if (xml.name() == QLatin1String("RegExpr") && attrToBool(xml.attributes().value(QStringLiteral("dynamic")))) {
- static const QRegularExpression placeHolder(QStringLiteral("%\\d+"));
- if (!string.contains(placeHolder)) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "broken regex:" << string << "problem: dynamic=true but no %\\d+ placeholder";
+ //! Read a positive integer type attribute, \c sucess = \c false when \p positive is already greater than or equal to 0
+ //! \return \c true when attr.name() == attrName, otherwise false
+ bool extractPositive(int &positive, const QString &attrName)
+ {
+ if (attr.name() != attrName)
return false;
+
+ bool ok = true;
+ positive = attr.value().toInt(&ok);
+
+ if (!ok || positive < 0) {
+ qWarning() << filename << "line" << xml.lineNumber() << attrName << "should be a positive integer:" << attr.value();
+ success = false;
}
+
+ return true;
}
- }
- return true;
-}
+ //! Read a color, \c sucess = \c false when \p color is already greater than or equal to 0
+ //! \return \c true when attr.name() == attrName, otherwise false
+ bool checkColor(const QString &attrName)
+ {
+ if (attr.name() != attrName)
+ return false;
-//! Check that keyword list items do not have trailing or leading spaces,
-//! e.g.: <item> keyword </item>
-bool checkItemsTrimmed(const QString &hlFilename, QXmlStreamReader &xml)
-{
- if (xml.name() == QLatin1String("item")) {
- const QString keyword = xml.readElementText();
- if (keyword != keyword.trimmed()) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "keyword with leading/trailing spaces:" << keyword;
- return false;
+ const auto value = attr.value().toString();
+ if (value.isEmpty() /*|| QColor(value).isValid()*/) {
+ qWarning() << filename << "line" << xml.lineNumber() << attrName << "should be a color:" << attr.value();
+ success = false;
+ }
+
+ return true;
}
- }
- return true;
-}
+ //! Read a QChar, \c sucess = \c false when \p c is not \c '\0' or does not have one char
+ //! \return \c true when attr.name() == attrName, otherwise false
+ bool extractChar(QChar &c, const QString &attrName)
+ {
+ if (attr.name() != attrName)
+ return false;
-//! Checks that DetectChar and Detect2Chars really only have one char
-//! in the attributes 'char' and 'char1'.
-bool checkSingleChars(const QString &hlFilename, QXmlStreamReader &xml)
-{
- const bool testChar1 = xml.name() == QLatin1String("Detect2Chars");
- const bool testChar = testChar1 || xml.name() == QLatin1String("DetectChar");
+ if (attr.value().size() == 1)
+ c = attr.value()[0];
+ else {
+ c = QLatin1Char('_');
+ qWarning() << filename << "line" << xml.lineNumber() << attrName << "must contain exactly one char:" << attr.value();
+ success = false;
+ }
- if (testChar) {
- const QString c = xml.attributes().value(QLatin1String("char")).toString();
- if (c.size() != 1) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "'char' must contain exactly one char:" << c;
- return false;
+ return true;
}
- }
- if (testChar1) {
- const QString c = xml.attributes().value(QLatin1String("char1")).toString();
- if (c.size() != 1) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "'char1' must contain exactly one char:" << c;
+ //! \return parsing status when \p isExtracted is \c true, otherwise \c false
+ bool checkIfExtracted(bool isExtracted)
+ {
+ if (isExtracted)
+ return success;
+
+ qWarning() << filename << "line" << xml.lineNumber() << "unknown attribute:" << attr.name();
return false;
}
- }
+ };
- return true;
-}
+ struct Keywords {
+ struct Items {
+ struct Item {
+ QString content;
+ int line;
+
+ friend uint qHash(const Item &item, uint seed = 0)
+ {
+ return qHash(item.content, seed);
+ }
-//! Search for rules with lookAhead="true" and context="#stay".
-//! This would cause an infinite loop.
-bool checkLookAhead(const QString &hlFilename, QXmlStreamReader &xml)
-{
- if (xml.attributes().hasAttribute(QStringLiteral("lookAhead"))) {
- auto lookAhead = xml.attributes().value(QStringLiteral("lookAhead"));
- if (attrToBool(lookAhead)) {
- auto context = xml.attributes().value(QStringLiteral("context"));
- if (context == QStringLiteral("#stay")) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "Infinite loop: lookAhead with context #stay";
+ friend bool operator==(const Item &item0, const Item &item1)
+ {
+ return item0.content == item1.content;
+ }
+ };
+
+ QVector<Item> keywords;
+ QSet<Item> includes;
+
+ bool parseElement(const QString &filename, QXmlStreamReader &xml)
+ {
+ bool success = true;
+
+ const int line = xml.lineNumber();
+ QString content = xml.readElementText();
+
+ if (content.isEmpty()) {
+ qWarning() << filename << "line" << line << "is empty:" << xml.name();
+ success = false;
+ }
+
+ if (xml.name() == QStringLiteral("include")) {
+ includes.insert({content, line});
+ } else if (xml.name() == QStringLiteral("item")) {
+ keywords.append({content, line});
+ } else {
+ qWarning() << filename << "line" << line << "invalid element:" << xml.name();
+ success = false;
+ }
+
+ return success;
+ }
+ };
+
+ QString name;
+ Items items;
+ int line;
+
+ bool parseElement(const QString &filename, QXmlStreamReader &xml)
+ {
+ line = xml.lineNumber();
+
+ bool success = true;
+ for (auto &attr : xml.attributes()) {
+ Parser parser{filename, xml, attr, success};
+
+ const bool isExtracted = parser.extractString(name, QStringLiteral("name"));
+
+ success = parser.checkIfExtracted(isExtracted);
+ }
+ return success;
+ }
+ };
+
+ struct Context {
+ struct Rule {
+ enum class Type {
+ Unknown,
+ AnyChar,
+ Detect2Chars,
+ DetectChar,
+ DetectIdentifier,
+ DetectSpaces,
+ Float,
+ HlCChar,
+ HlCHex,
+ HlCOct,
+ HlCStringChar,
+ IncludeRules,
+ Int,
+ LineContinue,
+ RangeDetect,
+ RegExpr,
+ StringDetect,
+ WordDetect,
+ keyword,
+ };
+
+ Type type{};
+
+ bool isDotRegex = false;
+ int line = -1;
+
+ // commonAttributes
+ QString attribute;
+ ContextName context;
+ QString beginRegion;
+ QString endRegion;
+ int column = -1;
+ XmlBool lookAhead{};
+ XmlBool firstNonSpace{};
+
+ // StringDetect, WordDetect, keyword
+ XmlBool insensitive{};
+
+ // DetectChar, StringDetect, RegExpr, keyword
+ XmlBool dynamic{};
+
+ // Regex
+ XmlBool minimal{};
+
+ // DetectChar, Detect2Chars, LineContinue, RangeDetect
+ QChar char0;
+ // Detect2Chars, RangeDetect
+ QChar char1;
+
+ // AnyChar, DetectChar, StringDetect, RegExpr, WordDetect, keyword
+ QString string;
+
+ // Float, HlCHex, HlCOct, Int, WordDetect, keyword
+ QString additionalDeliminator;
+ QString weakDeliminator;
+
+ // rules included by IncludeRules
+ QVector<const Rule *> includedRules;
+
+ // IncludeRules included by IncludeRules
+ QSet<const Rule *> includedIncludeRules;
+
+ QString filename;
+
+ bool parseElement(const QString &filename, QXmlStreamReader &xml)
+ {
+ this->filename = filename;
+
+ line = xml.lineNumber();
+
+ using Pair = QPair<QString, Type>;
+ static const auto pairs = {
+ Pair{QStringLiteral("AnyChar"), Type::AnyChar},
+ Pair{QStringLiteral("Detect2Chars"), Type::Detect2Chars},
+ Pair{QStringLiteral("DetectChar"), Type::DetectChar},
+ Pair{QStringLiteral("DetectIdentifier"), Type::DetectIdentifier},
+ Pair{QStringLiteral("DetectSpaces"), Type::DetectSpaces},
+ Pair{QStringLiteral("Float"), Type::Float},
+ Pair{QStringLiteral("HlCChar"), Type::HlCChar},
+ Pair{QStringLiteral("HlCHex"), Type::HlCHex},
+ Pair{QStringLiteral("HlCOct"), Type::HlCOct},
+ Pair{QStringLiteral("HlCStringChar"), Type::HlCStringChar},
+ Pair{QStringLiteral("IncludeRules"), Type::IncludeRules},
+ Pair{QStringLiteral("Int"), Type::Int},
+ Pair{QStringLiteral("LineContinue"), Type::LineContinue},
+ Pair{QStringLiteral("RangeDetect"), Type::RangeDetect},
+ Pair{QStringLiteral("RegExpr"), Type::RegExpr},
+ Pair{QStringLiteral("StringDetect"), Type::StringDetect},
+ Pair{QStringLiteral("WordDetect"), Type::WordDetect},
+ Pair{QStringLiteral("keyword"), Type::keyword},
+ };
+
+ for (auto pair : pairs) {
+ if (xml.name() == pair.first) {
+ type = pair.second;
+ bool success = parseAttributes(filename, xml);
+ success = checkMandoryAttributes(filename, xml) && success;
+ if (success && type == Type::RegExpr) {
+ // ., (.) followed by *, +, {1} or nothing
+ static const QRegularExpression isDot(QStringLiteral(R"(^\(?\.(?:[*+][*+?]?|[*+]|\{1\})?\$?$)"));
+ // remove "(?:" and ")"
+ static const QRegularExpression removeParentheses(QStringLiteral(R"(\((?:\?:)?|\))"));
+ // remove parentheses on a double from the string
+ auto reg = QString(string).replace(removeParentheses, QString());
+ isDotRegex = reg.contains(isDot);
+ }
+ return success;
+ }
+ }
+
+ qWarning() << filename << "line" << xml.lineNumber() << "unknown element:" << xml.name();
return false;
}
+
+ private:
+ bool parseAttributes(const QString &filename, QXmlStreamReader &xml)
+ {
+ bool success = true;
+
+ for (auto &attr : xml.attributes()) {
+ Parser parser{filename, xml, attr, success};
+ XmlBool includeAttrib{};
+
+ // clang-format off
+ const bool isExtracted
+ = parser.extractString(attribute, QStringLiteral("attribute"))
+ || parser.extractString(context.name, QStringLiteral("context"))
+ || parser.extractXmlBool(lookAhead, QStringLiteral("lookAhead"))
+ || parser.extractXmlBool(firstNonSpace, QStringLiteral("firstNonSpace"))
+ || parser.extractString(beginRegion, QStringLiteral("beginRegion"))
+ || parser.extractString(endRegion, QStringLiteral("endRegion"))
+ || parser.extractPositive(column, QStringLiteral("column"))
+ || ((type == Type::RegExpr
+ || type == Type::StringDetect
+ || type == Type::WordDetect
+ || type == Type::keyword
+ ) && parser.extractXmlBool(insensitive, QStringLiteral("insensitive")))
+ || ((type == Type::DetectChar
+ || type == Type::RegExpr
+ || type == Type::StringDetect
+ || type == Type::keyword
+ ) && parser.extractXmlBool(dynamic, QStringLiteral("dynamic")))
+ || ((type == Type::RegExpr)
+ && parser.extractXmlBool(minimal, QStringLiteral("minimal")))
+ || ((type == Type::DetectChar
+ || type == Type::Detect2Chars
+ || type == Type::LineContinue
+ || type == Type::RangeDetect
+ ) && parser.extractChar(char0, QStringLiteral("char")))
+ || ((type == Type::Detect2Chars
+ || type == Type::RangeDetect
+ ) && parser.extractChar(char1, QStringLiteral("char1")))
+ || ((type == Type::AnyChar
+ || type == Type::RegExpr
+ || type == Type::StringDetect
+ || type == Type::WordDetect
+ || type == Type::keyword
+ ) && parser.extractString(string, QStringLiteral("String")))
+ || ((type == Type::IncludeRules)
+ && parser.extractXmlBool(includeAttrib, QStringLiteral("includeAttrib")))
+ || ((type == Type::Float
+ || type == Type::HlCHex
+ || type == Type::HlCOct
+ || type == Type::Int
+ || type == Type::keyword
+ || type == Type::WordDetect
+ ) && (parser.extractString(additionalDeliminator, QStringLiteral("additionalDeliminator"))
+ || parser.extractString(weakDeliminator, QStringLiteral("weakDeliminator"))))
+ ;
+ // clang-format on
+
+ success = parser.checkIfExtracted(isExtracted);
+
+ if (type == Type::LineContinue && char0 == QLatin1Char('\0')) {
+ char0 = QLatin1Char('\\');
+ }
+ }
+
+ return success;
+ }
+
+ bool checkMandoryAttributes(const QString &filename, QXmlStreamReader &xml)
+ {
+ QString missingAttr;
+
+ switch (type) {
+ case Type::Unknown:
+ return false;
+
+ case Type::AnyChar:
+ case Type::RegExpr:
+ case Type::StringDetect:
+ case Type::WordDetect:
+ case Type::keyword:
+ missingAttr = string.isEmpty() ? QStringLiteral("String") : QString();
+ break;
+
+ case Type::DetectChar:
+ missingAttr = !char0.unicode() ? QStringLiteral("char") : QString();
+ break;
+
+ case Type::Detect2Chars:
+ case Type::RangeDetect:
+ missingAttr = !char0.unicode() && !char1.unicode() ? QStringLiteral("char and char1")
+ : !char0.unicode() ? QStringLiteral("char")
+ : !char1.unicode() ? QStringLiteral("char1")
+ : QString();
+ break;
+
+ case Type::IncludeRules:
+ missingAttr = context.name.isEmpty() ? QStringLiteral("context") : QString();
+ break;
+
+ case Type::DetectIdentifier:
+ case Type::DetectSpaces:
+ case Type::Float:
+ case Type::HlCChar:
+ case Type::HlCHex:
+ case Type::HlCOct:
+ case Type::HlCStringChar:
+ case Type::Int:
+ case Type::LineContinue:
+ break;
+ }
+
+ if (!missingAttr.isEmpty()) {
+ qWarning() << filename << "line" << xml.lineNumber() << "missing attribute:" << missingAttr;
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ int line;
+ QString name;
+ QString attribute;
+ ContextName lineEndContext;
+ ContextName lineEmptyContext;
+ ContextName fallthroughContext;
+ QVector<Rule> rules;
+ XmlBool dynamic{};
+ XmlBool fallthrough{};
+
+ bool parseElement(const QString &filename, QXmlStreamReader &xml)
+ {
+ line = xml.lineNumber();
+
+ bool success = true;
+
+ for (auto &attr : xml.attributes()) {
+ Parser parser{filename, xml, attr, success};
+ XmlBool noIndentationBasedFolding{};
+
+ const bool isExtracted = parser.extractString(name, QStringLiteral("name")) || parser.extractString(attribute, QStringLiteral("attribute"))
+ || parser.extractString(lineEndContext.name, QStringLiteral("lineEndContext"))
+ || parser.extractString(lineEmptyContext.name, QStringLiteral("lineEmptyContext"))
+ || parser.extractString(fallthroughContext.name, QStringLiteral("fallthroughContext"))
+ || parser.extractXmlBool(dynamic, QStringLiteral("dynamic")) || parser.extractXmlBool(fallthrough, QStringLiteral("fallthrough"))
+ || parser.extractXmlBool(noIndentationBasedFolding, QStringLiteral("noIndentationBasedFolding"));
+
+ success = parser.checkIfExtracted(isExtracted);
+ }
+
+ if (name.isEmpty()) {
+ qWarning() << filename << "line" << xml.lineNumber() << "missing attribute: name";
+ success = false;
+ }
+
+ if (attribute.isEmpty()) {
+ qWarning() << filename << "line" << xml.lineNumber() << "missing attribute: attribute";
+ success = false;
+ }
+
+ if (lineEndContext.name.isEmpty()) {
+ qWarning() << filename << "line" << xml.lineNumber() << "missing attribute: lineEndContext";
+ success = false;
+ }
+
+ return success;
}
+ };
+
+ struct Version {
+ int majorRevision;
+ int minorRevision;
+
+ Version(int majorRevision = 0, int minorRevision = 0)
+ : majorRevision(majorRevision)
+ , minorRevision(minorRevision)
+ {
+ }
+
+ bool operator<(const Version &version) const
+ {
+ return majorRevision < version.majorRevision || (majorRevision == version.majorRevision && minorRevision < version.minorRevision);
+ }
+ };
+
+ struct ItemDatas {
+ struct Style {
+ QString name;
+ int line;
+
+ friend uint qHash(const Style &style, uint seed = 0)
+ {
+ return qHash(style.name, seed);
+ }
+
+ friend bool operator==(const Style &style0, const Style &style1)
+ {
+ return style0.name == style1.name;
+ }
+ };
+
+ QSet<Style> styleNames;
+
+ bool parseElement(const QString &filename, QXmlStreamReader &xml)
+ {
+ bool success = true;
+
+ QString name;
+ QString defStyleNum;
+ XmlBool boolean;
+
+ for (auto &attr : xml.attributes()) {
+ Parser parser{filename, xml, attr, success};
+
+ const bool isExtracted = parser.extractString(name, QStringLiteral("name")) || parser.extractString(defStyleNum, QStringLiteral("defStyleNum"))
+ || parser.extractXmlBool(boolean, QStringLiteral("bold")) || parser.extractXmlBool(boolean, QStringLiteral("italic"))
+ || parser.extractXmlBool(boolean, QStringLiteral("underline")) || parser.extractXmlBool(boolean, QStringLiteral("strikeOut"))
+ || parser.extractXmlBool(boolean, QStringLiteral("spellChecking")) || parser.checkColor(QStringLiteral("color"))
+ || parser.checkColor(QStringLiteral("selColor")) || parser.checkColor(QStringLiteral("backgroundColor"))
+ || parser.checkColor(QStringLiteral("selBackgroundColor"));
+
+ success = parser.checkIfExtracted(isExtracted);
+ }
+
+ if (!name.isEmpty()) {
+ const auto len = styleNames.size();
+ styleNames.insert({name, int(xml.lineNumber())});
+ if (len == styleNames.size()) {
+ qWarning() << filename << "line" << xml.lineNumber() << "itemData duplicate:" << name;
+ success = false;
+ }
+ }
+
+ return success;
+ }
+ };
+
+ struct Definition {
+ QMap<QString, Keywords> keywordsList;
+ QMap<QString, Context> contexts;
+ ItemDatas itemDatas;
+ QString firstContextName;
+ const Context *firstContext = nullptr;
+ QString filename;
+ WordDelimiters wordDelimiters;
+ XmlBool casesensitive{};
+ Version kateVersion{};
+ QString kateVersionStr;
+ QString languageName;
+ QSet<const Definition *> referencedDefinitions;
+
+ // Parse <keywords ...>
+ bool parseKeywords(QXmlStreamReader &xml)
+ {
+ wordDelimiters.append(xml.attributes().value(QStringLiteral("additionalDeliminator")));
+ wordDelimiters.remove(xml.attributes().value(QStringLiteral("weakDeliminator")));
+ return true;
+ }
+ };
+
+ // Parse <context>
+ void processContextElement(QXmlStreamReader &xml)
+ {
+ Context context;
+ m_success = context.parseElement(m_currentDefinition->filename, xml) && m_success;
+ if (m_currentDefinition->firstContextName.isEmpty())
+ m_currentDefinition->firstContextName = context.name;
+ if (m_currentDefinition->contexts.contains(context.name)) {
+ qWarning() << m_currentDefinition->filename << "line" << xml.lineNumber() << "duplicate context:" << context.name;
+ m_success = false;
+ }
+ m_currentContext = &*m_currentDefinition->contexts.insert(context.name, context);
}
- return true;
-}
-/**
- * Helper class to search for non-existing keyword include.
- */
-class KeywordIncludeChecker
-{
-public:
- void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml)
+ // Parse <list name="...">
+ void processListElement(QXmlStreamReader &xml)
{
- if (xml.name() == QLatin1String("list")) {
- auto &keywords = m_keywordMap[hlName];
- keywords.filename = hlFilename;
- auto name = xml.attributes().value(QLatin1String("name")).toString();
- m_currentIncludes = &keywords.includes[name];
- } else if (xml.name() == QLatin1String("include")) {
- if (!m_currentIncludes) {
- qWarning() << hlFilename << "line" << xml.lineNumber() << "<include> tag ouside <list>";
- m_success = false;
- } else {
- m_currentIncludes->push_back({xml.lineNumber(), xml.readElementText()});
+ Keywords keywords;
+ m_success = keywords.parseElement(m_currentDefinition->filename, xml) && m_success;
+ if (m_currentDefinition->keywordsList.contains(keywords.name)) {
+ qWarning() << m_currentDefinition->filename << "line" << xml.lineNumber() << "duplicate list:" << keywords.name;
+ m_success = false;
+ }
+ m_currentKeywords = &*m_currentDefinition->keywordsList.insert(keywords.name, keywords);
+ }
+
+ const Definition *maxKateVersionDefinition(const Definition &definition, QMap<const Definition *, const Definition *> &maxVersionByDefinitions) const
+ {
+ auto it = maxVersionByDefinitions.find(&definition);
+ if (it != maxVersionByDefinitions.end()) {
+ return it.value();
+ } else {
+ auto it = maxVersionByDefinitions.insert(&definition, &definition);
+ for (const auto &referencedDef : definition.referencedDefinitions) {
+ auto *maxDef = maxKateVersionDefinition(*referencedDef, maxVersionByDefinitions);
+ if (it.value()->kateVersion < maxDef->kateVersion) {
+ it.value() = maxDef;
+ }
}
+ return it.value();
}
}
- bool check() const
+ // Initialize the referenced rules (Rule::includedRules)
+ void resolveIncludeRules()
{
- bool success = m_success;
- for (auto &keywords : m_keywordMap) {
- QMapIterator<QString, QVector<Keywords::Include>> includes(keywords.includes);
- while (includes.hasNext()) {
- includes.next();
- for (auto &include : includes.value()) {
- bool containsKeywordName = true;
- int const idx = include.name.indexOf(QStringLiteral("##"));
- if (idx == -1) {
- auto &keywordName = includes.key();
- containsKeywordName = keywords.includes.contains(keywordName);
- } else {
- auto defName = include.name.mid(idx + 2);
- auto listName = include.name.left(idx);
- auto it = m_keywordMap.find(defName);
- if (it == m_keywordMap.end()) {
- qWarning() << keywords.filename << "line" << include.line << "unknown definition in" << include.name;
- success = false;
- } else {
- containsKeywordName = it->includes.contains(listName);
- }
+ QSet<const Context *> usedContexts;
+ QVector<const Context *> contexts;
+
+ QMutableMapIterator<QString, Definition> def(m_definitions);
+ while (def.hasNext()) {
+ def.next();
+ auto &definition = def.value();
+ QMutableMapIterator<QString, Context> contextIt(definition.contexts);
+ while (contextIt.hasNext()) {
+ contextIt.next();
+ for (auto &rule : contextIt.value().rules) {
+ if (rule.type != Context::Rule::Type::IncludeRules) {
+ continue;
}
- if (!containsKeywordName) {
- qWarning() << keywords.filename << "line" << include.line << "unknown keyword name in" << include.name;
- success = false;
+ if (rule.context.stay) {
+ qWarning() << definition.filename << "line" << rule.line << "IncludeRules refers to himself";
+ m_success = false;
+ continue;
+ }
+ if (rule.context.popCount) {
+ qWarning() << definition.filename << "line" << rule.line << "IncludeRules with #pop prefix";
+ m_success = false;
+ }
+
+ if (!rule.context.context) {
+ m_success = false;
+ continue;
+ }
+
+ usedContexts.clear();
+ usedContexts.insert(rule.context.context);
+ contexts.clear();
+ contexts.append(rule.context.context);
+
+ for (int i = 0; i < contexts.size(); ++i) {
+ for (const auto &includedRule : contexts[i]->rules) {
+ if (includedRule.type != Context::Rule::Type::IncludeRules) {
+ rule.includedRules.append(&includedRule);
+ } else if (&rule == &includedRule) {
+ qWarning() << definition.filename << "line" << rule.line << "IncludeRules refers to himself by recursivity";
+ m_success = false;
+ } else {
+ rule.includedIncludeRules.insert(&includedRule);
+
+ if (includedRule.includedRules.isEmpty()) {
+ const auto *context = includedRule.context.context;
+ if (context && !usedContexts.contains(context)) {
+ contexts.append(context);
+ usedContexts.insert(context);
+ }
+ } else {
+ rule.includedRules.append(includedRule.includedRules);
+ }
+ }
+ }
}
}
}
}
- return success;
}
-private:
- struct Keywords {
- QString filename;
- struct Include {
- qint64 line;
- QString name;
- };
- QMap<QString, QVector<Include>> includes;
- };
- QHash<QString, Keywords> m_keywordMap;
- QVector<Keywords::Include> *m_currentIncludes = nullptr;
- bool m_success = true;
-};
+ //! Recursively extracts the contexts used from the first context of the definitions.
+ //! This method detects groups of contexts which are only used among themselves.
+ QSet<const Context *> extractUsedContexts() const
+ {
+ QSet<const Context *> usedContexts;
+ QVector<const Context *> contexts;
+
+ QMapIterator<QString, Definition> def(m_definitions);
+ while (def.hasNext()) {
+ def.next();
+ const auto &definition = def.value();
+
+ if (definition.firstContext) {
+ usedContexts.insert(definition.firstContext);
+ contexts.clear();
+ contexts.append(definition.firstContext);
+
+ for (int i = 0; i < contexts.size(); ++i) {
+ auto appendContext = [&](const Context *context) {
+ if (context && !usedContexts.contains(context)) {
+ contexts.append(context);
+ usedContexts.insert(context);
+ }
+ };
-/**
- * Helper class to search for non-existing or unreferenced keyword lists.
- */
-class KeywordChecker
-{
-public:
- KeywordChecker(const QString &filename)
- : m_filename(filename)
+ const auto *context = contexts[i];
+ appendContext(context->lineEndContext.context);
+ appendContext(context->lineEmptyContext.context);
+ appendContext(context->fallthroughContext.context);
+
+ for (auto &rule : context->rules) {
+ appendContext(rule.context.context);
+ }
+ }
+ }
+ }
+
+ return usedContexts;
+ }
+
+ //! Check contexts and rules
+ bool checkContexts(const Definition &definition,
+ QSet<const Keywords *> &referencedKeywords,
+ QSet<ItemDatas::Style> &usedAttributeNames,
+ const QSet<const Context *> &usedContexts) const
{
+ bool success = true;
+
+ QMapIterator<QString, Context> contextIt(definition.contexts);
+ while (contextIt.hasNext()) {
+ contextIt.next();
+
+ const auto &context = contextIt.value();
+ const auto &filename = definition.filename;
+
+ if (!usedContexts.contains(&context)) {
+ qWarning() << filename << "line" << context.line << "unused context:" << context.name;
+ success = false;
+ continue;
+ }
+
+ if (context.name.startsWith(QStringLiteral("#pop"))) {
+ qWarning() << filename << "line" << context.line << "the context name must not start with '#pop':" << context.name;
+ success = false;
+ }
+
+ if (!context.attribute.isEmpty())
+ usedAttributeNames.insert({context.attribute, context.line});
+
+ success = checkfallthrough(definition, context) && success;
+ success = checkUreachableRules(definition.filename, context) && success;
+ success = suggestRuleMerger(definition.filename, context) && success;
+
+ for (const auto &rule : context.rules) {
+ if (!rule.attribute.isEmpty())
+ usedAttributeNames.insert({rule.attribute, rule.line});
+ success = checkLookAhead(rule) && success;
+ success = checkStringDetect(rule) && success;
+ success = checkAnyChar(rule) && success;
+ success = checkKeyword(definition, rule, referencedKeywords) && success;
+ success = checkRegExpr(filename, rule, context) && success;
+ success = checkDelimiters(definition, rule) && success;
+ }
+ }
+
+ return success;
}
- void processElement(QXmlStreamReader &xml)
+ //! Check that a regular expression in a RegExpr rule:
+ //! - isValid()
+ //! - character ranges such as [A-Z] are valid and not accidentally e.g. [A-z].
+ //! - dynamic=true but no place holder used?
+ //! - is not . with lookAhead="1"
+ //! - is not ^... without column ou firstNonSpace attribute
+ //! - is not equivalent to DetectSpaces, DetectChar, Detect2Chars, StringDetect, DetectIdentifier, RangeDetect
+ //! - has no unused captures
+ //! - has no unnecessary quantifier with lookAhead
+ bool checkRegExpr(const QString &filename, const Context::Rule &rule, const Context &context) const
{
- if (xml.name() == QLatin1String("list")) {
- const QString name = xml.attributes().value(QLatin1String("name")).toString();
- if (m_existingNames.contains(name)) {
- qWarning() << m_filename << "list duplicate:" << name;
- m_success = false;
+ if (rule.type == Context::Rule::Type::RegExpr) {
+ const QRegularExpression regexp(rule.string);
+ if (!checkRegularExpression(rule.filename, regexp, rule.line)) {
+ return false;
+ }
+
+ // dynamic == true and no place holder?
+ if (rule.dynamic == XmlBool::True) {
+ static const QRegularExpression placeHolder(QStringLiteral("%\\d+"));
+ if (!rule.string.contains(placeHolder)) {
+ qWarning() << rule.filename << "line" << rule.line << "broken regex:" << rule.string << "problem: dynamic=true but no %\\d+ placeholder";
+ return false;
+ }
+ }
+
+ auto reg = rule.string;
+ reg.replace(QStringLiteral("{1}"), QString());
+
+ // is DetectSpaces
+ // optional ^ then \s, [\s], [\t ], [ \t] possibly in (...) or (?:...) followed by *, +
+ static const QRegularExpression isDetectSpaces(
+ QStringLiteral(R"(^\^?(?:\((?:\?:)?)?\^?(?:\\s|\[(?:\\s| (?:\t|\\t)|(?:\t|\\t) )\])\)?(?:[*+][*+?]?|[*+])?\)?\)?$)"));
+ if (rule.string.contains(isDetectSpaces)) {
+ char const *extraMsg = rule.string.contains(QLatin1Char('^')) ? "+ column=\"0\" or firstNonSpace=\"1\"" : "";
+ qWarning() << rule.filename << "line" << rule.line << "RegExpr should be replaced by DetectSpaces / DetectChar / AnyChar" << extraMsg << ":"
+ << rule.string;
+ return false;
+ }
+
+ // is RangeDetect
+ static const QRegularExpression isRange(QStringLiteral(
+ R"(^(\\(?:[^0BDPSWbdpswoux]|x[0-9a-fA-F]{2}|x\{[0-9a-fA-F]+\}|0\d\d|o\{[0-7]+\}|u[0-9a-fA-F]{4})|[^[.]|\[[^].\\]\])(?:\.|\[^\1\])\*[?*]?\1$)"));
+ if (( rule.lookAhead == XmlBool::True
+ || rule.minimal == XmlBool::True
+ || rule.string.contains(QStringLiteral(".*?"))
+ || rule.string.contains(QStringLiteral("[^"))
+ ) && reg.contains(isRange)
+ ) {
+ qWarning() << filename << "line" << rule.line << "RegExpr should be replaced by RangeDetect:" << rule.string;
+ return false;
+ }
+
+ // replace \c, \xhhh, \x{hhh...}, \0dd, \o{ddd}, \uhhhh, with _
+ static const QRegularExpression sanitize1(
+ QStringLiteral(R"(\\(?:[^0BDPSWbdpswoux]|x[0-9a-fA-F]{2}|x\{[0-9a-fA-F]+\}|0\d\d|o\{[0-7]+\}|u[0-9a-fA-F]{4}))"));
+ reg.replace(sanitize1, QStringLiteral("_"));
+
+ // use minimal or lazy operator
+ static const QRegularExpression isMinimal(QStringLiteral(
+ R"([.][*+][^][?+*()|$]*$)"));
+ if (rule.lookAhead == XmlBool::True
+ && rule.minimal != XmlBool::True
+ && reg.contains(isMinimal)
+ ) {
+ qWarning() << filename << "line" << rule.line << "RegExpr should be have minimal=\"1\" or use lazy operator (i.g, '.*' -> '.*?'):" << rule.string;
+ return false;
+ }
+
+ // replace [:...:] with ___
+ static const QRegularExpression sanitize2(QStringLiteral(R"(\[:\w+:\])"));
+ reg.replace(sanitize2, QStringLiteral("___"));
+
+ // replace [ccc...], [special] with ...
+ static const QRegularExpression sanitize3(QStringLiteral(R"(\[(?:\^\]?[^]]*|\]?[^]\\]*?\\.[^]]*|\][^]]{2,}|[^]]{3,})\]|(\[\]?[^]]*\]))"));
+ reg.replace(sanitize3, QStringLiteral("...\\1"));
+
+ // replace [c] with _
+ static const QRegularExpression sanitize4(QStringLiteral(R"(\[.\])"));
+ reg.replace(sanitize4, QStringLiteral("_"));
+
+ const int len = reg.size();
+ // replace [cC] with _
+ static const QRegularExpression toInsensitive(QStringLiteral(R"(\[(?:([^]])\1)\])"));
+ reg = reg.toUpper();
+ reg.replace(toInsensitive, QString());
+
+ // is StringDetect
+ // ignore (?:, ) and {n}
+ static const QRegularExpression isStringDetect(QStringLiteral(R"(^\^?(?:[^|\\?*+$^[{(.]|{(?!\d+,\d*}|,\d+})|\(\?:)+$)"));
+ if (reg.contains(isStringDetect)) {
+ char const *extraMsg = rule.string.contains(QLatin1Char('^')) ? "+ column=\"0\" or firstNonSpace=\"1\"" : "";
+ qWarning() << rule.filename << "line" << rule.line << "RegExpr should be replaced by StringDetect / Detect2Chars / DetectChar" << extraMsg
+ << ":" << rule.string;
+ if (len != reg.size())
+ qWarning() << rule.filename << "line" << rule.line << "insensitive=\"1\" missing:" << rule.string;
+ return false;
+ }
+
+ // column="0" or firstNonSpace="1"
+ if (rule.column == -1 && rule.firstNonSpace != XmlBool::True) {
+ // ^ without |
+ // (^sas*) -> ok
+ // (^sa|s*) -> ko
+ // (^(sa|s*)) -> ok
+ auto first = qAsConst(reg).begin();
+ auto last = qAsConst(reg).end();
+ int depth = 0;
+
+ while (QLatin1Char('(') == *first) {
+ ++depth;
+ ++first;
+ if (QLatin1Char('?') == *first || QLatin1Char(':') == first[1]) {
+ first += 2;
+ }
+ }
+
+ if (QLatin1Char('^') == *first) {
+ const int bolDepth = depth;
+ bool replace = true;
+
+ while (++first != last) {
+ if (QLatin1Char('(') == *first) {
+ ++depth;
+ } else if (QLatin1Char(')') == *first) {
+ --depth;
+ if (depth < bolDepth) {
+ // (^a)? === (^a|) -> ko
+ if (first + 1 != last && QStringLiteral("*?").contains(first[1])) {
+ replace = false;
+ break;
+ }
+ }
+ } else if (QLatin1Char('|') == *first) {
+ // ignore '|' within subgroup
+ if (depth <= bolDepth) {
+ replace = false;
+ break;
+ }
+ }
+ }
+
+ if (replace) {
+ qWarning() << rule.filename << "line" << rule.line << "column=\"0\" or firstNonSpace=\"1\" missing with RegExpr:" << rule.string;
+ return false;
+ }
+ }
+ }
+
+ // add ^ with column=0
+ if (rule.column == 0 && !rule.isDotRegex) {
+ bool hasStartOfLine = false;
+ auto first = qAsConst(reg).begin();
+ auto last = qAsConst(reg).end();
+ for (; first != last; ++first) {
+ if (*first == QLatin1Char('^')) {
+ hasStartOfLine = true;
+ break;
+ }
+ else if (*first == QLatin1Char('(')) {
+ if (last - first >= 3 && first[1] == QLatin1Char('?') && first[2] == QLatin1Char(':')) {
+ first += 2;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ if (!hasStartOfLine) {
+ qWarning() << rule.filename << "line" << rule.line << "start of line missing in the pattern with column=\"0\" (i.e. abc -> ^abc):" << rule.string;
+ return false;
+ }
+ }
+
+ bool useCapture = false;
+
+ // detection of unnecessary capture
+ if (regexp.captureCount()) {
+ auto maximalCapture = [](const QString(&referenceNames)[9], const QString &s) {
+ int maxCapture = 9;
+ while (maxCapture && !s.contains(referenceNames[maxCapture - 1])) {
+ --maxCapture;
+ }
+ return maxCapture;
+ };
+
+ int maxCaptureUsed = 0;
+ // maximal dynamic reference
+ if (rule.context.context && !rule.context.stay) {
+ for (const auto &nextRule : rule.context.context->rules) {
+ if (nextRule.dynamic == XmlBool::True) {
+ static const QString cap[]{
+ QStringLiteral("%1"),
+ QStringLiteral("%2"),
+ QStringLiteral("%3"),
+ QStringLiteral("%4"),
+ QStringLiteral("%5"),
+ QStringLiteral("%6"),
+ QStringLiteral("%7"),
+ QStringLiteral("%8"),
+ QStringLiteral("%9"),
+ };
+ int maxDynamicCapture = maximalCapture(cap, nextRule.string);
+ maxCaptureUsed = std::max(maxCaptureUsed, maxDynamicCapture);
+ }
+ }
+ }
+
+ static const QString num1[]{
+ QStringLiteral("\\1"),
+ QStringLiteral("\\2"),
+ QStringLiteral("\\3"),
+ QStringLiteral("\\4"),
+ QStringLiteral("\\5"),
+ QStringLiteral("\\6"),
+ QStringLiteral("\\7"),
+ QStringLiteral("\\8"),
+ QStringLiteral("\\9"),
+ };
+ static const QString num2[]{
+ QStringLiteral("\\g1"),
+ QStringLiteral("\\g2"),
+ QStringLiteral("\\g3"),
+ QStringLiteral("\\g4"),
+ QStringLiteral("\\g5"),
+ QStringLiteral("\\g6"),
+ QStringLiteral("\\g7"),
+ QStringLiteral("\\g8"),
+ QStringLiteral("\\g9"),
+ };
+ const int maxBackReference = std::max(maximalCapture(num1, rule.string), maximalCapture(num1, rule.string));
+
+ const int maxCapture = std::max(maxCaptureUsed, maxBackReference);
+
+ if (maxCapture && regexp.captureCount() > maxCapture) {
+ qWarning() << rule.filename << "line" << rule.line << "RegExpr with" << regexp.captureCount() << "captures but only" << maxCapture
+ << "are used. Please, replace '(...)' with '(?:...)':" << rule.string;
+ return false;
+ }
+
+ useCapture = maxCapture;
+ }
+
+ if (!useCapture) {
+ // is DetectIdentifier
+ static const QRegularExpression isInsensitiveDetectIdentifier(
+ QStringLiteral(R"(^(\((\?:)?)?\[((a-z|_){2}|(A-Z|_){2})\]([+][*?]?)?\[((0-9|a-z|_){3}|(0-9|A-Z|_){3})\][*][*?]?(\))?$)"));
+ static const QRegularExpression isSensitiveDetectIdentifier(
+ QStringLiteral(R"(^(\((\?:)?)?\[(a-z|A-Z|_){3}\]([+][*?]?)?\[(0-9|a-z|A-Z|_){4}\][*][*?]?(\))?$)"));
+ auto &isDetectIdentifier = (rule.insensitive == XmlBool::True) ? isInsensitiveDetectIdentifier : isSensitiveDetectIdentifier;
+ if (rule.string.contains(isDetectIdentifier)) {
+ qWarning() << rule.filename << "line" << rule.line << "RegExpr should be replaced by DetectIdentifier:" << rule.string;
+ return false;
+ }
+ }
+
+ if (rule.isDotRegex) {
+ // search next rule with same column or firstNonSpace
+ int i = &rule - context.rules.data() + 1;
+ const bool hasColumn = (rule.column != -1);
+ const bool hasFirstNonSpace = (rule.firstNonSpace == XmlBool::True);
+ const bool isSpecial = (hasColumn || hasFirstNonSpace);
+ for (; i < context.rules.size(); ++i) {
+ auto &rule2 = context.rules[i];
+ if (rule2.type == Context::Rule::Type::IncludeRules && isSpecial) {
+ i = context.rules.size();
+ break;
+ }
+
+ const bool hasColumn2 = (rule2.column != -1);
+ const bool hasFirstNonSpace2 = (rule2.firstNonSpace == XmlBool::True);
+ if ((!isSpecial && !hasColumn2 && !hasFirstNonSpace2) || (hasColumn && rule.column == rule2.column)
+ || (hasFirstNonSpace && hasFirstNonSpace2)) {
+ break;
+ }
+ }
+
+ auto ruleFilename = (filename == rule.filename) ? QString() : QStringLiteral("in ") + rule.filename;
+ if (i == context.rules.size()) {
+ if (rule.lookAhead == XmlBool::True && rule.firstNonSpace != XmlBool::True && rule.column == -1 && rule.beginRegion.isEmpty()
+ && rule.endRegion.isEmpty() && !useCapture) {
+ qWarning() << filename << "context line" << context.line << ": RegExpr line" << rule.line << ruleFilename
+ << "should be replaced by fallthroughContext:" << rule.string;
+ }
+ } else {
+ auto &nextRule = context.rules[i];
+ auto nextRuleFilename = (filename == nextRule.filename) ? QString() : QStringLiteral("in ") + nextRule.filename;
+ qWarning() << filename << "context line" << context.line << "contains unreachable element line" << nextRule.line << nextRuleFilename
+ << "because a dot RegExpr is used line" << rule.line << ruleFilename;
+ }
+
+ // unnecessary quantifier
+ static const QRegularExpression unnecessaryQuantifier1(QStringLiteral(
+ R"([*+?]([.][*+?]{0,2})?$)"));
+ static const QRegularExpression unnecessaryQuantifier2(QStringLiteral(
+ R"([*+?]([.][*+?]{0,2})?[)]*$)"));
+ auto &unnecessaryQuantifier = useCapture ? unnecessaryQuantifier1 : unnecessaryQuantifier2;
+ if (rule.lookAhead == XmlBool::True && rule.minimal != XmlBool::True && reg.contains(unnecessaryQuantifier)) {
+ qWarning() << filename << "line" << rule.line << "Last quantifier is not necessary (i.g., 'xyz*' -> 'xy', 'xyz+.' -> 'xyz.'):"
+ << rule.string;
+ return false;
+ }
}
- m_existingNames.insert(name);
- } else if (xml.name() == QLatin1String("keyword")) {
- const QString context = xml.attributes().value(QLatin1String("String")).toString();
- if (!context.isEmpty())
- m_usedNames.insert(context);
}
+
+ return true;
}
- bool check() const
+ // Parse and check <emptyLine>
+ bool parseEmptyLine(const QString &filename, QXmlStreamReader &xml)
{
- bool success = m_success;
- const auto invalidNames = m_usedNames - m_existingNames;
- if (!invalidNames.isEmpty()) {
- qWarning() << m_filename << "Reference of non-existing keyword list:" << invalidNames;
- success = false;
+ bool success = true;
+
+ QString pattern;
+ XmlBool casesensitive{};
+
+ for (auto &attr : xml.attributes()) {
+ Parser parser{filename, xml, attr, success};
+
+ const bool isExtracted =
+ parser.extractString(pattern, QStringLiteral("regexpr")) || parser.extractXmlBool(casesensitive, QStringLiteral("casesensitive"));
+
+ success = parser.checkIfExtracted(isExtracted);
}
- const auto unusedNames = m_existingNames - m_usedNames;
- if (!unusedNames.isEmpty()) {
- qWarning() << m_filename << "Unused keyword lists:" << unusedNames;
+ if (pattern.isEmpty()) {
+ qWarning() << filename << "line" << xml.lineNumber() << "missing attribute: regexpr";
success = false;
+ } else {
+ success = checkRegularExpression(filename, QRegularExpression(pattern), xml.lineNumber());
}
return success;
}
-private:
- QString m_filename;
- QSet<QString> m_usedNames;
- QSet<QString> m_existingNames;
- bool m_success = true;
-};
+ //! Check that a regular expression:
+ //! - isValid()
+ //! - character ranges such as [A-Z] are valid and not accidentally e.g. [A-z].
+ bool checkRegularExpression(const QString &filename, const QRegularExpression &regexp, int line) const
+ {
+ const auto pattern = regexp.pattern();
-/**
- * Helper class to search for non-existing contexts and invalid version
- */
-class ContextChecker
-{
-public:
- void setKateVersion(const QStringRef &verStr, const QString &hlFilename, const QString &hlName)
+ // validate regexp
+ if (!regexp.isValid()) {
+ qWarning() << filename << "line" << line << "broken regex:" << pattern << "problem:" << regexp.errorString() << "at offset"
+ << regexp.patternErrorOffset();
+ return false;
+ }
+
+ // catch possible case typos: [A-z] or [a-Z]
+ const int azOffset = std::max(pattern.indexOf(QStringLiteral("A-z")), pattern.indexOf(QStringLiteral("a-Z")));
+ if (azOffset >= 0) {
+ qWarning() << filename << "line" << line << "broken regex:" << pattern << "problem: [a-Z] or [A-z] at offset" << azOffset;
+ return false;
+ }
+
+ return true;
+ }
+
+ //! Search for rules with lookAhead="true" and context="#stay".
+ //! This would cause an infinite loop.
+ bool checkfallthrough(const Definition &definition, const Context &context) const
{
- const auto idx = verStr.indexOf(QLatin1Char('.'));
- if (idx <= 0) {
- qWarning() << hlFilename << "invalid kateversion" << verStr;
- m_success = false;
- } else {
- auto &language = m_contextMap[hlName];
- language.version = {verStr.left(idx).toInt(), verStr.mid(idx + 1).toInt()};
+ bool success = true;
+
+ if (!context.fallthroughContext.name.isEmpty()) {
+ if (context.fallthroughContext.stay) {
+ qWarning() << definition.filename << "line" << context.line << "possible infinite loop due to fallthroughContext=\"#stay\" in context "
+ << context.name;
+ success = false;
+ }
+
+ const bool mandatoryFallthroughAttribute = definition.kateVersion < Version{5, 62};
+ if (context.fallthrough == XmlBool::True && !mandatoryFallthroughAttribute) {
+ qWarning() << definition.filename << "line" << context.line << "fallthrough attribute is unnecessary with kateversion >= 5.62 in context"
+ << context.name;
+ success = false;
+ } else if (context.fallthrough != XmlBool::True && mandatoryFallthroughAttribute) {
+ qWarning() << definition.filename << "line" << context.line
+ << "fallthroughContext attribute without fallthrough=\"1\" attribute is only valid with kateversion >= 5.62 in context"
+ << context.name;
+ success = false;
+ }
}
+
+ return success;
}
- void processElement(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml)
+ //! Search for additionalDeliminator/weakDeliminator which has no effect.
+ bool checkDelimiters(const Definition &definition, const Context::Rule &rule) const
{
- if (xml.name() == QLatin1String("context")) {
- auto &language = m_contextMap[hlName];
- language.hlFilename = hlFilename;
- const QString name = xml.attributes().value(QLatin1String("name")).toString();
- if (language.isFirstContext) {
- language.isFirstContext = false;
- language.usedContextNames.insert(name);
+ if (rule.additionalDeliminator.isEmpty() && rule.weakDeliminator.isEmpty())
+ return true;
+
+ bool success = true;
+
+ if (definition.kateVersion < Version{5, 79}) {
+ qWarning() << definition.filename << "line" << rule.line
+ << "additionalDeliminator and weakDeliminator are only available since version \"5.79\". Please, increase kateversion.";
+ success = false;
+ }
+
+ for (QChar c : rule.additionalDeliminator) {
+ if (!definition.wordDelimiters.contains(c)) {
+ return success;
}
+ }
- if (language.existingContextNames.contains(name)) {
- qWarning() << hlFilename << "Duplicate context:" << name;
- m_success = false;
+ for (QChar c : rule.weakDeliminator) {
+ if (definition.wordDelimiters.contains(c)) {
+ return success;
+ }
+ }
+
+ qWarning() << rule.filename << "line" << rule.line << "unnecessary use of additionalDeliminator and/or weakDeliminator" << rule.string;
+ return false;
+ }
+
+ //! Search for rules with lookAhead="true" and context="#stay".
+ //! This would cause an infinite loop.
+ bool checkKeyword(const Definition &definition, const Context::Rule &rule, QSet<const Keywords *> &referencedKeywords) const
+ {
+ if (rule.type == Context::Rule::Type::keyword) {
+ auto it = definition.keywordsList.find(rule.string);
+ if (it != definition.keywordsList.end()) {
+ referencedKeywords.insert(&*it);
} else {
- language.existingContextNames.insert(name);
+ qWarning() << rule.filename << "line" << rule.line << "reference of non-existing keyword list:" << rule.string;
+ return false;
}
+ }
+ return true;
+ }
- auto fallthroughContext = xml.attributes().value(QLatin1String("fallthroughContext"));
- if (!fallthroughContext.isEmpty()) {
- if (fallthroughContext == QLatin1String("#stay")) {
- qWarning() << hlFilename << "possible infinite loop due to fallthroughContext=\"#stay\" in context " << name;
- m_success = false;
+ //! Search for rules with lookAhead="true" and context="#stay".
+ //! This would cause an infinite loop.
+ bool checkLookAhead(const Context::Rule &rule) const
+ {
+ if (rule.lookAhead == XmlBool::True && rule.context.stay) {
+ qWarning() << rule.filename << "line" << rule.line << "infinite loop: lookAhead with context #stay";
+ }
+ return true;
+ }
+
+ //! Check that StringDetect contains more that 2 characters
+ //! Fix with following command:
+ //! \code
+ //! sed -E
+ //! '/StringDetect/{/dynamic="(1|true)|insensitive="(1|true)/!{s/StringDetect(.*)String="(.|&lt;|&gt;|&quot;|&amp;)(.|&lt;|&gt;|&quot;|&amp;)"/Detect2Chars\1char="\2"
+ //! char1="\3"/;t;s/StringDetect(.*)String="(.|&lt;|&gt;|&quot;|&amp;)"/DetectChar\1char="\2"/}}' -i file.xml...
+ //! \endcode
+ bool checkStringDetect(const Context::Rule &rule) const
+ {
+ if (rule.type == Context::Rule::Type::StringDetect) {
+ // dynamic == true and no place holder?
+ if (rule.dynamic == XmlBool::True) {
+ static const QRegularExpression placeHolder(QStringLiteral("%\\d+"));
+ if (!rule.string.contains(placeHolder)) {
+ qWarning() << rule.filename << "line" << rule.line << "broken regex:" << rule.string << "problem: dynamic=true but no %\\d+ placeholder";
+ return false;
+ }
+ } else {
+ if (rule.string.size() <= 1) {
+ const auto replacement = rule.insensitive == XmlBool::True ? QStringLiteral("AnyChar") : QStringLiteral("DetectChar");
+ qWarning() << rule.filename << "line" << rule.line << "StringDetect should be replaced by" << replacement;
+ return false;
}
- auto fallthrough = xml.attributes().value(QLatin1String("fallthrough"));
- if (fallthrough.isEmpty() && language.version < Version{5, 62}) {
- qWarning() << hlFilename << "fallthroughContext attribute without fallthrough attribute is only valid with kateversion >= 5.62 in context " << name;
- m_success = false;
+ if (rule.string.size() <= 2 && rule.insensitive != XmlBool::True) {
+ qWarning() << rule.filename << "line" << rule.line << "StringDetect should be replaced by Detect2Chars";
+ return false;
}
}
+ }
+ return true;
+ }
- processContext(hlName, xml.attributes().value(QLatin1String("lineEndContext")).toString());
- processContext(hlName, xml.attributes().value(QLatin1String("lineEmptyContext")).toString());
- processContext(hlName, xml.attributes().value(QLatin1String("fallthroughContext")).toString());
- } else if (xml.name() == QLatin1String("include")) {
- // <include> tag inside <list>
- processVersion(hlFilename, hlName, xml, {5, 53}, QLatin1String("<include>"));
- } else {
- if (xml.attributes().hasAttribute(QLatin1String("context"))) {
- const QString context = xml.attributes().value(QLatin1String("context")).toString();
- if (context.isEmpty()) {
- qWarning() << hlFilename << "Missing context name in line" << xml.lineNumber();
- m_success = false;
- } else {
- processContext(hlName, context);
+ //! Check that AnyChar contains more that 1 character
+ bool checkAnyChar(const Context::Rule &rule) const
+ {
+ if (rule.type == Context::Rule::Type::AnyChar && rule.string.size() <= 1) {
+ qWarning() << rule.filename << "line" << rule.line << "AnyChar should be replaced by DetectChar";
+ return false;
+ }
+ return true;
+ }
+
+ //! Check \<include> and delimiter in a keyword list
+ bool checkKeywordsList(const Definition &definition, QSet<const Keywords *> &referencedKeywords) const
+ {
+ bool success = true;
+
+ bool includeNotSupport = (definition.kateVersion < Version{5, 53});
+ QMapIterator<QString, Keywords> keywordsIt(definition.keywordsList);
+ while (keywordsIt.hasNext()) {
+ keywordsIt.next();
+
+ for (const auto &include : keywordsIt.value().items.includes) {
+ if (includeNotSupport) {
+ qWarning() << definition.filename << "line" << include.line
+ << "<include> is only available since version \"5.53\". Please, increase kateversion.";
+ success = false;
+ }
+ success = checkKeywordInclude(definition, include, referencedKeywords) && success;
+ }
+
+ // Check that keyword list items do not have deliminator character
+#if 0
+ for (const auto& keyword : keywordsIt.value().items.keywords) {
+ for (QChar c : keyword.content) {
+ if (definition.wordDelimiters.contains(c)) {
+ qWarning() << definition.filename << "line" << keyword.line << "keyword with delimiter:" << c << "in" << keyword.content;
+ success = false;
+ }
}
}
+#endif
}
+
+ return success;
}
- bool check() const
+ //! Search for non-existing keyword include.
+ bool checkKeywordInclude(const Definition &definition, const Keywords::Items::Item &include, QSet<const Keywords *> &referencedKeywords) const
{
- bool success = m_success;
+ bool containsKeywordName = true;
+ int const idx = include.content.indexOf(QStringLiteral("##"));
+ if (idx == -1) {
+ auto it = definition.keywordsList.find(include.content);
+ containsKeywordName = (it != definition.keywordsList.end());
+ if (containsKeywordName) {
+ referencedKeywords.insert(&*it);
+ }
+ } else {
+ auto defName = include.content.mid(idx + 2);
+ auto listName = include.content.left(idx);
+ auto it = m_definitions.find(defName);
+ if (it == m_definitions.end()) {
+ qWarning() << definition.filename << "line" << include.line << "unknown definition in" << include.content;
+ return false;
+ }
+ containsKeywordName = it->keywordsList.contains(listName);
+ }
+
+ if (!containsKeywordName) {
+ qWarning() << definition.filename << "line" << include.line << "unknown keyword name in" << include.content;
+ }
+
+ return containsKeywordName;
+ }
+
+ //! Check if a rule is hidden by another
+ //! - rule hidden by DetectChar or AnyChar
+ //! - DetectSpaces, AnyChar, Int, Float with all their characters hidden by DetectChar or AnyChar
+ //! - StringDetect, WordDetect, RegExpr with as prefix Detect2Chars or other strings
+ //! - duplicate rule (Int, Float, keyword with same String, etc)
+ //! - Rule hidden by a dot regex
+ bool checkUreachableRules(const QString &filename, const Context &context) const
+ {
+ struct RuleAndInclude {
+ const Context::Rule *rule;
+ const Context::Rule *includeRules;
+
+ explicit operator bool() const
+ {
+ return rule;
+ }
+ };
+
+ // Associate QChar with RuleAndInclude
+ struct CharTable {
+ /// Search RuleAndInclude associated with @p c.
+ RuleAndInclude find(QChar c) const
+ {
+ if (c.unicode() < 128)
+ return m_asciiMap[c.unicode()];
+ auto it = m_utf8Map.find(c);
+ return it == m_utf8Map.end() ? RuleAndInclude{nullptr, nullptr} : it.value();
+ }
+
+ /// Search RuleAndInclude associated with the characters of @p s.
+ /// \return an empty QVector when at least one character is not found.
+ QVector<RuleAndInclude> find(QStringView s) const
+ {
+ QVector<RuleAndInclude> result;
+
+ for (QChar c : s) {
+ if (!find(c)) {
+ return result;
+ }
+ }
- // recursive search for the required miximal version
- struct GetRequiredVersion {
- QHash<const Language *, Version> versionMap;
+ for (QChar c : s) {
+ result.append(find(c));
+ }
- Version operator()(const QHash<QString, Language> &contextMap, const Language &language)
+ return result;
+ }
+
+ /// Associates @p c with a rule.
+ void append(QChar c, const Context::Rule &rule, const Context::Rule *includeRule = nullptr)
{
- auto &version = versionMap[&language];
- if (version < language.version) {
- version = language.version;
- for (auto &languageName : language.usedLanguageName) {
- auto it = contextMap.find(languageName);
- if (it != contextMap.end()) {
- version = std::max(operator()(contextMap, *it), version);
+ if (c.unicode() < 128)
+ m_asciiMap[c.unicode()] = {&rule, includeRule};
+ else
+ m_utf8Map[c] = {&rule, includeRule};
+ }
+
+ /// Associates each character of @p s with a rule.
+ void append(QStringView s, const Context::Rule &rule, const Context::Rule *includeRule = nullptr)
+ {
+ for (QChar c : s) {
+ append(c, rule, includeRule);
+ }
+ }
+
+ private:
+ RuleAndInclude m_asciiMap[127]{};
+ QMap<QChar, RuleAndInclude> m_utf8Map;
+ };
+
+ struct Char4Tables {
+ CharTable chars;
+ CharTable charsColumn0;
+ QMap<int, CharTable> charsColumnGreaterThan0;
+ CharTable charsFirstNonSpace;
+ };
+
+ // View on Char4Tables members
+ struct CharTableArray {
+ // Append Char4Tables members that satisfies firstNonSpace and column.
+ // Char4Tables::char is always added.
+ CharTableArray(Char4Tables &tables, const Context::Rule &rule)
+ {
+ if (rule.firstNonSpace == XmlBool::True)
+ appendTable(tables.charsFirstNonSpace);
+
+ if (rule.column == 0)
+ appendTable(tables.charsColumn0);
+ else if (rule.column > 0)
+ appendTable(tables.charsColumnGreaterThan0[rule.column]);
+
+ appendTable(tables.chars);
+ }
+
+ // Removes Char4Tables::chars when the rule contains firstNonSpace or column
+ void removeNonSpecialWhenSpecial()
+ {
+ if (m_size > 1) {
+ --m_size;
+ }
+ }
+
+ /// Search RuleAndInclude associated with @p c.
+ RuleAndInclude find(QChar c) const
+ {
+ for (int i = 0; i < m_size; ++i) {
+ if (auto ruleAndInclude = m_charTables[i]->find(c))
+ return ruleAndInclude;
+ }
+ return RuleAndInclude{nullptr, nullptr};
+ }
+
+ /// Search RuleAndInclude associated with the characters of @p s.
+ /// \return an empty QVector when at least one character is not found.
+ QVector<RuleAndInclude> find(QStringView s) const
+ {
+ for (int i = 0; i < m_size; ++i) {
+ auto result = m_charTables[i]->find(s);
+ if (result.size()) {
+ while (++i < m_size) {
+ result.append(m_charTables[i]->find(s));
}
+ return result;
}
}
- return version;
- };
+ return QVector<RuleAndInclude>();
+ }
+
+ /// Associates @p c with a rule.
+ void append(QChar c, const Context::Rule &rule, const Context::Rule *includeRule = nullptr)
+ {
+ for (int i = 0; i < m_size; ++i) {
+ m_charTables[i]->append(c, rule, includeRule);
+ }
+ }
+
+ /// Associates each character of @p s with a rule.
+ void append(QStringView s, const Context::Rule &rule, const Context::Rule *includeRule = nullptr)
+ {
+ for (int i = 0; i < m_size; ++i) {
+ m_charTables[i]->append(s, rule, includeRule);
+ }
+ }
+
+ private:
+ void appendTable(CharTable &t)
+ {
+ m_charTables[m_size] = &t;
+ ++m_size;
+ }
+
+ CharTable *m_charTables[3];
+ int m_size = 0;
};
- GetRequiredVersion getRequiredVersion;
- for (auto &language : m_contextMap) {
- const auto invalidContextNames = language.usedContextNames - language.existingContextNames;
- if (!invalidContextNames.isEmpty()) {
- qWarning() << language.hlFilename << "Reference of non-existing contexts:" << invalidContextNames;
- success = false;
+ // Iterates over all the rules, including those in includedRules
+ struct RuleIterator {
+ RuleIterator(const QVector<Context::Rule> &rules, const Context::Rule &endRule)
+ : m_end(&endRule - rules.data())
+ , m_rules(rules)
+ {
}
- const auto unusedNames = language.existingContextNames - language.usedContextNames;
- if (!unusedNames.isEmpty()) {
- qWarning() << language.hlFilename << "Unused contexts:" << unusedNames;
- success = false;
+ /// \return next rule or nullptr
+ const Context::Rule *next()
+ {
+ // if in includedRules
+ if (m_includedRules) {
+ ++m_i2;
+ if (m_i2 != m_rules[m_i].includedRules.size()) {
+ return (*m_includedRules)[m_i2];
+ }
+ ++m_i;
+ m_includedRules = nullptr;
+ }
+
+ // if is a includedRules
+ while (m_i < m_end && m_rules[m_i].type == Context::Rule::Type::IncludeRules) {
+ if (m_rules[m_i].includedRules.size()) {
+ m_i2 = 0;
+ m_includedRules = &m_rules[m_i].includedRules;
+ return (*m_includedRules)[m_i2];
+ }
+ ++m_i;
+ }
+
+ if (m_i < m_end) {
+ ++m_i;
+ return &m_rules[m_i - 1];
+ }
+
+ return nullptr;
+ }
+
+ /// \return current IncludeRules or nullptr
+ const Context::Rule *currentIncludeRules() const
+ {
+ return m_includedRules ? &m_rules[m_i] : nullptr;
+ }
+
+ private:
+ int m_i = 0;
+ int m_i2;
+ int m_end;
+ const QVector<Context::Rule> &m_rules;
+ const QVector<const Context::Rule *> *m_includedRules = nullptr;
+ };
+
+ // Dot regex container that satisfies firstNonSpace and column.
+ struct DotRegex {
+ /// Append a dot regex rule.
+ void append(const Context::Rule &rule, const Context::Rule *includedRule)
+ {
+ auto array = extractDotRegexes(rule);
+ if (array[0])
+ *array[0] = {&rule, includedRule};
+ if (array[1])
+ *array[1] = {&rule, includedRule};
+ }
+
+ /// Search dot regex which hides @p rule
+ RuleAndInclude find(const Context::Rule &rule)
+ {
+ auto array = extractDotRegexes(rule);
+ if (array[0])
+ return *array[0];
+ if (array[1])
+ return *array[1];
+ return RuleAndInclude{};
+ }
+
+ private:
+ using Array = std::array<RuleAndInclude *, 2>;
+
+ Array extractDotRegexes(const Context::Rule &rule)
+ {
+ Array ret{};
+
+ if (rule.firstNonSpace != XmlBool::True && rule.column == -1) {
+ ret[0] = &dotRegex;
+ } else {
+ if (rule.firstNonSpace == XmlBool::True)
+ ret[0] = &dotRegexFirstNonSpace;
+
+ if (rule.column == 0)
+ ret[1] = &dotRegexColumn0;
+ else if (rule.column > 0)
+ ret[1] = &dotRegexColumnGreaterThan0[rule.column];
+ }
+
+ return ret;
+ }
+
+ RuleAndInclude dotRegex{};
+ RuleAndInclude dotRegexColumn0{};
+ QMap<int, RuleAndInclude> dotRegexColumnGreaterThan0{};
+ RuleAndInclude dotRegexFirstNonSpace{};
+ };
+
+ bool success = true;
+
+ // characters of DetectChar/AnyChar
+ Char4Tables detectChars;
+ // characters of dynamic DetectChar
+ Char4Tables dynamicDetectChars;
+ // characters of LineContinue
+ Char4Tables lineContinueChars;
+
+ RuleAndInclude intRule{};
+ RuleAndInclude floatRule{};
+ RuleAndInclude hlCCharRule{};
+ RuleAndInclude hlCOctRule{};
+ RuleAndInclude hlCHexRule{};
+ RuleAndInclude hlCStringCharRule{};
+
+ // Contains includedRules and included includedRules
+ QMap<Context const*, RuleAndInclude> includeContexts;
+
+ DotRegex dotRegex;
+
+ for (const Context::Rule &rule : context.rules) {
+ bool isUnreachable = false;
+ QVector<RuleAndInclude> unreachableBy;
+
+ // declare rule as unreacheable if ruleAndInclude is not empty
+ auto updateUnreachable1 = [&](RuleAndInclude ruleAndInclude) {
+ if (ruleAndInclude) {
+ isUnreachable = true;
+ unreachableBy.append(ruleAndInclude);
+ }
+ };
+
+ // declare rule as unreacheable if ruleAndIncludes is not empty
+ auto updateUnreachable2 = [&](const QVector<RuleAndInclude> &ruleAndIncludes) {
+ if (!ruleAndIncludes.isEmpty()) {
+ isUnreachable = true;
+ unreachableBy.append(ruleAndIncludes);
+ }
+ };
+
+ // check if rule2.firstNonSpace/column is compatible with those of rule
+ auto isCompatible = [&rule](Context::Rule const &rule2) {
+ return (rule2.firstNonSpace != XmlBool::True && rule2.column == -1) || (rule.column == rule2.column && rule.column != -1)
+ || (rule.firstNonSpace == rule2.firstNonSpace && rule.firstNonSpace == XmlBool::True);
+ };
+
+ updateUnreachable1(dotRegex.find(rule));
+
+ switch (rule.type) {
+ // checks if hidden by DetectChar/AnyChar
+ // then add the characters to detectChars
+ case Context::Rule::Type::AnyChar: {
+ auto tables = CharTableArray(detectChars, rule);
+ updateUnreachable2(tables.find(rule.string));
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(rule.string, rule);
+ break;
+ }
+
+ // check if is hidden by DetectChar/AnyChar
+ // then add the characters to detectChars or dynamicDetectChars
+ case Context::Rule::Type::DetectChar: {
+ auto &chars4 = (rule.dynamic != XmlBool::True) ? detectChars : dynamicDetectChars;
+ auto tables = CharTableArray(chars4, rule);
+ updateUnreachable1(tables.find(rule.char0));
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(rule.char0, rule);
+ break;
+ }
+
+ // check if hidden by DetectChar/AnyChar
+ // then add spaces characters to detectChars
+ case Context::Rule::Type::DetectSpaces: {
+ auto tables = CharTableArray(detectChars, rule);
+ updateUnreachable2(tables.find(QStringLiteral(" \t")));
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(QLatin1Char(' '), rule);
+ tables.append(QLatin1Char('\t'), rule);
+ break;
+ }
+
+ // check if hidden by DetectChar/AnyChar
+ case Context::Rule::Type::HlCChar:
+ updateUnreachable1(CharTableArray(detectChars, rule).find(QLatin1Char('\'')));
+ updateUnreachable1(hlCCharRule);
+ hlCCharRule = {&rule, nullptr};
+ break;
+
+ // check if hidden by DetectChar/AnyChar
+ case Context::Rule::Type::HlCHex:
+ updateUnreachable1(CharTableArray(detectChars, rule).find(QLatin1Char('0')));
+ updateUnreachable1(hlCHexRule);
+ hlCHexRule = {&rule, nullptr};
+ break;
+
+ // check if hidden by DetectChar/AnyChar
+ case Context::Rule::Type::HlCOct:
+ updateUnreachable1(CharTableArray(detectChars, rule).find(QLatin1Char('0')));
+ updateUnreachable1(hlCOctRule);
+ hlCOctRule = {&rule, nullptr};
+ break;
+
+ // check if hidden by DetectChar/AnyChar
+ case Context::Rule::Type::HlCStringChar:
+ updateUnreachable1(CharTableArray(detectChars, rule).find(QLatin1Char('\\')));
+ updateUnreachable1(hlCStringCharRule);
+ hlCStringCharRule = {&rule, nullptr};
+ break;
+
+ // check if hidden by DetectChar/AnyChar
+ case Context::Rule::Type::Int:
+ updateUnreachable2(CharTableArray(detectChars, rule).find(QStringLiteral("0123456789")));
+ updateUnreachable1(intRule);
+ intRule = {&rule, nullptr};
+ break;
+
+ // check if hidden by DetectChar/AnyChar
+ case Context::Rule::Type::Float:
+ updateUnreachable2(CharTableArray(detectChars, rule).find(QStringLiteral("0123456789.")));
+ updateUnreachable1(floatRule);
+ floatRule = {&rule, nullptr};
+ break;
+
+ // check if hidden by DetectChar/AnyChar or another LineContinue
+ case Context::Rule::Type::LineContinue: {
+ updateUnreachable1(CharTableArray(detectChars, rule).find(rule.char0));
+
+ auto tables = CharTableArray(lineContinueChars, rule);
+ updateUnreachable1(tables.find(rule.char0));
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(rule.char0, rule);
+ break;
+ }
+
+ // check if hidden by DetectChar/AnyChar or another Detect2Chars/RangeDetect
+ case Context::Rule::Type::Detect2Chars:
+ case Context::Rule::Type::RangeDetect:
+ updateUnreachable1(CharTableArray(detectChars, rule).find(rule.char0));
+ if (!isUnreachable) {
+ RuleIterator ruleIterator(context.rules, rule);
+ while (const auto *rulePtr = ruleIterator.next()) {
+ if (isUnreachable)
+ break;
+ const auto &rule2 = *rulePtr;
+ if (rule2.type == rule.type && isCompatible(rule2) && rule.char0 == rule2.char0 && rule.char1 == rule2.char1) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ }
+ }
+ break;
+
+ case Context::Rule::Type::RegExpr: {
+ if (rule.isDotRegex) {
+ dotRegex.append(rule, nullptr);
+ break;
+ }
+
+ // check that `rule` does not have another RegExpr as a prefix
+ RuleIterator ruleIterator(context.rules, rule);
+ while (const auto *rulePtr = ruleIterator.next()) {
+ if (isUnreachable)
+ break;
+ const auto &rule2 = *rulePtr;
+ if (rule2.type == Context::Rule::Type::RegExpr && isCompatible(rule2) && rule.insensitive == rule2.insensitive
+ && rule.dynamic == rule2.dynamic && rule.string.startsWith(rule2.string)) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ }
+
+ Q_FALLTHROUGH();
+ }
+ // check if a rule does not have another rule as a prefix
+ case Context::Rule::Type::WordDetect:
+ case Context::Rule::Type::StringDetect: {
+ // check that dynamic `rule` does not have another dynamic StringDetect as a prefix
+ if (rule.type == Context::Rule::Type::StringDetect && rule.dynamic == XmlBool::True) {
+ RuleIterator ruleIterator(context.rules, rule);
+ while (const auto *rulePtr = ruleIterator.next()) {
+ if (isUnreachable)
+ break;
+
+ const auto &rule2 = *rulePtr;
+ if (rule2.type != Context::Rule::Type::StringDetect || rule2.dynamic != XmlBool::True || !isCompatible(rule2)) {
+ continue;
+ }
+
+ const bool isSensitive = (rule2.insensitive == XmlBool::True);
+ const auto caseSensitivity = isSensitive ? Qt::CaseInsensitive : Qt::CaseSensitive;
+ if ((isSensitive || rule.insensitive != XmlBool::True) && rule.string.startsWith(rule2.string, caseSensitivity)) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ }
+ }
+
+ // string used for comparison and truncated from "dynamic" part
+ QStringView s = rule.string;
+
+ // truncate to '%' with dynamic rules
+ if (rule.dynamic == XmlBool::True) {
+ static const QRegularExpression dynamicPosition(QStringLiteral(R"(^(?:[^%]*|%(?![1-9]))*)"));
+ auto result = dynamicPosition.match(rule.string);
+ s = s.left(result.capturedLength());
+ }
+
+ QString sanitizedRegex;
+ // truncate to special character with RegExpr.
+ // If regexp contains '|', `s` becomes empty.
+ if (rule.type == Context::Rule::Type::RegExpr) {
+ static const QRegularExpression regularChars(QStringLiteral(R"(^(?:[^.?*+^$[{(\\|]+|\\[-.?*+^$[\]{}()\\|]+|\[[^^\\]\])+)"));
+ static const QRegularExpression sanitizeChars(QStringLiteral(R"(\\([-.?*+^$[\]{}()\\|])|\[([^^\\])\])"));
+ const qsizetype result = regularChars.match(rule.string).capturedLength();
+ const qsizetype pos = qMin(result, s.size());
+ if (rule.string.indexOf(QLatin1Char('|'), pos) < pos) {
+ sanitizedRegex = rule.string.left(qMin(result, s.size()));
+ sanitizedRegex.replace(sanitizeChars, QStringLiteral("\\1"));
+ s = sanitizedRegex;
+ } else {
+ s = QStringView();
+ }
+ }
+
+ // check if hidden by DetectChar/AnyChar
+ if (s.size() > 0) {
+ auto t = CharTableArray(detectChars, rule);
+ if (rule.insensitive != XmlBool::True) {
+ updateUnreachable1(t.find(s[0]));
+ } else {
+ QChar c2[]{s[0].toLower(), s[0].toUpper()};
+ updateUnreachable2(t.find(QStringView(c2, 2)));
+ }
+ }
+
+ // check if Detect2Chars, StringDetect, WordDetect is not a prefix of s
+ if (s.size() > 0 && !isUnreachable) {
+ // combination of uppercase and lowercase
+ RuleAndInclude detect2CharsInsensitives[]{{}, {}, {}, {}};
+
+ RuleIterator ruleIterator(context.rules, rule);
+ while (const auto *rulePtr = ruleIterator.next()) {
+ if (isUnreachable)
+ break;
+ const auto &rule2 = *rulePtr;
+ const bool isSensitive = (rule2.insensitive == XmlBool::True);
+ const auto caseSensitivity = isSensitive ? Qt::CaseInsensitive : Qt::CaseSensitive;
+
+ switch (rule2.type) {
+ // check that it is not a detectChars prefix
+ case Context::Rule::Type::Detect2Chars:
+ if (isCompatible(rule2) && s.size() >= 2) {
+ if (rule.insensitive != XmlBool::True) {
+ if (rule2.char0 == s[0] && rule2.char1 == s[1]) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ } else {
+ // when the string is case insensitive,
+ // all 4 upper/lower case combinations must be found
+ auto set = [&](RuleAndInclude &x, QChar c1, QChar c2) {
+ if (!x && rule2.char0 == c1 && rule2.char0 == c2) {
+ x = {&rule2, ruleIterator.currentIncludeRules()};
+ }
+ };
+ set(detect2CharsInsensitives[0], s[0].toLower(), s[1].toLower());
+ set(detect2CharsInsensitives[1], s[0].toLower(), s[1].toUpper());
+ set(detect2CharsInsensitives[2], s[0].toUpper(), s[1].toUpper());
+ set(detect2CharsInsensitives[3], s[0].toUpper(), s[1].toLower());
+
+ if (detect2CharsInsensitives[0] && detect2CharsInsensitives[1] && detect2CharsInsensitives[2]
+ && detect2CharsInsensitives[3]) {
+ isUnreachable = true;
+ unreachableBy.append(detect2CharsInsensitives[0]);
+ unreachableBy.append(detect2CharsInsensitives[1]);
+ unreachableBy.append(detect2CharsInsensitives[2]);
+ unreachableBy.append(detect2CharsInsensitives[3]);
+ }
+ }
+ }
+ break;
+
+ // check that it is not a StringDetect prefix
+ case Context::Rule::Type::StringDetect:
+ if (isCompatible(rule2) && rule2.dynamic != XmlBool::True && (isSensitive || rule.insensitive != XmlBool::True)
+ && s.startsWith(rule2.string, caseSensitivity)) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ break;
+
+ // check if a WordDetect is hidden by another WordDetect
+ case Context::Rule::Type::WordDetect:
+ if (rule.type == Context::Rule::Type::WordDetect && isCompatible(rule2) && (isSensitive || rule.insensitive != XmlBool::True)
+ && 0 == rule.string.compare(rule2.string, caseSensitivity)) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ break;
+
+ default:;
+ }
+ }
+ }
+
+ break;
}
- auto requiredVersion = getRequiredVersion(m_contextMap, language);
- if (language.version < requiredVersion) {
- qWarning().nospace() << language.hlFilename << " depends on a language in version " << requiredVersion.majorRevision << "." << requiredVersion.minorRevision << ". Please, increase kateversion.";
+ // check if hidden by another keyword rule
+ case Context::Rule::Type::keyword: {
+ RuleIterator ruleIterator(context.rules, rule);
+ while (const auto *rulePtr = ruleIterator.next()) {
+ if (isUnreachable)
+ break;
+ const auto &rule2 = *rulePtr;
+ if (rule2.type == Context::Rule::Type::keyword && isCompatible(rule2) && rule.string == rule2.string) {
+ updateUnreachable1({&rule2, ruleIterator.currentIncludeRules()});
+ }
+ }
+ // TODO check that all keywords are hidden by another rules
+ break;
+ }
+
+ // add characters in those used but without checking if they are already.
+ // <DetectChar char="}" />
+ // <includedRules .../> <- reference an another <DetectChar char="}" /> who will not be checked
+ // <includedRules .../> <- reference a <DetectChar char="{" /> who will be added
+ // <DetectChar char="{" /> <- hidden by previous rule
+ case Context::Rule::Type::IncludeRules:
+ if (auto &ruleAndInclude = includeContexts[rule.context.context]) {
+ updateUnreachable1(ruleAndInclude);
+ }
+ else {
+ ruleAndInclude.rule = &rule;
+ }
+
+ for (const auto *rulePtr : rule.includedIncludeRules) {
+ includeContexts.insert(rulePtr->context.context, RuleAndInclude{rulePtr, &rule});
+ }
+
+ for (const auto *rulePtr : rule.includedRules) {
+ const auto &rule2 = *rulePtr;
+ switch (rule2.type) {
+ case Context::Rule::Type::AnyChar: {
+ auto tables = CharTableArray(detectChars, rule2);
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(rule2.string, rule2, &rule);
+ break;
+ }
+
+ case Context::Rule::Type::DetectChar: {
+ auto &chars4 = (rule.dynamic != XmlBool::True) ? detectChars : dynamicDetectChars;
+ auto tables = CharTableArray(chars4, rule2);
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(rule2.char0, rule2, &rule);
+ break;
+ }
+
+ case Context::Rule::Type::DetectSpaces: {
+ auto tables = CharTableArray(detectChars, rule2);
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(QLatin1Char(' '), rule2, &rule);
+ tables.append(QLatin1Char('\t'), rule2, &rule);
+ break;
+ }
+
+ case Context::Rule::Type::HlCChar:
+ hlCCharRule = {&rule2, &rule};
+ break;
+
+ case Context::Rule::Type::HlCHex:
+ hlCHexRule = {&rule2, &rule};
+ break;
+
+ case Context::Rule::Type::HlCOct:
+ hlCOctRule = {&rule2, &rule};
+ break;
+
+ case Context::Rule::Type::HlCStringChar:
+ hlCStringCharRule = {&rule2, &rule};
+ break;
+
+ case Context::Rule::Type::Int:
+ intRule = {&rule2, &rule};
+ break;
+
+ case Context::Rule::Type::Float:
+ floatRule = {&rule2, &rule};
+ break;
+
+ case Context::Rule::Type::LineContinue: {
+ auto tables = CharTableArray(lineContinueChars, rule2);
+ tables.removeNonSpecialWhenSpecial();
+ tables.append(rule2.char0, rule2, &rule);
+ break;
+ }
+
+ case Context::Rule::Type::RegExpr:
+ if (rule2.isDotRegex) {
+ dotRegex.append(rule2, &rule);
+ }
+ break;
+
+ case Context::Rule::Type::WordDetect:
+ case Context::Rule::Type::StringDetect:
+ case Context::Rule::Type::Detect2Chars:
+ case Context::Rule::Type::IncludeRules:
+ case Context::Rule::Type::DetectIdentifier:
+ case Context::Rule::Type::keyword:
+ case Context::Rule::Type::Unknown:
+ case Context::Rule::Type::RangeDetect:
+ break;
+ }
+ }
+ break;
+
+ case Context::Rule::Type::DetectIdentifier:
+ case Context::Rule::Type::Unknown:
+ break;
+ }
+
+ if (isUnreachable) {
success = false;
+ QString message;
+ message.reserve(128);
+ for (auto &ruleAndInclude : unreachableBy) {
+ message += QStringLiteral("line ");
+ if (ruleAndInclude.includeRules) {
+ message += QString::number(ruleAndInclude.includeRules->line);
+ message += QStringLiteral(" [by '");
+ message += ruleAndInclude.includeRules->context.name;
+ message += QStringLiteral("' line ");
+ message += QString::number(ruleAndInclude.rule->line);
+ if (ruleAndInclude.includeRules->filename != ruleAndInclude.rule->filename) {
+ message += QStringLiteral(" (");
+ message += ruleAndInclude.rule->filename;
+ message += QLatin1Char(')');
+ }
+ message += QLatin1Char(']');
+ } else {
+ message += QString::number(ruleAndInclude.rule->line);
+ }
+ message += QStringLiteral(", ");
+ }
+ message.chop(2);
+ qWarning() << filename << "line" << rule.line << "unreachable element by" << message;
}
}
return success;
}
-private:
- //! Extract the referenced context name and language.
+ //! Proposes to merge certain rule sequences
+ //! - several DetectChar/AnyChar into AnyChar
+ //! - several RegExpr into one RegExpr
+ bool suggestRuleMerger(const QString &filename, const Context &context) const
+ {
+ bool success = true;
+
+ if (context.rules.isEmpty())
+ return success;
+
+ auto it = context.rules.begin();
+ const auto end = context.rules.end() - 1;
+
+ for (; it < end; ++it) {
+ auto& rule1 = *it;
+ auto& rule2 = it[1];
+
+ auto isCommonCompatible = [&]{
+ return rule1.attribute == rule2.attribute
+ && rule1.beginRegion == rule2.beginRegion
+ && rule1.endRegion == rule2.endRegion
+ && rule1.lookAhead == rule2.lookAhead
+ && rule1.firstNonSpace == rule2.firstNonSpace
+ && rule1.context.context == rule2.context.context
+ && rule1.context.popCount == rule2.context.popCount
+ ;
+ };
+
+ switch (rule1.type) {
+ // request to merge AnyChar/DetectChar
+ case Context::Rule::Type::AnyChar:
+ case Context::Rule::Type::DetectChar:
+ if ((rule2.type == Context::Rule::Type::AnyChar || rule2.type == Context::Rule::Type::DetectChar) && isCommonCompatible() && rule1.column == rule2.column) {
+ qWarning() << filename << "line" << rule2.line << "can be merged as AnyChar with the previous rule";
+ success = false;
+ }
+ break;
+
+ // request to merge multiple RegExpr
+ case Context::Rule::Type::RegExpr:
+ if (rule2.type == Context::Rule::Type::RegExpr && isCommonCompatible() && rule1.dynamic == rule2.dynamic && (rule1.column == rule2.column || (rule1.column <= 0 && rule2.column <= 0))) {
+ qWarning() << filename << "line" << rule2.line << "can be merged with the previous rule";
+ success = false;
+ }
+ break;
+
+ case Context::Rule::Type::DetectSpaces:
+ case Context::Rule::Type::HlCChar:
+ case Context::Rule::Type::HlCHex:
+ case Context::Rule::Type::HlCOct:
+ case Context::Rule::Type::HlCStringChar:
+ case Context::Rule::Type::Int:
+ case Context::Rule::Type::Float:
+ case Context::Rule::Type::LineContinue:
+ case Context::Rule::Type::WordDetect:
+ case Context::Rule::Type::StringDetect:
+ case Context::Rule::Type::Detect2Chars:
+ case Context::Rule::Type::IncludeRules:
+ case Context::Rule::Type::DetectIdentifier:
+ case Context::Rule::Type::keyword:
+ case Context::Rule::Type::Unknown:
+ case Context::Rule::Type::RangeDetect:
+ break;
+ }
+ }
+
+ return success;
+ }
+
+ //! Initialize the referenced context (ContextName::context)
//! Some input / output examples are:
//! - "#stay" -> ""
//! - "#pop" -> ""
@@ -445,161 +2249,140 @@ private:
//! - "#pop!Comment" -> "Comment"
//! - "##ISO C++" -> ""
//! - "Comment##ISO C++"-> "Comment" in ISO C++
- void processContext(const QString &language, QString context)
+ void resolveContextName(Definition &definition, const Context &context, ContextName &contextName, int line)
{
- if (context.isEmpty())
- return;
-
- // filter out #stay and #pop
- static QRegularExpression stayPop(QStringLiteral("^(#stay|#pop)+"));
- context.remove(stayPop);
-
- // handle cross-language context references
- if (context.contains(QStringLiteral("##"))) {
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- const QStringList list = context.split(QStringLiteral("##"), QString::SkipEmptyParts);
-#else
- const QStringList list = context.split(QStringLiteral("##"), Qt::SkipEmptyParts);
-#endif
- if (list.size() == 1) {
- // nothing to do, other language is included: e.g. ##Doxygen
- } else if (list.size() == 2) {
- // specific context of other language, e.g. Comment##ISO C++
- m_contextMap[list[1]].usedContextNames.insert(list[0]);
- m_contextMap[language].usedLanguageName.insert(list[1]);
+ QString name = contextName.name;
+ if (name.isEmpty()) {
+ contextName.stay = true;
+ } else if (name.startsWith(QStringLiteral("#stay"))) {
+ name = name.mid(5);
+ contextName.stay = true;
+ contextName.context = &context;
+ if (!name.isEmpty()) {
+ qWarning() << definition.filename << "line" << line << "invalid context in" << context.name;
+ m_success = false;
+ }
+ } else {
+ while (name.startsWith(QStringLiteral("#pop"))) {
+ name = name.mid(4);
+ ++contextName.popCount;
}
- return;
- }
-
- // handle #pop!context" (#pop was already removed above)
- if (context.startsWith(QLatin1Char('!')))
- context.remove(0, 1);
-
- if (!context.isEmpty())
- m_contextMap[language].usedContextNames.insert(context);
- }
-
-private:
- struct Version {
- int majorRevision;
- int minorRevision;
-
- Version(int majorRevision = 0, int minorRevision = 0)
- : majorRevision(majorRevision)
- , minorRevision(minorRevision)
- {
- }
- bool operator<(const Version &version) const
- {
- return majorRevision < version.majorRevision || (majorRevision == version.majorRevision && minorRevision < version.minorRevision);
- }
- };
+ if (contextName.popCount && !name.isEmpty()) {
+ if (name.startsWith(QLatin1Char('!')) && name.size() > 1) {
+ name = name.mid(1);
+ } else {
+ qWarning() << definition.filename << "line" << line << "'!' missing between '#pop' and context name" << context.name;
+ m_success = false;
+ }
+ }
- void processVersion(const QString &hlFilename, const QString &hlName, QXmlStreamReader &xml, Version const &requiredVersion, QLatin1String item)
- {
- auto &language = m_contextMap[hlName];
+ if (!name.isEmpty()) {
+ const int idx = name.indexOf(QStringLiteral("##"));
+ if (idx == -1) {
+ auto it = definition.contexts.find(name);
+ if (it != definition.contexts.end())
+ contextName.context = &*it;
+ } else {
+ auto defName = name.mid(idx + 2);
+ auto listName = name.left(idx);
+ auto it = m_definitions.find(defName);
+ if (it != m_definitions.end()) {
+ definition.referencedDefinitions.insert(&*it);
+ auto ctxIt = it->contexts.find(listName.isEmpty() ? it->firstContextName : listName);
+ if (ctxIt != it->contexts.end()) {
+ contextName.context = &*ctxIt;
+ }
+ } else {
+ qWarning() << definition.filename << "line" << line << "unknown definition in" << context.name;
+ m_success = false;
+ }
+ }
- if (language.version < requiredVersion) {
- qWarning().nospace() << hlFilename << " " << item << " in line " << xml.lineNumber() << " is only available since version " << requiredVersion.majorRevision << "." << requiredVersion.minorRevision
- << ". Please, increase kateversion.";
- // update the version to cancel future warnings
- language.version = requiredVersion;
- m_success = false;
+ if (!contextName.context) {
+ qWarning() << definition.filename << "line" << line << "unknown context" << name << "in" << context.name;
+ m_success = false;
+ }
+ }
}
}
- class Language
- {
- public:
- // filename on disk or in Qt resource
- QString hlFilename;
-
- // Is true, if the first context in xml file is encountered, and
- // false in all other cases. This is required, since the first context
- // is typically not referenced explicitly. So we will simply add the
- // first context to the usedContextNames list.
- bool isFirstContext = true;
+ QMap<QString, Definition> m_definitions;
+ Definition *m_currentDefinition = nullptr;
+ Keywords *m_currentKeywords = nullptr;
+ Context *m_currentContext = nullptr;
+ bool m_success = true;
+};
- // holds all contexts that were referenced
- QSet<QString> usedContextNames;
+namespace
+{
+QStringList readListing(const QString &fileName)
+{
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly)) {
+ return QStringList();
+ }
- // holds all existing context names
- QSet<QString> existingContextNames;
+ QXmlStreamReader xml(&file);
+ QStringList listing;
+ while (!xml.atEnd()) {
+ xml.readNext();
- // holds all existing language names
- QSet<QString> usedLanguageName;
+ // add only .xml files, no .json or stuff
+ if (xml.isCharacters() && xml.text().toString().contains(QLatin1String(".xml"))) {
+ listing.append(xml.text().toString());
+ }
+ }
- // kateversion language attribute
- Version version;
- };
+ if (xml.hasError()) {
+ qWarning() << "XML error while reading" << fileName << " - " << qPrintable(xml.errorString()) << "@ offset" << xml.characterOffset();
+ listing.clear();
+ }
- /**
- * "Language name" to "Language" map.
- * Example key: "Doxygen"
- */
- QHash<QString, Language> m_contextMap;
- bool m_success = true;
-};
+ return listing;
+}
/**
- * Helper class to search for non-existing itemDatas.
+ * check if the "extensions" attribute have valid wildcards
+ * @param extensions extensions string to check
+ * @return valid?
*/
-class AttributeChecker
+bool checkExtensions(const QString &extensions)
{
-public:
- AttributeChecker(const QString &filename)
- : m_filename(filename)
- {
+ // get list of extensions
+ const QStringList extensionParts = extensions.split(QLatin1Char(';'), Qt::SkipEmptyParts);
+
+ // ok if empty
+ if (extensionParts.isEmpty()) {
+ return true;
}
- void processElement(QXmlStreamReader &xml)
- {
- if (xml.name() == QLatin1String("itemData")) {
- const QString name = xml.attributes().value(QLatin1String("name")).toString();
- if (!name.isEmpty()) {
- if (m_existingAttributeNames.contains(name)) {
- qWarning() << m_filename << "itemData duplicate:" << name;
- m_success = false;
- } else {
- m_existingAttributeNames.insert(name);
- }
+ // check that only valid wildcard things are inside the parts
+ for (const auto &extension : extensionParts) {
+ for (const auto c : extension) {
+ // eat normal things
+ if (c.isDigit() || c.isLetter()) {
+ continue;
}
- } else if (xml.attributes().hasAttribute(QLatin1String("attribute"))) {
- const QString name = xml.attributes().value(QLatin1String("attribute")).toString();
- if (name.isEmpty()) {
- qWarning() << m_filename << "specified attribute is empty:" << xml.name();
- m_success = false;
- } else {
- m_usedAttributeNames.insert(name);
+
+ // allow some special characters
+ if (c == QLatin1Char('.') || c == QLatin1Char('-') || c == QLatin1Char('_') || c == QLatin1Char('+')) {
+ continue;
}
- }
- }
- bool check() const
- {
- bool success = m_success;
- const auto invalidNames = m_usedAttributeNames - m_existingAttributeNames;
- if (!invalidNames.isEmpty()) {
- qWarning() << m_filename << "Reference of non-existing itemData attributes:" << invalidNames;
- success = false;
- }
+ // only allowed wildcard things: '?' and '*'
+ if (c == QLatin1Char('?') || c == QLatin1Char('*')) {
+ continue;
+ }
- auto unusedNames = m_existingAttributeNames - m_usedAttributeNames;
- if (!unusedNames.isEmpty()) {
- qWarning() << m_filename << "Unused itemData:" << unusedNames;
- success = false;
+ qWarning() << "invalid character" << c << "seen in extensions wildcard";
+ return false;
}
-
- return success;
}
-private:
- QString m_filename;
- QSet<QString> m_usedAttributeNames;
- QSet<QString> m_existingAttributeNames;
- bool m_success = true;
-};
+ // all checks passed
+ return true;
+}
}
@@ -631,12 +2414,12 @@ int main(int argc, char *argv[])
}
// text attributes
- const QStringList textAttributes = QStringList() << QStringLiteral("name") << QStringLiteral("section") << QStringLiteral("mimetype") << QStringLiteral("extensions") << QStringLiteral("style") << QStringLiteral("author")
+ const QStringList textAttributes = QStringList() << QStringLiteral("name") << QStringLiteral("section") << QStringLiteral("mimetype")
+ << QStringLiteral("extensions") << QStringLiteral("style") << QStringLiteral("author")
<< QStringLiteral("license") << QStringLiteral("indenter");
// index all given highlightings
- ContextChecker contextChecker;
- KeywordIncludeChecker keywordIncludeChecker;
+ HlFilesChecker filesChecker;
QVariantMap hls;
int anyError = 0;
for (const QString &hlFilename : qAsConst(hlFilenames)) {
@@ -693,70 +2476,20 @@ int main(int argc, char *argv[])
// remember hl
hls[QFileInfo(hlFile).fileName()] = hl;
- AttributeChecker attributeChecker(hlFilename);
- KeywordChecker keywordChecker(hlFilename);
-
const QString hlName = hl[QStringLiteral("name")].toString();
- contextChecker.setKateVersion(xml.attributes().value(QStringLiteral("kateversion")), hlFilename, hlName);
+ filesChecker.setDefinition(xml.attributes().value(QStringLiteral("kateversion")), hlFilename, hlName);
// scan for broken regex or keywords with spaces
while (!xml.atEnd()) {
xml.readNext();
- if (!xml.isStartElement()) {
- continue;
- }
-
- // search for used/existing contexts if applicable
- contextChecker.processElement(hlFilename, hlName, xml);
-
- // search for existing keyword includes
- keywordIncludeChecker.processElement(hlFilename, hlName, xml);
-
- // search for used/existing attributes if applicable
- attributeChecker.processElement(xml);
-
- // search for used/existing keyword lists if applicable
- keywordChecker.processElement(xml);
-
- // scan for bad regex
- if (!checkRegularExpression(hlFilename, xml)) {
- anyError = 7;
- continue;
- }
-
- // scan for bogus <item> lala </item> spaces
- if (!checkItemsTrimmed(hlFilename, xml)) {
- anyError = 8;
- continue;
- }
-
- // check single chars in DetectChar and Detect2Chars
- if (!checkSingleChars(hlFilename, xml)) {
- anyError = 8;
- continue;
- }
-
- // scan for lookAhead="true" with context="#stay"
- if (!checkLookAhead(hlFilename, xml)) {
- anyError = 7;
- continue;
- }
- }
-
- if (!attributeChecker.check()) {
- anyError = 7;
- }
-
- if (!keywordChecker.check()) {
- anyError = 7;
+ filesChecker.processElement(xml);
}
}
- if (!contextChecker.check())
- anyError = 7;
+ filesChecker.resolveContexts();
- if (!keywordIncludeChecker.check())
+ if (!filesChecker.check())
anyError = 7;
// bail out if any problem was seen
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/CMakeLists.txt b/src/libs/3rdparty/syntax-highlighting/src/lib/CMakeLists.txt
index 97aeee3dbc..722f92742b 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/CMakeLists.txt
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/CMakeLists.txt
@@ -55,7 +55,7 @@ ecm_generate_headers(SyntaxHighlighting_HEADERS
REQUIRED_HEADERS SyntaxHighlighting_HEADERS
)
-install(TARGETS KF5SyntaxHighlighting EXPORT KF5SyntaxHighlightingTargets ${INSTALL_TARGETS_DEFAULT_ARGS})
+install(TARGETS KF5SyntaxHighlighting EXPORT KF5SyntaxHighlightingTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
${SyntaxHighlighting_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/ksyntaxhighlighting_export.h
@@ -66,7 +66,7 @@ if(BUILD_QCH)
KF5SyntaxHighlighting_QCH
NAME KSyntaxHighlighting
BASE_NAME KF5SyntaxHighlighting
- VERSION ${KF5_VERSION}
+ VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${SyntaxHighlighting_HEADERS}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
index 5eefc3a48d..2ad9d371f9 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter.cpp
@@ -128,7 +128,8 @@ State AbstractHighlighter::highlightLine(const QString &text, const State &state
* see https://phabricator.kde.org/D18509
*/
int endlessLoopingCounter = 0;
- while (!stateData->topContext()->lineEmptyContext().isStay() || (stateData->topContext()->lineEmptyContext().isStay() && !stateData->topContext()->lineEndContext().isStay())) {
+ while (!stateData->topContext()->lineEmptyContext().isStay()
+ || (stateData->topContext()->lineEmptyContext().isStay() && !stateData->topContext()->lineEndContext().isStay())) {
/**
* line empty context switches
*/
@@ -143,7 +144,8 @@ State AbstractHighlighter::highlightLine(const QString &text, const State &state
* line end context switches only when lineEmptyContext is #stay. This avoids
* skipping empty lines after a line continuation character (see bug 405903)
*/
- } else if (!stateData->topContext()->lineEndContext().isStay() && !d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList()))
+ } else if (!stateData->topContext()->lineEndContext().isStay()
+ && !d->switchContext(stateData, stateData->topContext()->lineEndContext(), QStringList()))
break;
// guard against endless loops
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter_p.h
index 6128beccfa..11332fdcfd 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/abstracthighlighter_p.h
@@ -10,6 +10,10 @@
#include "definition.h"
#include "theme.h"
+QT_BEGIN_NAMESPACE
+class QStringList;
+QT_END_NAMESPACE
+
namespace KSyntaxHighlighting
{
class ContextSwitch;
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.cpp
index abe5e8d2be..688f0c6bf0 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.cpp
@@ -5,36 +5,39 @@
*/
#include "ansihighlighter.h"
+#include "context_p.h"
#include "definition.h"
+#include "definition_p.h"
#include "format.h"
#include "ksyntaxhighlighting_logging.h"
#include "state.h"
-#include "theme.h"
#include "state_p.h"
-#include "context_p.h"
-#include "definition_p.h"
+#include "theme.h"
+#include <QColor>
#include <QFile>
#include <QFileInfo>
-#include <QTextStream>
-#include <QColor>
#include <QMap>
-#include <QtMath>
+#include <QTextStream>
-#include <vector>
#include <cmath>
+#include <vector>
using namespace KSyntaxHighlighting;
namespace
{
- struct CieLab
- {
- double l;
- double a;
- double b;
- };
+struct CieLab {
+ double l;
+ double a;
+ double b;
+};
+#ifndef M_PI
+constexpr double M_PI = 3.14159265358979323846;
+#endif
+
+// clang-format off
// xterm color reference
// constexpr Rgb888 xterm256Colors[] {
// {0x00, 0x00, 0x00}, {0x80, 0x00, 0x00}, {0x00, 0x80, 0x00}, {0x80, 0x80, 0x00},
@@ -377,207 +380,208 @@ namespace
constexpr double illuminant_D65[] {
0.95047, 1.00000, 1.08883,
};
+// clang-format on
- // convert a sRGB (D65) color to CIELAB (D65)
- CieLab rgbToLab(QRgb rgb)
- {
- // Perform the inverse gamma companding for a sRGB color
- // http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
- auto inverseGammaCompanding = [](int c){
- if (c <= 10)
- return c / (255.0 * 12.92);
- else
- return std::pow((c / 255.0 + 0.055) / 1.055, 2.4);
- };
+// convert a sRGB (D65) color to CIELAB (D65)
+CieLab rgbToLab(QRgb rgb)
+{
+ // Perform the inverse gamma companding for a sRGB color
+ // http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
+ auto inverseGammaCompanding = [](int c) {
+ if (c <= 10)
+ return c / (255.0 * 12.92);
+ else
+ return std::pow((c / 255.0 + 0.055) / 1.055, 2.4);
+ };
- const double r = inverseGammaCompanding(qRed(rgb));
- const double g = inverseGammaCompanding(qGreen(rgb));
- const double b = inverseGammaCompanding(qBlue(rgb));
+ const double r = inverseGammaCompanding(qRed(rgb));
+ const double g = inverseGammaCompanding(qGreen(rgb));
+ const double b = inverseGammaCompanding(qBlue(rgb));
- const double x = (r * sRGB_D65[0] + g * sRGB_D65[1] + b * sRGB_D65[2]);
- const double y = (r * sRGB_D65[3] + g * sRGB_D65[4] + b * sRGB_D65[5]);
- const double z = (r * sRGB_D65[6] + g * sRGB_D65[7] + b * sRGB_D65[8]);
+ const double x = (r * sRGB_D65[0] + g * sRGB_D65[1] + b * sRGB_D65[2]);
+ const double y = (r * sRGB_D65[3] + g * sRGB_D65[4] + b * sRGB_D65[5]);
+ const double z = (r * sRGB_D65[6] + g * sRGB_D65[7] + b * sRGB_D65[8]);
- // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
- auto f = [](double t) {
- if (t > 216.0 / 24389.0)
- return std::cbrt(t);
- else
- return t * (24389.0 / (27.0 * 116.0)) + 4.0 / 29.0;
- };
+ // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
+ auto f = [](double t) {
+ if (t > 216.0 / 24389.0)
+ return std::cbrt(t);
+ else
+ return t * (24389.0 / (27.0 * 116.0)) + 4.0 / 29.0;
+ };
- const double f_x = f(x / illuminant_D65[0]);
- const double f_y = f(y / illuminant_D65[1]);
- const double f_z = f(z / illuminant_D65[2]);
+ const double f_x = f(x / illuminant_D65[0]);
+ const double f_y = f(y / illuminant_D65[1]);
+ const double f_z = f(z / illuminant_D65[2]);
- return CieLab{
- 116.0 * f_y - 16.0,
- 500.0 * (f_x - f_y),
- 200.0 * (f_y - f_z),
- };
- }
+ return CieLab{
+ 116.0 * f_y - 16.0,
+ 500.0 * (f_x - f_y),
+ 200.0 * (f_y - f_z),
+ };
+}
+constexpr double epsilon = 1e-15;
- constexpr double epsilon = 1e-15;
+inline double sinDegree(double x)
+{
+ return std::sin(x * M_PI / 180.0);
+}
- inline double sinDegree(double x) { return std::sin(x * M_PI / 180.0); }
+inline double cosDegree(double x)
+{
+ return std::cos(x * M_PI / 180.0);
+}
- inline double cosDegree(double x) { return std::cos(x * M_PI / 180.0); }
+inline double pow2(double x)
+{
+ return x * x;
+}
- inline double pow2(double x) { return x * x; }
+inline double computeHPrime(double a_prime, double b)
+{
+ if (std::abs(a_prime) < epsilon && std::abs(b) < epsilon)
+ return 0.0;
- inline double computeHPrime(double a_prime, double b)
- {
- if (std::abs(a_prime) < epsilon && std::abs(b) < epsilon)
- return 0.0;
+ const double value = std::atan2(b, a_prime) * 180.0 / M_PI;
+ return (value < 0.0) ? value + 360.0 : value;
+}
- const double value = std::atan2(b, a_prime) * 180.0 / M_PI;
- return (value < 0.0) ? value + 360.0 : value;
- }
+inline double computeDeltaHPrime(double C1_prime, double C2_prime, double h1_prime, double h2_prime)
+{
+ if (C1_prime * C2_prime < epsilon)
+ return 0.0;
- inline double computeDeltaHPrime(double C1_prime, double C2_prime, double h1_prime, double h2_prime)
- {
- if (C1_prime * C2_prime < epsilon)
- return 0.0;
+ const double diff = h2_prime - h1_prime;
- const double diff = h2_prime - h1_prime;
+ if (std::abs(diff) <= 180.0)
+ return diff;
+ else if (diff > 180.0)
+ return diff - 360.0;
+ else
+ return diff + 360.0;
+}
- if (std::abs(diff) <= 180.0)
- return diff;
- else if (diff > 180.0)
- return diff - 360.0;
- else
- return diff + 360.0;
- }
+inline double computeHPrimeBar(double C1_prime, double C2_prime, double h1_prime, double h2_prime)
+{
+ const double sum = h1_prime + h2_prime;
- inline double computeHPrimeBar(double C1_prime, double C2_prime, double h1_prime, double h2_prime)
- {
- const double sum = h1_prime + h2_prime;
+ if (C1_prime * C2_prime < epsilon)
+ return sum;
- if (C1_prime * C2_prime < epsilon)
- return sum;
+ const double dist = std::abs(h1_prime - h2_prime);
- const double dist = std::abs(h1_prime - h2_prime);
+ if (dist <= 180.0)
+ return 0.5 * sum;
+ else if (sum < 360.0)
+ return 0.5 * (sum + 360.0);
+ else
+ return 0.5 * (sum - 360.0);
+}
- if (dist <= 180.0)
- return 0.5 * sum;
- else if (sum < 360.0)
- return 0.5 * (sum + 360.0);
- else
- return 0.5 * (sum - 360.0);
- }
+/// Calculate the perceptual color difference based on CIEDE2000.
+/// https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
+/// \return The color difference of the two colors.
+double calculate_CIEDE2000(const CieLab &color1, const CieLab &color2)
+{
+ const double L1 = color1.l;
+ const double a1 = color1.a;
+ const double b1 = color1.b;
+ const double L2 = color2.l;
+ const double a2 = color2.a;
+ const double b2 = color2.b;
+
+ const double _25_pow_7 = /*std::pow(25.0, 7.0) = */ 6103515625.0;
+
+ const double C1_ab = std::sqrt(a1 * a1 + b1 * b1);
+ const double C2_ab = std::sqrt(a2 * a2 + b2 * b2);
+ const double C_ab_bar = 0.5 * (C1_ab + C2_ab);
+ const double c_ab_bar_pow_7 = std::pow(C_ab_bar, 7.0);
+ const double G = 0.5 * (1.0 - std::sqrt(c_ab_bar_pow_7 / (c_ab_bar_pow_7 + _25_pow_7)));
+ const double a1_prime = (1.0 + G) * a1;
+ const double a2_prime = (1.0 + G) * a2;
+ const double C1_prime = std::sqrt(a1_prime * a1_prime + b1 * b1);
+ const double C2_prime = std::sqrt(a2_prime * a2_prime + b2 * b2);
+ const double h1_prime = computeHPrime(a1_prime, b1);
+ const double h2_prime = computeHPrime(a2_prime, b2);
+
+ const double deltaL_prime = L2 - L1;
+ const double deltaC_prime = C2_prime - C1_prime;
+ const double deltah_prime = computeDeltaHPrime(C1_prime, C2_prime, h1_prime, h2_prime);
+ const double deltaH_prime = 2.0 * std::sqrt(C1_prime * C2_prime) * sinDegree(0.5 * deltah_prime);
+
+ const double L_primeBar = 0.5 * (L1 + L2);
+ const double C_primeBar = 0.5 * (C1_prime + C2_prime);
+ const double h_primeBar = computeHPrimeBar(C1_prime, C2_prime, h1_prime, h2_prime);
+
+ const double T = 1.0 - 0.17 * cosDegree(h_primeBar - 30.0) + 0.24 * cosDegree(2.0 * h_primeBar) + 0.32 * cosDegree(3.0 * h_primeBar + 6.0)
+ - 0.20 * cosDegree(4.0 * h_primeBar - 63.0);
+
+ const double C_primeBar_pow7 = std::pow(C_primeBar, 7.0);
+ const double R_C = 2.0 * std::sqrt(C_primeBar_pow7 / (C_primeBar_pow7 + _25_pow_7));
+ const double S_L = 1.0 + (0.015 * pow2(L_primeBar - 50.0)) / std::sqrt(20.0 + pow2(L_primeBar - 50.0));
+ const double S_C = 1.0 + 0.045 * C_primeBar;
+ const double S_H = 1.0 + 0.015 * C_primeBar * T;
+ const double R_T = -R_C * sinDegree(60.0 * std::exp(-pow2((h_primeBar - 275) / 25.0)));
+
+ constexpr double kL = 1.0;
+ constexpr double kC = 1.0;
+ constexpr double kH = 1.0;
+
+ const double deltaL = deltaL_prime / (kL * S_L);
+ const double deltaC = deltaC_prime / (kC * S_C);
+ const double deltaH = deltaH_prime / (kH * S_H);
+
+ return /*std::sqrt*/ (deltaL * deltaL + deltaC * deltaC + deltaH * deltaH + R_T * deltaC * deltaH);
+}
+
+struct AnsiBuffer {
+ using ColorCache = QMap<QRgb, int>;
- /// Calculate the perceptual color difference based on CIEDE2000.
- /// https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
- /// \return The color difference of the two colors.
- double calculate_CIEDE2000(const CieLab& color1, const CieLab& color2)
+ void append(char c)
{
- const double L1 = color1.l;
- const double a1 = color1.a;
- const double b1 = color1.b;
- const double L2 = color2.l;
- const double a2 = color2.a;
- const double b2 = color2.b;
-
- const double _25_pow_7 = /*std::pow(25.0, 7.0) = */6103515625.0;
-
- const double C1_ab = std::sqrt(a1 * a1 + b1 * b1);
- const double C2_ab = std::sqrt(a2 * a2 + b2 * b2);
- const double C_ab_bar = 0.5 * (C1_ab + C2_ab);
- const double c_ab_bar_pow_7 = std::pow(C_ab_bar, 7.0);
- const double G = 0.5 * (1.0 - std::sqrt(c_ab_bar_pow_7 / (c_ab_bar_pow_7 + _25_pow_7)));
- const double a1_prime = (1.0 + G) * a1;
- const double a2_prime = (1.0 + G) * a2;
- const double C1_prime = std::sqrt(a1_prime * a1_prime + b1 * b1);
- const double C2_prime = std::sqrt(a2_prime * a2_prime + b2 * b2);
- const double h1_prime = computeHPrime(a1_prime, b1);
- const double h2_prime = computeHPrime(a2_prime, b2);
-
- const double deltaL_prime = L2 - L1;
- const double deltaC_prime = C2_prime - C1_prime;
- const double deltah_prime = computeDeltaHPrime(C1_prime, C2_prime, h1_prime, h2_prime);
- const double deltaH_prime = 2.0 * std::sqrt(C1_prime * C2_prime) * sinDegree(0.5 * deltah_prime);
-
- const double L_primeBar = 0.5 * (L1 + L2);
- const double C_primeBar = 0.5 * (C1_prime + C2_prime);
- const double h_primeBar = computeHPrimeBar(C1_prime, C2_prime, h1_prime, h2_prime);
-
- const double T = 1.0
- - 0.17 * cosDegree(h_primeBar - 30.0)
- + 0.24 * cosDegree(2.0 * h_primeBar)
- + 0.32 * cosDegree(3.0 * h_primeBar + 6.0)
- - 0.20 * cosDegree(4.0 * h_primeBar - 63.0);
-
- const double C_primeBar_pow7 = std::pow(C_primeBar, 7.0);
- const double R_C = 2.0 * std::sqrt(C_primeBar_pow7 / (C_primeBar_pow7 + _25_pow_7));
- const double S_L = 1.0 + (0.015 * pow2(L_primeBar - 50.0))
- / std::sqrt(20.0 + pow2(L_primeBar - 50.0));
- const double S_C = 1.0 + 0.045 * C_primeBar;
- const double S_H = 1.0 + 0.015 * C_primeBar * T;
- const double R_T = -R_C * sinDegree(60.0 * std::exp(-pow2((h_primeBar - 275) / 25.0)));
-
- constexpr double kL = 1.0;
- constexpr double kC = 1.0;
- constexpr double kH = 1.0;
-
- const double deltaL = deltaL_prime / (kL * S_L);
- const double deltaC = deltaC_prime / (kC * S_C);
- const double deltaH = deltaH_prime / (kH * S_H);
-
- return /*std::sqrt*/( deltaL * deltaL
- + deltaC * deltaC
- + deltaH * deltaH
- + R_T * deltaC * deltaH);
+ Q_ASSERT(remaining() >= 1);
+ m_data[m_size] = c;
+ ++m_size;
}
- struct AnsiBuffer
+ void append(QLatin1String str)
{
- using ColorCache = QMap<QRgb, int>;
-
- void append(char c)
- {
- Q_ASSERT(remaining() >= 1);
- m_data[m_size] = c;
- ++m_size;
- }
-
- void append(QLatin1String str)
- {
- Q_ASSERT(remaining() >= str.size());
- memcpy(m_data + m_size, str.data(), str.size());
- m_size += str.size();
- }
+ Q_ASSERT(remaining() >= str.size());
+ memcpy(m_data + m_size, str.data(), str.size());
+ m_size += str.size();
+ }
- void appendForeground(QRgb rgb, bool is256Colors, ColorCache& colorCache)
- {
- append(QLatin1String("38;"));
- append(rgb, is256Colors, colorCache);
- }
+ void appendForeground(QRgb rgb, bool is256Colors, ColorCache &colorCache)
+ {
+ append(QLatin1String("38;"));
+ append(rgb, is256Colors, colorCache);
+ }
- void appendBackground(QRgb rgb, bool is256Colors, ColorCache& colorCache)
- {
- append(QLatin1String("48;"));
- append(rgb, is256Colors, colorCache);
- }
+ void appendBackground(QRgb rgb, bool is256Colors, ColorCache &colorCache)
+ {
+ append(QLatin1String("48;"));
+ append(rgb, is256Colors, colorCache);
+ }
- void append(QRgb rgb, bool is256Colors, ColorCache& colorCache)
- {
- auto appendUInt8 = [&](int x){
- Q_ASSERT(x <= 255 && x >= 0);
- if (x > 99) {
- if (x >= 200) {
- append('2');
- x -= 200;
- }
- else {
- append('1');
- x -= 100;
- }
- } else if (x < 10) {
- append(char('0' + x));
- return ;
+ void append(QRgb rgb, bool is256Colors, ColorCache &colorCache)
+ {
+ auto appendUInt8 = [&](int x) {
+ Q_ASSERT(x <= 255 && x >= 0);
+ if (x > 99) {
+ if (x >= 200) {
+ append('2');
+ x -= 200;
+ } else {
+ append('1');
+ x -= 100;
}
+ } else if (x < 10) {
+ append(char('0' + x));
+ return;
+ }
+ // clang-format off
constexpr char const* tb2digits =
"00" "01" "02" "03" "04" "05" "06" "07" "08" "09"
"10" "11" "12" "13" "14" "15" "16" "17" "18" "19"
@@ -588,518 +592,540 @@ namespace
"60" "61" "62" "63" "64" "65" "66" "67" "68" "69"
"70" "71" "72" "73" "74" "75" "76" "77" "78" "79"
"80" "81" "82" "83" "84" "85" "86" "87" "88" "89"
- "90" "91" "92" "93" "94" "95" "96" "97" "98" "99"
- ;
- auto* p = tb2digits + x * 2;
- append(p[0]);
- append(p[1]);
- };
+ "90" "91" "92" "93" "94" "95" "96" "97" "98" "99";
+ // clang-format on
+
+ auto *p = tb2digits + x * 2;
+ append(p[0]);
+ append(p[1]);
+ };
- if (is256Colors) {
- double dist = 1e24;
- int idx = 0;
- auto it = colorCache.find(rgb);
- if (it == colorCache.end()) {
- const auto lab = rgbToLab(rgb);
- // find the nearest xterm color
- for (CieLab const& xtermLab : xterm240Labs) {
- auto dist2 = calculate_CIEDE2000(lab, xtermLab);
- if (dist2 < dist) {
- dist = dist2;
- idx = &xtermLab - xterm240Labs;
- }
+ if (is256Colors) {
+ double dist = 1e24;
+ int idx = 0;
+ auto it = colorCache.find(rgb);
+ if (it == colorCache.end()) {
+ const auto lab = rgbToLab(rgb);
+ // find the nearest xterm color
+ for (CieLab const &xtermLab : xterm240Labs) {
+ auto dist2 = calculate_CIEDE2000(lab, xtermLab);
+ if (dist2 < dist) {
+ dist = dist2;
+ idx = &xtermLab - xterm240Labs;
}
- // add 16 to convert 240 colors mode to 256 colors mode
- idx += 16;
- colorCache.insert(rgb, idx);
}
- else {
- idx = it.value();
- }
-
- append('5');
- append(';');
- appendUInt8(idx);
+ // add 16 to convert 240 colors mode to 256 colors mode
+ idx += 16;
+ colorCache.insert(rgb, idx);
} else {
- append('2');
- append(';');
- appendUInt8(qRed(rgb));
- append(';');
- appendUInt8(qGreen(rgb));
- append(';');
- appendUInt8(qBlue(rgb));
+ idx = it.value();
}
+
+ append('5');
append(';');
+ appendUInt8(idx);
+ } else {
+ append('2');
+ append(';');
+ appendUInt8(qRed(rgb));
+ append(';');
+ appendUInt8(qGreen(rgb));
+ append(';');
+ appendUInt8(qBlue(rgb));
}
+ append(';');
+ }
- // Replace last character with 'm'. Last character must be ';'
- void setFinalStyle()
- {
- Q_ASSERT(m_data[m_size-1] == ';');
- m_data[m_size-1] = 'm';
- }
+ // Replace last character with 'm'. Last character must be ';'
+ void setFinalStyle()
+ {
+ Q_ASSERT(m_data[m_size - 1] == ';');
+ m_data[m_size - 1] = 'm';
+ }
- void clear()
- {
- m_size = 0;
- }
+ void clear()
+ {
+ m_size = 0;
+ }
- QLatin1String latin1() const
- {
- return QLatin1String(m_data, m_size);
- }
+ QLatin1String latin1() const
+ {
+ return QLatin1String(m_data, m_size);
+ }
- private:
- char m_data[128];
- int m_size = 0;
+private:
+ char m_data[128];
+ int m_size = 0;
- int remaining() const noexcept
- {
- return 128 - m_size;
+ int remaining() const noexcept
+ {
+ return 128 - m_size;
+ }
+};
+
+void fillString(QString &s, int n, const QString &fill)
+{
+ if (n > 0) {
+ for (; n > fill.size(); n -= fill.size()) {
+ s += fill;
}
- };
+ s += fill.left(n);
+ }
+}
- void fillString(QString &s, int n, const QString &fill)
+struct GraphLine {
+ QString graphLine;
+ QString labelLine;
+ int graphLineLength = 0;
+ int labelLineLength = 0;
+ int nextLabelOffset = 0;
+
+ template<class String> void pushLabel(int offset, String const &s, int charCounter)
{
- if (n > 0) {
- for (; n > fill.size(); n -= fill.size()) {
- s += fill;
- }
- s += fill.left(n);
- }
+ Q_ASSERT(offset >= labelLineLength);
+ const int n = offset - labelLineLength;
+ labelLineLength += charCounter + n;
+ fillLine(labelLine, n);
+ labelLine += s;
+ nextLabelOffset = labelLineLength;
}
- struct GraphLine
+ template<class String> void pushGraph(int offset, String const &s, int charCounter)
{
- QString graphLine;
- QString labelLine;
- int graphLineLength = 0;
- int labelLineLength = 0;
- int nextLabelOffset = 0;
-
- template<class String>
- void pushLabel(int offset, String const& s, int charCounter)
- {
- Q_ASSERT(offset >= labelLineLength);
- const int n = offset - labelLineLength;
- labelLineLength += charCounter + n;
- fillLine(labelLine, n);
- labelLine += s;
- nextLabelOffset = labelLineLength;
+ Q_ASSERT(offset >= graphLineLength);
+ const int n = offset - graphLineLength;
+ graphLineLength += charCounter + n;
+ fillLine(graphLine, n);
+ const int ps1 = graphLine.size();
+ graphLine += s;
+ if (offset >= labelLineLength) {
+ const int n2 = offset - labelLineLength;
+ labelLineLength += n2 + 1;
+ fillLine(labelLine, n2);
+ labelLine += graphLine.rightRef(graphLine.size() - ps1);
}
+ }
- template<class String>
- void pushGraph(int offset, String const& s, int charCounter)
- {
- Q_ASSERT(offset >= graphLineLength);
- const int n = offset - graphLineLength;
- graphLineLength += charCounter + n;
- fillLine(graphLine, n);
- const int ps1 = graphLine.size();
- graphLine += s;
- if (offset >= labelLineLength) {
- const int n2 = offset - labelLineLength;
- labelLineLength += n2 + 1;
- fillLine(labelLine, n2);
- labelLine += graphLine.rightRef(graphLine.size() - ps1);
- }
- }
+private:
+ static void fillLine(QString &s, int n)
+ {
+ Q_ASSERT(n >= 0);
+ fillString(s,
+ n,
+ QStringLiteral(" "
+ " "
+ " "));
+ }
+};
- private:
- static void fillLine(QString& s, int n)
- {
- Q_ASSERT(n >= 0);
- fillString(s, n, QStringLiteral(
- " "
- " "
- " "
- ));
- }
- };
+/**
+ * Returns the first free line at a given position or create a new one
+ */
+GraphLine &lineAtOffset(std::vector<GraphLine> &graphLines, int offset)
+{
+ const auto last = graphLines.end();
+ auto p = std::find_if(graphLines.begin(), last, [=](GraphLine const &line) {
+ return line.nextLabelOffset < offset;
+ });
+ if (p == last) {
+ graphLines.emplace_back();
+ return graphLines.back();
+ }
+ return *p;
+}
- /**
- * Returns the first free line at a given position or create a new one
- */
- GraphLine& lineAtOffset(std::vector<GraphLine> &graphLines, int offset)
+// disable bold, italic and underline on |
+const QLatin1String graphSymbol("\x1b[21;23;24m|");
+// reverse video
+const QLatin1String nameStyle("\x1b[7m");
+
+/**
+ * ANSI Highlighter dedicated to traces
+ */
+class DebugSyntaxHighlighter : public KSyntaxHighlighting::AbstractHighlighter
+{
+public:
+ using TraceOption = KSyntaxHighlighting::AnsiHighlighter::TraceOption;
+ using TraceOptions = KSyntaxHighlighting::AnsiHighlighter::TraceOptions;
+
+ void setDefinition(const KSyntaxHighlighting::Definition &def) override
{
- const auto last = graphLines.end();
- auto p = std::find_if(graphLines.begin(), last, [=](GraphLine const& line) {
- return line.nextLabelOffset < offset;
- });
- if (p == last) {
- graphLines.emplace_back();
- return graphLines.back();
- }
- return *p;
+ AbstractHighlighter::setDefinition(def);
+ m_defData = DefinitionData::get(def);
+ m_contextCapture.setDefinition(def);
}
- // disable bold, italic and underline on |
- const QLatin1String graphSymbol("\x1b[21;23;24m|");
- // reverse video
- const QLatin1String nameStyle("\x1b[7m");
-
- /**
- * ANSI Highlighter dedicated to traces
- */
- class DebugSyntaxHighlighter : public KSyntaxHighlighting::AbstractHighlighter
+ void highlightData(QTextStream &in,
+ QTextStream &out,
+ QLatin1String infoStyle,
+ QLatin1String editorBackground,
+ const std::vector<QPair<QString, QString>> &ansiStyles,
+ TraceOptions traceOptions)
{
- public:
- using TraceOption = KSyntaxHighlighting::AnsiHighlighter::TraceOption;
- using TraceOptions = KSyntaxHighlighting::AnsiHighlighter::TraceOptions;
+ initRegionStyles(ansiStyles);
- void setDefinition(const KSyntaxHighlighting::Definition & def) override
- {
- AbstractHighlighter::setDefinition(def);
- m_defData = DefinitionData::get(def);
- m_contextCapture.setDefinition(def);
- }
+ m_hasFormatTrace = traceOptions.testFlag(TraceOption::Format);
+ m_hasRegionTrace = traceOptions.testFlag(TraceOption::Region);
+ m_hasStackSizeTrace = traceOptions.testFlag(TraceOption::StackSize);
+ m_hasContextTrace = traceOptions.testFlag(TraceOption::Context);
+ const bool hasFormatOrContextTrace = m_hasFormatTrace || m_hasContextTrace || m_hasStackSizeTrace;
- void highlightData(QTextStream &in, QTextStream &out, QLatin1String infoStyle, QLatin1String editorBackground, const std::vector<QPair<QString, QString>> &ansiStyles, TraceOptions traceOptions)
- {
- initRegionStyles(ansiStyles);
-
- m_hasFormatTrace = traceOptions.testFlag(TraceOption::Format);
- m_hasRegionTrace = traceOptions.testFlag(TraceOption::Region);
- const bool hasContextTrace = traceOptions.testFlag(TraceOption::Context);
- const bool hasFormatOrContextTrace = m_hasFormatTrace || hasContextTrace;
-
- const bool hasSeparator = hasFormatOrContextTrace && m_hasRegionTrace;
- const QString resetBgColor = (editorBackground.isEmpty() ? QStringLiteral("\x1b[0m") : editorBackground);
-
- bool firstLine = true;
- State state;
- while (!in.atEnd()) {
- const QString currentLine = in.readLine();
- auto oldState = state;
- state = highlightLine(currentLine, state);
-
- if (hasSeparator) {
- if (!firstLine)
- out << QStringLiteral("\x1b[0m────────────────────────────────────────────────────\x1b[K\n");
- firstLine = false;
- }
+ const bool hasSeparator = hasFormatOrContextTrace && m_hasRegionTrace;
+ const QString resetBgColor = (editorBackground.isEmpty() ? QStringLiteral("\x1b[0m") : editorBackground);
- if (!m_regions.empty()) {
- printRegions(out, infoStyle, currentLine.size());
- out << resetBgColor;
- }
+ bool firstLine = true;
+ State state;
+ while (!in.atEnd()) {
+ const QString currentLine = in.readLine();
+ auto oldState = state;
+ state = highlightLine(currentLine, state);
+
+ if (hasSeparator) {
+ if (!firstLine)
+ out << QStringLiteral("\x1b[0m────────────────────────────────────────────────────\x1b[K\n");
+ firstLine = false;
+ }
- for (const auto &fragment : m_highlightedFragments) {
- auto const& ansiStyle = ansiStyles[fragment.formatId];
- out << ansiStyle.first << currentLine.midRef(fragment.offset, fragment.length) << ansiStyle.second;
- }
+ if (!m_regions.empty()) {
+ printRegions(out, infoStyle, currentLine.size());
+ out << resetBgColor;
+ }
- out << QStringLiteral("\x1b[K\n");
+ for (const auto &fragment : m_highlightedFragments) {
+ auto const &ansiStyle = ansiStyles[fragment.formatId];
+ out << ansiStyle.first << currentLine.midRef(fragment.offset, fragment.length) << ansiStyle.second;
+ }
- if (hasFormatOrContextTrace && !m_highlightedFragments.empty()) {
- if (hasContextTrace)
- appendContextNames(oldState, currentLine);
+ out << QStringLiteral("\x1b[K\n");
- printFormats(out, infoStyle, ansiStyles);
- out << resetBgColor;
- }
+ if (hasFormatOrContextTrace && !m_highlightedFragments.empty()) {
+ if (m_hasContextTrace || m_hasStackSizeTrace)
+ appendContextNames(oldState, currentLine);
- m_highlightedFragments.clear();
+ printFormats(out, infoStyle, ansiStyles);
+ out << resetBgColor;
}
- }
- void applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) override
- {
- m_highlightedFragments.push_back({m_hasFormatTrace ? format.name() : QString(), offset, length, format.id()});
+ m_highlightedFragments.clear();
}
+ }
- void applyFolding(int offset, int /*length*/, FoldingRegion region) override
- {
- if (!m_hasRegionTrace) return;
-
- const auto id = region.id();
+ void applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) override
+ {
+ m_highlightedFragments.push_back({m_hasFormatTrace ? format.name() : QString(), offset, length, format.id()});
+ }
- if (region.type() == FoldingRegion::Begin) {
- m_regions.push_back(Region{m_regionDepth, offset, -1, id, Region::State::Open});
- ++m_regionDepth;
- } else {
- // find open region
- auto it = m_regions.rbegin();
- auto eit = m_regions.rend();
- for (int depth = 0; it != eit; ++it) {
- if (it->regionId == id && it->bindIndex < 0) {
- if (it->state == Region::State::Close)
- ++depth;
- else if (--depth < 0)
- break;
- }
+ void applyFolding(int offset, int /*length*/, FoldingRegion region) override
+ {
+ if (!m_hasRegionTrace)
+ return;
+
+ const auto id = region.id();
+
+ if (region.type() == FoldingRegion::Begin) {
+ m_regions.push_back(Region{m_regionDepth, offset, -1, id, Region::State::Open});
+ // swap with previous region if this is a closing region with same offset in order to superimpose labels
+ if (m_regions.size() >= 2) {
+ auto &previousRegion = m_regions[m_regions.size() - 2];
+ if (previousRegion.state == Region::State::Close && previousRegion.offset == offset) {
+ std::swap(previousRegion, m_regions.back());
+ if (previousRegion.bindIndex != -1)
+ m_regions[previousRegion.bindIndex].bindIndex = m_regions.size() - 1;
}
-
- if (it != eit) {
- it->bindIndex = int(m_regions.size());
- int bindIndex = int(&*it - m_regions.data());
- m_regions.push_back(Region{it->depth, offset, bindIndex, id, Region::State::Close});
- } else {
- m_regions.push_back(Region{-1, offset, -1, id, Region::State::Close});
+ }
+ ++m_regionDepth;
+ } else {
+ // find open region
+ auto it = m_regions.rbegin();
+ auto eit = m_regions.rend();
+ for (int depth = 0; it != eit; ++it) {
+ if (it->regionId == id && it->bindIndex < 0) {
+ if (it->state == Region::State::Close)
+ ++depth;
+ else if (--depth < 0)
+ break;
}
+ }
- m_regionDepth = std::max(m_regionDepth-1, 0);
+ if (it != eit) {
+ it->bindIndex = int(m_regions.size());
+ int bindIndex = int(&*it - m_regions.data());
+ m_regions.push_back(Region{it->depth, offset, bindIndex, id, Region::State::Close});
+ } else {
+ m_regions.push_back(Region{-1, offset, -1, id, Region::State::Close});
}
+
+ m_regionDepth = std::max(m_regionDepth - 1, 0);
}
+ }
- using KSyntaxHighlighting::AbstractHighlighter::highlightLine;
+ using KSyntaxHighlighting::AbstractHighlighter::highlightLine;
- private:
- /**
- * Initializes with colors of \p ansiStyle without duplicate.
- */
- void initRegionStyles(const std::vector<QPair<QString, QString>> &ansiStyles)
- {
- m_regionStyles.resize(ansiStyles.size());
- for (std::size_t i = 0; i < m_regionStyles.size(); ++i)
- m_regionStyles[i] = ansiStyles[i].first;
+private:
+ /**
+ * Initializes with colors of \p ansiStyle without duplicate.
+ */
+ void initRegionStyles(const std::vector<QPair<QString, QString>> &ansiStyles)
+ {
+ m_regionStyles.resize(ansiStyles.size());
+ for (std::size_t i = 0; i < m_regionStyles.size(); ++i)
+ m_regionStyles[i] = ansiStyles[i].first;
- std::sort(m_regionStyles.begin(), m_regionStyles.end());
- m_regionStyles.erase(std::unique(m_regionStyles.begin(), m_regionStyles.end()), m_regionStyles.end());
- }
+ std::sort(m_regionStyles.begin(), m_regionStyles.end());
+ m_regionStyles.erase(std::unique(m_regionStyles.begin(), m_regionStyles.end()), m_regionStyles.end());
+ }
- /**
- * Append the context name in front of the format name.
- */
- void appendContextNames(State &state, const QString &currentLine)
- {
- auto newState = state;
- for (auto &fragment : m_highlightedFragments) {
- QString contextName = extractContextName(StateData::get(newState));
-
- m_contextCapture.offsetNext = 0;
- m_contextCapture.lengthNext = 0;
- // truncate the line to deduce the context from the format
- const auto lineFragment = currentLine.mid(0, fragment.offset + fragment.length + 1);
- newState = m_contextCapture.highlightLine(lineFragment, state);
-
- // Deduced context does not start at the position of the format.
- // This can happen because of lookAhead/fallthrought attribute,
- // assertion in regex, etc.
- if (m_contextCapture.offset != fragment.offset && m_contextCapture.length != fragment.length) {
- contextName.insert(0, QLatin1Char('~'));
- }
- fragment.name.insert(0, contextName);
+ /**
+ * Append the context name in front of the format name.
+ */
+ void appendContextNames(State &state, const QString &currentLine)
+ {
+ auto newState = state;
+ for (auto &fragment : m_highlightedFragments) {
+ QString contextName = extractContextName(StateData::get(newState));
+
+ m_contextCapture.offsetNext = 0;
+ m_contextCapture.lengthNext = 0;
+ // truncate the line to deduce the context from the format
+ const auto lineFragment = currentLine.mid(0, fragment.offset + fragment.length + 1);
+ newState = m_contextCapture.highlightLine(lineFragment, state);
+
+ // Deduced context does not start at the position of the format.
+ // This can happen because of lookAhead/fallthrought attribute,
+ // assertion in regex, etc.
+ if (m_contextCapture.offset != fragment.offset && m_contextCapture.length != fragment.length) {
+ contextName.insert(0, QLatin1Char('~'));
}
+ fragment.name.insert(0, contextName);
}
+ }
- /**
- * \return Current context name with definition name if different
- * from the current definition name
- */
- QString extractContextName(StateData *stateData) const
- {
+ /**
+ * \return Current context name with definition name if different
+ * from the current definition name
+ */
+ QString extractContextName(StateData *stateData) const
+ {
+ QString label;
+
+ if (m_hasStackSizeTrace) {
+ label += QLatin1Char('(') % QString::number(stateData->size()) % QLatin1Char(')');
+ }
+
+ if (m_hasContextTrace) {
// first state is empty
if (stateData->isEmpty()) {
- return QStringLiteral("[???]");
+ return label + QStringLiteral("[???]");
}
const auto context = stateData->topContext();
const auto defData = DefinitionData::get(context->definition());
- const auto contextName = (defData != m_defData)
- ? QString(QLatin1Char('<') % defData->name % QLatin1Char('>'))
- : QString();
- return QString(contextName % QLatin1Char('[') % context->name() % QLatin1Char(']'));
+ const auto contextName = (defData != m_defData) ? QString(QLatin1Char('<') % defData->name % QLatin1Char('>')) : QString();
+ return QString(label % contextName % QLatin1Char('[') % context->name() % QLatin1Char(']'));
}
- void printFormats(QTextStream &out, QLatin1String regionStyle, const std::vector<QPair<QString, QString>> &ansiStyles)
- {
- // init graph
- m_formatGraph.clear();
- for (auto const& fragment : m_highlightedFragments) {
- GraphLine& line = lineAtOffset(m_formatGraph, fragment.offset);
- auto const& style = ansiStyles[fragment.formatId].first;
- line.pushLabel(fragment.offset, style % nameStyle % fragment.name % regionStyle, fragment.name.size());
-
- for (GraphLine* pline = m_formatGraph.data(); pline <= &line; ++pline) {
- pline->pushGraph(fragment.offset, style % graphSymbol % regionStyle, 1);
- }
- }
+ return label;
+ }
- // display graph
- out << regionStyle;
- auto first = m_formatGraph.begin();
- auto last = m_formatGraph.end();
- --last;
- for (; first != last; ++first) {
- out << first->graphLine << "\x1b[K\n" << first->labelLine << "\x1b[K\n";
+ void printFormats(QTextStream &out, QLatin1String regionStyle, const std::vector<QPair<QString, QString>> &ansiStyles)
+ {
+ // init graph
+ m_formatGraph.clear();
+ for (auto const &fragment : m_highlightedFragments) {
+ GraphLine &line = lineAtOffset(m_formatGraph, fragment.offset);
+ auto const &style = ansiStyles[fragment.formatId].first;
+ line.pushLabel(fragment.offset, style % nameStyle % fragment.name % regionStyle, fragment.name.size());
+
+ for (GraphLine *pline = m_formatGraph.data(); pline <= &line; ++pline) {
+ pline->pushGraph(fragment.offset, style % graphSymbol % regionStyle, 1);
}
- out << first->graphLine << "\x1b[K\n" << first->labelLine << "\x1b[K\x1b[0m\n";
}
- void printRegions(QTextStream &out, QLatin1String regionStyle, int lineLength)
- {
- const QString continuationLine = QStringLiteral(
- "------------------------------"
- "------------------------------"
- "------------------------------");
-
- bool hasContinuation = false;
-
- m_regionGraph.clear();
- QString label;
- QString numStr;
-
- // init graph
- for (Region& region : m_regions) {
- if (region.state == Region::State::Continuation) {
- hasContinuation = true;
- continue;
- }
+ // display graph
+ out << regionStyle;
+ auto first = m_formatGraph.begin();
+ auto last = m_formatGraph.end();
+ --last;
+ for (; first != last; ++first) {
+ out << first->graphLine << "\x1b[K\n" << first->labelLine << "\x1b[K\n";
+ }
+ out << first->graphLine << "\x1b[K\n" << first->labelLine << "\x1b[K\x1b[0m\n";
+ }
+
+ void printRegions(QTextStream &out, QLatin1String regionStyle, int lineLength)
+ {
+ const QString continuationLine = QStringLiteral(
+ "------------------------------"
+ "------------------------------"
+ "------------------------------");
+
+ bool hasContinuation = false;
+
+ m_regionGraph.clear();
+ QString label;
+ QString numStr;
+
+ // init graph
+ for (Region &region : m_regions) {
+ if (region.state == Region::State::Continuation) {
+ hasContinuation = true;
+ continue;
+ }
- auto pushGraphs = [&](int offset, const GraphLine *endline, const QStringView &style){
- for (GraphLine* pline = m_regionGraph.data(); pline <= endline; ++pline) {
- // a label can hide a graph
- if (pline->graphLineLength <= offset) {
- pline->pushGraph(offset, style % graphSymbol % regionStyle, 1);
- }
+ auto pushGraphs = [&](int offset, const GraphLine *endline, const QStringView &style) {
+ for (GraphLine *pline = m_regionGraph.data(); pline <= endline; ++pline) {
+ // a label can hide a graph
+ if (pline->graphLineLength <= offset) {
+ pline->pushGraph(offset, style % graphSymbol % regionStyle, 1);
}
- };
+ }
+ };
- QChar openChar;
- QChar closeChar;
- int lpad = 0;
- int rpad = 0;
+ QChar openChar;
+ QChar closeChar;
+ int lpad = 0;
+ int rpad = 0;
- int offsetLabel = region.offset;
+ int offsetLabel = region.offset;
- numStr.setNum(region.regionId);
+ numStr.setNum(region.regionId);
- if (region.state == Region::State::Open) {
- openChar = QLatin1Char('(');
- if (region.bindIndex == -1) {
- rpad = lineLength - region.offset - numStr.size();
- } else {
- rpad = m_regions[region.bindIndex].offset - region.offset - 2;
- closeChar = QLatin1Char(')');
- }
- // close without open
- } else if (region.bindIndex == -1) {
- closeChar = QLatin1Char('>');
- // label already present, we only display the graph
- } else if (m_regions[region.bindIndex].state == Region::State::Open) {
- const auto &openRegion = m_regions[region.bindIndex];
- // here offset is a graph index
- const GraphLine &line = m_regionGraph[openRegion.offset];
- const auto &style = m_regionStyles[openRegion.depth % m_regionStyles.size()];
- pushGraphs(region.offset, &line, style);
- continue;
+ if (region.state == Region::State::Open) {
+ openChar = QLatin1Char('(');
+ if (region.bindIndex == -1) {
+ rpad = lineLength - region.offset - numStr.size();
} else {
+ rpad = m_regions[region.bindIndex].offset - region.offset - 2;
closeChar = QLatin1Char(')');
- lpad = region.offset - numStr.size();
- offsetLabel = 0;
}
+ // close without open
+ } else if (region.bindIndex == -1) {
+ closeChar = QLatin1Char('>');
+ // label already present, we only display the graph
+ } else if (m_regions[region.bindIndex].state == Region::State::Open) {
+ const auto &openRegion = m_regions[region.bindIndex];
+ // here offset is a graph index
+ const GraphLine &line = m_regionGraph[openRegion.offset];
+ const auto &style = m_regionStyles[openRegion.depth % m_regionStyles.size()];
+ pushGraphs(region.offset, &line, style);
+ continue;
+ } else {
+ closeChar = QLatin1Char(')');
+ lpad = region.offset - numStr.size();
+ offsetLabel = 0;
+ }
- const QStringView openS(&openChar, openChar.unicode() ? 1 : 0);
- const QStringView closeS(&closeChar, closeChar.unicode() ? 1 : 0);
+ const QStringView openS(&openChar, openChar.unicode() ? 1 : 0);
+ const QStringView closeS(&closeChar, closeChar.unicode() ? 1 : 0);
- label.clear();
- fillString(label, lpad, continuationLine);
- label += numStr;
- fillString(label, rpad, continuationLine);
+ label.clear();
+ fillString(label, lpad, continuationLine);
+ label += numStr;
+ fillString(label, rpad, continuationLine);
- GraphLine &line = lineAtOffset(m_regionGraph, offsetLabel);
- const auto &style = m_regionStyles[region.depth % m_regionStyles.size()];
- line.pushLabel(offsetLabel, style % nameStyle % openS % label % closeS % regionStyle, label.size() + openS.size() + closeS.size());
- pushGraphs(region.offset, &line, style);
+ GraphLine &line = lineAtOffset(m_regionGraph, offsetLabel);
+ const auto &style = m_regionStyles[region.depth % m_regionStyles.size()];
+ line.pushLabel(offsetLabel, style % nameStyle % openS % label % closeS % regionStyle, label.size() + openS.size() + closeS.size());
+ pushGraphs(region.offset, &line, style);
- // transforms offset into graph index when region is on 1 line
- if (region.state == Region::State::Open && region.bindIndex != -1) {
- region.offset = &line - m_regionGraph.data();
- }
+ // transforms offset into graph index when region is on 1 line
+ if (region.state == Region::State::Open && region.bindIndex != -1) {
+ region.offset = &line - m_regionGraph.data();
}
+ }
- out << regionStyle;
-
- // display regions which are neither closed nor open
- if (hasContinuation) {
- label.clear();
- fillString(label, lineLength ? lineLength : 5, continuationLine);
- for (const auto &region : m_regions) {
- if (region.state == Region::State::Continuation && region.bindIndex == -1) {
- const auto &style = m_regionStyles[region.depth % m_regionStyles.size()];
- out << style << nameStyle << label << regionStyle << "\x1b[K\n";
- }
- }
- }
+ out << regionStyle;
- // display graph
- if (!m_regionGraph.empty()) {
- auto first = m_regionGraph.rbegin();
- auto last = m_regionGraph.rend();
- --last;
- for (; first != last; ++first) {
- out << first->labelLine << "\x1b[K\n" << first->graphLine << "\x1b[K\n";
+ // display regions which are neither closed nor open
+ if (hasContinuation) {
+ label.clear();
+ fillString(label, lineLength ? lineLength : 5, continuationLine);
+ for (const auto &region : m_regions) {
+ if (region.state == Region::State::Continuation && region.bindIndex == -1) {
+ const auto &style = m_regionStyles[region.depth % m_regionStyles.size()];
+ out << style << nameStyle << label << regionStyle << "\x1b[K\n";
}
- out << first->labelLine << "\x1b[K\n" << first->graphLine << "\x1b[K\x1b[0m\n";
}
+ }
- // keep regions that are not closed
- m_regions.erase(std::remove_if(m_regions.begin(), m_regions.end(), [](Region const& region){
- return region.bindIndex != -1 || region.state == Region::State::Close;
- }), m_regions.end());
- // all remaining regions become Continuation
- for (auto& region : m_regions) {
- region.offset = 0;
- region.state = Region::State::Continuation;
+ // display graph
+ if (!m_regionGraph.empty()) {
+ auto first = m_regionGraph.rbegin();
+ auto last = m_regionGraph.rend();
+ --last;
+ for (; first != last; ++first) {
+ out << first->labelLine << "\x1b[K\n" << first->graphLine << "\x1b[K\n";
}
+ out << first->labelLine << "\x1b[K\n" << first->graphLine << "\x1b[K\x1b[0m\n";
}
- struct HighlightFragment
- {
- QString name;
- int offset;
- int length;
- quint16 formatId;
- };
+ // keep regions that are not closed
+ m_regions.erase(std::remove_if(m_regions.begin(),
+ m_regions.end(),
+ [](Region const &region) {
+ return region.bindIndex != -1 || region.state == Region::State::Close;
+ }),
+ m_regions.end());
+ // all remaining regions become Continuation
+ for (auto &region : m_regions) {
+ region.offset = 0;
+ region.state = Region::State::Continuation;
+ }
+ }
- struct ContextCaptureHighlighter : KSyntaxHighlighting::AbstractHighlighter
- {
- int offset;
- int length;
- int offsetNext;
- int lengthNext;
-
- void applyFormat(int offset, int length, const KSyntaxHighlighting::Format &/*format*/) override
- {
- offset = offsetNext;
- length = lengthNext;
- offsetNext = offset;
- lengthNext = length;
- }
+ struct HighlightFragment {
+ QString name;
+ int offset;
+ int length;
+ quint16 formatId;
+ };
- using KSyntaxHighlighting::AbstractHighlighter::highlightLine;
- };
+ struct ContextCaptureHighlighter : KSyntaxHighlighting::AbstractHighlighter {
+ int offset;
+ int length;
+ int offsetNext;
+ int lengthNext;
- struct Region
+ void applyFormat(int offset, int length, const KSyntaxHighlighting::Format & /*format*/) override
{
- enum class State : int8_t
- {
- Open,
- Close,
- Continuation,
- };
+ offset = offsetNext;
+ length = lengthNext;
+ offsetNext = offset;
+ lengthNext = length;
+ }
+
+ using KSyntaxHighlighting::AbstractHighlighter::highlightLine;
+ };
- int depth;
- int offset;
- int bindIndex;
- quint16 regionId;
- State state;
+ struct Region {
+ enum class State : int8_t {
+ Open,
+ Close,
+ Continuation,
};
- bool m_hasFormatTrace;
- bool m_hasRegionTrace;
+ int depth;
+ int offset;
+ int bindIndex;
+ quint16 regionId;
+ State state;
+ };
- std::vector<HighlightFragment> m_highlightedFragments;
- std::vector<GraphLine> m_formatGraph;
- ContextCaptureHighlighter m_contextCapture;
- DefinitionData* m_defData;
+ bool m_hasFormatTrace;
+ bool m_hasRegionTrace;
+ bool m_hasStackSizeTrace;
+ bool m_hasContextTrace;
- int m_regionDepth = 0;
- std::vector<Region> m_regions;
- std::vector<GraphLine> m_regionGraph;
- std::vector<QStringView> m_regionStyles;
- };
+ std::vector<HighlightFragment> m_highlightedFragments;
+ std::vector<GraphLine> m_formatGraph;
+ ContextCaptureHighlighter m_contextCapture;
+ DefinitionData *m_defData;
+
+ int m_regionDepth = 0;
+ std::vector<Region> m_regions;
+ std::vector<GraphLine> m_regionGraph;
+ std::vector<QStringView> m_regionStyles;
+};
} // anonymous namespace
class KSyntaxHighlighting::AnsiHighlighterPrivate
@@ -1186,13 +1212,13 @@ void AnsiHighlighter::highlightData(QIODevice *dev, AnsiFormat format, bool useE
}
// initialize ansiStyles
- for (auto&& definition : qAsConst(definitions)) {
+ for (auto &&definition : qAsConst(definitions)) {
const auto formats = definition.formats();
- for (auto&& format : formats) {
+ for (auto &&format : formats) {
const auto id = format.id();
if (id >= d->ansiStyles.size()) {
// better than id + 1 to avoid successive allocations
- d->ansiStyles.resize(std::max(std::size_t(id*2), std::size_t(32)));
+ d->ansiStyles.resize(std::max(std::size_t(id * 2), std::size_t(32)));
}
AnsiBuffer buffer;
@@ -1210,11 +1236,16 @@ void AnsiHighlighter::highlightData(QIODevice *dev, AnsiFormat format, bool useE
buffer.appendForeground(format.textColor(theme).rgb(), is256Colors, colorCache);
else
buffer.append(foregroundDefaultColor);
- if (hasBg) buffer.appendBackground(format.backgroundColor(theme).rgb(), is256Colors, colorCache);
- if (hasBold) buffer.append(QLatin1String("1;"));
- if (hasItalic) buffer.append(QLatin1String("3;"));
- if (hasUnderline) buffer.append(QLatin1String("4;"));
- if (hasStrikeThrough) buffer.append(QLatin1String("9;"));
+ if (hasBg)
+ buffer.appendBackground(format.backgroundColor(theme).rgb(), is256Colors, colorCache);
+ if (hasBold)
+ buffer.append(QLatin1String("1;"));
+ if (hasItalic)
+ buffer.append(QLatin1String("3;"));
+ if (hasUnderline)
+ buffer.append(QLatin1String("4;"));
+ if (hasStrikeThrough)
+ buffer.append(QLatin1String("9;"));
// if there is ANSI style
if (buffer.latin1().size() > 2) {
@@ -1231,10 +1262,14 @@ void AnsiHighlighter::highlightData(QIODevice *dev, AnsiFormat format, bool useE
d->ansiStyles[id].second = buffer.latin1();
} else if (hasEffect) {
buffer.append(QLatin1String("\x1b["));
- if (hasBold) buffer.append(QLatin1String("21;"));
- if (hasItalic) buffer.append(QLatin1String("23;"));
- if (hasUnderline) buffer.append(QLatin1String("24;"));
- if (hasStrikeThrough) buffer.append(QLatin1String("29;"));
+ if (hasBold)
+ buffer.append(QLatin1String("21;"));
+ if (hasItalic)
+ buffer.append(QLatin1String("23;"));
+ if (hasUnderline)
+ buffer.append(QLatin1String("24;"));
+ if (hasStrikeThrough)
+ buffer.append(QLatin1String("29;"));
buffer.setFinalStyle();
d->ansiStyles[id].second = buffer.latin1();
}
@@ -1286,6 +1321,6 @@ void AnsiHighlighter::highlightData(QIODevice *dev, AnsiFormat format, bool useE
void AnsiHighlighter::applyFormat(int offset, int length, const Format &format)
{
- auto const& ansiStyle = d->ansiStyles[format.id()];
+ auto const &ansiStyle = d->ansiStyles[format.id()];
d->out << ansiStyle.first << d->currentLine.midRef(offset, length) << ansiStyle.second;
}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.h b/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.h
index 6593799234..e4a7cb09f8 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/ansihighlighter.h
@@ -10,9 +10,9 @@
#include "abstracthighlighter.h"
#include "ksyntaxhighlighting_export.h"
+#include <QFlags>
#include <QIODevice>
#include <QString>
-#include <QFlags>
#include <memory>
@@ -23,26 +23,29 @@ class AnsiHighlighterPrivate;
class KSYNTAXHIGHLIGHTING_EXPORT AnsiHighlighter final : public AbstractHighlighter
{
public:
- enum class AnsiFormat
- {
+ enum class AnsiFormat {
TrueColor,
XTerm256Color,
};
- enum class TraceOption
- {
+ enum class TraceOption {
NoOptions,
- Format = 1 << 0,
- Region = 1 << 1,
+ Format = 1 << 0,
+ Region = 1 << 1,
Context = 1 << 2,
+ StackSize = 1 << 3,
};
Q_DECLARE_FLAGS(TraceOptions, TraceOption)
AnsiHighlighter();
~AnsiHighlighter() override;
- void highlightFile(const QString &fileName, AnsiFormat format = AnsiFormat::TrueColor, bool useEditorBackground = true, TraceOptions traceOptions = TraceOptions());
- void highlightData(QIODevice *device, AnsiFormat format = AnsiFormat::TrueColor, bool useEditorBackground = true, TraceOptions traceOptions = TraceOptions());
+ void highlightFile(const QString &fileName,
+ AnsiFormat format = AnsiFormat::TrueColor,
+ bool useEditorBackground = true,
+ TraceOptions traceOptions = TraceOptions());
+ void
+ highlightData(QIODevice *device, AnsiFormat format = AnsiFormat::TrueColor, bool useEditorBackground = true, TraceOptions traceOptions = TraceOptions());
void setOutputFile(const QString &fileName);
void setOutputFile(FILE *fileHandle);
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/context.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/context.cpp
index 4d83da3d05..f980ea5be8 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/context.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/context.cpp
@@ -135,7 +135,8 @@ void Context::resolveIncludes()
context = defData->contextByName(inc->contextName());
}
if (!context) {
- qCWarning(Log) << "Unable to resolve include rule for definition" << inc->contextName() << "##" << inc->definitionName() << "in" << m_def.definition().name();
+ qCWarning(Log) << "Unable to resolve include rule for definition" << inc->contextName() << "##" << inc->definitionName() << "in"
+ << m_def.definition().name();
++it;
continue;
}
@@ -171,7 +172,8 @@ void Context::resolveAttributeFormat()
m_attributeFormat = DefinitionData::get(def)->formatByName(m_attribute);
if (!m_attributeFormat.isValid()) {
if (m_attributeContext) {
- qCWarning(Log) << "Context: Unknown format" << m_attribute << "in context" << m_name << "of definition" << m_def.definition().name() << "from included context" << m_attributeContext->m_name << "of definition" << def.name();
+ qCWarning(Log) << "Context: Unknown format" << m_attribute << "in context" << m_name << "of definition" << m_def.definition().name()
+ << "from included context" << m_attributeContext->m_name << "of definition" << def.name();
} else {
qCWarning(Log) << "Context: Unknown format" << m_attribute << "in context" << m_name << "of definition" << m_def.definition().name();
}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch.cpp
index 1ec4759103..14c50396d8 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch.cpp
@@ -27,7 +27,7 @@ Context *ContextSwitch::context() const
return m_context;
}
-void ContextSwitch::parse(const QStringView &contextInstr)
+void ContextSwitch::parse(const QStringRef &contextInstr)
{
if (contextInstr.isEmpty() || contextInstr == QLatin1String("#stay"))
return;
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch_p.h
index e861cbaded..c84948d9f7 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/contextswitch_p.h
@@ -25,7 +25,7 @@ public:
int popCount() const;
Context *context() const;
- void parse(const QStringView &contextInstr);
+ void parse(const QStringRef &contextInstr);
void resolve(const Definition &def);
private:
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/definition.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/definition.cpp
index 0b6975d775..2ff090f61b 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/definition.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/definition.cpp
@@ -19,15 +19,14 @@
#include "repository.h"
#include "repository_p.h"
#include "rule_p.h"
-#include "xml_p.h"
#include "worddelimiters_p.h"
+#include "xml_p.h"
#include <QCborMap>
#include <QCoreApplication>
#include <QFile>
#include <QHash>
#include <QStringList>
-#include <QVector>
#include <QXmlStreamReader>
#include <algorithm>
@@ -234,7 +233,9 @@ QVector<Format> Definition::formats() const
// sort formats so that the order matches the order of the itemDatas in the xml files.
auto formatList = QVector<Format>::fromList(d->formats.values());
- std::sort(formatList.begin(), formatList.end(), [](const KSyntaxHighlighting::Format &lhs, const KSyntaxHighlighting::Format &rhs) { return lhs.id() < rhs.id(); });
+ std::sort(formatList.begin(), formatList.end(), [](const KSyntaxHighlighting::Format &lhs, const KSyntaxHighlighting::Format &rhs) {
+ return lhs.id() < rhs.id();
+ });
return formatList;
}
@@ -244,15 +245,16 @@ QVector<Definition> Definition::includedDefinitions() const
d->load();
// init worklist and result used as guard with this definition
- QVector<Definition> queue {*this};
- QVector<Definition> definitions {*this};
+ QVector<Definition> queue{*this};
+ QVector<Definition> definitions{*this};
while (!queue.isEmpty()) {
// Iterate all context rules to find associated Definitions. This will
// automatically catch other Definitions referenced with IncludeRuldes or ContextSwitch.
const auto definition = queue.takeLast();
for (const auto &context : qAsConst(definition.d->contexts)) {
// handle context switch attributes of this context itself
- for (const auto switchContext : {context->lineEndContext().context(), context->lineEmptyContext().context(), context->fallthroughContext().context()}) {
+ for (const auto switchContext :
+ {context->lineEndContext().context(), context->lineEmptyContext().context(), context->fallthroughContext().context()}) {
if (switchContext) {
if (!definitions.contains(switchContext->definition())) {
queue.push_back(switchContext->definition());
@@ -387,6 +389,12 @@ bool DefinitionData::load(OnlyKeywords onlyKeywords)
context->resolveAttributeFormat();
}
+ for (const auto context : qAsConst(contexts)) {
+ for (const auto &rule : context->rules()) {
+ rule->resolvePostProcessing();
+ }
+ }
+
return true;
}
@@ -449,18 +457,10 @@ bool DefinitionData::loadMetaData(const QString &file, const QCborMap &obj)
fileName = file;
const auto exts = obj.value(QLatin1String("extensions")).toString();
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- for (const auto &ext : exts.split(QLatin1Char(';'), QString::SkipEmptyParts))
-#else
for (const auto &ext : exts.split(QLatin1Char(';'), Qt::SkipEmptyParts))
-#endif
extensions.push_back(ext);
const auto mts = obj.value(QLatin1String("mimetype")).toString();
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- for (const auto &mt : mts.split(QLatin1Char(';'), QString::SkipEmptyParts))
-#else
for (const auto &mt : mts.split(QLatin1Char(';'), Qt::SkipEmptyParts))
-#endif
mimetypes.push_back(mt);
return true;
@@ -485,18 +485,10 @@ bool DefinitionData::loadLanguage(QXmlStreamReader &reader)
author = reader.attributes().value(QLatin1String("author")).toString();
license = reader.attributes().value(QLatin1String("license")).toString();
const auto exts = reader.attributes().value(QLatin1String("extensions")).toString();
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- for (const auto &ext : exts.split(QLatin1Char(';'), QString::SkipEmptyParts))
-#else
for (const auto &ext : exts.split(QLatin1Char(';'), Qt::SkipEmptyParts))
-#endif
extensions.push_back(ext);
const auto mts = reader.attributes().value(QLatin1String("mimetype")).toString();
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- for (const auto &mt : mts.split(QLatin1Char(';'), QString::SkipEmptyParts))
-#else
for (const auto &mt : mts.split(QLatin1Char(';'), Qt::SkipEmptyParts))
-#endif
mimetypes.push_back(mt);
if (reader.attributes().hasAttribute(QLatin1String("casesensitive")))
caseSensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
@@ -630,19 +622,15 @@ void DefinitionData::loadGeneral(QXmlStreamReader &reader)
caseSensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive;
// adapt wordDelimiters
- for (QChar c : reader.attributes().value(QLatin1String("additionalDeliminator")))
- wordDelimiters.append(c);
- for (QChar c : reader.attributes().value(QLatin1String("weakDeliminator")))
- wordDelimiters.remove(c);
+ wordDelimiters.append(reader.attributes().value(QLatin1String("additionalDeliminator")));
+ wordDelimiters.remove(reader.attributes().value(QLatin1String("weakDeliminator")));
// adapt WordWrapDelimiters
- auto wordWrapDeliminatorAttr = reader.attributes().value(
- QLatin1String("wordWrapDeliminator"));
+ QStringRef wordWrapDeliminatorAttr = reader.attributes().value(QLatin1String("wordWrapDeliminator"));
if (wordWrapDeliminatorAttr.isEmpty())
wordWrapDelimiters = wordDelimiters;
else {
- for (QChar c : wordWrapDeliminatorAttr)
- wordWrapDelimiters.append(c);
+ wordWrapDelimiters.append(wordWrapDeliminatorAttr);
}
} else if (reader.name() == QLatin1String("folding")) {
if (reader.attributes().hasAttribute(QLatin1String("indentationsensitive")))
@@ -776,15 +764,15 @@ void DefinitionData::loadSpellchecking(QXmlStreamReader &reader)
}
}
-bool DefinitionData::checkKateVersion(const QStringView &verStr)
+bool DefinitionData::checkKateVersion(const QStringRef &verStr)
{
const auto idx = verStr.indexOf(QLatin1Char('.'));
if (idx <= 0) {
qCWarning(Log) << "Skipping" << fileName << "due to having no valid kateversion attribute:" << verStr;
return false;
}
- const auto major = verStr.left(idx).toString().toInt();
- const auto minor = verStr.mid(idx + 1).toString().toInt();
+ const auto major = verStr.left(idx).toInt();
+ const auto minor = verStr.mid(idx + 1).toInt();
if (major > SyntaxHighlighting_VERSION_MAJOR || (major == SyntaxHighlighting_VERSION_MAJOR && minor > SyntaxHighlighting_VERSION_MINOR)) {
qCWarning(Log) << "Skipping" << fileName << "due to being too new, version:" << verStr;
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/definition.h b/src/libs/3rdparty/syntax-highlighting/src/lib/definition.h
index 8226fbdd24..0cc2df70d0 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/definition.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/definition.h
@@ -17,6 +17,8 @@
QT_BEGIN_NAMESPACE
class QChar;
class QString;
+class QStringList;
+template<typename T> class QVector;
QT_END_NAMESPACE
namespace KSyntaxHighlighting
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/definition_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/definition_p.h
index 274f7640b5..c334e31ac0 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/definition_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/definition_p.h
@@ -53,7 +53,7 @@ public:
void loadComments(QXmlStreamReader &reader);
void loadFoldingIgnoreList(QXmlStreamReader &reader);
void loadSpellchecking(QXmlStreamReader &reader);
- bool checkKateVersion(const QStringView &verStr);
+ bool checkKateVersion(const QStringRef &verStr);
void resolveIncludeKeywords();
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/definitiondownloader.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/definitiondownloader.cpp
index 35bb29f82f..93db5ac62a 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/definitiondownloader.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/definitiondownloader.cpp
@@ -43,7 +43,7 @@ void DefinitionDownloaderPrivate::definitionListDownloadFinished(QNetworkReply *
const auto networkError = reply->error();
if (networkError != QNetworkReply::NoError) {
qCWarning(Log) << networkError;
- emit q->done(); // TODO return error
+ Q_EMIT q->done(); // TODO return error
return;
}
@@ -60,7 +60,7 @@ void DefinitionDownloaderPrivate::definitionListDownloadFinished(QNetworkReply *
}
if (pendingDownloads == 0)
- emit q->informationMessage(QObject::tr("All syntax definitions are up-to-date."));
+ Q_EMIT q->informationMessage(QObject::tr("All syntax definitions are up-to-date."));
checkDone();
}
@@ -72,14 +72,14 @@ void DefinitionDownloaderPrivate::updateDefinition(QXmlStreamReader &parser)
auto localDef = repo->definitionForName(name.toString());
if (!localDef.isValid()) {
- emit q->informationMessage(QObject::tr("Downloading new syntax definition for '%1'...").arg(name.toString()));
+ Q_EMIT q->informationMessage(QObject::tr("Downloading new syntax definition for '%1'...").arg(name.toString()));
downloadDefinition(QUrl(parser.attributes().value(QLatin1String("url")).toString()));
return;
}
const auto version = parser.attributes().value(QLatin1String("version"));
if (localDef.version() < version.toFloat()) {
- emit q->informationMessage(QObject::tr("Updating syntax definition for '%1' to version %2...").arg(name.toString(), version.toString()));
+ Q_EMIT q->informationMessage(QObject::tr("Updating syntax definition for '%1' to version %2...").arg(name.toString(), version.toString()));
downloadDefinition(QUrl(parser.attributes().value(QLatin1String("url")).toString()));
}
}
@@ -94,7 +94,9 @@ void DefinitionDownloaderPrivate::downloadDefinition(const QUrl &downloadUrl)
QNetworkRequest req(url);
auto reply = nam->get(req);
- QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() { downloadDefinitionFinished(reply); });
+ QObject::connect(reply, &QNetworkReply::finished, q, [this, reply]() {
+ downloadDefinitionFinished(reply);
+ });
++pendingDownloads;
needsReload = true;
}
@@ -134,7 +136,7 @@ void DefinitionDownloaderPrivate::checkDone()
if (needsReload)
repo->reload();
- emit QTimer::singleShot(0, q, &DefinitionDownloader::done);
+ Q_EMIT QTimer::singleShot(0, q, &DefinitionDownloader::done);
}
}
@@ -161,10 +163,12 @@ DefinitionDownloader::~DefinitionDownloader()
void DefinitionDownloader::start()
{
- const QString url = QLatin1String("https://www.kate-editor.org/syntax/update-") + QString::number(SyntaxHighlighting_VERSION_MAJOR) + QLatin1Char('.') + QString::number(SyntaxHighlighting_VERSION_MINOR) + QLatin1String(".xml");
+ const QString url = QLatin1String("https://www.kate-editor.org/syntax/update-") + QString::number(SyntaxHighlighting_VERSION_MAJOR) + QLatin1Char('.')
+ + QString::number(SyntaxHighlighting_VERSION_MINOR) + QLatin1String(".xml");
auto req = QNetworkRequest(QUrl(url));
- req.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
- QNetworkRequest::NoLessSafeRedirectPolicy);
+ req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
auto reply = d->nam->get(req);
- QObject::connect(reply, &QNetworkReply::finished, this, [=]() { d->definitionListDownloadFinished(reply); });
+ QObject::connect(reply, &QNetworkReply::finished, this, [=]() {
+ d->definitionListDownloadFinished(reply);
+ });
}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/format.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/format.cpp
index 716ff197af..4bd25fb31c 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/format.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/format.cpp
@@ -19,7 +19,7 @@
using namespace KSyntaxHighlighting;
-static Theme::TextStyle stringToDefaultFormat(const QStringView &str)
+static Theme::TextStyle stringToDefaultFormat(const QStringRef &str)
{
if (!str.startsWith(QLatin1String("ds")))
return Theme::Normal;
@@ -98,13 +98,16 @@ Theme::TextStyle Format::textStyle() const
bool Format::isDefaultTextStyle(const Theme &theme) const
{
// use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
- return (!hasTextColor(theme)) && (!hasBackgroundColor(theme)) && (selectedTextColor(theme) == theme.selectedTextColor(Theme::Normal)) && (selectedBackgroundColor(theme) == QColor::fromRgba(theme.selectedBackgroundColor(Theme::Normal))) &&
- (isBold(theme) == theme.isBold(Theme::Normal)) && (isItalic(theme) == theme.isItalic(Theme::Normal)) && (isUnderline(theme) == theme.isUnderline(Theme::Normal)) && (isStrikeThrough(theme) == theme.isStrikeThrough(Theme::Normal));
+ return (!hasTextColor(theme)) && (!hasBackgroundColor(theme)) && (selectedTextColor(theme).rgba() == theme.selectedTextColor(Theme::Normal))
+ && (selectedBackgroundColor(theme).rgba() == (theme.selectedBackgroundColor(Theme::Normal))) && (isBold(theme) == theme.isBold(Theme::Normal))
+ && (isItalic(theme) == theme.isItalic(Theme::Normal)) && (isUnderline(theme) == theme.isUnderline(Theme::Normal))
+ && (isStrikeThrough(theme) == theme.isStrikeThrough(Theme::Normal));
}
bool Format::hasTextColor(const Theme &theme) const
{
- return textColor(theme) != theme.textColor(Theme::Normal) && (d->style.textColor || theme.textColor(d->defaultStyle) || d->styleOverride(theme).textColor);
+ return textColor(theme) != QColor::fromRgba(theme.textColor(Theme::Normal))
+ && (d->style.textColor || theme.textColor(d->defaultStyle) || d->styleOverride(theme).textColor);
}
QColor Format::textColor(const Theme &theme) const
@@ -112,7 +115,7 @@ QColor Format::textColor(const Theme &theme) const
const auto overrideStyle = d->styleOverride(theme);
if (overrideStyle.textColor)
return overrideStyle.textColor;
- return d->style.textColor ? d->style.textColor : theme.textColor(d->defaultStyle);
+ return d->style.textColor ? QColor::fromRgba(d->style.textColor) : QColor::fromRgba(theme.textColor(d->defaultStyle));
}
QColor Format::selectedTextColor(const Theme &theme) const
@@ -120,13 +123,14 @@ QColor Format::selectedTextColor(const Theme &theme) const
const auto overrideStyle = d->styleOverride(theme);
if (overrideStyle.selectedTextColor)
return overrideStyle.selectedTextColor;
- return d->style.selectedTextColor ? d->style.selectedTextColor : theme.selectedTextColor(d->defaultStyle);
+ return d->style.selectedTextColor ? QColor::fromRgba(d->style.selectedTextColor) : QColor::fromRgba(theme.selectedTextColor(d->defaultStyle));
}
bool Format::hasBackgroundColor(const Theme &theme) const
{
// use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
- return backgroundColor(theme) != QColor::fromRgba(theme.backgroundColor(Theme::Normal)) && (d->style.backgroundColor || theme.backgroundColor(d->defaultStyle) || d->styleOverride(theme).backgroundColor);
+ return backgroundColor(theme) != QColor::fromRgba(theme.backgroundColor(Theme::Normal))
+ && (d->style.backgroundColor || theme.backgroundColor(d->defaultStyle) || d->styleOverride(theme).backgroundColor);
}
QColor Format::backgroundColor(const Theme &theme) const
@@ -136,7 +140,7 @@ QColor Format::backgroundColor(const Theme &theme) const
return overrideStyle.backgroundColor;
// use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
- return d->style.backgroundColor ? d->style.backgroundColor : QColor::fromRgba(theme.backgroundColor(d->defaultStyle));
+ return d->style.backgroundColor ? QColor::fromRgba(d->style.backgroundColor) : QColor::fromRgba(theme.backgroundColor(d->defaultStyle));
}
QColor Format::selectedBackgroundColor(const Theme &theme) const
@@ -146,7 +150,8 @@ QColor Format::selectedBackgroundColor(const Theme &theme) const
return overrideStyle.selectedBackgroundColor;
// use QColor::fromRgba for background QRgb => QColor conversion to avoid unset colors == black!
- return d->style.selectedBackgroundColor ? d->style.selectedBackgroundColor : QColor::fromRgba(theme.selectedBackgroundColor(d->defaultStyle));
+ return d->style.selectedBackgroundColor ? QColor::fromRgba(d->style.selectedBackgroundColor)
+ : QColor::fromRgba(theme.selectedBackgroundColor(d->defaultStyle));
}
bool Format::isBold(const Theme &theme) const
@@ -231,7 +236,7 @@ void FormatPrivate::load(QXmlStreamReader &reader)
name = reader.attributes().value(QLatin1String("name")).toString();
defaultStyle = stringToDefaultFormat(reader.attributes().value(QLatin1String("defStyleNum")));
- QStringView attribute = reader.attributes().value(QLatin1String("color"));
+ QStringRef attribute = reader.attributes().value(QLatin1String("color"));
if (!attribute.isEmpty()) {
style.textColor = QColor(attribute.toString()).rgba();
}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/htmlhighlighter.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/htmlhighlighter.cpp
index a95888f40f..a191fab7e9 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/htmlhighlighter.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/htmlhighlighter.cpp
@@ -44,21 +44,13 @@ void HtmlHighlighter::setOutputFile(const QString &fileName)
return;
}
d->out.reset(new QTextStream(d->file.get()));
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- d->out->setEncoding(QStringConverter::Utf8);
-#else
d->out->setCodec("UTF-8");
-#endif
}
void HtmlHighlighter::setOutputFile(FILE *fileHandle)
{
d->out.reset(new QTextStream(fileHandle, QIODevice::WriteOnly));
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- d->out->setEncoding(QStringConverter::Utf8);
-#else
d->out->setCodec("UTF-8");
-#endif
}
void HtmlHighlighter::highlightFile(const QString &fileName, const QString &title)
@@ -76,6 +68,33 @@ void HtmlHighlighter::highlightFile(const QString &fileName, const QString &titl
highlightData(&f, title);
}
+/**
+ * @brief toHtmlRgba
+ * Converts QColor -> rgba(r, g, b, a) if there is an alpha channel
+ * otherwise it will just return the hexcode. This is because QColor
+ * outputs #AARRGGBB, whereas browser support #RRGGBBAA.
+ *
+ * @param color
+ * @return
+ */
+static QString toHtmlRgbaString(const QColor &color)
+{
+ if (color.alpha() == 0xFF)
+ return color.name();
+
+ QString rgba = QStringLiteral("rgba(");
+ rgba.append(QString::number(color.red()));
+ rgba.append(QLatin1Char(','));
+ rgba.append(QString::number(color.green()));
+ rgba.append(QLatin1Char(','));
+ rgba.append(QString::number(color.blue()));
+ rgba.append(QLatin1Char(','));
+ // this must be alphaF
+ rgba.append(QString::number(color.alphaF()));
+ rgba.append(QLatin1Char(')'));
+ return rgba;
+}
+
void HtmlHighlighter::highlightData(QIODevice *dev, const QString &title)
{
if (!d->out) {
@@ -94,19 +113,16 @@ void HtmlHighlighter::highlightData(QIODevice *dev, const QString &title)
*d->out << "<html><head>\n";
*d->out << "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n";
*d->out << "<title>" << htmlTitle << "</title>\n";
- *d->out << "<meta name=\"generator\" content=\"KF5::SyntaxHighlighting - Definition (" << definition().name() << ") - Theme (" << theme().name() << ")\"/>\n";
+ *d->out << "<meta name=\"generator\" content=\"KF5::SyntaxHighlighting - Definition (" << definition().name() << ") - Theme (" << theme().name()
+ << ")\"/>\n";
*d->out << "</head><body";
- *d->out << " style=\"background-color:" << QColor(theme().editorColor(Theme::BackgroundColor)).name();
+ *d->out << " style=\"background-color:" << toHtmlRgbaString(QColor::fromRgba(theme().editorColor(Theme::BackgroundColor)));
if (theme().textColor(Theme::Normal))
- *d->out << ";color:" << QColor(theme().textColor(Theme::Normal)).name();
+ *d->out << ";color:" << toHtmlRgbaString(QColor::fromRgba(theme().textColor(Theme::Normal)));
*d->out << "\"><pre>\n";
QTextStream in(dev);
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- in.setEncoding(QStringConverter::Utf8);
-#else
in.setCodec("UTF-8");
-#endif
while (!in.atEnd()) {
d->currentLine = in.readLine();
state = highlightLine(d->currentLine, state);
@@ -128,9 +144,9 @@ void HtmlHighlighter::applyFormat(int offset, int length, const Format &format)
// collect potential output, cheaper than thinking about "is there any?"
QVarLengthArray<QString, 16> formatOutput;
if (format.hasTextColor(theme()))
- formatOutput << QStringLiteral("color:") << format.textColor(theme()).name() << QStringLiteral(";");
+ formatOutput << QStringLiteral("color:") << toHtmlRgbaString(format.textColor(theme())) << QStringLiteral(";");
if (format.hasBackgroundColor(theme()))
- formatOutput << QStringLiteral("background-color:") << format.backgroundColor(theme()).name() << QStringLiteral(";");
+ formatOutput << QStringLiteral("background-color:") << toHtmlRgbaString(format.backgroundColor(theme())) << QStringLiteral(";");
if (format.isBold(theme()))
formatOutput << QStringLiteral("font-weight:bold;");
if (format.isItalic(theme()))
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist.cpp
index b599ce29a1..b763fd7505 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist.cpp
@@ -16,7 +16,7 @@
using namespace KSyntaxHighlighting;
-bool KeywordList::contains(const QStringView &str, Qt::CaseSensitivity caseSensitive) const
+bool KeywordList::contains(const QStringRef &str, Qt::CaseSensitivity caseSensitive) const
{
/**
* get right vector to search in
@@ -26,7 +26,9 @@ bool KeywordList::contains(const QStringView &str, Qt::CaseSensitivity caseSensi
/**
* search with right predicate
*/
- return std::binary_search(vectorToSearch.begin(), vectorToSearch.end(), QStringView(str), [caseSensitive](const QStringView &a, const QStringView &b) { return a.compare(b, caseSensitive) < 0; });
+ return std::binary_search(vectorToSearch.begin(), vectorToSearch.end(), QStringView(str), [caseSensitive](const QStringView &a, const QStringView &b) {
+ return a.compare(b, caseSensitive) < 0;
+ });
}
void KeywordList::load(QXmlStreamReader &reader)
@@ -90,7 +92,9 @@ void KeywordList::initLookupForCaseSensitivity(Qt::CaseSensitivity caseSensitive
/**
* sort with right predicate
*/
- std::sort(vectorToSort.begin(), vectorToSort.end(), [caseSensitive](const QStringView &a, const QStringView &b) { return a.compare(b, caseSensitive) < 0; });
+ std::sort(vectorToSort.begin(), vectorToSort.end(), [caseSensitive](const QStringView &a, const QStringView &b) {
+ return a.compare(b, caseSensitive) < 0;
+ });
}
void KeywordList::resolveIncludeKeywords(DefinitionData &def)
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist_p.h
index 68b1ec6290..de5e58804e 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/keywordlist_p.h
@@ -9,8 +9,8 @@
#define KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H
#include <QString>
-#include <QStringView>
#include <QStringList>
+#include <QStringView>
#include <vector>
@@ -53,10 +53,13 @@ public:
}
/** Checks if @p str is a keyword in this list. */
- bool contains(const QStringView &str) const { return contains(str, m_caseSensitive); }
+ bool contains(const QStringRef &str) const
+ {
+ return contains(str, m_caseSensitive);
+ }
/** Checks if @p str is a keyword in this list, overriding the global case-sensitivity setting. */
- bool contains(const QStringView &str, Qt::CaseSensitivity caseSensitive) const;
+ bool contains(const QStringRef &str, Qt::CaseSensitivity caseSensitive) const;
void load(QXmlStreamReader &reader);
void setCaseSensitivity(Qt::CaseSensitivity caseSensitive);
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/repository.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/repository.cpp
index 2252bc67d2..1e3191a7bc 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/repository.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/repository.cpp
@@ -18,6 +18,7 @@
#include <QDirIterator>
#include <QFile>
#include <QFileInfo>
+#include <QPalette>
#ifndef NO_STANDARD_PATHS
#include <QStandardPaths>
@@ -62,7 +63,9 @@ Definition Repository::definitionForName(const QString &defName) const
static void sortDefinitions(QVector<Definition> &definitions)
{
- std::stable_sort(definitions.begin(), definitions.end(), [](const Definition &lhs, const Definition &rhs) { return lhs.priority() > rhs.priority(); });
+ std::stable_sort(definitions.begin(), definitions.end(), [](const Definition &lhs, const Definition &rhs) {
+ return lhs.priority() > rhs.priority();
+ });
}
Definition Repository::definitionForFileName(const QString &fileName) const
@@ -133,13 +136,52 @@ Theme Repository::theme(const QString &themeName) const
return Theme();
}
-Theme Repository::defaultTheme(Repository::DefaultTheme t)
+Theme Repository::defaultTheme(Repository::DefaultTheme t) const
{
if (t == DarkTheme)
return theme(QLatin1String("Breeze Dark"));
return theme(QLatin1String("Breeze Light"));
}
+Theme Repository::defaultTheme(Repository::DefaultTheme t)
+{
+ return qAsConst(*this).defaultTheme(t);
+}
+
+Theme Repository::themeForPalette(const QPalette &palette) const
+{
+ const auto base = palette.color(QPalette::Base);
+ const auto themes = d->m_themes;
+
+ // find themes with matching background colors
+ QVector<KSyntaxHighlighting::Theme> matchingThemes;
+ for (const auto &theme : themes) {
+ const auto background = theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::BackgroundColor);
+ if (background == base.rgb()) {
+ matchingThemes.append(theme);
+ }
+ }
+ if (!matchingThemes.empty()) {
+ // if there's multiple, search for one with a matching highlight color
+ const auto highlight = palette.color(QPalette::Highlight);
+ for (const auto &theme : qAsConst(matchingThemes)) {
+ auto selection = theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::TextSelection);
+ if (selection == highlight.rgb()) {
+ return theme;
+ }
+ }
+ return matchingThemes.first();
+ }
+
+ // fallback to just use the default light or dark theme
+ return defaultTheme((base.lightness() < 128) ? KSyntaxHighlighting::Repository::DarkTheme : KSyntaxHighlighting::Repository::LightTheme);
+}
+
+Theme Repository::themeForPalette(const QPalette &palette)
+{
+ return qAsConst(*this).themeForPalette(palette);
+}
+
void RepositoryPrivate::load(Repository *repo)
{
// always add invalid default "None" highlighting
@@ -147,7 +189,8 @@ void RepositoryPrivate::load(Repository *repo)
// do lookup in standard paths, if not disabled
#ifndef NO_STANDARD_PATHS
- for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/syntax"), QStandardPaths::LocateDirectory))
+ for (const auto &dir :
+ QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/syntax"), QStandardPaths::LocateDirectory))
loadSyntaxFolder(repo, dir);
// backward compatibility with Kate
@@ -176,7 +219,8 @@ void RepositoryPrivate::load(Repository *repo)
// do lookup in standard paths, if not disabled
#ifndef NO_STANDARD_PATHS
- for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/themes"), QStandardPaths::LocateDirectory))
+ for (const auto &dir :
+ QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/themes"), QStandardPaths::LocateDirectory))
loadThemeFolder(dir);
#endif
@@ -256,7 +300,9 @@ static int themeRevision(const Theme &theme)
void RepositoryPrivate::addTheme(const Theme &theme)
{
- const auto it = std::lower_bound(m_themes.begin(), m_themes.end(), theme, [](const Theme &lhs, const Theme &rhs) { return lhs.name() < rhs.name(); });
+ const auto it = std::lower_bound(m_themes.begin(), m_themes.end(), theme, [](const Theme &lhs, const Theme &rhs) {
+ return lhs.name() < rhs.name();
+ });
if (it == m_themes.end() || (*it).name() != theme.name()) {
m_themes.insert(it, theme);
return;
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/repository.h b/src/libs/3rdparty/syntax-highlighting/src/lib/repository.h
index 9da4474685..bf91018242 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/repository.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/repository.h
@@ -11,10 +11,11 @@
#include <memory>
#include <qglobal.h>
-#include <qvector.h>
QT_BEGIN_NAMESPACE
class QString;
+template<typename T> class QVector;
+class QPalette;
QT_END_NAMESPACE
/**
@@ -209,10 +210,33 @@ public:
/**
* Returns a default theme instance of the given type.
* The returned Theme is guaranteed to be a valid theme.
+ * @since 5.79
+ */
+ Theme defaultTheme(DefaultTheme t = LightTheme) const;
+
+ /**
+ * Returns a default theme instance of the given type.
+ * The returned Theme is guaranteed to be a valid theme.
+ *
+ * KF6: remove in favor of const variant
*/
Theme defaultTheme(DefaultTheme t = LightTheme);
/**
+ * Returns the best matching theme for the given palette
+ * @since 5.79
+ **/
+ Theme themeForPalette(const QPalette &palette) const;
+
+ /**
+ * Returns the best matching theme for the given palette
+ * @since 5.77
+ *
+ * KF6: remove in favor of const variant
+ **/
+ Theme themeForPalette(const QPalette &palette);
+
+ /**
* Reloads the repository.
* This is a moderately expensive operations and should thus only be
* triggered when the installed syntax definition files changed.
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/rule.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/rule.cpp
index 22f59e0211..98fe756875 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/rule.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/rule.cpp
@@ -10,8 +10,8 @@
#include "definition_p.h"
#include "ksyntaxhighlighting_logging.h"
#include "rule_p.h"
-#include "xml_p.h"
#include "worddelimiters_p.h"
+#include "xml_p.h"
#include <QString>
#include <QXmlStreamReader>
@@ -31,9 +31,7 @@ static bool isOctalChar(QChar c)
static bool isHexChar(QChar c)
{
- return isDigit(c)
- || (c <= QLatin1Char('f') && QLatin1Char('a') <= c)
- || (c <= QLatin1Char('F') && QLatin1Char('A') <= c);
+ return isDigit(c) || (c <= QLatin1Char('f') && QLatin1Char('a') <= c) || (c <= QLatin1Char('F') && QLatin1Char('A') <= c);
}
static int matchEscapedChar(const QString &text, int offset)
@@ -44,9 +42,18 @@ static int matchEscapedChar(const QString &text, int offset)
const auto c = text.at(offset + 1);
switch (c.unicode()) {
// control chars
- case 'a': case 'b': case 'e': case 'f':
- case 'n': case 'r': case 't': case 'v':
- case '"': case '\'': case '?': case '\\':
+ case 'a':
+ case 'b':
+ case 'e':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ case '"':
+ case '\'':
+ case '?':
+ case '\\':
return offset + 2;
// hex encoded character
@@ -59,8 +66,14 @@ static int matchEscapedChar(const QString &text, int offset)
return offset;
// octal encoding, simple \0 is OK, too, unlike simple \x above
- case '0': case '1': case '2': case '3':
- case '4': case '5': case '6': case '7':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
if (offset + 2 < text.size() && isOctalChar(text.at(offset + 2))) {
if (offset + 3 < text.size() && isOctalChar(text.at(offset + 3)))
return offset + 4;
@@ -81,6 +94,13 @@ static QString replaceCaptures(const QString &pattern, const QStringList &captur
return result;
}
+Rule::~Rule()
+{
+ if (!m_additionalDeliminator.isEmpty() || !m_weakDeliminator.isEmpty()) {
+ delete m_wordDelimiters;
+ }
+}
+
Definition Rule::definition() const
{
return m_def.definition();
@@ -124,12 +144,17 @@ bool Rule::load(QXmlStreamReader &reader)
void Rule::resolveContext()
{
- auto const& def = m_def.definition();
+ auto const &def = m_def.definition();
m_context.resolve(def);
// cache for DefinitionData::wordDelimiters, is accessed VERY often
m_wordDelimiters = &DefinitionData::get(def)->wordDelimiters;
+ if (!m_additionalDeliminator.isEmpty() || !m_weakDeliminator.isEmpty()) {
+ m_wordDelimiters = new WordDelimiters(*m_wordDelimiters);
+ m_wordDelimiters->append(m_additionalDeliminator);
+ m_wordDelimiters->remove(m_weakDeliminator);
+ }
}
void Rule::resolveAttributeFormat(Context *lookupContext)
@@ -151,7 +176,13 @@ bool Rule::doLoad(QXmlStreamReader &reader)
return true;
}
-Rule::Ptr Rule::create(const QStringView &name)
+void Rule::loadAdditionalWordDelimiters(QXmlStreamReader &reader)
+{
+ m_additionalDeliminator = reader.attributes().value(QLatin1String("additionalDeliminator")).toString();
+ m_weakDeliminator = reader.attributes().value(QLatin1String("weakDeliminator")).toString();
+}
+
+Rule::Ptr Rule::create(const QStringRef &name)
{
if (name == QLatin1String("AnyChar"))
return std::make_shared<AnyChar>();
@@ -283,6 +314,12 @@ MatchResult DetectSpaces::doMatch(const QString &text, int offset, const QString
return offset;
}
+bool Float::doLoad(QXmlStreamReader &reader)
+{
+ loadAdditionalWordDelimiters(reader);
+ return true;
+}
+
MatchResult Float::doMatch(const QString &text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1)))
@@ -344,6 +381,12 @@ MatchResult HlCChar::doMatch(const QString &text, int offset, const QStringList
return offset;
}
+bool HlCHex::doLoad(QXmlStreamReader &reader)
+{
+ loadAdditionalWordDelimiters(reader);
+ return true;
+}
+
MatchResult HlCHex::doMatch(const QString &text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1)))
@@ -367,6 +410,12 @@ MatchResult HlCHex::doMatch(const QString &text, int offset, const QStringList &
return offset;
}
+bool HlCOct::doLoad(QXmlStreamReader &reader)
+{
+ loadAdditionalWordDelimiters(reader);
+ return true;
+}
+
MatchResult HlCOct::doMatch(const QString &text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1)))
@@ -411,11 +460,7 @@ bool IncludeRules::includeAttribute() const
bool IncludeRules::doLoad(QXmlStreamReader &reader)
{
const auto s = reader.attributes().value(QLatin1String("context"));
-#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
- const auto split = s.split(QLatin1String("##"), QString::KeepEmptyParts);
-#else
- const auto split = s.split(QString::fromLatin1("##"), Qt::KeepEmptyParts);
-#endif
+ const auto split = s.split(QLatin1String("##"), Qt::KeepEmptyParts);
if (split.isEmpty())
return false;
m_contextName = split.at(0).toString();
@@ -433,6 +478,12 @@ MatchResult IncludeRules::doMatch(const QString &text, int offset, const QString
return offset;
}
+bool Int::doLoad(QXmlStreamReader &reader)
+{
+ loadAdditionalWordDelimiters(reader);
+ return true;
+}
+
MatchResult Int::doMatch(const QString &text, int offset, const QStringList &) const
{
if (offset > 0 && !isWordDelimiter(text.at(offset - 1)))
@@ -466,6 +517,8 @@ bool KeywordListRule::doLoad(QXmlStreamReader &reader)
m_hasCaseSensitivityOverride = false;
}
+ loadAdditionalWordDelimiters(reader);
+
return !m_keywordList->isEmpty();
}
@@ -478,11 +531,10 @@ MatchResult KeywordListRule::doMatch(const QString &text, int offset, const QStr
return offset;
if (m_hasCaseSensitivityOverride) {
- if (m_keywordList->contains(QStringView(text).mid(offset, newOffset - offset),
- m_caseSensitivityOverride))
+ if (m_keywordList->contains(text.midRef(offset, newOffset - offset), m_caseSensitivityOverride))
return newOffset;
} else {
- if (m_keywordList->contains(QStringView(text).mid(offset, newOffset - offset)))
+ if (m_keywordList->contains(text.midRef(offset, newOffset - offset)))
return newOffset;
}
@@ -540,21 +592,52 @@ bool RegExpr::doLoad(QXmlStreamReader &reader)
const auto isMinimal = Xml::attrToBool(reader.attributes().value(QLatin1String("minimal")));
const auto isCaseInsensitive = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive")));
- m_regexp.setPatternOptions((isMinimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption) | (isCaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption));
+ m_regexp.setPatternOptions((isMinimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption)
+ | (isCaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption)
+ // DontCaptureOption is removed by resolvePostProcessing() when necessary
+ | QRegularExpression::DontCaptureOption);
- // optimize the pattern for the non-dynamic case, we use them OFTEN
m_dynamic = Xml::attrToBool(reader.attributes().value(QLatin1String("dynamic")));
+
+ return !m_regexp.pattern().isEmpty();
+}
+
+void KSyntaxHighlighting::RegExpr::resolvePostProcessing()
+{
+ if (m_isResolved)
+ return;
+
+ m_isResolved = true;
+ bool hasCapture = false;
+
+ // disable DontCaptureOption when reference a context with dynamic rule
+ if (auto *ctx = context().context()) {
+ for (const Rule::Ptr &rule : ctx->rules()) {
+ if (rule->isDynamic()) {
+ hasCapture = true;
+ m_regexp.setPatternOptions(m_regexp.patternOptions() & ~QRegularExpression::DontCaptureOption);
+ break;
+ }
+ }
+ }
+
+ // optimize the pattern for the non-dynamic case, we use them OFTEN
if (!m_dynamic) {
m_regexp.optimize();
}
- // always using m_regexp.isValid() would be better, but parses the regexp and thus is way too expensive for release builds
+ bool isValid = m_regexp.isValid();
+ if (!isValid) {
+ // DontCaptureOption with back reference capture is an error, remove this option then try again
+ if (!hasCapture) {
+ m_regexp.setPatternOptions(m_regexp.patternOptions() & ~QRegularExpression::DontCaptureOption);
+ isValid = m_regexp.isValid();
+ }
- if (Log().isDebugEnabled()) {
- if (!m_regexp.isValid())
+ if (!isValid) {
qCDebug(Log) << "Invalid regexp:" << m_regexp.pattern();
+ }
}
- return !m_regexp.pattern().isEmpty();
}
MatchResult RegExpr::doMatch(const QString &text, int offset, const QStringList &captures) const
@@ -606,8 +689,7 @@ MatchResult StringDetect::doMatch(const QString &text, int offset, const QString
*/
const auto &pattern = m_dynamic ? replaceCaptures(m_string, captures, false) : m_string;
- if (offset + pattern.size() <= text.size()
- && QStringView(text).mid(offset, pattern.size()).compare(pattern, m_caseSensitivity) == 0)
+ if (text.midRef(offset, pattern.size()).compare(pattern, m_caseSensitivity) == 0)
return offset + pattern.size();
return offset;
}
@@ -616,6 +698,7 @@ bool WordDetect::doLoad(QXmlStreamReader &reader)
{
m_word = reader.attributes().value(QLatin1String("String")).toString();
m_caseSensitivity = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive;
+ loadAdditionalWordDelimiters(reader);
return !m_word.isEmpty();
}
@@ -631,7 +714,7 @@ MatchResult WordDetect::doMatch(const QString &text, int offset, const QStringLi
if (offset > 0 && !isWordDelimiter(text.at(offset - 1)) && !isWordDelimiter(text.at(offset)))
return offset;
- if (QStringView(text).mid(offset, m_word.size()).compare(m_word, m_caseSensitivity) != 0)
+ if (text.midRef(offset, m_word.size()).compare(m_word, m_caseSensitivity) != 0)
return offset;
if (text.size() == offset + m_word.size() || isWordDelimiter(text.at(offset + m_word.size())) || isWordDelimiter(text.at(offset + m_word.size() - 1)))
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/rule_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/rule_p.h
index 788aecdad3..678f47af17 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/rule_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/rule_p.h
@@ -33,7 +33,7 @@ class Rule
{
public:
Rule() = default;
- virtual ~Rule() = default;
+ virtual ~Rule();
typedef std::shared_ptr<Rule> Ptr;
@@ -83,16 +83,21 @@ public:
bool load(QXmlStreamReader &reader);
void resolveContext();
void resolveAttributeFormat(Context *lookupContext);
+ virtual void resolvePostProcessing()
+ {
+ }
virtual MatchResult doMatch(const QString &text, int offset, const QStringList &captures) const = 0;
- static Rule::Ptr create(const QStringView &name);
+ static Rule::Ptr create(const QStringRef &name);
protected:
virtual bool doLoad(QXmlStreamReader &reader);
bool isWordDelimiter(QChar c) const;
+ void loadAdditionalWordDelimiters(QXmlStreamReader &reader);
+
private:
Q_DISABLE_COPY(Rule)
@@ -107,7 +112,10 @@ private:
bool m_lookAhead = false;
// cache for DefinitionData::wordDelimiters, is accessed VERY often
- WordDelimiters* m_wordDelimiters = nullptr;
+ WordDelimiters *m_wordDelimiters = nullptr;
+
+ QString m_additionalDeliminator;
+ QString m_weakDeliminator;
protected:
bool m_dynamic = false;
@@ -160,6 +168,7 @@ protected:
class Float : public Rule
{
protected:
+ bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(const QString &text, int offset, const QStringList &) const override;
};
@@ -183,6 +192,7 @@ private:
class Int : public Rule
{
protected:
+ bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(const QString &text, int offset, const QStringList &captures) const override;
};
@@ -195,12 +205,14 @@ protected:
class HlCHex : public Rule
{
protected:
+ bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(const QString &text, int offset, const QStringList &) const override;
};
class HlCOct : public Rule
{
protected:
+ bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(const QString &text, int offset, const QStringList &) const override;
};
@@ -246,11 +258,13 @@ private:
class RegExpr : public Rule
{
protected:
+ void resolvePostProcessing() override;
bool doLoad(QXmlStreamReader &reader) override;
MatchResult doMatch(const QString &text, int offset, const QStringList &captures) const override;
private:
QRegularExpression m_regexp;
+ bool m_isResolved = false;
};
class StringDetect : public Rule
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/state.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/state.cpp
index f9b4f4b4ab..ea5bd36e4f 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/state.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/state.cpp
@@ -56,7 +56,7 @@ bool StateData::pop(int popCount)
// keep the initial context alive in any case
Q_ASSERT(!isEmpty());
const bool initialContextSurvived = m_contextStack.size() > popCount;
- m_contextStack.resize(std::max(1, int(m_contextStack.size()) - popCount));
+ m_contextStack.resize(std::max(1, m_contextStack.size() - popCount));
return initialContextSurvived;
}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/state_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/state_p.h
index d76f84c93b..80bf8f4b02 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/state_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/state_p.h
@@ -13,6 +13,10 @@
#include "definitionref_p.h"
+QT_BEGIN_NAMESPACE
+class QStringList;
+QT_END_NAMESPACE
+
namespace KSyntaxHighlighting
{
class Context;
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/theme.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/theme.cpp
index 56b244a8ee..beb1e87797 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/theme.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/theme.cpp
@@ -47,7 +47,7 @@ QString Theme::name() const
QString Theme::translatedName() const
{
- return m_data ? QCoreApplication::translate("Theme", m_data->name().toUtf8().constData()) : QString();
+ return m_data ? QCoreApplication::instance()->translate("Theme", m_data->name().toUtf8().constData()) : QString();
}
bool Theme::isReadOnly() const
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/themedata.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/themedata.cpp
index f8283b4456..2919a31a6e 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/themedata.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/themedata.cpp
@@ -30,7 +30,7 @@ ThemeData::ThemeData()
/**
* Convert QJsonValue @p val into a color, if possible. Valid colors are only
- * in hex format: #rrggbb. On error, returns 0x00000000.
+ * in hex format: #aarrggbb. On error, returns 0x00000000.
*/
static inline QRgb readColor(const QJsonValue &val)
{
@@ -43,7 +43,7 @@ static inline QRgb readColor(const QJsonValue &val)
return unsetColor;
}
const QColor color(str);
- return color.isValid() ? color.rgb() : unsetColor;
+ return color.isValid() ? color.rgba() : unsetColor;
}
static inline TextStyleData readThemeData(const QJsonObject &obj)
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters.cpp b/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters.cpp
index 71cf73ca33..634eeb70bb 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters.cpp
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters.cpp
@@ -11,7 +11,7 @@ using namespace KSyntaxHighlighting;
WordDelimiters::WordDelimiters()
: asciiDelimiters{}
{
- for(const char *p = "\t !%&()*+,-./:;<=>?[\\]^{|}~"; *p; ++p)
+ for (const char *p = "\t !%&()*+,-./:;<=>?[\\]^{|}~"; *p; ++p)
// int(*p) fix -Wchar-subscripts
asciiDelimiters[int(*p)] = true;
}
@@ -24,20 +24,24 @@ bool WordDelimiters::contains(QChar c) const
return notAsciiDelimiters.contains(c);
}
-void WordDelimiters::append(QChar c)
+void WordDelimiters::append(QStringView s)
{
- if (c.unicode() < 128) {
- asciiDelimiters[c.unicode()] = true;
- } else {
- notAsciiDelimiters.append(c);
+ for (QChar c : s) {
+ if (c.unicode() < 128) {
+ asciiDelimiters[c.unicode()] = true;
+ } else {
+ notAsciiDelimiters.append(c);
+ }
}
}
-void WordDelimiters::remove(QChar c)
+void WordDelimiters::remove(QStringView s)
{
- if (c.unicode() < 128) {
- asciiDelimiters[c.unicode()] = false;
- } else {
- notAsciiDelimiters.remove(c);
+ for (QChar c : s) {
+ if (c.unicode() < 128) {
+ asciiDelimiters[c.unicode()] = false;
+ } else {
+ notAsciiDelimiters.remove(c);
+ }
}
}
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters_p.h
index 51708fc13f..3fa5db10a4 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/worddelimiters_p.h
@@ -30,14 +30,14 @@ public:
bool contains(QChar c) const;
/**
- * Appends the character @p c to word delimiter.
+ * Appends each character of @p s to word delimiters.
*/
- void append(QChar c);
+ void append(QStringView s);
/**
- * Removes the character @p c from word delimiters.
+ * Removes each character of @p s from word delimiters.
*/
- void remove(QChar c);
+ void remove(QStringView c);
private:
/**
diff --git a/src/libs/3rdparty/syntax-highlighting/src/lib/xml_p.h b/src/libs/3rdparty/syntax-highlighting/src/lib/xml_p.h
index eddf97f218..65f46a6abc 100644
--- a/src/libs/3rdparty/syntax-highlighting/src/lib/xml_p.h
+++ b/src/libs/3rdparty/syntax-highlighting/src/lib/xml_p.h
@@ -15,9 +15,9 @@ namespace KSyntaxHighlighting
namespace Xml
{
/** Parse a xs:boolean attribute. */
-inline bool attrToBool(const QStringView &str)
+inline bool attrToBool(const QStringRef &str)
{
- return str == QLatin1String("1") || str.compare(QString("true"), Qt::CaseInsensitive) == 0;
+ return str == QLatin1String("1") || str.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0;
}
}