diff --git a/src/lib/repository.cpp b/src/lib/repository.cpp index 2c2ef8d..aaba961 100644 --- a/src/lib/repository.cpp +++ b/src/lib/repository.cpp @@ -1,334 +1,330 @@ /* 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 "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 #ifndef NO_STANDARD_PATHS #include #endif #include using namespace KSyntaxHighlighting; static void initResource() { #ifdef HAS_SYNTAX_RESOURCE Q_INIT_RESOURCE(syntax_data); #endif Q_INIT_RESOURCE(theme_data); } 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 for (const auto &def : qAsConst(d->m_sortedDefs)) DefinitionData::get(def)->repo = nullptr; } Definition Repository::definitionForName(const QString& defName) const { return d->m_defs.value(defName); } -static Definition bestCandidate(QVector &&candidates) +static void sortDefinitions(QVector &definitions) { - if (candidates.isEmpty()) - return Definition(); - - std::partial_sort(candidates.begin(), candidates.begin() + 1, candidates.end(), [](const Definition &lhs, const Definition &rhs) { + std::stable_sort(definitions.begin(), definitions.end(), [](const Definition &lhs, const Definition &rhs) { return lhs.priority() > rhs.priority(); }); - - return candidates.at(0); } Definition Repository::definitionForFileName(const QString& fileName) const { - return bestCandidate(definitionsForFileName(fileName)); + return definitionsForFileName(fileName).value(0); } QVector Repository::definitionsForFileName(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(); + for (const Definition &def : qAsConst(d->m_sortedDefs)) { for (const auto &pattern : def.extensions()) { if (WildcardMatcher::exactMatch(name, pattern)) { candidates.push_back(def); break; } } } + sortDefinitions(candidates); return candidates; } Definition Repository::definitionForMimeType(const QString& mimeType) const { - return bestCandidate(definitionsForMimeType(mimeType)); + return definitionsForMimeType(mimeType).value(0); } QVector Repository::definitionsForMimeType(const QString &mimeType) const { QVector candidates; - for (auto it = d->m_defs.constBegin(); it != d->m_defs.constEnd(); ++it) { - auto def = it.value(); + for (const Definition &def : qAsConst(d->m_sortedDefs)) { for (const auto &matchType : def.mimeTypes()) { if (mimeType == matchType) { candidates.push_back(def); break; } } } + + sortDefinitions(candidates); return candidates; } 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 : qAsConst(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) { // always add invalid default "None" highlighting addDefinition(Definition()); // do lookup in standard paths, if not disabled #ifndef NO_STANDARD_PATHS for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/syntax"), QStandardPaths::LocateDirectory)) loadSyntaxFolder(repo, dir); // backward compatibility with Kate for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("katepart5/syntax"), QStandardPaths::LocateDirectory)) loadSyntaxFolder(repo, dir); #endif // default resources are always used loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax")); // user given extra paths for (const auto &path : qAsConst(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 // do lookup in standard paths, if not disabled #ifndef NO_STANDARD_PATHS for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/themes"), QStandardPaths::LocateDirectory)) loadThemeFolder(dir); #endif // default resources are always used loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes")); // user given extra paths for (const auto &path : qAsConst(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!"; for (const auto &def : qAsConst(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.h b/src/lib/repository.h index 93e8e83..2bc6696 100644 --- a/src/lib/repository.h +++ b/src/lib/repository.h @@ -1,273 +1,273 @@ /* 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_REPOSITORY_H #define KSYNTAXHIGHLIGHTING_REPOSITORY_H #include "ksyntaxhighlighting_export.h" #include #include QT_BEGIN_NAMESPACE class QString; template class QVector; QT_END_NAMESPACE /** * @namespace KSyntaxHighlighting * * Syntax highlighting engine for Kate syntax definitions. * In order to access the syntax highlighting Definition files, use the * class Repository. * * @see Repository */ namespace KSyntaxHighlighting { class Definition; class RepositoryPrivate; class Theme; /** * @brief Syntax highlighting repository. * * @section repo_intro Introduction * * The Repository gives access to all syntax Definitions available on the * system, typically described in *.xml files. The Definition files are read * from the resource that is compiled into the executable, and from the file * system. If a Definition exists in the resource and on the file system, * then the one from the file system is chosen. * * @section repo_access Definitions and Themes * * Typically, only one instance of the Repository is needed. This single * instance can be thought of as a singleton you keep alive throughout the * lifetime of your application. Then, either call definitionForName() with the * given language name (e.g. "QML" or "Java"), or definitionForFileName() to * obtain a Definition based on the filename/mimetype of the file. The * function definitions() returns a list of all available syntax Definition%s. * * In addition to Definitions, the Repository also provides a list of Themes. * A Theme is defined by a set of default text style colors as well as editor * colors. These colors together provide all required colros for drawing all * primitives of a text editor. All available Theme%s can be queried through * themes(), and a Theme with a specific name is obtained through theme(). * Additionally, defaultTheme() provides a way to obtain a default theme for * either a light or a black color theme. * * @section repo_search_paths Search Paths * * All highlighting Definition and Theme files are compiled into the shared * KSyntaxHighlighting library by using the Qt resource system. Loading * additional files from disk is supported as well. * * Loading syntax Definition files works as follows: * * -# First, all syntax highlighting files are loaded that are located in * QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/syntax"), QStandardPaths::LocateDirectory); * Under Unix, this uses $XDG_DATA_HOME and $XDG_DATA_DIRS, which could * map to $HOME/.local5/share/org.kde.syntax-highlighting/syntax and * /usr/share/org.kde.syntax-highlighting/syntax. * * -# Next, for backwards compatibility with Kate, all syntax highlighting * files are loaded that are located in * QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("katepart5/syntax"), QStandardPaths::LocateDirectory); * Again, under Unix, this uses $XDG_DATA_HOME and $XDG_DATA_DIRS, which * could map to $HOME/.local5/share/katepart5/syntax and * /usr/share/katepart5/syntax. * * -# Then, all files compiled into the library through resources are loaded. * The internal resource path is ":/org.kde.syntax-highlighting/syntax". * This path should never be touched by other applications. * * -# Finally, the search path can be extended by calling addCustomSearchPath(). * A custom search path can either be a path on disk or again a path to * a Qt resource. * * Similarly, loading Theme files works as follows: * * -# First, all Theme files are loaded that are located in * QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("org.kde.syntax-highlighting/themes"), QStandardPaths::LocateDirectory); * Under Unix, this uses $XDG_DATA_HOME and $XDG_DATA_DIRS, which could * map to $HOME/.local5/share/org.kde.syntax-highlighting/themes and * /usr/share/org.kde.syntax-highlighting/themes. * * -# Then, all files compiled into the library through resources are loaded. * The internal resource path is ":/org.kde.syntax-highlighting/themes". * This path should never be touched by other applications. * * -# Finally, all Theme%s located in the paths added addCustomSearchPath() * are loaded. * * @note Whenever a Definition or a Theme exists twice, the variant with * higher version is used. * * @note The QStandardPaths lookup can be disabled by compiling the framework with the -DNO_STANDARD_PATHS define. * * @see Definition, Theme, AbstractHighlighter * @since 5.28 */ class KSYNTAXHIGHLIGHTING_EXPORT Repository { public: /** * Create a new syntax definition repository. * This will read the meta data information of all available syntax * definition, which is a moderately expensive operation, it's therefore * recommended to keep a single instance of Repository around as long * as you need highlighting in your application. */ Repository(); ~Repository(); /** * Returns the Definition named @p defName. * * If no Definition is found, Definition::isValid() of the returned instance * returns false. * * @note This uses case sensitive, untranslated names. For instance, * the javascript.xml definition file sets its name to @e JavaScript. * Therefore, only the string "JavaScript" will return a valid * Definition file. */ Definition definitionForName(const QString &defName) const; /** * Returns the best matching Definition for the file named @p fileName. * The match is performed based on the \e extensions and @e mimetype of * the definition files. If multiple matches are found, the one with the * highest priority is returned. * * If no match is found, Definition::isValid() of the returned instance * returns false. */ Definition definitionForFileName(const QString &fileName) const; /** - * Returns all Definition%s for the file named @p fileName. + * Returns all Definition%s for the file named @p fileName sorted by priority. * The match is performed based on the \e extensions and @e mimetype of * the definition files. * * @since 5.56 */ QVector definitionsForFileName(const QString &fileName) const; /** * Returns the best matching Definition to the type named @p mimeType * * If no match is found, Definition::isValid() of the returned instance * returns false. * * @since 5.50 */ Definition definitionForMimeType(const QString &mimeType) const; /** - * Returns all Definition%s to the type named @p mimeType + * Returns all Definition%s to the type named @p mimeType sorted by priority * * @since 5.56 */ QVector definitionsForMimeType(const QString &mimeType) const; /** * Returns all available Definition%s. * Definition%ss are ordered by translated section and translated names, * for consistent displaying. */ QVector definitions() const; /** * Returns all available color themes. * The returned list should never be empty. */ QVector themes() const; /** * Returns the theme called @p themeName. * If the requested theme cannot be found, the retunred Theme is invalid, * see Theme::isValid(). */ Theme theme(const QString &themeName) const; /** * Built-in default theme types. * @see defaultTheme() */ enum DefaultTheme { //! Theme with a light background color. LightTheme, //! Theme with a dark background color. DarkTheme }; /** * Returns a default theme instance of the given type. * The returned Theme is guaranteed to be a valid theme. */ Theme defaultTheme(DefaultTheme t = LightTheme); /** * Reloads the repository. * This is a moderately expensive operations and should thus only be * triggered when the installed syntax definition files changed. */ void reload(); /** * Add a custom search path to the repository. * This path will be searched in addition to the usual locations for syntax * and theme definition files. Both locations on disk as well as Qt * resource paths are supported. * * @note Internally, the two sub-folders @p path/syntax as well as * @p path/themes are searched for additional Definition%s and * Theme%s. Do not append @e syntax or @e themes to @p path * yourself. * * @note Calling this triggers a reload() of the repository. * * @since 5.39 */ void addCustomSearchPath(const QString &path); /** * Returns the list of custom search paths added to the repository. * By default, this list is empty. * * @see addCustomSearchPath() * @since 5.39 */ QVector customSearchPaths() const; private: Q_DISABLE_COPY(Repository) friend class RepositoryPrivate; std::unique_ptr d; }; } #endif // KSYNTAXHIGHLIGHTING_REPOSITORY_H