diff --git a/src/lib/context.cpp b/src/lib/context.cpp index a7a0760..95b3e80 100644 --- a/src/lib/context.cpp +++ b/src/lib/context.cpp @@ -1,232 +1,221 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "context_p.h" #include "definition_p.h" #include "format.h" #include "repository.h" #include "rule_p.h" #include "ksyntaxhighlighting_logging.h" #include "xml_p.h" #include #include using namespace KSyntaxHighlighting; -Context::Context() - : m_resolveState(Unknown) - , m_fallthrough(false) - , m_noIndentationBasedFolding(false) -{ -} - -Context::~Context() -{ -} - Definition Context::definition() const { return m_def.definition(); } void Context::setDefinition(const DefinitionRef &def) { m_def = def; } QString Context::name() const { return m_name; } QString Context::attribute() const { return m_attribute; } ContextSwitch Context::lineEndContext() const { return m_lineEndContext; } ContextSwitch Context::lineEmptyContext() const { return m_lineEmptyContext; } bool Context::fallthrough() const { return m_fallthrough; } ContextSwitch Context::fallthroughContext() const { return m_fallthroughContext; } bool Context::indentationBasedFoldingEnabled() const { if (m_noIndentationBasedFolding) return false; return m_def.definition().indentationBasedFoldingEnabled(); } QVector Context::rules() const { return m_rules; } Format Context::formatByName(const QString &name) const { auto defData = DefinitionData::get(m_def.definition()); auto format = defData->formatByName(name); if (format.isValid()) return format; // TODO we can avoid multiple lookups in the same definition here, many rules will share definitions foreach (const auto &rule, m_rules) { auto defData = DefinitionData::get(rule->definition()); format = defData->formatByName(name); if (format.isValid()) return format; } qCWarning(Log) << "Unknown format" << name << "in context" << m_name << "of definition" << m_def.definition().name(); return format; } void Context::load(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("context")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); m_name = reader.attributes().value(QStringLiteral("name")).toString(); m_attribute = reader.attributes().value(QStringLiteral("attribute")).toString(); m_lineEndContext.parse(reader.attributes().value(QStringLiteral("lineEndContext"))); m_lineEmptyContext.parse(reader.attributes().value(QStringLiteral("lineEmptyContext"))); m_fallthrough = Xml::attrToBool(reader.attributes().value(QStringLiteral("fallthrough"))); m_fallthroughContext.parse(reader.attributes().value(QStringLiteral("fallthroughContext"))); if (m_fallthroughContext.isStay()) m_fallthrough = false; m_noIndentationBasedFolding = Xml::attrToBool(reader.attributes().value(QStringLiteral("noIndentationBasedFolding"))); reader.readNext(); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: { auto rule = Rule::create(reader.name()); if (rule) { rule->setDefinition(m_def.definition()); if (rule->load(reader)) m_rules.push_back(rule); } else { reader.skipCurrentElement(); } reader.readNext(); break; } case QXmlStreamReader::EndElement: return; default: reader.readNext(); break; } } } void Context::resolveContexts() { const auto def = m_def.definition(); m_lineEndContext.resolve(def); m_lineEmptyContext.resolve(def); m_fallthroughContext.resolve(def); foreach (const auto &rule, m_rules) rule->resolveContext(); } Context::ResolveState Context::resolveState() { if (m_resolveState == Unknown) { foreach (const auto &rule, m_rules) { auto inc = std::dynamic_pointer_cast(rule); if (inc) { m_resolveState = Unresolved; return m_resolveState; } } m_resolveState = Resolved; } return m_resolveState; } void Context::resolveIncludes() { if (resolveState() == Resolved) return; if (resolveState() == Resolving) { qCWarning(Log) << "Cyclic dependency!"; return; } Q_ASSERT(resolveState() == Unresolved); m_resolveState = Resolving; // cycle guard for (auto it = m_rules.begin(); it != m_rules.end();) { auto inc = std::dynamic_pointer_cast(*it); if (!inc) { ++it; continue; } Context* context = nullptr; auto myDefData = DefinitionData::get(m_def.definition()); if (inc->definitionName().isEmpty()) { // local include context = myDefData->contextByName(inc->contextName()); } else { auto def = myDefData->repo->definitionForName(inc->definitionName()); if (!def.isValid()) { qCWarning(Log) << "Unable to resolve external include rule for definition" << inc->definitionName() << "in" << m_def.definition().name(); ++it; continue; } auto defData = DefinitionData::get(def); defData->load(); if (inc->contextName().isEmpty()) context = defData->initialContext(); else 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(); ++it; continue; } context->resolveIncludes(); if (inc->includeAttribute()) { m_attribute = context->attribute(); } it = m_rules.erase(it); foreach (const auto &rule, context->rules()) { it = m_rules.insert(it, rule); ++it; } } m_resolveState = Resolved; } diff --git a/src/lib/context_p.h b/src/lib/context_p.h index 9752c69..f5883d7 100644 --- a/src/lib/context_p.h +++ b/src/lib/context_p.h @@ -1,94 +1,94 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_CONTEXT_P_H #define KSYNTAXHIGHLIGHTING_CONTEXT_P_H #include "rule_p.h" #include "contextswitch_p.h" #include "definition.h" #include "definitionref_p.h" #include #include class QXmlStreamReader; namespace KSyntaxHighlighting { class Context { public: - Context(); - ~Context(); + Context() = default; + ~Context() = default; Definition definition() const; void setDefinition(const DefinitionRef &def); QString name() const; QString attribute() const; ContextSwitch lineEndContext() const; ContextSwitch lineEmptyContext() const; bool fallthrough() const; ContextSwitch fallthroughContext() const; /** * Returns @c true, when indentationBasedFolding is enabled for the * associated Definition and when "noIndentationBasedFolding" is NOT set. */ bool indentationBasedFoldingEnabled() const; QVector rules() const; /** Attempts to find the format named @p name in the * enclosing definition, or the source definition of any * included rule. */ Format formatByName(const QString &name) const; void load(QXmlStreamReader &reader); void resolveContexts(); void resolveIncludes(); private: Q_DISABLE_COPY(Context) enum ResolveState { Unknown, Unresolved, Resolving, Resolved }; ResolveState resolveState(); DefinitionRef m_def; QString m_name; QString m_attribute; ContextSwitch m_lineEndContext; ContextSwitch m_lineEmptyContext; ContextSwitch m_fallthroughContext; QVector m_rules; - ResolveState m_resolveState; - bool m_fallthrough; - bool m_noIndentationBasedFolding; + ResolveState m_resolveState = Unknown; + bool m_fallthrough = false; + bool m_noIndentationBasedFolding = false; }; } #endif // KSYNTAXHIGHLIGHTING_CONTEXT_P_H diff --git a/src/lib/contextswitch.cpp b/src/lib/contextswitch.cpp index 1e8f6d2..00b9481 100644 --- a/src/lib/contextswitch.cpp +++ b/src/lib/contextswitch.cpp @@ -1,94 +1,84 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "contextswitch_p.h" #include "definition.h" #include "definition_p.h" #include "repository.h" #include "ksyntaxhighlighting_logging.h" using namespace KSyntaxHighlighting; -ContextSwitch::ContextSwitch() : - m_context(nullptr), - m_popCount(0) -{ -} - -ContextSwitch::~ContextSwitch() -{ -} - bool ContextSwitch::isStay() const { return m_popCount == 0 && !m_context && m_contextName.isEmpty() && m_defName.isEmpty(); } int ContextSwitch::popCount() const { return m_popCount; } Context* ContextSwitch::context() const { return m_context; } void ContextSwitch::parse(const QStringRef& contextInstr) { if (contextInstr.isEmpty() || contextInstr == QLatin1String("#stay")) return; if (contextInstr.startsWith(QLatin1String("#pop!"))) { ++m_popCount; m_contextName = contextInstr.mid(5).toString(); return; } if (contextInstr.startsWith(QLatin1String("#pop"))) { ++m_popCount; parse(contextInstr.mid(4)); return; } const auto idx = contextInstr.indexOf(QLatin1String("##")); if (idx >= 0) { m_contextName = contextInstr.left(idx).toString(); m_defName = contextInstr.mid(idx + 2).toString(); } else { m_contextName = contextInstr.toString(); } } void ContextSwitch::resolve(const Definition &def) { auto d = def; if (!m_defName.isEmpty()) { d = DefinitionData::get(def)->repo->definitionForName(m_defName); auto data = DefinitionData::get(d); data->load(); if (m_contextName.isEmpty()) m_context = data->initialContext(); } if (!m_contextName.isEmpty()) { m_context = DefinitionData::get(d)->contextByName(m_contextName); if (!m_context) qCWarning(Log) << "cannot find context" << m_contextName << "in" << def.name(); } } diff --git a/src/lib/contextswitch_p.h b/src/lib/contextswitch_p.h index c9916c1..cbbbdeb 100644 --- a/src/lib/contextswitch_p.h +++ b/src/lib/contextswitch_p.h @@ -1,50 +1,50 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_CONTEXTSWITCH_P_H #define KSYNTAXHIGHLIGHTING_CONTEXTSWITCH_P_H #include namespace KSyntaxHighlighting { class Context; class Definition; class ContextSwitch { public: - ContextSwitch(); - ~ContextSwitch(); + ContextSwitch() = default; + ~ContextSwitch() = default; bool isStay() const; int popCount() const; Context* context() const; void parse(const QStringRef &contextInstr); void resolve(const Definition &def); private: QString m_defName; QString m_contextName; - Context *m_context; - int m_popCount; + Context *m_context = nullptr; + int m_popCount = 0; }; } #endif // KSYNTAXHIGHLIGHTING_CONTEXTSWITCH_P_H diff --git a/src/lib/definition.cpp b/src/lib/definition.cpp index 410b21e..fa6f299 100644 --- a/src/lib/definition.cpp +++ b/src/lib/definition.cpp @@ -1,608 +1,602 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "definition.h" #include "definition_p.h" #include "definitionref_p.h" #include "context_p.h" #include "format.h" #include "format_p.h" #include "repository_p.h" #include "rule_p.h" #include "ksyntaxhighlighting_logging.h" #include "ksyntaxhighlighting_version.h" #include "xml_p.h" #include #include #include #include #include #include #include #include #include #include using namespace KSyntaxHighlighting; -DefinitionData::DefinitionData() : - repo(nullptr), - delimiters(QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")), // must be sorted! - indentationBasedFolding(false), - caseSensitive(Qt::CaseSensitive), - version(0.0f), - priority(0), - hidden(false) +DefinitionData::DefinitionData() + : delimiters(QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) // must be sorted! { } DefinitionData::~DefinitionData() { qDeleteAll(contexts); } DefinitionData* DefinitionData::get(const Definition &def) { return def.d.get(); } Definition::Definition() : d(new DefinitionData) { } Definition::Definition(const Definition &other) : d(other.d) { d->q = *this; } Definition::Definition(const std::shared_ptr &dd) : d(dd) { } Definition::~Definition() { } Definition& Definition::operator=(const Definition &rhs) { d = rhs.d; return *this; } bool Definition::operator==(const Definition &other) const { return d->fileName == other.d->fileName; } bool Definition::operator!=(const Definition& other) const { return d->fileName != other.d->fileName; } bool Definition::isValid() const { return d->repo && !d->fileName.isEmpty() && !d->name.isEmpty(); } QString Definition::filePath() const { return d->fileName; } QString Definition::name() const { return d->name; } QString Definition::translatedName() const { return QCoreApplication::instance()->translate("Language", d->name.toUtf8().constData()); } QString Definition::section() const { return d->section; } QString Definition::translatedSection() const { return QCoreApplication::instance()->translate("Language Section", d->section.toUtf8().constData()); } QVector Definition::mimeTypes() const { return d->mimetypes; } QVector Definition::extensions() const { return d->extensions; } int Definition::version() const { return d->version; } int Definition::priority() const { return d->priority; } bool Definition::isHidden() const { return d->hidden; } QString Definition::style() const { return d->style; } QString Definition::indenter() const { return d->indenter; } QString Definition::author() const { return d->author; } QString Definition::license() const { return d->license; } bool Definition::indentationBasedFoldingEnabled() const { d->load(); return d->indentationBasedFolding; } QStringList Definition::foldingIgnoreList() const { d->load(); return d->foldingIgnoreList; } QStringList Definition::keywordLists() const { d->load(); return d->keywordLists.keys(); } QStringList Definition::keywordList(const QString& name) const { d->load(); return d->keywordList(name).keywords(); } QVector Definition::formats() const { d->load(); return QVector::fromList(d->formats.values()); } QVector Definition::includedDefinitions() const { d->load(); QVector definitions; QQueue queue; queue.enqueue(*this); while (!queue.isEmpty()) { const auto definition = queue.dequeue(); definitions.push_back(definition); // Iterate all context rules to find associated Definitions. This will // automatically catch other Definitions referenced with IncludeRuldes. foreach (const auto & context, definition.d->contexts) { foreach (const auto &rule, context->rules()) { if ((!definitions.contains(rule->definition())) && (!queue.contains(rule->definition()))) { queue.enqueue(rule->definition()); } } } } return definitions; } Context* DefinitionData::initialContext() const { Q_ASSERT(!contexts.isEmpty()); return contexts.first(); } Context* DefinitionData::contextByName(const QString& name) const { foreach (auto context, contexts) { if (context->name() == name) return context; } return nullptr; } KeywordList DefinitionData::keywordList(const QString& name) const { return keywordLists.value(name); } bool DefinitionData::isDelimiter(QChar c) const { return std::binary_search(delimiters.constBegin(), delimiters.constEnd(), c); } Format DefinitionData::formatByName(const QString& name) const { const auto it = formats.constFind(name); if (it != formats.constEnd()) return it.value(); return Format(); } bool DefinitionData::isLoaded() const { return !contexts.isEmpty(); } bool DefinitionData::load() { if (isLoaded()) return true; Q_ASSERT(!fileName.isEmpty()); QFile file(fileName); if (!file.open(QFile::ReadOnly)) return false; QXmlStreamReader reader(&file); while (!reader.atEnd()) { const auto token = reader.readNext(); if (token != QXmlStreamReader::StartElement) continue; if (reader.name() == QLatin1String("highlighting")) loadHighlighting(reader); else if (reader.name() == QLatin1String("general")) loadGeneral(reader); } for (auto it = keywordLists.begin(); it != keywordLists.end(); ++it) (*it).setCaseSensitivity(caseSensitive); foreach (auto context, contexts) { context->resolveContexts(); context->resolveIncludes(); } Q_ASSERT(std::is_sorted(delimiters.constBegin(), delimiters.constEnd())); return true; } void DefinitionData::clear() { // keep only name and repo, so we can re-lookup to make references persist over repo reloads keywordLists.clear(); qDeleteAll(contexts); contexts.clear(); formats.clear(); fileName.clear(); section.clear(); style.clear(); indenter.clear(); author.clear(); license.clear(); mimetypes.clear(); extensions.clear(); delimiters = QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~"); // must be sorted! caseSensitive = Qt::CaseSensitive; version = 0.0f; priority = 0; hidden = false; } bool DefinitionData::loadMetaData(const QString& definitionFileName) { fileName = definitionFileName; QFile file(definitionFileName); if (!file.open(QFile::ReadOnly)) return false; QXmlStreamReader reader(&file); while (!reader.atEnd()) { const auto token = reader.readNext(); if (token != QXmlStreamReader::StartElement) continue; if (reader.name() == QLatin1String("language")) { return loadLanguage(reader); } } return false; } bool DefinitionData::loadMetaData(const QString &file, const QJsonObject &obj) { name = obj.value(QLatin1String("name")).toString(); section = obj.value(QLatin1String("section")).toString(); version = obj.value(QLatin1String("version")).toInt(); priority = obj.value(QLatin1String("priority")).toInt(); style = obj.value(QLatin1String("style")).toString(); author = obj.value(QLatin1String("author")).toString(); license = obj.value(QLatin1String("license")).toString(); indenter = obj.value(QLatin1String("indenter")).toString(); hidden = obj.value(QLatin1String("hidden")).toBool(); fileName = file; const auto exts = obj.value(QLatin1String("extensions")).toString(); foreach (const auto &ext, exts.split(QLatin1Char(';'), QString::SkipEmptyParts)) extensions.push_back(ext); const auto mts = obj.value(QLatin1String("mimetype")).toString(); foreach (const auto &mt, mts.split(QLatin1Char(';'), QString::SkipEmptyParts)) mimetypes.push_back(mt); return true; } bool DefinitionData::loadLanguage(QXmlStreamReader &reader) { Q_ASSERT(reader.name() == QLatin1String("language")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); if (!checkKateVersion(reader.attributes().value(QStringLiteral("kateversion")))) return false; name = reader.attributes().value(QStringLiteral("name")).toString(); section = reader.attributes().value(QStringLiteral("section")).toString(); // toFloat instead of toInt for backward compatibility with old Kate files version = reader.attributes().value(QStringLiteral("version")).toFloat(); priority = reader.attributes().value(QStringLiteral("priority")).toInt(); hidden = Xml::attrToBool(reader.attributes().value(QStringLiteral("hidden"))); style = reader.attributes().value(QStringLiteral("style")).toString(); indenter = reader.attributes().value(QStringLiteral("indenter")).toString(); author = reader.attributes().value(QStringLiteral("author")).toString(); license = reader.attributes().value(QStringLiteral("license")).toString(); const auto exts = reader.attributes().value(QStringLiteral("extensions")).toString(); foreach (const auto &ext, exts.split(QLatin1Char(';'), QString::SkipEmptyParts)) extensions.push_back(ext); const auto mts = reader.attributes().value(QStringLiteral("mimetype")).toString(); foreach (const auto &mt, mts.split(QLatin1Char(';'), QString::SkipEmptyParts)) mimetypes.push_back(mt); if (reader.attributes().hasAttribute(QStringLiteral("casesensitive"))) caseSensitive = Xml::attrToBool(reader.attributes().value(QStringLiteral("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive; return true; } void DefinitionData::loadHighlighting(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("highlighting")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: if (reader.name() == QLatin1String("list")) { KeywordList keywords; keywords.load(reader); keywordLists.insert(keywords.name(), keywords); } else if (reader.name() == QLatin1String("contexts")) { loadContexts(reader); reader.readNext(); } else if (reader.name() == QLatin1String("itemDatas")) { loadItemData(reader); } else { reader.readNext(); } break; case QXmlStreamReader::EndElement: return; default: reader.readNext(); break; } } } void DefinitionData::loadContexts(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("contexts")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: if (reader.name() == QLatin1String("context")) { auto context = new Context; context->setDefinition(q); context->load(reader); contexts.push_back(context); } reader.readNext(); break; case QXmlStreamReader::EndElement: return; default: reader.readNext(); break; } } } void DefinitionData::loadItemData(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("itemDatas")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: if (reader.name() == QLatin1String("itemData")) { Format f; auto formatData = FormatPrivate::get(f); formatData->definition = q; formatData->load(reader); formatData->id = RepositoryPrivate::get(repo)->nextFormatId(); formats.insert(f.name(), f); reader.readNext(); } reader.readNext(); break; case QXmlStreamReader::EndElement: return; default: reader.readNext(); break; } } } void DefinitionData::loadGeneral(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("general")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); reader.readNext(); // reference counter to count XML child elements, to not return too early int elementRefCounter = 1; while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: ++elementRefCounter; if (reader.name() == QLatin1String("keywords")) { if (reader.attributes().hasAttribute(QStringLiteral("casesensitive"))) caseSensitive = Xml::attrToBool(reader.attributes().value(QStringLiteral("casesensitive"))) ? Qt::CaseSensitive : Qt::CaseInsensitive; delimiters += reader.attributes().value(QStringLiteral("additionalDeliminator")); std::sort(delimiters.begin(), delimiters.end()); auto it = std::unique(delimiters.begin(), delimiters.end()); delimiters.truncate(std::distance(delimiters.begin(), it)); foreach (const auto c, reader.attributes().value(QLatin1String("weakDeliminator"))) delimiters.remove(c); } else if (reader.name() == QLatin1String("folding")) { if (reader.attributes().hasAttribute(QStringLiteral("indentationsensitive"))) indentationBasedFolding = Xml::attrToBool(reader.attributes().value(QStringLiteral("indentationsensitive"))); } else if (reader.name() == QLatin1String("emptyLines")) { loadFoldingIgnoreList(reader); } else { reader.skipCurrentElement(); } reader.readNext(); break; case QXmlStreamReader::EndElement: --elementRefCounter; if (elementRefCounter == 0) return; default: reader.readNext(); break; } } } void DefinitionData::loadFoldingIgnoreList(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("emptyLines")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); reader.readNext(); // reference counter to count XML child elements, to not return too early int elementRefCounter = 1; while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: ++elementRefCounter; if (reader.name() == QLatin1String("emptyLine")) { foldingIgnoreList << reader.attributes().value(QStringLiteral("regexpr")).toString(); } reader.readNext(); break; case QXmlStreamReader::EndElement: --elementRefCounter; if (elementRefCounter == 0) return; default: reader.readNext(); break; } } } 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).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; return false; } return true; } quint16 DefinitionData::foldingRegionId(const QString &foldName) { return RepositoryPrivate::get(repo)->foldingRegionId(name, foldName); } DefinitionRef::DefinitionRef() { } DefinitionRef::DefinitionRef(const Definition &def) : d(def.d) { } DefinitionRef::~DefinitionRef() { } DefinitionRef& DefinitionRef::operator=(const Definition &def) { d = def.d; return *this; } Definition DefinitionRef::definition() const { if (!d.expired()) return Definition(d.lock()); return Definition(); } diff --git a/src/lib/definition_p.h b/src/lib/definition_p.h index c31d37b..c61b0da 100644 --- a/src/lib/definition_p.h +++ b/src/lib/definition_p.h @@ -1,96 +1,96 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_DEFINITION_P_H #define KSYNTAXHIGHLIGHTING_DEFINITION_P_H #include "definitionref_p.h" #include #include #include QT_BEGIN_NAMESPACE class QXmlStreamReader; class QJsonObject; QT_END_NAMESPACE namespace KSyntaxHighlighting { class Definition; class Repository; class DefinitionData { public: DefinitionData(); ~DefinitionData(); static DefinitionData* get(const Definition &def); bool isLoaded() const; bool loadMetaData(const QString &definitionFileName); bool loadMetaData(const QString &fileName, const QJsonObject &obj); void clear(); bool load(); bool loadLanguage(QXmlStreamReader &reader); void loadHighlighting(QXmlStreamReader &reader); void loadContexts(QXmlStreamReader &reader); void loadItemData(QXmlStreamReader &reader); void loadGeneral(QXmlStreamReader &reader); void loadFoldingIgnoreList(QXmlStreamReader &reader); bool checkKateVersion(const QStringRef &verStr); KeywordList keywordList(const QString &name) const; bool isDelimiter(QChar c) const; Context* initialContext() const; Context* contextByName(const QString &name) const; Format formatByName(const QString &name) const; quint16 foldingRegionId(const QString &foldName); DefinitionRef q; - Repository *repo; + Repository *repo = nullptr; QHash keywordLists; QVector contexts; QHash formats; QString delimiters; - bool indentationBasedFolding; + bool indentationBasedFolding = false; QStringList foldingIgnoreList; QString fileName; QString name; QString section; QString style; QString indenter; QString author; QString license; QVector mimetypes; QVector extensions; - Qt::CaseSensitivity caseSensitive; - int version; - int priority; - bool hidden; + Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive; + int version = 0; + int priority = 0; + bool hidden = false; }; } #endif diff --git a/src/lib/format.cpp b/src/lib/format.cpp index 06c3ad0..e2c9166 100644 --- a/src/lib/format.cpp +++ b/src/lib/format.cpp @@ -1,258 +1,251 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "format.h" #include "format_p.h" #include "definition.h" #include "definitionref_p.h" #include "textstyledata_p.h" #include "themedata_p.h" #include "xml_p.h" #include #include #include #include using namespace KSyntaxHighlighting; static Theme::TextStyle stringToDefaultFormat(const QStringRef &str) { if (!str.startsWith(QLatin1String("ds"))) return Theme::Normal; static const auto idx = Theme::staticMetaObject.indexOfEnumerator("TextStyle"); Q_ASSERT(idx >= 0); const auto metaEnum = Theme::staticMetaObject.enumerator(idx); bool ok = false; const auto value = metaEnum.keyToValue(str.mid(2).toLatin1().constData(), &ok); if (!ok || value < 0) return Theme::Normal; return static_cast(value); } -FormatPrivate::FormatPrivate() - : defaultStyle(Theme::Normal) - , id(0) - , spellCheck(true) -{ -} - FormatPrivate* FormatPrivate::get(const Format &format) { return format.d.data(); } TextStyleData FormatPrivate::styleOverride(const Theme &theme) const { const auto themeData = ThemeData::get(theme); if (themeData) return themeData->textStyleOverride(definition.definition().name(), name); return TextStyleData(); } Format::Format() : d(new FormatPrivate) { } Format::Format(const Format &other) : d(other.d) { } Format::~Format() { } Format& Format::operator=(const Format& other) { d = other.d; return *this; } bool Format::isValid() const { return !d->name.isEmpty(); } QString Format::name() const { return d->name; } quint16 Format::id() const { return d->id; } Theme::TextStyle Format::textStyle() const { return d->defaultStyle; } bool Format::isDefaultTextStyle(const Theme &theme) const { return (!hasTextColor(theme)) && (!hasBackgroundColor(theme)) && (selectedTextColor(theme) == theme.selectedTextColor(Theme::Normal)) && (selectedBackgroundColor(theme) == 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 { const auto overrideStyle = d->styleOverride(theme); return textColor(theme) != theme.textColor(Theme::Normal) && (d->style.textColor || theme.textColor(d->defaultStyle) || overrideStyle.textColor); } 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); } 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); } bool Format::hasBackgroundColor(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); return backgroundColor(theme) != theme.backgroundColor(Theme::Normal) && (d->style.backgroundColor || theme.backgroundColor(d->defaultStyle) || overrideStyle.backgroundColor); } QColor Format::backgroundColor(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); if (overrideStyle.backgroundColor) return overrideStyle.backgroundColor; return d->style.backgroundColor ? d->style.backgroundColor : theme.backgroundColor(d->defaultStyle); } QColor Format::selectedBackgroundColor(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); if (overrideStyle.selectedBackgroundColor) return overrideStyle.selectedBackgroundColor; return d->style.selectedBackgroundColor ? d->style.selectedBackgroundColor : theme.selectedBackgroundColor(d->defaultStyle); } bool Format::isBold(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); if (overrideStyle.hasBold) return overrideStyle.bold; return d->style.hasBold ? d->style.bold : theme.isBold(d->defaultStyle); } bool Format::isItalic(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); if (overrideStyle.hasItalic) return overrideStyle.italic; return d->style.hasItalic ? d->style.italic : theme.isItalic(d->defaultStyle); } bool Format::isUnderline(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); if (overrideStyle.hasUnderline) return overrideStyle.underline; return d->style.hasUnderline ? d->style.underline : theme.isUnderline(d->defaultStyle); } bool Format::isStrikeThrough(const Theme &theme) const { const auto overrideStyle = d->styleOverride(theme); if (overrideStyle.hasStrikeThrough) return overrideStyle.strikeThrough; return d->style.hasStrikeThrough ? d->style.strikeThrough : theme.isStrikeThrough(d->defaultStyle); } bool Format::spellCheck() const { return d->spellCheck; } void FormatPrivate::load(QXmlStreamReader& reader) { name = reader.attributes().value(QStringLiteral("name")).toString(); defaultStyle = stringToDefaultFormat(reader.attributes().value(QStringLiteral("defStyleNum"))); QStringRef ref = reader.attributes().value(QStringLiteral("color")); if (!ref.isEmpty()) { style.textColor = QColor(ref.toString()).rgba(); } ref = reader.attributes().value(QStringLiteral("selColor")); if (!ref.isEmpty()) { style.selectedTextColor = QColor(ref.toString()).rgba(); } ref = reader.attributes().value(QStringLiteral("backgroundColor")); if (!ref.isEmpty()) { style.backgroundColor = QColor(ref.toString()).rgba(); } ref = reader.attributes().value(QStringLiteral("selBackgroundColor")); if (!ref.isEmpty()) { style.selectedBackgroundColor = QColor(ref.toString()).rgba(); } ref = reader.attributes().value(QStringLiteral("italic")); if (!ref.isEmpty()) { style.hasItalic = true; style.italic = Xml::attrToBool(ref); } ref = reader.attributes().value(QStringLiteral("bold")); if (!ref.isEmpty()) { style.hasBold = true; style.bold = Xml::attrToBool(ref); } ref = reader.attributes().value(QStringLiteral("underline")); if (!ref.isEmpty()) { style.hasUnderline = true; style.underline = Xml::attrToBool(ref); } ref = reader.attributes().value(QStringLiteral("strikeOut")); if (!ref.isEmpty()) { style.hasStrikeThrough = true; style.strikeThrough = Xml::attrToBool(ref); } ref = reader.attributes().value(QStringLiteral("spellChecking")); if (!ref.isEmpty()) { spellCheck = Xml::attrToBool(ref); } } diff --git a/src/lib/format_p.h b/src/lib/format_p.h index 5648b3e..8d2323f 100644 --- a/src/lib/format_p.h +++ b/src/lib/format_p.h @@ -1,49 +1,49 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_FORMAT_P_H #define KSYNTAXHIGHLIGHTING_FORMAT_P_H #include "definitionref_p.h" #include "textstyledata_p.h" #include "theme.h" #include #include namespace KSyntaxHighlighting { class FormatPrivate : public QSharedData { public: - FormatPrivate(); + FormatPrivate() = default; static FormatPrivate* get(const Format &format); TextStyleData styleOverride(const Theme &theme) const; void load(QXmlStreamReader &reader); DefinitionRef definition; QString name; TextStyleData style; - Theme::TextStyle defaultStyle; - quint16 id; - bool spellCheck; + Theme::TextStyle defaultStyle = Theme::Normal; + quint16 id = 0; + bool spellCheck = true; }; } #endif diff --git a/src/lib/keywordlist.cpp b/src/lib/keywordlist.cpp index 5b4dd15..e09c004 100644 --- a/src/lib/keywordlist.cpp +++ b/src/lib/keywordlist.cpp @@ -1,97 +1,88 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "keywordlist_p.h" #include #include using namespace KSyntaxHighlighting; -KeywordList::KeywordList() : - m_caseSensitive(Qt::CaseSensitive) -{ -} - -KeywordList::~KeywordList() -{ -} - bool KeywordList::isEmpty() const { return m_keywords.isEmpty(); } QString KeywordList::name() const { return m_name; } QStringList KeywordList::keywords() const { return m_keywords.values(); } bool KeywordList::contains(const QStringRef &str) const { return contains(str, m_caseSensitive); } bool KeywordList::contains(const QStringRef &str, Qt::CaseSensitivity caseSensitivityOverride) const { if (Q_UNLIKELY(caseSensitivityOverride == Qt::CaseInsensitive && m_lowerCaseKeywords.isEmpty())) { foreach (const auto &kw, m_keywords) m_lowerCaseKeywords.insert(kw.toLower()); } // TODO avoid the copy in toString! if (caseSensitivityOverride == Qt::CaseSensitive) return m_keywords.contains(str.toString()); return m_lowerCaseKeywords.contains(str.toString().toLower()); } void KeywordList::load(QXmlStreamReader& reader) { Q_ASSERT(reader.name() == QLatin1String("list")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); m_name = reader.attributes().value(QStringLiteral("name")).toString(); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: if (reader.name() == QLatin1String("item")) { m_keywords.insert(reader.readElementText().trimmed()); reader.readNextStartElement(); break; } reader.readNext(); break; case QXmlStreamReader::EndElement: reader.readNext(); return; default: reader.readNext(); break; } } } void KeywordList::setCaseSensitivity(Qt::CaseSensitivity caseSensitive) { m_caseSensitive = caseSensitive; } diff --git a/src/lib/keywordlist_p.h b/src/lib/keywordlist_p.h index c1a6542..e01f622 100644 --- a/src/lib/keywordlist_p.h +++ b/src/lib/keywordlist_p.h @@ -1,57 +1,57 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H #define KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H #include #include #include class QXmlStreamReader; namespace KSyntaxHighlighting { class KeywordList { public: - KeywordList(); - ~KeywordList(); + KeywordList() = default; + ~KeywordList() = default; bool isEmpty() const; QString name() const; QStringList keywords() const; /** Checks if @p str is a keyword in this list. */ bool contains(const QStringRef &str) const; /** Checks if @p str is a keyword in this list, overriding the global case-sensitivity setting. */ bool contains(const QStringRef &str, Qt::CaseSensitivity caseSensitivityOverride) const; void load(QXmlStreamReader &reader); void setCaseSensitivity(Qt::CaseSensitivity caseSensitive); private: QString m_name; QSet m_keywords; mutable QSet m_lowerCaseKeywords; - Qt::CaseSensitivity m_caseSensitive; + Qt::CaseSensitivity m_caseSensitive = Qt::CaseSensitive; }; } #endif // KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H diff --git a/src/lib/matchresult.cpp b/src/lib/matchresult.cpp index c6d886a..32628aa 100644 --- a/src/lib/matchresult.cpp +++ b/src/lib/matchresult.cpp @@ -1,48 +1,47 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "matchresult_p.h" using namespace KSyntaxHighlighting; -MatchResult::MatchResult(int offset, const QStringList &captures) : - m_captures(captures), - m_offset(offset), - m_skipOffset(0) +MatchResult::MatchResult(int offset, const QStringList &captures) + : m_captures(captures) + , m_offset(offset) { } MatchResult::MatchResult(int offset, int skipOffset) : m_offset(offset), m_skipOffset(skipOffset) { } int MatchResult::offset() const { return m_offset; } int MatchResult::skipOffset() const { return m_skipOffset; } QStringList MatchResult::captures() const { return m_captures; } diff --git a/src/lib/matchresult_p.h b/src/lib/matchresult_p.h index 13972aa..bb524f9 100644 --- a/src/lib/matchresult_p.h +++ b/src/lib/matchresult_p.h @@ -1,42 +1,42 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_MATCHRESULT_P_H #define KSYNTAXHIGHLIGHTING_MATCHRESULT_P_H #include namespace KSyntaxHighlighting { class MatchResult { public: MatchResult(int offset, const QStringList &captures = QStringList()); // implicit explicit MatchResult(int offset, int skipOffset); int offset() const; int skipOffset() const; QStringList captures() const; private: QStringList m_captures; int m_offset; - int m_skipOffset; + int m_skipOffset = 0; }; } #endif // KSYNTAXHIGHLIGHTING_MATCHRESULT_P_H diff --git a/src/lib/repository.cpp b/src/lib/repository.cpp index 3152b8a..4ab6dd9 100644 --- a/src/lib/repository.cpp +++ b/src/lib/repository.cpp @@ -1,285 +1,279 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "repository.h" #include "repository_p.h" #include "definition.h" #include "definition_p.h" #include "theme.h" #include "themedata_p.h" #include "ksyntaxhighlighting_logging.h" #include "wildcardmatcher_p.h" #include #include #include #include #include #include #include #include using namespace KSyntaxHighlighting; static void initResource() { Q_INIT_RESOURCE(syntax_data); } -RepositoryPrivate::RepositoryPrivate() : - m_foldingRegionId(0), - m_formatId(0) -{ -} - RepositoryPrivate* RepositoryPrivate::get(Repository *repo) { return repo->d.get(); } Repository::Repository() : d(new RepositoryPrivate) { initResource(); d->load(this); } Repository::~Repository() { // reset repo so we can detect in still alive definition instances // that the repo was deleted foreach (const auto &def, d->m_sortedDefs) DefinitionData::get(def)->repo = nullptr; } Definition Repository::definitionForName(const QString& defName) const { return d->m_defs.value(defName); } Definition Repository::definitionForFileName(const QString& fileName) const { QFileInfo fi(fileName); const auto name = fi.fileName(); QVector candidates; for (auto it = d->m_defs.constBegin(); it != d->m_defs.constEnd(); ++it) { auto def = it.value(); foreach (const auto &pattern, def.extensions()) { if (WildcardMatcher::exactMatch(name, pattern)) { candidates.push_back(def); break; } } } if (candidates.isEmpty()) return Definition(); std::partial_sort(candidates.begin(), candidates.begin() + 1, candidates.end(), [](const Definition &lhs, const Definition &rhs) { return lhs.priority() > rhs.priority(); }); return candidates.at(0); } QVector Repository::definitions() const { return d->m_sortedDefs; } QVector Repository::themes() const { return d->m_themes; } Theme Repository::theme(const QString &themeName) const { for (const auto &theme : d->m_themes) { if (theme.name() == themeName) { return theme; } } return Theme(); } Theme Repository::defaultTheme(Repository::DefaultTheme t) { if (t == DarkTheme) return theme(QLatin1String("Breeze Dark")); return theme(QLatin1String("Default")); } void RepositoryPrivate::load(Repository *repo) { auto dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/syntax"), QStandardPaths::LocateDirectory); foreach (const auto &dir, dirs) loadSyntaxFolder(repo, dir); // backward compatibility with Kate dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("katepart5/syntax"), QStandardPaths::LocateDirectory); foreach (const auto &dir, dirs) loadSyntaxFolder(repo, dir); loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax")); foreach (const auto &path, m_customSearchPaths) loadSyntaxFolder(repo, path + QStringLiteral("/syntax")); m_sortedDefs.reserve(m_defs.size()); for (auto it = m_defs.constBegin(); it != m_defs.constEnd(); ++it) m_sortedDefs.push_back(it.value()); std::sort(m_sortedDefs.begin(), m_sortedDefs.end(), [](const Definition &left, const Definition &right) { auto comparison = left.translatedSection().compare(right.translatedSection(), Qt::CaseInsensitive); if (comparison == 0) comparison = left.translatedName().compare(right.translatedName(), Qt::CaseInsensitive); return comparison < 0; }); // load themes dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/themes"), QStandardPaths::LocateDirectory); foreach (const auto &dir, dirs) loadThemeFolder(dir); loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes")); foreach (const auto &path, m_customSearchPaths) loadThemeFolder(path + QStringLiteral("/themes")); } void RepositoryPrivate::loadSyntaxFolder(Repository *repo, const QString &path) { if (loadSyntaxFolderFromIndex(repo, path)) return; QDirIterator it(path, QStringList() << QLatin1String("*.xml"), QDir::Files); while (it.hasNext()) { Definition def; auto defData = DefinitionData::get(def); defData->repo = repo; if (defData->loadMetaData(it.next())) addDefinition(def); } } bool RepositoryPrivate::loadSyntaxFolderFromIndex(Repository *repo, const QString &path) { QFile indexFile(path + QLatin1String("/index.katesyntax")); if (!indexFile.open(QFile::ReadOnly)) return false; const auto indexDoc(QJsonDocument::fromBinaryData(indexFile.readAll())); const auto index = indexDoc.object(); for (auto it = index.begin(); it != index.end(); ++it) { if (!it.value().isObject()) continue; const auto fileName = QString(path + QLatin1Char('/') + it.key()); const auto defMap = it.value().toObject(); Definition def; auto defData = DefinitionData::get(def); defData->repo = repo; if (defData->loadMetaData(fileName, defMap)) addDefinition(def); } return true; } void RepositoryPrivate::addDefinition(const Definition &def) { const auto it = m_defs.constFind(def.name()); if (it == m_defs.constEnd()) { m_defs.insert(def.name(), def); return; } if (it.value().version() >= def.version()) return; m_defs.insert(def.name(), def); } void RepositoryPrivate::loadThemeFolder(const QString &path) { QDirIterator it(path, QStringList() << QLatin1String("*.theme"), QDir::Files); while (it.hasNext()) { auto themeData = std::unique_ptr(new ThemeData); if (themeData->load(it.next())) addTheme(Theme(themeData.release())); } } static int themeRevision(const Theme &theme) { auto data = ThemeData::get(theme); return data->revision(); } 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(); }); if (it == m_themes.end() || (*it).name() != theme.name()) { m_themes.insert(it, theme); return; } if (themeRevision(*it) < themeRevision(theme)) *it = theme; } quint16 RepositoryPrivate::foldingRegionId(const QString &defName, const QString &foldName) { const auto it = m_foldingRegionIds.constFind(qMakePair(defName, foldName)); if (it != m_foldingRegionIds.constEnd()) return it.value(); m_foldingRegionIds.insert(qMakePair(defName, foldName), ++m_foldingRegionId); return m_foldingRegionId; } quint16 RepositoryPrivate::nextFormatId() { Q_ASSERT(m_formatId < std::numeric_limits::max()); return ++m_formatId; } void Repository::reload() { qCDebug(Log) << "Reloading syntax definitions!"; foreach (const auto &def, d->m_sortedDefs) DefinitionData::get(def)->clear(); d->m_defs.clear(); d->m_sortedDefs.clear(); d->m_themes.clear(); d->m_foldingRegionId = 0; d->m_foldingRegionIds.clear(); d->m_formatId = 0; d->load(this); } void Repository::addCustomSearchPath(const QString &path) { d->m_customSearchPaths.append(path); reload(); } QVector Repository::customSearchPaths() const { return d->m_customSearchPaths; } diff --git a/src/lib/repository_p.h b/src/lib/repository_p.h index f42aca4..20ab111 100644 --- a/src/lib/repository_p.h +++ b/src/lib/repository_p.h @@ -1,63 +1,64 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_REPOSITORY_P_H #define KSYNTAXHIGHLIGHTING_REPOSITORY_P_H #include #include class QString; namespace KSyntaxHighlighting { class Definition; class Repository; class Theme; class RepositoryPrivate { public: - RepositoryPrivate(); + RepositoryPrivate() = default; static RepositoryPrivate* get(Repository *repo); void load(Repository *repo); void loadSyntaxFolder(Repository *repo, const QString &path); bool loadSyntaxFolderFromIndex(Repository *repo, const QString &path); + void addDefinition(const Definition &def); void loadThemeFolder(const QString &path); void addTheme(const Theme &theme); quint16 foldingRegionId(const QString &defName, const QString &foldName); quint16 nextFormatId(); QVector m_customSearchPaths; QHash m_defs; QVector m_sortedDefs; QVector m_themes; QHash, quint16> m_foldingRegionIds; - quint16 m_foldingRegionId; - quint16 m_formatId; + quint16 m_foldingRegionId = 0; + quint16 m_formatId = 0; }; } #endif diff --git a/src/lib/rule.cpp b/src/lib/rule.cpp index c803309..6d1f843 100644 --- a/src/lib/rule.cpp +++ b/src/lib/rule.cpp @@ -1,694 +1,682 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "rule_p.h" #include "definition_p.h" #include "ksyntaxhighlighting_logging.h" #include "xml_p.h" #include #include using namespace KSyntaxHighlighting; static bool isOctalChar(QChar c) { return c.isNumber() && c != QLatin1Char('9') && c != QLatin1Char('8'); } static bool isHexChar(QChar c) { return c.isNumber() || c == QLatin1Char('a') || c == QLatin1Char('A') || c == QLatin1Char('b') || c == QLatin1Char('B') || c == QLatin1Char('c') || c == QLatin1Char('C') || c == QLatin1Char('d') || c == QLatin1Char('D') || c == QLatin1Char('e') || c == QLatin1Char('E') || c == QLatin1Char('f') || c == QLatin1Char('F'); } static int matchEscapedChar(const QString &text, int offset) { if (text.at(offset) != QLatin1Char('\\') || text.size() < offset + 2) return offset; const auto c = text.at(offset + 1); static const auto controlChars = QStringLiteral("abefnrtv\"'?\\"); if (controlChars.contains(c)) return offset + 2; if (c == QLatin1Char('x')) { // hex encoded character auto newOffset = offset + 2; for (int i = 0; i < 2 && newOffset + i < text.size(); ++i, ++newOffset) { if (!isHexChar(text.at(newOffset))) break; } if (newOffset == offset + 2) return offset; return newOffset; } if (isOctalChar(c)) { // octal encoding auto newOffset = offset + 2; for (int i = 0; i < 2 && newOffset + i < text.size(); ++i, ++newOffset) { if (!isOctalChar(text.at(newOffset))) break; } if (newOffset == offset + 2) return offset; return newOffset; } return offset; } static QString replaceCaptures(const QString &pattern, const QStringList &captures, bool quote) { auto result = pattern; for (int i = captures.size() - 1; i >= 1; --i) { result.replace(QLatin1Char('%') + QString::number(i), quote ? QRegularExpression::escape(captures.at(i)) : captures.at(i)); } return result; } -Rule::Rule() : - m_column(-1), - m_firstNonSpace(false), - m_lookAhead(false), - m_dynamic(false) -{ -} - -Rule::~Rule() -{ -} - Definition Rule::definition() const { return m_def.definition(); } void Rule::setDefinition(const Definition &def) { m_def = def; } QString Rule::attribute() const { return m_attribute; } ContextSwitch Rule::context() const { return m_context; } bool Rule::isLookAhead() const { return m_lookAhead; } bool Rule::isDynamic() const { return m_dynamic; } bool Rule::firstNonSpace() const { return m_firstNonSpace; } int Rule::requiredColumn() const { return m_column; } FoldingRegion Rule::beginRegion() const { return m_beginRegion; } FoldingRegion Rule::endRegion() const { return m_endRegion; } bool Rule::load(QXmlStreamReader &reader) { Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); m_attribute = reader.attributes().value(QStringLiteral("attribute")).toString(); if (reader.name() != QLatin1String("IncludeRules")) // IncludeRules uses this with a different semantic m_context.parse(reader.attributes().value(QStringLiteral("context"))); m_firstNonSpace = Xml::attrToBool(reader.attributes().value(QStringLiteral("firstNonSpace"))); m_lookAhead = Xml::attrToBool(reader.attributes().value(QStringLiteral("lookAhead"))); bool colOk = false; m_column = reader.attributes().value(QStringLiteral("column")).toInt(&colOk); if (!colOk) m_column = -1; m_dynamic = Xml::attrToBool(reader.attributes().value(QStringLiteral("dynamic"))); auto regionName = reader.attributes().value(QLatin1String("beginRegion")); if (!regionName.isEmpty()) m_beginRegion = FoldingRegion(FoldingRegion::Begin, DefinitionData::get(m_def.definition())->foldingRegionId(regionName.toString())); regionName = reader.attributes().value(QLatin1String("endRegion")); if (!regionName.isEmpty()) m_endRegion = FoldingRegion(FoldingRegion::End, DefinitionData::get(m_def.definition())->foldingRegionId(regionName.toString())); auto result = doLoad(reader); if (m_lookAhead && m_context.isStay()) result = false; reader.readNext(); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: { auto rule = Rule::create(reader.name()); if (rule) { rule->setDefinition(m_def.definition()); if (rule->load(reader)) { m_subRules.push_back(rule); reader.readNext(); } } else { reader.skipCurrentElement(); } break; } case QXmlStreamReader::EndElement: return result; default: reader.readNext(); break; } } return result; } void Rule::resolveContext() { m_context.resolve(m_def.definition()); foreach (const auto &rule, m_subRules) rule->resolveContext(); } bool Rule::doLoad(QXmlStreamReader& reader) { Q_UNUSED(reader); return true; } MatchResult Rule::match(const QString &text, int offset, const QStringList &captures) { Q_ASSERT(!text.isEmpty()); const auto result = doMatch(text, offset, captures); if (result.offset() == offset || result.offset() == text.size()) return result; foreach (const auto &subRule, m_subRules) { const auto subResult = subRule->match(text, result.offset(), QStringList()); if (subResult.offset() > result.offset()) return MatchResult(subResult.offset(), result.captures()); } return result; } Rule::Ptr Rule::create(const QStringRef& name) { Rule *rule = nullptr; if (name == QLatin1String("AnyChar")) rule = new AnyChar; else if (name == QLatin1String("DetectChar")) rule = new DetectChar; else if (name == QLatin1String("Detect2Chars")) rule = new Detect2Char; else if (name == QLatin1String("DetectIdentifier")) rule = new DetectIdentifier; else if (name == QLatin1String("DetectSpaces")) rule = new DetectSpaces; else if (name == QLatin1String("Float")) rule = new Float; else if (name == QLatin1String("Int")) rule = new Int; else if (name == QLatin1String("HlCChar")) rule = new HlCChar; else if (name == QLatin1String("HlCHex")) rule = new HlCHex; else if (name == QLatin1String("HlCOct")) rule = new HlCOct; else if (name == QLatin1String("HlCStringChar")) rule = new HlCStringChar; else if (name == QLatin1String("IncludeRules")) rule = new IncludeRules; else if (name == QLatin1String("keyword")) rule = new KeywordListRule; else if (name == QLatin1String("LineContinue")) rule = new LineContinue; else if (name == QLatin1String("RangeDetect")) rule = new RangeDetect; else if (name == QLatin1String("RegExpr")) rule = new RegExpr; else if (name == QLatin1String("StringDetect")) rule = new StringDetect; else if (name == QLatin1String("WordDetect")) rule = new WordDetect; else qCWarning(Log) << "Unknown rule type:" << name; return Ptr(rule); } bool Rule::isDelimiter(QChar c) const { auto defData = DefinitionData::get(m_def.definition()); return defData->isDelimiter(c); } bool AnyChar::doLoad(QXmlStreamReader& reader) { m_chars = reader.attributes().value(QStringLiteral("String")).toString(); if (m_chars.size() == 1) qCDebug(Log) << "AnyChar rule with just one char: use DetectChar instead."; return !m_chars.isEmpty(); } MatchResult AnyChar::doMatch(const QString& text, int offset, const QStringList&) { if (m_chars.contains(text.at(offset))) return offset + 1; return offset; } bool DetectChar::doLoad(QXmlStreamReader& reader) { const auto s = reader.attributes().value(QStringLiteral("char")); if (s.isEmpty()) return false; m_char = s.at(0); if (isDynamic()) { m_captureIndex = m_char.digitValue(); } return true; } MatchResult DetectChar::doMatch(const QString& text, int offset, const QStringList &captures) { if (isDynamic()) { if (captures.size() <= m_captureIndex || captures.at(m_captureIndex).isEmpty()) return offset; if (text.at(offset) == captures.at(m_captureIndex).at(0)) return offset + 1; return offset; } if (text.at(offset) == m_char) return offset + 1; return offset; } bool Detect2Char::doLoad(QXmlStreamReader& reader) { const auto s1 = reader.attributes().value(QStringLiteral("char")); const auto s2 = reader.attributes().value(QStringLiteral("char1")); if (s1.isEmpty() || s2.isEmpty()) return false; m_char1 = s1.at(0); m_char2 = s2.at(0); return true; } MatchResult Detect2Char::doMatch(const QString& text, int offset, const QStringList &captures) { Q_UNUSED(captures); // TODO if (text.size() - offset < 2) return offset; if (text.at(offset) == m_char1 && text.at(offset + 1) == m_char2) return offset + 2; return offset; } MatchResult DetectIdentifier::doMatch(const QString& text, int offset, const QStringList&) { if (!text.at(offset).isLetter() && text.at(offset) != QLatin1Char('_')) return offset; for (int i = offset + 1; i < text.size(); ++i) { const auto c = text.at(i); if (!c.isLetterOrNumber() && c != QLatin1Char('_')) return i; } return text.size(); } MatchResult DetectSpaces::doMatch(const QString& text, int offset, const QStringList&) { while(offset < text.size() && text.at(offset).isSpace()) ++offset; return offset; } MatchResult Float::doMatch(const QString& text, int offset, const QStringList&) { if (offset > 0 && !isDelimiter(text.at(offset - 1))) return offset; auto newOffset = offset; while (newOffset < text.size() && text.at(newOffset).isDigit()) ++newOffset; if (newOffset >= text.size() || text.at(newOffset) != QLatin1Char('.')) return offset; ++newOffset; while (newOffset < text.size() && text.at(newOffset).isDigit()) ++newOffset; if (newOffset == offset + 1) // we only found a decimal point return offset; auto expOffset = newOffset; if (expOffset >= text.size() || (text.at(expOffset) != QLatin1Char('e') && text.at(expOffset) != QLatin1Char('E'))) return newOffset; ++expOffset; if (expOffset < text.size() && (text.at(expOffset) == QLatin1Char('+') || text.at(expOffset) == QLatin1Char('-'))) ++expOffset; bool foundExpDigit = false; while (expOffset < text.size() && text.at(expOffset).isDigit()) { ++expOffset; foundExpDigit = true; } if (!foundExpDigit) return newOffset; return expOffset; } MatchResult HlCChar::doMatch(const QString& text, int offset, const QStringList&) { if (text.size() < offset + 3) return offset; if (text.at(offset) != QLatin1Char('\'') || text.at(offset + 1) == QLatin1Char('\'')) return offset; auto newOffset = matchEscapedChar(text, offset + 1); if (newOffset == offset + 1) { if (text.at(newOffset) == QLatin1Char('\\')) return offset; else ++newOffset; } if (newOffset >= text.size()) return offset; if (text.at(newOffset) == QLatin1Char('\'')) return newOffset + 1; return offset; } MatchResult HlCHex::doMatch(const QString& text, int offset, const QStringList&) { if (offset > 0 && !isDelimiter(text.at(offset - 1))) return offset; if (text.size() < offset + 3) return offset; if (text.at(offset) != QLatin1Char('0') || (text.at(offset + 1) != QLatin1Char('x') && text.at(offset + 1) != QLatin1Char('X'))) return offset; if (!isHexChar(text.at(offset + 2))) return offset; offset += 3; while (offset < text.size() && isHexChar(text.at(offset))) ++offset; // TODO Kate matches U/L suffix, QtC does not? return offset; } MatchResult HlCOct::doMatch(const QString& text, int offset, const QStringList&) { if (offset > 0 && !isDelimiter(text.at(offset - 1))) return offset; if (text.size() < offset + 2) return offset; if (text.at(offset) != QLatin1Char('0')) return offset; if (!isOctalChar(text.at(offset + 1))) return offset; offset += 2; while (offset < text.size() && isOctalChar(text.at(offset))) ++offset; return offset; } MatchResult HlCStringChar::doMatch(const QString& text, int offset, const QStringList&) { return matchEscapedChar(text, offset); } QString IncludeRules::contextName() const { return m_contextName; } QString IncludeRules::definitionName() const { return m_defName; } bool IncludeRules::includeAttribute() const { return m_includeAttribute; } bool IncludeRules::doLoad(QXmlStreamReader& reader) { const auto s = reader.attributes().value(QLatin1String("context")); auto splitted = s.split(QLatin1String("##"), QString::KeepEmptyParts); if (splitted.isEmpty()) return false; m_contextName = splitted.at(0).toString(); if (splitted.size() > 1) m_defName = splitted.at(1).toString(); m_includeAttribute = Xml::attrToBool(reader.attributes().value(QLatin1String("includeAttrib"))); return !m_contextName.isEmpty() || !m_defName.isEmpty(); } MatchResult IncludeRules::doMatch(const QString& text, int offset, const QStringList&) { Q_UNUSED(text); qCWarning(Log) << "Unresolved include rule for" << m_contextName << "##" << m_defName; return offset; } MatchResult Int::doMatch(const QString& text, int offset, const QStringList &captures) { if (offset > 0 && !isDelimiter(text.at(offset - 1))) return offset; Q_UNUSED(captures); // ### the doc says this can be dynamic, but how?? while(offset < text.size() && text.at(offset).isDigit()) ++offset; return offset; } bool KeywordListRule::doLoad(QXmlStreamReader& reader) { m_listName = reader.attributes().value(QLatin1String("String")).toString(); if (reader.attributes().hasAttribute(QLatin1String("insensitive"))) { m_hasCaseSensitivityOverride = true; m_caseSensitivityOverride = Xml::attrToBool(reader.attributes().value(QLatin1String("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive; } else { m_hasCaseSensitivityOverride = false; } return !m_listName.isEmpty(); } MatchResult KeywordListRule::doMatch(const QString& text, int offset, const QStringList&) { if (offset > 0 && !isDelimiter(text.at(offset - 1))) return offset; if (m_keywordList.isEmpty()) { const auto def = definition(); Q_ASSERT(def.isValid()); auto defData = DefinitionData::get(def); m_keywordList = defData->keywordList(m_listName); } auto newOffset = offset; while (text.size() > newOffset && !isDelimiter(text.at(newOffset))) ++newOffset; if (newOffset == offset) return offset; if (m_hasCaseSensitivityOverride) { if (m_keywordList.contains(text.midRef(offset, newOffset - offset), m_caseSensitivityOverride)) return newOffset; } else { if (m_keywordList.contains(text.midRef(offset, newOffset - offset))) return newOffset; } return offset; } bool LineContinue::doLoad(QXmlStreamReader& reader) { const auto s = reader.attributes().value(QStringLiteral("char")); if (s.isEmpty()) m_char = QLatin1Char('\\'); else m_char = s.at(0); return true; } MatchResult LineContinue::doMatch(const QString& text, int offset, const QStringList&) { if (offset == text.size() - 1 && text.at(offset) == m_char) return offset + 1; return offset; } bool RangeDetect::doLoad(QXmlStreamReader& reader) { const auto s1 = reader.attributes().value(QStringLiteral("char")); const auto s2 = reader.attributes().value(QStringLiteral("char1")); if (s1.isEmpty() || s2.isEmpty()) return false; m_begin = s1.at(0); m_end = s2.at(0); return true; } MatchResult RangeDetect::doMatch(const QString& text, int offset, const QStringList&) { if (text.size() - offset < 2) return offset; if (text.at(offset) != m_begin) return offset; auto newOffset = offset + 1; while (newOffset < text.size()) { if (text.at(newOffset) == m_end) return newOffset + 1; ++newOffset; } return offset; } bool RegExpr::doLoad(QXmlStreamReader& reader) { m_pattern = reader.attributes().value(QStringLiteral("String")).toString(); m_regexp.setPattern(m_pattern); const auto isMinimal = Xml::attrToBool(reader.attributes().value(QStringLiteral("minimal"))); const auto isCaseInsensitive = Xml::attrToBool(reader.attributes().value(QStringLiteral("insensitive"))); m_regexp.setPatternOptions( (isMinimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption) | (isCaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption)); return !m_pattern.isEmpty(); // m_regexp.isValid() would be better, but parses the regexp and thus is way too expensive } MatchResult RegExpr::doMatch(const QString& text, int offset, const QStringList &captures) { Q_ASSERT(m_regexp.isValid()); if (isDynamic()) m_regexp.setPattern(replaceCaptures(m_pattern, captures, true)); auto result = m_regexp.match(text, offset, QRegularExpression::NormalMatch, QRegularExpression::DontCheckSubjectStringMatchOption); if (result.capturedStart() == offset) return MatchResult(offset + result.capturedLength(), result.capturedTexts()); return MatchResult(offset, result.capturedStart()); } bool StringDetect::doLoad(QXmlStreamReader& reader) { m_string = reader.attributes().value(QStringLiteral("String")).toString(); m_caseSensitivity = Xml::attrToBool(reader.attributes().value(QStringLiteral("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive; return !m_string.isEmpty(); } MatchResult StringDetect::doMatch(const QString& text, int offset, const QStringList &captures) { auto pattern = m_string; if (isDynamic()) pattern = replaceCaptures(m_string, captures, false); if (text.midRef(offset, pattern.size()).compare(pattern, m_caseSensitivity) == 0) return offset + pattern.size(); return offset; } bool WordDetect::doLoad(QXmlStreamReader& reader) { m_word = reader.attributes().value(QStringLiteral("String")).toString(); m_caseSensitivity = Xml::attrToBool(reader.attributes().value(QStringLiteral("insensitive"))) ? Qt::CaseInsensitive : Qt::CaseSensitive; return !m_word.isEmpty(); } MatchResult WordDetect::doMatch(const QString& text, int offset, const QStringList &captures) { Q_UNUSED(captures); // TODO if (text.size() - offset < m_word.size()) return offset; if (offset > 0 && !isDelimiter(text.at(offset - 1))) return offset; if (text.midRef(offset, m_word.size()).compare(m_word, m_caseSensitivity) != 0) return offset; if (text.size() == offset + m_word.size() || isDelimiter(text.at(offset + m_word.size()))) return offset + m_word.size(); return offset; } diff --git a/src/lib/rule_p.h b/src/lib/rule_p.h index 2532e6e..a1cb2ce 100644 --- a/src/lib/rule_p.h +++ b/src/lib/rule_p.h @@ -1,254 +1,254 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_RULE_P_H #define KSYNTAXHIGHLIGHTING_RULE_P_H #include "contextswitch_p.h" #include "definition.h" #include "definitionref_p.h" #include "foldingregion.h" #include "keywordlist_p.h" #include "matchresult_p.h" #include #include #include #include class QXmlStreamReader; namespace KSyntaxHighlighting { class Rule { public: - Rule(); - virtual ~Rule(); + Rule() = default; + virtual ~Rule() = default; typedef std::shared_ptr Ptr; Definition definition() const; void setDefinition(const Definition &def); QString attribute() const; ContextSwitch context() const; bool isLookAhead() const; bool isDynamic() const; bool firstNonSpace() const; int requiredColumn() const; FoldingRegion beginRegion() const; FoldingRegion endRegion() const; bool load(QXmlStreamReader &reader); void resolveContext(); MatchResult match(const QString &text, int offset, const QStringList &captures); static Rule::Ptr create(const QStringRef &name); protected: virtual bool doLoad(QXmlStreamReader &reader); virtual MatchResult doMatch(const QString &text, int offset, const QStringList &captures) = 0; bool isDelimiter(QChar c) const; private: Q_DISABLE_COPY(Rule) DefinitionRef m_def; QString m_attribute; ContextSwitch m_context; QVector m_subRules; - int m_column; + int m_column = -1; FoldingRegion m_beginRegion; FoldingRegion m_endRegion; - bool m_firstNonSpace; - bool m_lookAhead; - bool m_dynamic; + bool m_firstNonSpace = false; + bool m_lookAhead = false; + bool m_dynamic = false; }; class AnyChar : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList&) override; private: QString m_chars; }; class DetectChar : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList &captures) override; private: QChar m_char; int m_captureIndex; }; class Detect2Char : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList &captures) override; private: QChar m_char1; QChar m_char2; }; class DetectIdentifier : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class DetectSpaces : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class Float : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class IncludeRules : public Rule { public: QString contextName() const; QString definitionName() const; bool includeAttribute() const; protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList&) override; private: QString m_contextName; QString m_defName; bool m_includeAttribute; }; class Int : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList &captures) override; }; class HlCChar : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class HlCHex : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class HlCOct : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class HlCStringChar : public Rule { protected: MatchResult doMatch(const QString & text, int offset, const QStringList&) override; }; class KeywordListRule : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList&) override; private: QString m_listName; KeywordList m_keywordList; bool m_hasCaseSensitivityOverride; Qt::CaseSensitivity m_caseSensitivityOverride; }; class LineContinue : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList&) override; private: QChar m_char; }; class RangeDetect : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList&) override; private: QChar m_begin; QChar m_end; }; class RegExpr : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList &captures) override; private: QString m_pattern; QRegularExpression m_regexp; }; class StringDetect : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList &captures) override; private: QString m_string; Qt::CaseSensitivity m_caseSensitivity; }; class WordDetect : public Rule { protected: bool doLoad(QXmlStreamReader & reader) override; MatchResult doMatch(const QString & text, int offset, const QStringList &captures) override; private: QString m_word; Qt::CaseSensitivity m_caseSensitivity; }; } #endif // KSYNTAXHIGHLIGHTING_RULE_P_H diff --git a/src/lib/state.cpp b/src/lib/state.cpp index bd8c866..0159ba5 100644 --- a/src/lib/state.cpp +++ b/src/lib/state.cpp @@ -1,117 +1,112 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "state.h" #include "state_p.h" #include "context_p.h" #include using namespace KSyntaxHighlighting; -StateData::StateData() : - m_defData(nullptr) -{ -} - StateData* StateData::get(State &state) { state.d.detach(); return state.d.data(); } bool StateData::isEmpty() const { Q_ASSERT(m_contextStack.size() == m_captureStack.size()); return m_contextStack.isEmpty(); } void StateData::clear() { m_contextStack.clear(); m_captureStack.clear(); } int StateData::size() const { Q_ASSERT(m_contextStack.size() == m_captureStack.size()); return m_contextStack.size(); } void StateData::push(Context *context, const QStringList &captures) { Q_ASSERT(context); m_contextStack.push(context); m_captureStack.push(captures); Q_ASSERT(m_contextStack.size() == m_captureStack.size()); } void StateData::pop() { m_contextStack.pop(); m_captureStack.pop(); } Context* StateData::topContext() const { Q_ASSERT(!isEmpty()); return m_contextStack.top(); } QStringList StateData::topCaptures() const { Q_ASSERT(!isEmpty()); return m_captureStack.top(); } State::State() : d(new StateData) { } State::State(const State &other) : d(other.d) { } State::~State() { } State& State::operator=(const State &other) { d = other.d; return *this; } bool State::operator==(const State &other) const { return d->m_contextStack == other.d->m_contextStack && d->m_captureStack == other.d->m_captureStack && d->m_defData == other.d->m_defData; } bool State::operator!=(const State &other) const { return !(*this == other); } bool State::indentationBasedFoldingEnabled() const { if (d->m_contextStack.isEmpty()) return false; return d->m_contextStack.top()->indentationBasedFoldingEnabled(); } diff --git a/src/lib/state_p.h b/src/lib/state_p.h index dd71a6c..4913232 100644 --- a/src/lib/state_p.h +++ b/src/lib/state_p.h @@ -1,57 +1,57 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_STATE_P_H #define KSYNTAXHIGHLIGHTING_STATE_P_H #include #include QT_BEGIN_NAMESPACE class QStringList; QT_END_NAMESPACE namespace KSyntaxHighlighting { class Context; class DefinitionData; class StateData : public QSharedData { public: - StateData(); + StateData() = default; static StateData* get(State &state); bool isEmpty() const; void clear(); int size() const; void push(Context *context, const QStringList &captures); void pop(); Context* topContext() const; QStringList topCaptures() const; - DefinitionData *m_defData; + DefinitionData *m_defData = nullptr; private: friend class State; QStack m_contextStack; QStack m_captureStack; }; } #endif diff --git a/src/lib/textstyledata_p.h b/src/lib/textstyledata_p.h index d527fb2..f5f7cfa 100644 --- a/src/lib/textstyledata_p.h +++ b/src/lib/textstyledata_p.h @@ -1,61 +1,57 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_TEXTSTYLEDATA_P_H #define KSYNTAXHIGHLIGHTING_TEXTSTYLEDATA_P_H #include namespace KSyntaxHighlighting { class TextStyleData { public: // Constructor initializing all data. TextStyleData() - : textColor(0x0) - , backgroundColor(0x0) - , selectedTextColor(0x0) - , selectedBackgroundColor(0x0) - , bold(false) + : bold(false) , italic(false) , underline(false) , strikeThrough(false) , hasBold(false) , hasItalic(false) , hasUnderline(false) , hasStrikeThrough(false) {} - QRgb textColor; - QRgb backgroundColor; - QRgb selectedTextColor; - QRgb selectedBackgroundColor; + QRgb textColor = 0x0; + QRgb backgroundColor = 0x0; + QRgb selectedTextColor = 0x0; + QRgb selectedBackgroundColor = 0x0; bool bold :1; bool italic :1; bool underline :1; bool strikeThrough :1; bool hasBold :1; bool hasItalic :1; bool hasUnderline :1; bool hasStrikeThrough :1; }; } #endif diff --git a/src/lib/themedata.cpp b/src/lib/themedata.cpp index 9abda83..ae9971d 100644 --- a/src/lib/themedata.cpp +++ b/src/lib/themedata.cpp @@ -1,250 +1,249 @@ /* Copyright (C) 2016 Volker Krause Copyright (C) 2016 Dominik Haumann This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "themedata_p.h" #include "ksyntaxhighlighting_logging.h" #include #include #include #include #include #include #include using namespace KSyntaxHighlighting; ThemeData* ThemeData::get(const Theme &theme) { return theme.m_data.data(); } ThemeData::ThemeData() - : m_revision(0) { memset(m_editorColors, 0, sizeof(m_editorColors)); } /** * Convert QJsonValue @p val into a color, if possible. Valid colors are only * in hex format: #rrggbb. On error, returns 0x00000000. */ static inline QRgb readColor(const QJsonValue &val) { const QRgb unsetColor = 0; if (!val.isString()) { return unsetColor; } const QString str = val.toString(); if (str.isEmpty() || str[0] != QLatin1Char('#')) { return unsetColor; } const QColor color(str); return color.isValid() ? color.rgb() : unsetColor; } static inline TextStyleData readThemeData(const QJsonObject &obj) { TextStyleData td; td.textColor = readColor(obj.value(QLatin1String("text-color"))); td.backgroundColor = readColor(obj.value(QLatin1String("background-color"))); td.selectedTextColor = readColor(obj.value(QLatin1String("selected-text-color"))); td.selectedBackgroundColor = readColor(obj.value(QLatin1String("selected-background-color"))); auto val = obj.value(QLatin1String("bold")); if (val.isBool()) { td.bold = val.toBool(); td.hasBold = true; } val = obj.value(QLatin1String("italic")); if (val.isBool()) { td.italic = val.toBool(); td.hasItalic = true; } val = obj.value(QLatin1String("underline")); if (val.isBool()) { td.underline = val.toBool(); td.hasUnderline = true; } val = obj.value(QLatin1String("strike-through")); if (val.isBool()) { td.strikeThrough = val.toBool(); td.hasStrikeThrough = true; } return td; } bool ThemeData::load(const QString &filePath) { QFile loadFile(filePath); if (!loadFile.open(QIODevice::ReadOnly)) { return false; } const QByteArray jsonData = loadFile.readAll(); QJsonParseError parseError; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError); if (parseError.error != QJsonParseError::NoError) { qCWarning(Log) << "Failed to parse theme file" << filePath << ":" << parseError.errorString(); return false; } m_filePath = filePath; QJsonObject obj = jsonDoc.object(); // read metadata const QJsonObject metadata = obj.value(QLatin1String("metadata")).toObject(); m_name = metadata.value(QLatin1String("name")).toString(); m_revision = metadata.value(QLatin1String("revision")).toInt(); // read text styles static const auto idx = Theme::staticMetaObject.indexOfEnumerator("TextStyle"); Q_ASSERT(idx >= 0); const auto metaEnum = Theme::staticMetaObject.enumerator(idx); const QJsonObject textStyles = obj.value(QLatin1String("text-styles")).toObject(); for (int i = 0; i < metaEnum.keyCount(); ++i) { Q_ASSERT(i == metaEnum.value(i)); m_textStyles[i] = readThemeData(textStyles.value(QLatin1String(metaEnum.key(i))).toObject()); } // read editor area colors const QJsonObject editorColors = obj.value(QLatin1String("editor-colors")).toObject(); m_editorColors[Theme::BackgroundColor] = readColor(editorColors.value(QLatin1String("background-color"))); m_editorColors[Theme::TextSelection] = readColor(editorColors.value(QLatin1String("selection"))); m_editorColors[Theme::CurrentLine] = readColor(editorColors.value(QLatin1String("current-line"))); m_editorColors[Theme::SearchHighlight] = readColor(editorColors.value(QLatin1String("search-highlight"))); m_editorColors[Theme::ReplaceHighlight] = readColor(editorColors.value(QLatin1String("replace-highlight"))); m_editorColors[Theme::BracketMatching] = readColor(editorColors.value(QLatin1String("bracket-matching"))); m_editorColors[Theme::TabMarker] = readColor(editorColors.value(QLatin1String("tab-marker"))); m_editorColors[Theme::SpellChecking] = readColor(editorColors.value(QLatin1String("spell-checking"))); m_editorColors[Theme::IndentationLine] = readColor(editorColors.value(QLatin1String("indentation-line"))); m_editorColors[Theme::IconBorder] = readColor(editorColors.value(QLatin1String("icon-border"))); m_editorColors[Theme::CodeFolding] = readColor(editorColors.value(QLatin1String("code-folding"))); m_editorColors[Theme::LineNumbers] = readColor(editorColors.value(QLatin1String("line-numbers"))); m_editorColors[Theme::CurrentLineNumber] = readColor(editorColors.value(QLatin1String("current-line-number"))); m_editorColors[Theme::WordWrapMarker] = readColor(editorColors.value(QLatin1String("word-wrap-marker"))); m_editorColors[Theme::ModifiedLines] = readColor(editorColors.value(QLatin1String("modified-lines"))); m_editorColors[Theme::SavedLines] = readColor(editorColors.value(QLatin1String("saved-lines"))); m_editorColors[Theme::Separator] = readColor(editorColors.value(QLatin1String("separator"))); m_editorColors[Theme::MarkBookmark] = readColor(editorColors.value(QLatin1String("mark-bookmark"))); m_editorColors[Theme::MarkBreakpointActive] = readColor(editorColors.value(QLatin1String("mark-breakpoint-active"))); m_editorColors[Theme::MarkBreakpointReached] = readColor(editorColors.value(QLatin1String("mark-breakpoint-reached"))); m_editorColors[Theme::MarkBreakpointDisabled] = readColor(editorColors.value(QLatin1String("mark-breakpoint-disabled"))); m_editorColors[Theme::MarkExecution] = readColor(editorColors.value(QLatin1String("mark-execution"))); m_editorColors[Theme::MarkWarning] = readColor(editorColors.value(QLatin1String("mark-warning"))); m_editorColors[Theme::MarkError] = readColor(editorColors.value(QLatin1String("mark-error"))); m_editorColors[Theme::TemplateBackground] = readColor(editorColors.value(QLatin1String("template-background"))); m_editorColors[Theme::TemplatePlaceholder] = readColor(editorColors.value(QLatin1String("template-placeholder"))); m_editorColors[Theme::TemplateFocusedPlaceholder] = readColor(editorColors.value(QLatin1String("template-focused-placeholder"))); m_editorColors[Theme::TemplateReadOnlyPlaceholder] = readColor(editorColors.value(QLatin1String("template-read-only-placeholder"))); // read per-definition style overrides const auto customStyles = obj.value(QLatin1String("custom-styles")).toObject(); for (auto it = customStyles.begin(); it != customStyles.end(); ++it) { const auto obj = it.value().toObject(); QHash overrideStyle; for (auto it2 = obj.begin(); it2 != obj.end(); ++it2) overrideStyle.insert(it2.key(), readThemeData(it2.value().toObject())); m_textStyleOverrides.insert(it.key(), overrideStyle); } return true; } QString ThemeData::name() const { return m_name; } int ThemeData::revision() const { return m_revision; } bool ThemeData::isReadOnly() const { return !QFileInfo(m_filePath).isWritable(); } QString ThemeData::filePath() const { return m_filePath; } QRgb ThemeData::textColor(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].textColor; } QRgb ThemeData::selectedTextColor(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].selectedTextColor; } QRgb ThemeData::backgroundColor(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].backgroundColor; } QRgb ThemeData::selectedBackgroundColor(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].selectedBackgroundColor; } bool ThemeData::isBold(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].bold; } bool ThemeData::isItalic(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].italic; } bool ThemeData::isUnderline(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].underline; } bool ThemeData::isStrikeThrough(Theme::TextStyle style) const { Q_ASSERT(static_cast(style) >= 0 && static_cast(style) <= static_cast(Theme::Others)); return m_textStyles[style].strikeThrough; } QRgb ThemeData::editorColor(Theme::EditorColorRole role) const { Q_ASSERT(static_cast(role) >= 0 && static_cast(role) <= static_cast(Theme::TemplateReadOnlyPlaceholder)); return m_editorColors[role]; } TextStyleData ThemeData::textStyleOverride(const QString& definitionName, const QString& attributeName) const { return m_textStyleOverrides.value(definitionName).value(attributeName); } diff --git a/src/lib/themedata_p.h b/src/lib/themedata_p.h index 7a033cc..b13fe8f 100644 --- a/src/lib/themedata_p.h +++ b/src/lib/themedata_p.h @@ -1,162 +1,162 @@ /* Copyright (C) 2016 Volker Krause Copyright (C) 2016 Dominik Haumann This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KSYNTAXHIGHLIGHTING_THEMEDATA_P_H #define KSYNTAXHIGHLIGHTING_THEMEDATA_P_H #include "theme.h" #include "textstyledata_p.h" #include #include namespace KSyntaxHighlighting { /** * Data container for a Theme. */ class ThemeData : public QSharedData { public: static ThemeData* get(const Theme &theme); /** * Default constructor, creating an uninitialized ThemeData instance. */ ThemeData(); /** * Load the Theme data from the file @p filePath. * Note, that @p filePath either is a local file, or a qt resource location. */ bool load(const QString &filePath); /** * Returns the unique name of this Theme. */ QString name() const; /** * Returns the revision of this Theme. * The revision in a .theme file should be increased with every change. */ int revision() const; /** * Returns @c true if this Theme is read-only. * Typically, themes that are shipped by default are read-only. */ bool isReadOnly() const; /** * Returns the full path and filename to this Theme. * Themes from the Qt resource return the Qt resource path. * Themes from disk return the local path. * * If the theme is invalid (isValid()), an empty string is returned. */ QString filePath() const; /** * Returns the text color to be used for @p style. * @c 0 is returned for styles that do not specify a text color, * use the default text color in that case. */ QRgb textColor(Theme::TextStyle style) const; /** * Returns the text color for selected to be used for @p style. * @c 0 is returned for styles that do not specify a selected text color, * use the textColor() in that case. */ QRgb selectedTextColor(Theme::TextStyle style) const; /** * Returns the background color to be used for @p style. * @c 0 is returned for styles that do not specify a background color, * use the default background color in that case. */ QRgb backgroundColor(Theme::TextStyle style) const; /** * Returns the background color for selected text to be used for @p style. * @c 0 is returned for styles that do not specify a selected background * color, use the default backgroundColor() in that case. */ QRgb selectedBackgroundColor(Theme::TextStyle style) const; /** * Returns whether the given style should be shown in bold. */ bool isBold(Theme::TextStyle style) const; /** * Returns whether the given style should be shown in italic. */ bool isItalic(Theme::TextStyle style) const; /** * Returns whether the given style should be shown underlined. */ bool isUnderline(Theme::TextStyle style) const; /** * Returns whether the given style should be shown struck through. */ bool isStrikeThrough(Theme::TextStyle style) const; public: /** * Returns the editor color for the requested @p role. */ QRgb editorColor(Theme::EditorColorRole role) const; /** * Returns the TextStyle override of a specific "itemData" with attributeName * in the syntax definition called definitionName. * * If no override exists, a valid TextStyleData with the respective default * TextStyle will be used, so the returned value is always valid. */ TextStyleData textStyleOverride(const QString &definitionName, const QString &attributeName) const; private: - int m_revision; + int m_revision = 0; QString m_name; //! Path to the file where the theme came from. //! This is either a resource location (":/themes/Default.theme"), or a file //! on disk (in a read-only or a writeable location). QString m_filePath; //! TextStyles TextStyleData m_textStyles[Theme::Others + 1]; //! style overrides for individual itemData entries //! definition name -> attribute name -> style QHash > m_textStyleOverrides; //! Editor area colors QRgb m_editorColors[Theme::TemplateReadOnlyPlaceholder + 1]; }; } Q_DECLARE_TYPEINFO(KSyntaxHighlighting::TextStyleData, Q_MOVABLE_TYPE); #endif // KSYNTAXHIGHLIGHTING_THEMEDATA_P_H