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"