diff --git a/autotests/syntaxrepository_test.cpp b/autotests/syntaxrepository_test.cpp index 76a2db8..a5e2e20 100644 --- a/autotests/syntaxrepository_test.cpp +++ b/autotests/syntaxrepository_test.cpp @@ -1,559 +1,581 @@ /* Copyright (C) 2016 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #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::setTestModeEnabled(true); initRepositorySearchPaths(m_repo); } 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("fs") << QStringLiteral("test.fs") << QStringLiteral("FSharp"); 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 testDefinitionsForFileName_data() { QTest::addColumn("fileName"); QTest::addColumn("expectedNames"); QTest::newRow("Matlab") << QStringLiteral("/bla/foo.m") << (QStringList() << QStringLiteral("Magma") << QStringLiteral("Matlab") << QStringLiteral("Octave") << QStringLiteral("Objective-C")); } void testDefinitionsForFileName() { QFETCH(QString, fileName); QFETCH(QStringList, expectedNames); const auto defs = m_repo.definitionsForFileName(fileName); QStringList names; for (auto def : defs) { names.push_back(def.name()); } QCOMPARE(names, expectedNames); } void testDefinitionsForMimeType_data() { QTest::addColumn("mimeType"); QTest::addColumn("expectedNames"); QTest::newRow("C Header") << QStringLiteral("text/x-chdr") << (QStringList() << QStringLiteral("C++") << QStringLiteral("ISO C++") << QStringLiteral("C") << QStringLiteral("GCCExtensions") << QStringLiteral("ANSI C89") << QStringLiteral("SystemC")); } void testDefinitionsForMimeType() { QFETCH(QString, mimeType); QFETCH(QStringList, expectedNames); const auto defs = m_repo.definitionsForMimeType(mimeType); QStringList names; for (auto def : defs) { names.push_back(def.name()); } QCOMPARE(names, expectedNames); } void testLoadAll() { for (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 || def.name() == QLatin1String("Broken Syntax")); } } 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; for (const auto & format : qAsConst(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("PHP (HTML)")); QVERIFY(def.isValid()); auto defs = def.includedDefinitions(); const QStringList expectedDefinitionNames = { QStringLiteral("PHP/PHP"), QStringLiteral("Alerts"), QStringLiteral("CSS/PHP"), QStringLiteral("JavaScript/PHP"), QStringLiteral("Doxygen"), QStringLiteral("JavaScript React/PHP"), QStringLiteral("TypeScript/PHP"), QStringLiteral("Mustache/Handlebars (HTML)/PHP"), QStringLiteral("Modelines"), QStringLiteral("HTML"), QStringLiteral("CSS"), QStringLiteral("SQL (MySQL)"), QStringLiteral("JavaScript"), QStringLiteral("JavaScript React"), QStringLiteral("TypeScript"), QStringLiteral("Mustache/Handlebars (HTML)") }; QStringList definitionNames; for (auto d : defs) { QVERIFY(d.isValid()); definitionNames.push_back(d.name()); // already check here a bit to make the test fails better fixable if (definitionNames.size() <= expectedDefinitionNames.size()) { QCOMPARE(d.name(), expectedDefinitionNames[definitionNames.size()-1]); } else { QCOMPARE(d.name(), QStringLiteral("too many included defs found, first one is this one")); } } QCOMPARE(definitionNames, expectedDefinitionNames); } void testIncludedFormats() { QStringList definitionNames; for (const auto &def : m_repo.definitions()) { definitionNames.push_back(def.name()); } for (const QString & name : qAsConst(definitionNames)) { Repository repo; initRepositorySearchPaths(repo); auto def = repo.definitionForName(name); QCOMPARE(m_repo.definitionForName(name).isValid(), def.isValid()); auto includedDefs = def.includedDefinitions(); includedDefs.push_front(def); // collect all formats, shall be numbered from 1.. QSet formatIds; for (auto d : qAsConst(includedDefs)) { const auto formats = d.formats(); for (const auto format : formats) { // no duplicates QVERIFY(!formatIds.contains(format.id())); formatIds.insert(format.id()); } } QVERIFY(!def.isValid() || def.name() == QLatin1String("Broken Syntax") || !formatIds.isEmpty()); // ensure all ids are there from 1..size for (int i = 1; i <= formatIds.size(); ++i) { QVERIFY(formatIds.contains(i)); } } } 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 '\' and '*' are 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}") })); } void testIncludeKeywordLists() { Repository repo; QTemporaryDir dir; // forge a syntax file { QVERIFY(QDir(dir.path()).mkpath(QLatin1String("syntax"))); const char syntax[] = R"xml( c a b a c b d e##AAA e f f )xml"; QFile file(dir.path() + QLatin1String("/syntax/a.xml")); QVERIFY(file.open(QIODevice::WriteOnly)); QTextStream stream(&file); stream << syntax; } repo.addCustomSearchPath(dir.path()); auto def = repo.definitionForName(QLatin1String("AAA")); QCOMPARE(def.name(), QLatin1String("AAA")); auto klist1 = def.keywordList(QLatin1String("a")); auto klist2 = def.keywordList(QLatin1String("b")); auto klist3 = def.keywordList(QLatin1String("c")); // internal QHash is arbitrarily ordered and undeterministic auto& klist = klist1.size() == 3 ? klist1 : klist2.size() == 3 ? klist2 : klist3; QCOMPARE(klist.size(), 3); QVERIFY(klist.contains(QLatin1String("a"))); QVERIFY(klist.contains(QLatin1String("b"))); QVERIFY(klist.contains(QLatin1String("c"))); klist = def.keywordList(QLatin1String("d")); QCOMPARE(klist.size(), 3); QVERIFY(klist.contains(QLatin1String("d"))); QVERIFY(klist.contains(QLatin1String("e"))); QVERIFY(klist.contains(QLatin1String("f"))); klist = def.keywordList(QLatin1String("e")); QCOMPARE(klist.size(), 2); QVERIFY(klist.contains(QLatin1String("e"))); QVERIFY(klist.contains(QLatin1String("f"))); klist = def.keywordList(QLatin1String("f")); QCOMPARE(klist.size(), 1); QVERIFY(klist.contains(QLatin1String("f"))); } + + void testKeywordListModification() + { + auto def = m_repo.definitionForName(QLatin1String("Python")); + QVERIFY(def.isValid()); + + const QStringList& lists = def.keywordLists(); + QVERIFY(!lists.isEmpty()); + + const QString& listName = lists.first(); + const QStringList keywords = def.keywordList(listName); + + QStringList modified = keywords; + modified.append(QLatin1String("test")); + + QVERIFY(def.setKeywordList(listName, modified) == true); + QCOMPARE(keywords + QStringList(QLatin1String("test")), def.keywordList(listName)); + + const QString& unexistedName = QLatin1String("unexisted-keyword-name"); + QVERIFY(lists.contains(unexistedName) == false); + QVERIFY(def.setKeywordList(unexistedName, QStringList()) == false); + } }; } QTEST_GUILESS_MAIN(KSyntaxHighlighting::RepositoryTest) #include "syntaxrepository_test.moc" diff --git a/src/lib/definition.cpp b/src/lib/definition.cpp index 93d3a0b..6a204dc 100644 --- a/src/lib/definition.cpp +++ b/src/lib/definition.cpp @@ -1,832 +1,845 @@ /* Copyright (C) 2016 Volker Krause Copyright (C) 2018 Dominik Haumann Copyright (C) 2018 Christoph Cullmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "definition.h" #include "definition_p.h" #include "definitionref_p.h" #include "context_p.h" #include "format.h" #include "format_p.h" #include "repository.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 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 const auto defs = includedDefinitions(); for (const auto &def : defs) { 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(DefinitionData::OnlyKeywords(true)); return d->keywordLists.keys(); } QStringList Definition::keywordList(const QString& name) const { d->load(DefinitionData::OnlyKeywords(true)); const auto list = d->keywordList(name); return list ? list->keywords() : QStringList(); } +bool Definition::setKeywordList(const QString& name, const QStringList& content) +{ + d->load(DefinitionData::OnlyKeywords(true)); + KeywordList* list = d->keywordList(name); + if (list) + { + list->setKeywordList(content); + return true; + } + else + return false; +} + 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(); // init worklist and result used as guard with this definition QVector queue{*this}; QVector definitions{*this}; while (!queue.isEmpty()) { // Iterate all context rules to find associated Definitions. This will // automatically catch other Definitions referenced with IncludeRuldes or ContextSwitch. const auto definition = queue.takeLast(); for (const auto & context : qAsConst(definition.d->contexts)) { // handle context switch attributes of this context itself for (const auto switchContext : {context->lineEndContext().context(), context->lineEmptyContext().context(), context->fallthroughContext().context()}) { if (switchContext) { if (!definitions.contains(switchContext->definition())) { queue.push_back(switchContext->definition()); definitions.push_back(switchContext->definition()); } } } // handle the embedded rules 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& wantedName) const { for (const auto context : contexts) { if (context->name() == wantedName) return context; } return nullptr; } KeywordList *DefinitionData::keywordList(const QString& wantedName) { auto it = keywordLists.find(wantedName); return (it == keywordLists.end()) ? nullptr : &it.value(); } bool DefinitionData::isWordDelimiter(QChar c) const { return std::binary_search(wordDelimiters.constBegin(), wordDelimiters.constEnd(), c); } Format DefinitionData::formatByName(const QString& wantedName) const { const auto it = formats.constFind(wantedName); if (it != formats.constEnd()) return it.value(); return Format(); } bool DefinitionData::isLoaded() const { return !contexts.isEmpty(); } bool DefinitionData::load(OnlyKeywords onlyKeywords) { if (fileName.isEmpty()) return false; if (isLoaded()) return true; if (bool(onlyKeywords) && keywordIsLoaded) 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, onlyKeywords); if (bool(onlyKeywords)) { return true; } } else if (reader.name() == QLatin1String("general")) loadGeneral(reader); } for (auto it = keywordLists.begin(); it != keywordLists.end(); ++it) { it->setCaseSensitivity(caseSensitive); } for (const auto context : qAsConst(contexts)) { context->resolveContexts(); context->resolveIncludes(); context->resolveAttributeFormat(); } 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(); for (const auto &ext : exts.split(QLatin1Char(';'), QString::SkipEmptyParts)) extensions.push_back(ext); const auto mts = obj.value(QLatin1String("mimetype")).toString(); for (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(); for (const auto &ext : exts.split(QLatin1Char(';'), QString::SkipEmptyParts)) extensions.push_back(ext); const auto mts = reader.attributes().value(QStringLiteral("mimetype")).toString(); for (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, OnlyKeywords onlyKeywords) { Q_ASSERT(reader.name() == QLatin1String("highlighting")); Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); // skip highlighting reader.readNext(); while (!reader.atEnd()) { switch (reader.tokenType()) { case QXmlStreamReader::StartElement: if (reader.name() == QLatin1String("list")) { if (!keywordIsLoaded) { KeywordList keywords; keywords.load(reader); keywordLists.insert(keywords.name(), keywords); } else { reader.skipCurrentElement(); reader.readNext(); // Skip } } else if (bool(onlyKeywords)) { resolveIncludeKeywords(); return; } else if (reader.name() == QLatin1String("contexts")) { resolveIncludeKeywords(); 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::resolveIncludeKeywords() { if (keywordIsLoaded) { return; } keywordIsLoaded = true; for (auto it = keywordLists.begin(); it != keywordLists.end(); ++it) { it->resolveIncludeKeywords(*this); } } 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::detachAndGet(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)); for (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(); } bool DefinitionRef::operator==(const DefinitionRef &other) const { if (d.expired() != other.d.expired()) { return false; } return d.expired() || d.lock().get() == other.d.lock().get(); } diff --git a/src/lib/definition.h b/src/lib/definition.h index dbbfcb4..b625b1b 100644 --- a/src/lib/definition.h +++ b/src/lib/definition.h @@ -1,399 +1,414 @@ /* Copyright (C) 2016 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KSYNTAXHIGHLIGHTING_DEFINITION_H #define KSYNTAXHIGHLIGHTING_DEFINITION_H #include "ksyntaxhighlighting_export.h" #include #include #include QT_BEGIN_NAMESPACE class QChar; class QString; class QStringList; template class QVector; QT_END_NAMESPACE namespace KSyntaxHighlighting { class Context; class Format; class KeywordList; class DefinitionData; /** * Defines the insert position when commenting code. * @since 5.50 * @see Definition::singleLineCommentPosition() */ enum class CommentPosition { //! The comment marker is inserted at the beginning of a line at column 0 StartOfLine = 0, //! The comment marker is inserted after leading whitespaces right befire //! the first non-whitespace character. AfterWhitespace }; /** * Represents a syntax definition. * * @section def_intro Introduction to Definitions * * A Definition is the short term for a syntax highlighting definition. It * typically is defined in terms of an XML syntax highlighting file, containing * all information about a particular syntax highlighting. This includes the * highlighting of keywords, information about code folding regions, and * indentation preferences. * * @section def_info General Header Data * * Each Definition contains a non-translated unique name() and a section(). * In addition, for putting this information e.g. into menus, the functions * translatedName() and translatedSection() are provided. However, if isHidden() * returns @e true, the Definition should not be visible in the UI. The location * of the Definition can be obtained through filePath(), which either is the * location on disk or a path to a compiled-in Qt resource. * * The supported files of a Definition are defined by the list of extensions(), * and additionally by the list of mimeTypes(). Note, that extensions() returns * wildcards that need to be matched against the filename of the file that * requires highlighting. If multiple Definition%s match the file, then the one * with higher priority() wins. * * @section def_metadata Advanced Definition Data * * Advanced text editors such as Kate require additional information from a * Definition. For instance, foldingEnabled() defines whether a Definition has * code folding regions that can be shown in a code folding pane. Or * singleLineCommentMarker() and multiLineCommentMarker() provide comment * markers that can be used for commenting/uncommenting code. Similarly, * formats() returns a list of Format items defined by this Definition (which * equal the itemDatas of a highlighing definition file). includedDefinitions() * returns a list of all included Definition%s referenced by this Definition via * the rule IncludeRules, which is useful for displaying all Format items for * color configuration in the user interface. * * @see Repository * @since 5.28 */ class KSYNTAXHIGHLIGHTING_EXPORT Definition { public: /** * Default constructor, creating an empty (invalid) Definition instance. * isValid() for this instance returns @e false. * * Use the Repository instead to obtain valid instances. */ Definition(); /** * Copy constructor. * Both this definition as well as @p other share the Definition data. */ Definition(const Definition &other); /** * Destructor. */ ~Definition(); /** * Assignment operator. * Both this definition as well as @p rhs share the Definition data. */ Definition& operator=(const Definition &rhs); /** * Checks two definitions for equality. */ bool operator==(const Definition &other) const; /** * Checks two definitions for inequality. */ bool operator!=(const Definition &other) const; /** * @name General Header Data * * @{ */ /** * Checks whether this object refers to a valid syntax definition. */ bool isValid() const; /** * Returns the full path to the definition XML file containing * the syntax definition. Note that this can be a path to QRC content. */ QString filePath() const; /** Name of the syntax. * Used for internal references, prefer translatedName() for display. */ QString name() const; /** * Translated name for display. */ QString translatedName() const; /** * The group this syntax definition belongs to. * For display, consider translatedSection(). */ QString section() const; /** * Translated group name for display. */ QString translatedSection() const; /** * Mime types associated with this syntax definition. */ QVector mimeTypes() const; /** * File extensions associated with this syntax definition. * The returned list contains wildcards. */ QVector extensions() const; /** * Returns the definition version. */ int version() const; /** * Returns the definition priority. * A Definition with higher priority wins over Definitions with lower priorities. */ int priority() const; /** * Returns @c true if this is an internal definition that should not be * displayed to the user. */ bool isHidden() const; /** * Generalized language style, used for indentation. */ QString style() const; /** * Indentation style to be used for this syntax. */ QString indenter() const; /** * Name and email of the author of this syntax definition. */ QString author() const; /** * License of this syntax definition. */ QString license() const; /** * @} * * @name Advanced Definition Data */ /** * Returns whether the character @p c is a word delimiter. * A delimiter defines whether a characters is a word boundary. Internally, * delimiters are used for matching keyword lists. As example, typically the * dot '.' is a word delimiter. However, if you have a keyword in a keyword * list that contains a dot, you have to add the dot to the * @e weakDeliminator attribute of the @e general section in your * highlighting definition. Similarly, sometimes additional delimiters are * required, which can be specified in @e additionalDeliminator. * * Checking whether a character is a delimiter is useful for instance if * text is selected with double click. Typically, the whole word should be * selected in this case. Similarly to the example above, the dot '.' * usually acts as word delimiter. However, using this function you can * implement text selection in such a way that keyword lists are correctly * selected. * * @note By default, the list of delimiters contains the following * characters: \\t !%&()*+,-./:;<=>?[\\]^{|}~ * * @since 5.50 * @see isWordWrapDelimiter() */ bool isWordDelimiter(QChar c) const; /** * Returns whether it is safe to break a line at before the character @c. * This is useful when wrapping a line e.g. by applying static word wrap. * * As example, consider the LaTeX code * @code * \command1\command2 * @endcode * Applying static word wrap could lead to the following code: * @code * \command1\ * command2 * @endcode * command2 without a leading backslash is invalid in LaTeX. If '\\' is set * as word wrap delimiter, isWordWrapDelimiter('\\') then returns true, * meaning that it is safe to break the line before @c. The resulting code * then would be * @code * \command1 * \command2 * @endcode * * @note By default, the word wrap delimiters are equal to the word * delimiters in isWordDelimiter(). * * @since 5.50 * @see isWordDelimiter() */ bool isWordWrapDelimiter(QChar c) const; /** * Returns whether the highlighting supports code folding. * Code folding is supported either if the highlighting defines code folding * regions or if indentationBasedFoldingEnabled() returns @e true. * @since 5.50 * @see indentationBasedFoldingEnabled() */ bool foldingEnabled() const; /** * Returns whether indentation-based folding is enabled. * An example for indentation-based folding is Python. * When indentation-based folding is enabled, make sure to also check * foldingIgnoreList() for lines that should be treated as empty. * * @see foldingIgnoreList(), State::indentationBasedFoldingEnabled() */ bool indentationBasedFoldingEnabled() const; /** * If indentationBasedFoldingEnabled() returns @c true, this function returns * a list of regular expressions that represent empty lines. That is, all * lines matching entirely one of the regular expressions should be treated * as empty lines when calculating the indentation-based folding ranges. * * @note This list is only of relevance, if indentationBasedFoldingEnabled() * returns @c true. * * @see indentationBasedFoldingEnabled() */ QStringList foldingIgnoreList() const; /** * Returns the section names of keywords. * @since 5.49 * @see keywordList() */ QStringList keywordLists() const; /** * Returns the list of keywords for the keyword list @p name. * @since 5.49 * @see keywordLists() */ QStringList keywordList(const QString& name) const; + /** + * Set content of keyword list with name @p name to @p content + * Only existing keywordLists() can be changed. For non-existent keyword lists, false is returned. + * @see keywordList(), keywordLists() + * + * This function can be usefull for dynamic higlighting, when the document rehiglights more, that one time. + * For example, you have a program for editing and running Octave scripts files and + * you are using KSyntaxHighlighting for syntax higlighting. But also, you can request list of + * variables, functions, etc from corresponed Octave process. + * And, used this function, you can provide Octave live higlighting - by passing, for + * example, variables names from Octave to KSyntaxHighlighting variable keyword list via this function. + * @since 5.62 + */ + bool setKeywordList(const QString& name, const QStringList& content); + /** * Returns a list of all Format items used by this definition. * The order of the Format items equals the order of the itemDatas in the xml file. * @since 5.49 */ QVector formats() const; /** * Returns a list of Definitions that are referenced with the IncludeRules rule. * The returned list does not include this Definition. In case no other * Definitions are referenced via IncludeRules, the returned list is empty. * * @since 5.49 */ QVector includedDefinitions() const; /** * Returns the marker that starts a single line comment. * For instance, in C++ the single line comment marker is "//". * @since 5.50 * @see singleLineCommentPosition(); */ QString singleLineCommentMarker() const; /** * Returns the insert position of the comment marker for sinle line * comments. * @since 5.50 * @see singleLineCommentMarker(); */ CommentPosition singleLineCommentPosition() const; /** * Returns the markers that start and end multiline comments. * For instance, in XML this is defined as "". * @since 5.50 */ QPair multiLineCommentMarker() const; /** * Returns a list of character/string mapping that can be used for spell * checking. This is useful for instance when spell checking LaTeX, where * the string \"{A} represents the character Ä. * @since 5.50 */ QVector> characterEncodings() const; /** * @} */ private: friend class DefinitionData; friend class DefinitionRef; explicit Definition(const std::shared_ptr &dd); std::shared_ptr d; }; } QT_BEGIN_NAMESPACE Q_DECLARE_TYPEINFO(KSyntaxHighlighting::Definition, Q_MOVABLE_TYPE); QT_END_NAMESPACE #endif diff --git a/src/lib/keywordlist_p.h b/src/lib/keywordlist_p.h index 7c44e65..f99a027 100644 --- a/src/lib/keywordlist_p.h +++ b/src/lib/keywordlist_p.h @@ -1,108 +1,117 @@ /* Copyright (C) 2016 Volker Krause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H #define KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H #include #include + #include QT_BEGIN_NAMESPACE class QXmlStreamReader; QT_END_NAMESPACE namespace KSyntaxHighlighting { class Repository; class DefinitionData; class KeywordList { public: KeywordList() = default; ~KeywordList() = default; bool isEmpty() const { return m_keywords.isEmpty(); } const QString &name() const { return m_name; } const QStringList &keywords() const { return m_keywords; } + void setKeywordList(const QStringList& keywords) + { + m_keywords = keywords; + m_keywordsSortedCaseSensitive.clear(); + m_keywordsSortedCaseInsensitive.clear(); + initLookupForCaseSensitivity(m_caseSensitive); + } + /** Checks if @p str is a keyword in this list. */ bool contains(const QStringRef &str) const { return contains(str, m_caseSensitive); } /** Checks if @p str is a keyword in this list, overriding the global case-sensitivity setting. */ bool contains(const QStringRef &str, Qt::CaseSensitivity caseSensitive) const; void load(QXmlStreamReader &reader); void setCaseSensitivity(Qt::CaseSensitivity caseSensitive); void initLookupForCaseSensitivity(Qt::CaseSensitivity caseSensitive); void resolveIncludeKeywords(DefinitionData &def); private: /** * name of keyword list as in XML */ QString m_name; /** * raw list of keywords, as seen in XML (but trimmed) */ QStringList m_keywords; /** * raw list of include keywords, as seen in XML (but trimmed) */ QStringList m_includes; /** * default case-sensitivity setting */ Qt::CaseSensitivity m_caseSensitive = Qt::CaseSensitive; /** * case-sensitive sorted string references to m_keywords for lookup */ std::vector m_keywordsSortedCaseSensitive; /** * case-insensitive sorted string references to m_keywords for lookup */ std::vector m_keywordsSortedCaseInsensitive; }; } #endif // KSYNTAXHIGHLIGHTING_KEYWORDLIST_P_H