File indexing completed on 2024-05-12 04:02:19

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "repository.h"
0008 #include "definition.h"
0009 #include "definition_p.h"
0010 #include "ksyntaxhighlighting_logging.h"
0011 #include "repository_p.h"
0012 #include "theme.h"
0013 #include "themedata_p.h"
0014 #include "wildcardmatcher.h"
0015 
0016 #include <QCborMap>
0017 #include <QCborValue>
0018 #include <QDirIterator>
0019 #include <QFile>
0020 #include <QFileInfo>
0021 #include <QPalette>
0022 #include <QString>
0023 #include <QStringView>
0024 
0025 #ifndef NO_STANDARD_PATHS
0026 #include <QStandardPaths>
0027 #endif
0028 
0029 #include <algorithm>
0030 #include <iterator>
0031 #include <limits>
0032 
0033 using namespace KSyntaxHighlighting;
0034 
0035 namespace
0036 {
0037 QString fileNameFromFilePath(const QString &filePath)
0038 {
0039     return QFileInfo{filePath}.fileName();
0040 }
0041 
0042 auto anyWildcardMatches(QStringView str)
0043 {
0044     return [str](const Definition &def) {
0045         const auto strings = def.extensions();
0046         return std::any_of(strings.cbegin(), strings.cend(), [str](QStringView wildcard) {
0047             return WildcardMatcher::exactMatch(str, wildcard);
0048         });
0049     };
0050 }
0051 
0052 auto anyMimeTypeEquals(QStringView mimeTypeName)
0053 {
0054     return [mimeTypeName](const Definition &def) {
0055         const auto strings = def.mimeTypes();
0056         return std::any_of(strings.cbegin(), strings.cend(), [mimeTypeName](QStringView name) {
0057             return mimeTypeName == name;
0058         });
0059     };
0060 }
0061 
0062 // The two function templates below take defs - a map sorted by highlighting name - to be deterministic and independent of translations.
0063 
0064 template<typename UnaryPredicate>
0065 Definition findHighestPriorityDefinitionIf(const QMap<QString, Definition> &defs, UnaryPredicate predicate)
0066 {
0067     const Definition *match = nullptr;
0068     auto matchPriority = std::numeric_limits<int>::lowest();
0069     for (const Definition &def : defs) {
0070         const auto defPriority = def.priority();
0071         if (defPriority > matchPriority && predicate(def)) {
0072             match = &def;
0073             matchPriority = defPriority;
0074         }
0075     }
0076     return match == nullptr ? Definition{} : *match;
0077 }
0078 
0079 template<typename UnaryPredicate>
0080 QList<Definition> findDefinitionsIf(const QMap<QString, Definition> &defs, UnaryPredicate predicate)
0081 {
0082     QList<Definition> matches;
0083     std::copy_if(defs.cbegin(), defs.cend(), std::back_inserter(matches), predicate);
0084     std::stable_sort(matches.begin(), matches.end(), [](const Definition &lhs, const Definition &rhs) {
0085         return lhs.priority() > rhs.priority();
0086     });
0087     return matches;
0088 }
0089 } // unnamed namespace
0090 
0091 static void initResource()
0092 {
0093 #ifdef HAS_SYNTAX_RESOURCE
0094     Q_INIT_RESOURCE(syntax_data);
0095 #endif
0096     Q_INIT_RESOURCE(theme_data);
0097 }
0098 
0099 RepositoryPrivate *RepositoryPrivate::get(Repository *repo)
0100 {
0101     return repo->d.get();
0102 }
0103 
0104 Repository::Repository()
0105     : d(new RepositoryPrivate)
0106 {
0107     initResource();
0108     d->load(this);
0109 }
0110 
0111 Repository::~Repository()
0112 {
0113     // reset repo so we can detect in still alive definition instances
0114     // that the repo was deleted
0115     for (const auto &def : std::as_const(d->m_sortedDefs)) {
0116         DefinitionData::get(def)->repo = nullptr;
0117     }
0118 }
0119 
0120 Definition Repository::definitionForName(const QString &defName) const
0121 {
0122     return d->m_defs.value(defName);
0123 }
0124 
0125 Definition Repository::definitionForFileName(const QString &fileName) const
0126 {
0127     return findHighestPriorityDefinitionIf(d->m_defs, anyWildcardMatches(fileNameFromFilePath(fileName)));
0128 }
0129 
0130 QList<Definition> Repository::definitionsForFileName(const QString &fileName) const
0131 {
0132     return findDefinitionsIf(d->m_defs, anyWildcardMatches(fileNameFromFilePath(fileName)));
0133 }
0134 
0135 Definition Repository::definitionForMimeType(const QString &mimeType) const
0136 {
0137     return findHighestPriorityDefinitionIf(d->m_defs, anyMimeTypeEquals(mimeType));
0138 }
0139 
0140 QList<Definition> Repository::definitionsForMimeType(const QString &mimeType) const
0141 {
0142     return findDefinitionsIf(d->m_defs, anyMimeTypeEquals(mimeType));
0143 }
0144 
0145 QList<Definition> Repository::definitions() const
0146 {
0147     return d->m_sortedDefs;
0148 }
0149 
0150 QList<Theme> Repository::themes() const
0151 {
0152     return d->m_themes;
0153 }
0154 
0155 static auto lowerBoundTheme(const QList<KSyntaxHighlighting::Theme> &themes, QStringView themeName)
0156 {
0157     return std::lower_bound(themes.begin(), themes.end(), themeName, [](const Theme &lhs, QStringView rhs) {
0158         return lhs.name() < rhs;
0159     });
0160 }
0161 
0162 Theme Repository::theme(const QString &themeName) const
0163 {
0164     const auto &themes = d->m_themes;
0165     const auto it = lowerBoundTheme(themes, themeName);
0166     if (it != themes.end() && (*it).name() == themeName) {
0167         return *it;
0168     }
0169     return Theme();
0170 }
0171 
0172 Theme Repository::defaultTheme(Repository::DefaultTheme t) const
0173 {
0174     if (t == DarkTheme) {
0175         return theme(QStringLiteral("Breeze Dark"));
0176     }
0177     return theme(QStringLiteral("Breeze Light"));
0178 }
0179 
0180 Theme Repository::themeForPalette(const QPalette &palette) const
0181 {
0182     const auto base = palette.color(QPalette::Base);
0183     const auto highlight = palette.color(QPalette::Highlight).rgb();
0184 
0185     // find themes with matching background and highlight colors
0186     const Theme *firstMatchingTheme = nullptr;
0187     for (const auto &theme : std::as_const(d->m_themes)) {
0188         const auto background = theme.editorColor(Theme::EditorColorRole::BackgroundColor);
0189         if (background == base.rgb()) {
0190             // find theme with a matching highlight color
0191             auto selection = theme.editorColor(Theme::EditorColorRole::TextSelection);
0192             if (selection == highlight) {
0193                 return theme;
0194             }
0195             if (!firstMatchingTheme) {
0196                 firstMatchingTheme = &theme;
0197             }
0198         }
0199     }
0200     if (firstMatchingTheme) {
0201         return *firstMatchingTheme;
0202     }
0203 
0204     // fallback to just use the default light or dark theme
0205     return defaultTheme((base.lightness() < 128) ? Repository::DarkTheme : Repository::LightTheme);
0206 }
0207 
0208 void RepositoryPrivate::load(Repository *repo)
0209 {
0210     // always add invalid default "None" highlighting
0211     addDefinition(Definition());
0212 
0213     // do lookup in standard paths, if not disabled
0214 #ifndef NO_STANDARD_PATHS
0215     // do lookup in installed path when has no syntax resource
0216 #ifndef HAS_SYNTAX_RESOURCE
0217     for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
0218                                                      QStringLiteral("org.kde.syntax-highlighting/syntax-bundled"),
0219                                                      QStandardPaths::LocateDirectory)) {
0220         if (!loadSyntaxFolderFromIndex(repo, dir)) {
0221             loadSyntaxFolder(repo, dir);
0222         }
0223     }
0224 #endif
0225 
0226     for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
0227                                                      QStringLiteral("org.kde.syntax-highlighting/syntax"),
0228                                                      QStandardPaths::LocateDirectory)) {
0229         loadSyntaxFolder(repo, dir);
0230     }
0231 #endif
0232 
0233     // default resources are always used, this is the one location that has a index cbor file
0234     loadSyntaxFolderFromIndex(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax"));
0235 
0236     // extra resources provided by 3rdparty libraries/applications
0237     loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax-addons"));
0238 
0239     // user given extra paths
0240     for (const auto &path : std::as_const(m_customSearchPaths)) {
0241         loadSyntaxFolder(repo, path + QStringLiteral("/syntax"));
0242     }
0243 
0244     m_sortedDefs.reserve(m_defs.size());
0245     for (auto it = m_defs.constBegin(); it != m_defs.constEnd(); ++it) {
0246         m_sortedDefs.push_back(it.value());
0247     }
0248     std::sort(m_sortedDefs.begin(), m_sortedDefs.end(), [](const Definition &left, const Definition &right) {
0249         auto comparison = left.translatedSection().compare(right.translatedSection(), Qt::CaseInsensitive);
0250         if (comparison == 0) {
0251             comparison = left.translatedName().compare(right.translatedName(), Qt::CaseInsensitive);
0252         }
0253         return comparison < 0;
0254     });
0255 
0256     // load themes
0257 
0258     // do lookup in standard paths, if not disabled
0259 #ifndef NO_STANDARD_PATHS
0260     for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
0261                                                      QStringLiteral("org.kde.syntax-highlighting/themes"),
0262                                                      QStandardPaths::LocateDirectory)) {
0263         loadThemeFolder(dir);
0264     }
0265 #endif
0266 
0267     // default resources are always used
0268     loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes"));
0269 
0270     // extra resources provided by 3rdparty libraries/applications
0271     loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes-addons"));
0272 
0273     // user given extra paths
0274     for (const auto &path : std::as_const(m_customSearchPaths)) {
0275         loadThemeFolder(path + QStringLiteral("/themes"));
0276     }
0277 }
0278 
0279 void RepositoryPrivate::loadSyntaxFolder(Repository *repo, const QString &path)
0280 {
0281     QDirIterator it(path, QStringList() << QLatin1String("*.xml"), QDir::Files);
0282     while (it.hasNext()) {
0283         Definition def;
0284         auto defData = DefinitionData::get(def);
0285         defData->repo = repo;
0286         if (defData->loadMetaData(it.next())) {
0287             addDefinition(def);
0288         }
0289     }
0290 }
0291 
0292 bool RepositoryPrivate::loadSyntaxFolderFromIndex(Repository *repo, const QString &path)
0293 {
0294     QFile indexFile(path + QLatin1String("/index.katesyntax"));
0295     if (!indexFile.open(QFile::ReadOnly)) {
0296         return false;
0297     }
0298 
0299     const auto indexDoc(QCborValue::fromCbor(indexFile.readAll()));
0300     const auto index = indexDoc.toMap();
0301     for (auto it = index.begin(); it != index.end(); ++it) {
0302         if (!it.value().isMap()) {
0303             continue;
0304         }
0305         const auto fileName = QString(path + QLatin1Char('/') + it.key().toString());
0306         const auto defMap = it.value().toMap();
0307         Definition def;
0308         auto defData = DefinitionData::get(def);
0309         defData->repo = repo;
0310         if (defData->loadMetaData(fileName, defMap)) {
0311             addDefinition(def);
0312         }
0313     }
0314 
0315     return true;
0316 }
0317 
0318 void RepositoryPrivate::addDefinition(const Definition &def)
0319 {
0320     const auto it = m_defs.constFind(def.name());
0321     if (it == m_defs.constEnd()) {
0322         m_defs.insert(def.name(), def);
0323         return;
0324     }
0325 
0326     if (it.value().version() >= def.version()) {
0327         return;
0328     }
0329     m_defs.insert(def.name(), def);
0330 }
0331 
0332 void RepositoryPrivate::loadThemeFolder(const QString &path)
0333 {
0334     QDirIterator it(path, QStringList() << QLatin1String("*.theme"), QDir::Files);
0335     while (it.hasNext()) {
0336         auto themeData = std::unique_ptr<ThemeData>(new ThemeData);
0337         if (themeData->load(it.next())) {
0338             addTheme(Theme(themeData.release()));
0339         }
0340     }
0341 }
0342 
0343 static int themeRevision(const Theme &theme)
0344 {
0345     auto data = ThemeData::get(theme);
0346     return data->revision();
0347 }
0348 
0349 void RepositoryPrivate::addTheme(const Theme &theme)
0350 {
0351     const auto &constThemes = m_themes;
0352     const auto themeName = theme.name();
0353     const auto constIt = lowerBoundTheme(constThemes, themeName);
0354     const auto index = constIt - constThemes.begin();
0355     if (constIt == constThemes.end() || (*constIt).name() != themeName) {
0356         m_themes.insert(index, theme);
0357         return;
0358     }
0359     if (themeRevision(*constIt) < themeRevision(theme)) {
0360         m_themes[index] = theme;
0361     }
0362 }
0363 
0364 int RepositoryPrivate::foldingRegionId(const QString &defName, const QString &foldName)
0365 {
0366     const auto it = m_foldingRegionIds.constFind(qMakePair(defName, foldName));
0367     if (it != m_foldingRegionIds.constEnd()) {
0368         return it.value();
0369     }
0370     Q_ASSERT(m_foldingRegionId < std::numeric_limits<int>::max());
0371     m_foldingRegionIds.insert(qMakePair(defName, foldName), ++m_foldingRegionId);
0372     return m_foldingRegionId;
0373 }
0374 
0375 int RepositoryPrivate::nextFormatId()
0376 {
0377     Q_ASSERT(m_formatId < std::numeric_limits<int>::max());
0378     return ++m_formatId;
0379 }
0380 
0381 void Repository::reload()
0382 {
0383     Q_EMIT aboutToReload();
0384 
0385     for (const auto &def : std::as_const(d->m_sortedDefs)) {
0386         DefinitionData::get(def)->clear();
0387     }
0388     d->m_defs.clear();
0389     d->m_sortedDefs.clear();
0390 
0391     d->m_themes.clear();
0392 
0393     d->m_foldingRegionId = 0;
0394     d->m_foldingRegionIds.clear();
0395 
0396     d->m_formatId = 0;
0397 
0398     d->load(this);
0399 
0400     Q_EMIT reloaded();
0401 }
0402 
0403 void Repository::addCustomSearchPath(const QString &path)
0404 {
0405     d->m_customSearchPaths.append(path);
0406     reload();
0407 }
0408 
0409 QList<QString> Repository::customSearchPaths() const
0410 {
0411     return d->m_customSearchPaths;
0412 }
0413 
0414 #include "moc_repository.cpp"