diff --git a/autotests/syntaxrepository_test.cpp b/autotests/syntaxrepository_test.cpp index 20269c3..ac01638 100644 --- a/autotests/syntaxrepository_test.cpp +++ b/autotests/syntaxrepository_test.cpp @@ -1,360 +1,365 @@ /* 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 "test-config.h" #include #include #include #include #include #include #include #include #include #include namespace KSyntaxHighlighting { class NullHighlighter : public AbstractHighlighter { public: using AbstractHighlighter::highlightLine; void applyFormat(int offset, int length, const Format &format) Q_DECL_OVERRIDE { Q_UNUSED(offset); Q_UNUSED(length); // only here to ensure we don't crash format.isDefaultTextStyle(theme()); format.textColor(theme()); } }; class RepositoryTest : public QObject { Q_OBJECT private: Repository m_repo; private Q_SLOTS: void initTestCase() { QStandardPaths::enableTestMode(true); } void testDefinitionByExtension_data() { QTest::addColumn("fileName"); QTest::addColumn("defName"); QTest::newRow("empty") << QString() << QString(); QTest::newRow("qml") << QStringLiteral("/bla/foo.qml") << QStringLiteral("QML"); QTest::newRow("glsl") << QStringLiteral("flat.frag") << QStringLiteral("GLSL"); // the following ones are defined in multiple syntax definitions QTest::newRow("c") << QStringLiteral("test.c") << QStringLiteral("C"); QTest::newRow("c++") << QStringLiteral("test.cpp") << QStringLiteral("C++"); QTest::newRow("markdown") << QStringLiteral("test.md") << QStringLiteral("Markdown"); QTest::newRow("Makefile 1") << QStringLiteral("Makefile") << QStringLiteral("Makefile"); QTest::newRow("Makefile 2") << QStringLiteral("/some/path/to/Makefile") << QStringLiteral("Makefile"); QTest::newRow("Makefile 3") << QStringLiteral("Makefile.am") << QStringLiteral("Makefile"); } void testDefinitionByExtension() { QFETCH(QString, fileName); QFETCH(QString, defName); auto def = m_repo.definitionForFileName(fileName); if (defName.isEmpty()) { QVERIFY(!def.isValid()); } else { QVERIFY(def.isValid()); QCOMPARE(def.name(), defName); } } void testLoadAll() { foreach (const auto &def, m_repo.definitions()) { QVERIFY(!def.name().isEmpty()); QVERIFY(!def.translatedName().isEmpty()); QVERIFY(!def.isValid() || !def.section().isEmpty()); QVERIFY(!def.isValid() || !def.translatedSection().isEmpty()); // indirectly trigger loading, as we can't reach that from public API // if the loading fails the highlighter will produce empty states NullHighlighter hl; State initialState; hl.setDefinition(def); const auto state = hl.highlightLine(QLatin1String("This should not crash } ] ) !"), initialState); QVERIFY(!def.isValid() || state != initialState); } } void testMetaData() { auto def = m_repo.definitionForName(QLatin1String("Alerts")); QVERIFY(def.isValid()); QVERIFY(def.extensions().isEmpty()); QVERIFY(def.mimeTypes().isEmpty()); QVERIFY(def.version() >= 1.11f); QVERIFY(def.isHidden()); QCOMPARE(def.section(), QLatin1String("Other")); QCOMPARE(def.license(), QLatin1String("MIT")); QVERIFY(def.author().contains(QLatin1String("Dominik"))); QFileInfo fi(def.filePath()); QVERIFY(fi.isAbsolute()); QVERIFY(def.filePath().endsWith(QLatin1String("alert.xml"))); def = m_repo.definitionForName(QLatin1String("C++")); QVERIFY(def.isValid()); QCOMPARE(def.section(), QLatin1String("Sources")); QCOMPARE(def.indenter(), QLatin1String("cstyle")); QCOMPARE(def.style(), QLatin1String("C++")); QVERIFY(def.mimeTypes().contains(QLatin1String("text/x-c++hdr"))); QVERIFY(def.extensions().contains(QLatin1String("*.h"))); QCOMPARE(def.priority(), 9); def = m_repo.definitionForName(QLatin1String("Apache Configuration")); QVERIFY(def.isValid()); QVERIFY(def.extensions().contains(QLatin1String("httpd.conf"))); QVERIFY(def.extensions().contains(QLatin1String(".htaccess*"))); } void testGeneralMetaData() { auto def = m_repo.definitionForName(QLatin1String("C++")); QVERIFY(def.isValid()); QVERIFY(!def.indentationBasedFoldingEnabled()); // comment markers QCOMPARE(def.singleLineCommentMarker(), QLatin1String("//")); QCOMPARE(def.singleLineCommentPosition(), KSyntaxHighlighting::CommentPosition::StartOfLine); const auto cppMultiLineCommentMarker = QPair(QLatin1String("/*"), QLatin1String("*/")); QCOMPARE(def.multiLineCommentMarker(), cppMultiLineCommentMarker); def = m_repo.definitionForName(QLatin1String("Python")); QVERIFY(def.isValid()); // indentation QVERIFY(def.indentationBasedFoldingEnabled()); QCOMPARE(def.foldingIgnoreList(), QStringList() << QLatin1String("(?:\\s+|\\s*#.*)")); // keyword lists QVERIFY(!def.keywordLists().isEmpty()); QVERIFY(def.keywordList(QLatin1String("operators")).contains(QLatin1String("and"))); QVERIFY(!def.keywordList(QLatin1String("does not exits")).contains(QLatin1String("and"))); } void testFormatData() { auto def = m_repo.definitionForName(QLatin1String("ChangeLog")); QVERIFY(def.isValid()); auto formats = def.formats(); QVERIFY(!formats.isEmpty()); // verify that the formats are sorted, such that the order matches the order of the itemDatas in the xml files. auto sortComparator = [](const KSyntaxHighlighting::Format & lhs, const KSyntaxHighlighting::Format & rhs) { return lhs.id() < rhs.id(); }; QVERIFY(std::is_sorted(formats.begin(), formats.end(), sortComparator)); // check all names are listed QStringList formatNames; foreach (const auto & format, formats) { formatNames.append(format.name()); } const QStringList expectedItemDatas = { QStringLiteral("Normal Text"), QStringLiteral("Name"), QStringLiteral("E-Mail"), QStringLiteral("Date"), QStringLiteral("Entry") }; QCOMPARE(formatNames, expectedItemDatas); } void testIncludedDefinitions() { - auto def = m_repo.definitionForName(QLatin1String("C++")); + auto def = m_repo.definitionForName(QLatin1String("PHP (HTML)")); QVERIFY(def.isValid()); auto defs = def.includedDefinitions(); const QStringList expectedDefinitionNames = { - QStringLiteral("ISO C++"), - QStringLiteral("GCCExtensions"), - QStringLiteral("Doxygen"), + QStringLiteral("PHP/PHP"), QStringLiteral("Alerts"), - QStringLiteral("Modelines") + QStringLiteral("CSS/PHP"), + QStringLiteral("JavaScript/PHP"), + QStringLiteral("Doxygen"), + QStringLiteral("Modelines"), + QStringLiteral("HTML"), + QStringLiteral("CSS"), + QStringLiteral("SQL (MySQL)"), + QStringLiteral("JavaScript") }; QStringList definitionNames; for (auto d : defs) { QVERIFY(d.isValid()); definitionNames.push_back(d.name()); } QCOMPARE(definitionNames, expectedDefinitionNames); } void testReload() { auto def = m_repo.definitionForName(QLatin1String("QML")); QVERIFY(!m_repo.definitions().isEmpty()); QVERIFY(def.isValid()); NullHighlighter hl; hl.setDefinition(def); auto oldState = hl.highlightLine(QLatin1String("/* TODO this should not crash */"), State()); m_repo.reload(); QVERIFY(!m_repo.definitions().isEmpty()); QVERIFY(!def.isValid()); hl.highlightLine(QLatin1String("/* TODO this should not crash */"), State()); hl.highlightLine(QLatin1String("/* FIXME neither should this crash */"), oldState); QVERIFY(hl.definition().isValid()); QCOMPARE(hl.definition().name(), QLatin1String("QML")); } void testLifetime() { // common mistake with value-type like Repo API, make sure this doesn'T crash NullHighlighter hl; { Repository repo; hl.setDefinition(repo.definitionForName(QLatin1String("C++"))); hl.setTheme(repo.defaultTheme()); } hl.highlightLine(QLatin1String("/**! @todo this should not crash .*/"), State()); } void testCustomPath() { QString testInputPath = QStringLiteral(TESTSRCDIR "/input"); Repository repo; QVERIFY(repo.customSearchPaths().empty()); repo.addCustomSearchPath(testInputPath); QCOMPARE(repo.customSearchPaths().size(), 1); QCOMPARE(repo.customSearchPaths()[0], testInputPath); auto customDefinition = repo.definitionForName(QLatin1String("Test Syntax")); QVERIFY(customDefinition.isValid()); auto customTheme = repo.theme(QLatin1String("Test Theme")); QVERIFY(customTheme.isValid()); } void testInvalidDefinition() { Definition def; QVERIFY(!def.isValid()); QVERIFY(def.filePath().isEmpty()); QCOMPARE(def.name(), QLatin1String("None")); QVERIFY(def.section().isEmpty()); QVERIFY(def.translatedSection().isEmpty()); QVERIFY(def.mimeTypes().isEmpty()); QVERIFY(def.extensions().isEmpty()); QCOMPARE(def.version(), 0); QCOMPARE(def.priority(), 0); QVERIFY(!def.isHidden()); QVERIFY(def.style().isEmpty()); QVERIFY(def.indenter().isEmpty()); QVERIFY(def.author().isEmpty()); QVERIFY(def.license().isEmpty()); QVERIFY(!def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); QVERIFY(def.foldingIgnoreList().isEmpty()); QVERIFY(def.keywordLists().isEmpty()); QVERIFY(def.formats().isEmpty()); QVERIFY(def.includedDefinitions().isEmpty()); QVERIFY(def.singleLineCommentMarker().isEmpty()); QCOMPARE(def.singleLineCommentPosition(), KSyntaxHighlighting::CommentPosition::StartOfLine); const auto emptyPair = QPair(); QCOMPARE(def.multiLineCommentMarker(), emptyPair); QVERIFY(def.characterEncodings().isEmpty()); for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) { QVERIFY(def.isWordDelimiter(c)); QVERIFY(def.isWordWrapDelimiter(c)); } } void testDelimiters() { auto def = m_repo.definitionForName(QLatin1String("LaTeX")); QVERIFY(def.isValid()); // check that backslash '\' is removed for (QChar c : QStringLiteral("\t !%&()*+,-./:;<=>?[]^{|}~")) QVERIFY(def.isWordDelimiter(c)); QVERIFY(!def.isWordDelimiter(QLatin1Char('\\'))); // check where breaking a line is valid for (QChar c : QStringLiteral(",{}[]")) QVERIFY(def.isWordWrapDelimiter(c)); } void testFoldingEnabled() { // test invalid folding Definition def; QVERIFY(!def.isValid()); QVERIFY(!def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // test no folding def = m_repo.definitionForName(QLatin1String("ChangeLog")); QVERIFY(def.isValid()); QVERIFY(!def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // C++ itself has no regions, but it includes ISO C++ def = m_repo.definitionForName(QLatin1String("C++")); QVERIFY(def.isValid()); QVERIFY(def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // ISO C++ itself has folding regions def = m_repo.definitionForName(QLatin1String("ISO C++")); QVERIFY(def.isValid()); QVERIFY(def.foldingEnabled()); QVERIFY(!def.indentationBasedFoldingEnabled()); // Python has indentation based folding def = m_repo.definitionForName(QLatin1String("Python")); QVERIFY(def.isValid()); QVERIFY(def.foldingEnabled()); QVERIFY(def.indentationBasedFoldingEnabled()); } void testCharacterEncodings() { auto def = m_repo.definitionForName(QLatin1String("LaTeX")); QVERIFY(def.isValid()); const auto encodings = def.characterEncodings(); QVERIFY(!encodings.isEmpty()); QVERIFY(encodings.contains({ QChar(196), QLatin1String("\\\"{A}") })); QVERIFY(encodings.contains({ QChar(227), QLatin1String("\\~{a}") })); } }; } QTEST_GUILESS_MAIN(KSyntaxHighlighting::RepositoryTest) #include "syntaxrepository_test.moc" diff --git a/src/lib/definition.cpp b/src/lib/definition.cpp index 4e6f4f0..9ad7119 100644 --- a/src/lib/definition.cpp +++ b/src/lib/definition.cpp @@ -1,762 +1,766 @@ /* 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() : wordDelimiters(QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~")) // must be sorted! , wordWrapDelimiters(wordDelimiters) { } 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::isWordDelimiter(QChar c) const { d->load(); return d->isWordDelimiter(c); } bool Definition::isWordWrapDelimiter(QChar c) const { d->load(); return std::binary_search(d->wordWrapDelimiters.constBegin(), d->wordWrapDelimiters.constEnd(), c); } bool Definition::foldingEnabled() const { d->load(); if (d->hasFoldingRegions || indentationBasedFoldingEnabled()) { return true; } // check included definitions for (const auto &def : includedDefinitions()) { if (def.foldingEnabled()) { d->hasFoldingRegions = true; break; } } return d->hasFoldingRegions; } 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(); // sort formats so that the order matches the order of the itemDatas in the xml files. auto formatList = QVector::fromList(d->formats.values()); std::sort(formatList.begin(), formatList.end(), [](const KSyntaxHighlighting::Format & lhs, const KSyntaxHighlighting::Format & rhs){ return lhs.id() < rhs.id(); }); return formatList; } QVector Definition::includedDefinitions() const { d->load(); - QVector definitions; - - QQueue queue; - queue.enqueue(*this); + // init worklist and result used as guard with this definition + QVector queue{*this}; + QVector definitions{*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()); + // automatically catch other Definitions referenced with IncludeRuldes or ContextSwitch. + const auto definition = queue.takeLast(); + for (const auto & context : definition.d->contexts) { + for (const auto &rule : context->rules()) { + // handle include rules like inclusion + if (!definitions.contains(rule->definition())) { + queue.push_back(rule->definition()); + definitions.push_back(rule->definition()); + } + + // handle context switch context inclusion + if (auto switchContext = rule->context().context()) { + if (!definitions.contains(switchContext->definition())) { + queue.push_back(switchContext->definition()); + definitions.push_back(switchContext->definition()); + } } } } } // remove the 1st entry, since it is this Definition definitions.pop_front(); return definitions; } QString Definition::singleLineCommentMarker() const { d->load(); return d->singleLineCommentMarker; } CommentPosition Definition::singleLineCommentPosition() const { d->load(); return d->singleLineCommentPosition; } QPair Definition::multiLineCommentMarker() const { d->load(); return { d->multiLineCommentStartMarker, d->multiLineCommentEndMarker }; } QVector> Definition::characterEncodings() const { d->load(); return d->characterEncodings; } 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::isWordDelimiter(QChar c) const { return std::binary_search(wordDelimiters.constBegin(), wordDelimiters.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 (fileName.isEmpty()) return false; if (isLoaded()) return true; 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(wordDelimiters.constBegin(), wordDelimiters.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(); wordDelimiters = QStringLiteral("\t !%&()*+,-./:;<=>?[\\]^{|}~"); // must be sorted! wordWrapDelimiters = wordDelimiters; 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; // adapt sorted wordDelimiters wordDelimiters += reader.attributes().value(QStringLiteral("additionalDeliminator")); std::sort(wordDelimiters.begin(), wordDelimiters.end()); auto it = std::unique(wordDelimiters.begin(), wordDelimiters.end()); wordDelimiters.truncate(std::distance(wordDelimiters.begin(), it)); foreach (const auto c, reader.attributes().value(QLatin1String("weakDeliminator"))) wordDelimiters.remove(c); // adaptWordWrapDelimiters, and sort wordWrapDelimiters = reader.attributes().value(QStringLiteral("wordWrapDeliminator")).toString(); std::sort(wordWrapDelimiters.begin(), wordWrapDelimiters.end()); if (wordWrapDelimiters.isEmpty()) wordWrapDelimiters = wordDelimiters; } 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 if (reader.name() == QLatin1String("comments")) { loadComments(reader); } else if (reader.name() == QLatin1String("spellchecking")) { loadSpellchecking(reader); } else { reader.skipCurrentElement(); } reader.readNext(); break; case QXmlStreamReader::EndElement: --elementRefCounter; if (elementRefCounter == 0) return; reader.readNext(); break; default: reader.readNext(); break; } } } void DefinitionData::loadComments(QXmlStreamReader &reader) { Q_ASSERT(reader.name() == QLatin1String("comments")); 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("comment")) { const bool isSingleLine = reader.attributes().value(QStringLiteral("name")) == QStringLiteral("singleLine"); if (isSingleLine) { singleLineCommentMarker = reader.attributes().value(QStringLiteral("start")).toString(); const bool afterWhiteSpace = reader.attributes().value(QStringLiteral("position")).toString() == QStringLiteral("afterwhitespace"); singleLineCommentPosition = afterWhiteSpace ? CommentPosition::AfterWhitespace : CommentPosition::StartOfLine; } else { multiLineCommentStartMarker = reader.attributes().value(QStringLiteral("start")).toString(); multiLineCommentEndMarker = reader.attributes().value(QStringLiteral("end")).toString(); } } reader.readNext(); break; case QXmlStreamReader::EndElement: --elementRefCounter; if (elementRefCounter == 0) return; reader.readNext(); break; 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; reader.readNext(); break; default: reader.readNext(); break; } } } void DefinitionData::loadSpellchecking(QXmlStreamReader &reader) { Q_ASSERT(reader.name() == QLatin1String("spellchecking")); 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("encoding")) { const auto charRef = reader.attributes().value(QStringLiteral("char")); if (!charRef.isEmpty()) { const auto str = reader.attributes().value(QStringLiteral("string")).toString(); characterEncodings.push_back({ charRef[0], str }); } } reader.readNext(); break; case QXmlStreamReader::EndElement: --elementRefCounter; if (elementRefCounter == 0) return; reader.readNext(); break; 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) { hasFoldingRegions = true; 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(); }