File indexing completed on 2024-05-12 15:50:05
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 QVector<Definition> findDefinitionsIf(const QMap<QString, Definition> &defs, UnaryPredicate predicate) 0081 { 0082 QVector<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 QVector<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 QVector<Definition> Repository::definitionsForMimeType(const QString &mimeType) const 0141 { 0142 return findDefinitionsIf(d->m_defs, anyMimeTypeEquals(mimeType)); 0143 } 0144 0145 QVector<Definition> Repository::definitions() const 0146 { 0147 return d->m_sortedDefs; 0148 } 0149 0150 QVector<Theme> Repository::themes() const 0151 { 0152 return d->m_themes; 0153 } 0154 0155 static auto lowerBoundTheme(const QVector<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::defaultTheme(Repository::DefaultTheme t) 0181 { 0182 return std::as_const(*this).defaultTheme(t); 0183 } 0184 0185 Theme Repository::themeForPalette(const QPalette &palette) const 0186 { 0187 const auto base = palette.color(QPalette::Base); 0188 0189 // find themes with matching background colors 0190 QVector<const KSyntaxHighlighting::Theme *> matchingThemes; 0191 for (const auto &theme : std::as_const(d->m_themes)) { 0192 const auto background = theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::BackgroundColor); 0193 if (background == base.rgb()) { 0194 matchingThemes.append(&theme); 0195 } 0196 } 0197 if (!matchingThemes.empty()) { 0198 // if there's multiple, search for one with a matching highlight color 0199 const auto highlight = palette.color(QPalette::Highlight); 0200 for (const auto *theme : std::as_const(matchingThemes)) { 0201 auto selection = theme->editorColor(KSyntaxHighlighting::Theme::EditorColorRole::TextSelection); 0202 if (selection == highlight.rgb()) { 0203 return *theme; 0204 } 0205 } 0206 return *matchingThemes.first(); 0207 } 0208 0209 // fallback to just use the default light or dark theme 0210 return defaultTheme((base.lightness() < 128) ? KSyntaxHighlighting::Repository::DarkTheme : KSyntaxHighlighting::Repository::LightTheme); 0211 } 0212 0213 Theme Repository::themeForPalette(const QPalette &palette) 0214 { 0215 return std::as_const(*this).themeForPalette(palette); 0216 } 0217 0218 void RepositoryPrivate::load(Repository *repo) 0219 { 0220 // always add invalid default "None" highlighting 0221 addDefinition(Definition()); 0222 0223 // do lookup in standard paths, if not disabled 0224 #ifndef NO_STANDARD_PATHS 0225 // do lookup in installed path when has no syntax resource 0226 #ifndef HAS_SYNTAX_RESOURCE 0227 for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0228 QStringLiteral("org.kde.syntax-highlighting/syntax-bundled"), 0229 QStandardPaths::LocateDirectory)) { 0230 if (!loadSyntaxFolderFromIndex(repo, dir)) { 0231 loadSyntaxFolder(repo, dir); 0232 } 0233 } 0234 #endif 0235 0236 for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0237 QStringLiteral("org.kde.syntax-highlighting/syntax"), 0238 QStandardPaths::LocateDirectory)) { 0239 loadSyntaxFolder(repo, dir); 0240 } 0241 0242 // backward compatibility with Kate 0243 for (const auto &dir : 0244 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("katepart5/syntax"), QStandardPaths::LocateDirectory)) { 0245 loadSyntaxFolder(repo, dir); 0246 } 0247 #endif 0248 0249 // default resources are always used, this is the one location that has a index cbor file 0250 loadSyntaxFolderFromIndex(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax")); 0251 0252 // extra resources provided by 3rdparty libraries/applications 0253 loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax-addons")); 0254 0255 // user given extra paths 0256 for (const auto &path : std::as_const(m_customSearchPaths)) { 0257 loadSyntaxFolder(repo, path + QStringLiteral("/syntax")); 0258 } 0259 0260 m_sortedDefs.reserve(m_defs.size()); 0261 for (auto it = m_defs.constBegin(); it != m_defs.constEnd(); ++it) { 0262 m_sortedDefs.push_back(it.value()); 0263 } 0264 std::sort(m_sortedDefs.begin(), m_sortedDefs.end(), [](const Definition &left, const Definition &right) { 0265 auto comparison = left.translatedSection().compare(right.translatedSection(), Qt::CaseInsensitive); 0266 if (comparison == 0) { 0267 comparison = left.translatedName().compare(right.translatedName(), Qt::CaseInsensitive); 0268 } 0269 return comparison < 0; 0270 }); 0271 0272 // load themes 0273 0274 // do lookup in standard paths, if not disabled 0275 #ifndef NO_STANDARD_PATHS 0276 for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0277 QStringLiteral("org.kde.syntax-highlighting/themes"), 0278 QStandardPaths::LocateDirectory)) { 0279 loadThemeFolder(dir); 0280 } 0281 #endif 0282 0283 // default resources are always used 0284 loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes")); 0285 0286 // extra resources provided by 3rdparty libraries/applications 0287 loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes-addons")); 0288 0289 // user given extra paths 0290 for (const auto &path : std::as_const(m_customSearchPaths)) { 0291 loadThemeFolder(path + QStringLiteral("/themes")); 0292 } 0293 } 0294 0295 void RepositoryPrivate::loadSyntaxFolder(Repository *repo, const QString &path) 0296 { 0297 QDirIterator it(path, QStringList() << QLatin1String("*.xml"), QDir::Files); 0298 while (it.hasNext()) { 0299 Definition def; 0300 auto defData = DefinitionData::get(def); 0301 defData->repo = repo; 0302 if (defData->loadMetaData(it.next())) { 0303 addDefinition(def); 0304 } 0305 } 0306 } 0307 0308 bool RepositoryPrivate::loadSyntaxFolderFromIndex(Repository *repo, const QString &path) 0309 { 0310 QFile indexFile(path + QLatin1String("/index.katesyntax")); 0311 if (!indexFile.open(QFile::ReadOnly)) { 0312 return false; 0313 } 0314 0315 const auto indexDoc(QCborValue::fromCbor(indexFile.readAll())); 0316 const auto index = indexDoc.toMap(); 0317 for (auto it = index.begin(); it != index.end(); ++it) { 0318 if (!it.value().isMap()) { 0319 continue; 0320 } 0321 const auto fileName = QString(path + QLatin1Char('/') + it.key().toString()); 0322 const auto defMap = it.value().toMap(); 0323 Definition def; 0324 auto defData = DefinitionData::get(def); 0325 defData->repo = repo; 0326 if (defData->loadMetaData(fileName, defMap)) { 0327 addDefinition(def); 0328 } 0329 } 0330 0331 return true; 0332 } 0333 0334 void RepositoryPrivate::addDefinition(const Definition &def) 0335 { 0336 const auto it = m_defs.constFind(def.name()); 0337 if (it == m_defs.constEnd()) { 0338 m_defs.insert(def.name(), def); 0339 return; 0340 } 0341 0342 if (it.value().version() >= def.version()) { 0343 return; 0344 } 0345 m_defs.insert(def.name(), def); 0346 } 0347 0348 void RepositoryPrivate::loadThemeFolder(const QString &path) 0349 { 0350 QDirIterator it(path, QStringList() << QLatin1String("*.theme"), QDir::Files); 0351 while (it.hasNext()) { 0352 auto themeData = std::unique_ptr<ThemeData>(new ThemeData); 0353 if (themeData->load(it.next())) { 0354 addTheme(Theme(themeData.release())); 0355 } 0356 } 0357 } 0358 0359 static int themeRevision(const Theme &theme) 0360 { 0361 auto data = ThemeData::get(theme); 0362 return data->revision(); 0363 } 0364 0365 void RepositoryPrivate::addTheme(const Theme &theme) 0366 { 0367 const auto &constThemes = m_themes; 0368 const auto themeName = theme.name(); 0369 const auto constIt = lowerBoundTheme(constThemes, themeName); 0370 const auto index = constIt - constThemes.begin(); 0371 if (constIt == constThemes.end() || (*constIt).name() != themeName) { 0372 m_themes.insert(index, theme); 0373 return; 0374 } 0375 if (themeRevision(*constIt) < themeRevision(theme)) { 0376 m_themes[index] = theme; 0377 } 0378 } 0379 0380 quint16 RepositoryPrivate::foldingRegionId(const QString &defName, const QString &foldName) 0381 { 0382 const auto it = m_foldingRegionIds.constFind(qMakePair(defName, foldName)); 0383 if (it != m_foldingRegionIds.constEnd()) { 0384 return it.value(); 0385 } 0386 m_foldingRegionIds.insert(qMakePair(defName, foldName), ++m_foldingRegionId); 0387 return m_foldingRegionId; 0388 } 0389 0390 quint16 RepositoryPrivate::nextFormatId() 0391 { 0392 Q_ASSERT(m_formatId < std::numeric_limits<quint16>::max()); 0393 return ++m_formatId; 0394 } 0395 0396 void Repository::reload() 0397 { 0398 qCDebug(Log) << "Reloading syntax definitions!"; 0399 for (const auto &def : std::as_const(d->m_sortedDefs)) { 0400 DefinitionData::get(def)->clear(); 0401 } 0402 d->m_defs.clear(); 0403 d->m_sortedDefs.clear(); 0404 0405 d->m_themes.clear(); 0406 0407 d->m_foldingRegionId = 0; 0408 d->m_foldingRegionIds.clear(); 0409 0410 d->m_formatId = 0; 0411 0412 d->load(this); 0413 } 0414 0415 void Repository::addCustomSearchPath(const QString &path) 0416 { 0417 d->m_customSearchPaths.append(path); 0418 reload(); 0419 } 0420 0421 QVector<QString> Repository::customSearchPaths() const 0422 { 0423 return d->m_customSearchPaths; 0424 }