KSyntaxHighlighting

repository.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "repository.h"
8#include "definition.h"
9#include "definition_p.h"
10#include "ksyntaxhighlighting_logging.h"
11#include "repository_p.h"
12#include "theme.h"
13#include "themedata_p.h"
14#include "wildcardmatcher.h"
15
16#include <QCborMap>
17#include <QCborValue>
18#include <QDirIterator>
19#include <QFile>
20#include <QFileInfo>
21#include <QPalette>
22#include <QString>
23#include <QStringView>
24
25#ifndef NO_STANDARD_PATHS
26#include <QStandardPaths>
27#endif
28
29#include <algorithm>
30#include <iterator>
31#include <limits>
32
33using namespace KSyntaxHighlighting;
34
35namespace
36{
37QString fileNameFromFilePath(const QString &filePath)
38{
39 return QFileInfo{filePath}.fileName();
40}
41
42auto anyWildcardMatches(QStringView str)
43{
44 return [str](const Definition &def) {
45 const auto strings = def.extensions();
46 return std::any_of(strings.cbegin(), strings.cend(), [str](QStringView wildcard) {
47 return WildcardMatcher::exactMatch(str, wildcard);
48 });
49 };
50}
51
52auto anyMimeTypeEquals(QStringView mimeTypeName)
53{
54 return [mimeTypeName](const Definition &def) {
55 const auto strings = def.mimeTypes();
56 return std::any_of(strings.cbegin(), strings.cend(), [mimeTypeName](QStringView name) {
57 return mimeTypeName == name;
58 });
59 };
60}
61
62// The two function templates below take defs - a map sorted by highlighting name - to be deterministic and independent of translations.
63
64template<typename UnaryPredicate>
65Definition findHighestPriorityDefinitionIf(const QMap<QString, Definition> &defs, UnaryPredicate predicate)
66{
67 const Definition *match = nullptr;
68 auto matchPriority = std::numeric_limits<int>::lowest();
69 for (const Definition &def : defs) {
70 const auto defPriority = def.priority();
71 if (defPriority > matchPriority && predicate(def)) {
72 match = &def;
73 matchPriority = defPriority;
74 }
75 }
76 return match == nullptr ? Definition{} : *match;
77}
78
79template<typename UnaryPredicate>
80QList<Definition> findDefinitionsIf(const QMap<QString, Definition> &defs, UnaryPredicate predicate)
81{
82 QList<Definition> matches;
83 std::copy_if(defs.cbegin(), defs.cend(), std::back_inserter(matches), predicate);
84 std::stable_sort(matches.begin(), matches.end(), [](const Definition &lhs, const Definition &rhs) {
85 return lhs.priority() > rhs.priority();
86 });
87 return matches;
88}
89} // unnamed namespace
90
91static void initResource()
92{
93#ifdef HAS_SYNTAX_RESOURCE
94 Q_INIT_RESOURCE(syntax_data);
95#endif
96 Q_INIT_RESOURCE(theme_data);
97}
98
99RepositoryPrivate *RepositoryPrivate::get(Repository *repo)
100{
101 return repo->d.get();
102}
103
104Repository::Repository()
105 : d(new RepositoryPrivate)
106{
107 initResource();
108 d->load(this);
109}
110
111Repository::~Repository()
112{
113 // reset repo so we can detect in still alive definition instances
114 // that the repo was deleted
115 for (const auto &def : std::as_const(d->m_sortedDefs)) {
116 DefinitionData::get(def)->repo = nullptr;
117 }
118}
119
121{
122 return d->m_fullDefs.value(defName.toLower());
123}
124
126{
127 return findHighestPriorityDefinitionIf(d->m_defs, anyWildcardMatches(fileNameFromFilePath(fileName)));
128}
129
131{
132 return findDefinitionsIf(d->m_defs, anyWildcardMatches(fileNameFromFilePath(fileName)));
133}
134
136{
137 return findHighestPriorityDefinitionIf(d->m_defs, anyMimeTypeEquals(mimeType));
138}
139
141{
142 return findDefinitionsIf(d->m_defs, anyMimeTypeEquals(mimeType));
143}
144
145QList<Definition> Repository::definitions() const
146{
147 return d->m_sortedDefs;
148}
149
150QList<Theme> Repository::themes() const
151{
152 return d->m_themes;
153}
154
155static auto lowerBoundTheme(const QList<KSyntaxHighlighting::Theme> &themes, QStringView themeName)
156{
157 return std::lower_bound(themes.begin(), themes.end(), themeName, [](const Theme &lhs, QStringView rhs) {
158 return lhs.name() < rhs;
159 });
160}
161
162Theme Repository::theme(const QString &themeName) const
163{
164 const auto &themes = d->m_themes;
165 const auto it = lowerBoundTheme(themes, themeName);
166 if (it != themes.end() && (*it).name() == themeName) {
167 return *it;
168 }
169 return Theme();
170}
171
173{
174 if (t == DarkTheme) {
175 return theme(QStringLiteral("Breeze Dark"));
176 }
177 return theme(QStringLiteral("Breeze Light"));
178}
179
181{
182 const auto base = palette.color(QPalette::Base);
183 const auto highlight = palette.color(QPalette::Highlight).rgb();
184
185 // find themes with matching background and highlight colors
186 const Theme *firstMatchingTheme = nullptr;
187 for (const auto &theme : std::as_const(d->m_themes)) {
189 if (background == base.rgb()) {
190 // find theme with a matching highlight color
192 if (selection == highlight) {
193 return theme;
194 }
195 if (!firstMatchingTheme) {
196 firstMatchingTheme = &theme;
197 }
198 }
199 }
200 if (firstMatchingTheme) {
201 return *firstMatchingTheme;
202 }
203
204 // fallback to just use the default light or dark theme
205 return defaultTheme((base.lightness() < 128) ? Repository::DarkTheme : Repository::LightTheme);
206}
207
208void RepositoryPrivate::load(Repository *repo)
209{
210 // always add invalid default "None" highlighting
211 addDefinition(Definition());
212
213 // do lookup in standard paths, if not disabled
214#ifndef NO_STANDARD_PATHS
215 // do lookup in installed path when has no syntax resource
216#ifndef HAS_SYNTAX_RESOURCE
217 for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
218 QStringLiteral("org.kde.syntax-highlighting/syntax-bundled"),
219 QStandardPaths::LocateDirectory)) {
220 if (!loadSyntaxFolderFromIndex(repo, dir)) {
221 loadSyntaxFolder(repo, dir);
222 }
223 }
224#endif
225
226 for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
227 QStringLiteral("org.kde.syntax-highlighting/syntax"),
228 QStandardPaths::LocateDirectory)) {
229 loadSyntaxFolder(repo, dir);
230 }
231#endif
232
233 // default resources are always used, this is the one location that has a index cbor file
234 loadSyntaxFolderFromIndex(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax"));
235
236 // extra resources provided by 3rdparty libraries/applications
237 loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax-addons"));
238
239 // user given extra paths
240 for (const auto &path : std::as_const(m_customSearchPaths)) {
241 loadSyntaxFolder(repo, path + QStringLiteral("/syntax"));
242 }
243
244 m_sortedDefs.reserve(m_defs.size());
245 for (auto it = m_defs.constBegin(); it != m_defs.constEnd(); ++it) {
246 m_sortedDefs.push_back(it.value());
247 }
248 std::sort(m_sortedDefs.begin(), m_sortedDefs.end(), [](const Definition &left, const Definition &right) {
249 auto comparison = left.translatedSection().compare(right.translatedSection(), Qt::CaseInsensitive);
250 if (comparison == 0) {
251 comparison = left.translatedName().compare(right.translatedName(), Qt::CaseInsensitive);
252 }
253 return comparison < 0;
254 });
255
256 for (auto it = m_sortedDefs.constBegin(); it != m_sortedDefs.constEnd(); ++it) {
257 m_fullDefs.insert(it->name().toLower(), *it);
258 for (const auto &altName : it->alternativeNames()) {
259 m_fullDefs.insert(altName.toLower(), *it);
260 }
261 }
262
263 // load themes
264
265 // do lookup in standard paths, if not disabled
266#ifndef NO_STANDARD_PATHS
267 for (const auto &dir : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
268 QStringLiteral("org.kde.syntax-highlighting/themes"),
269 QStandardPaths::LocateDirectory)) {
270 loadThemeFolder(dir);
271 }
272#endif
273
274 // default resources are always used
275 loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes"));
276
277 // extra resources provided by 3rdparty libraries/applications
278 loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes-addons"));
279
280 // user given extra paths
281 for (const auto &path : std::as_const(m_customSearchPaths)) {
282 loadThemeFolder(path + QStringLiteral("/themes"));
283 }
284}
285
286void RepositoryPrivate::loadSyntaxFolder(Repository *repo, const QString &path)
287{
288 QDirIterator it(path, QStringList() << QLatin1String("*.xml"), QDir::Files);
289 while (it.hasNext()) {
290 Definition def;
291 auto defData = DefinitionData::get(def);
292 defData->repo = repo;
293 if (defData->loadMetaData(it.next())) {
294 addDefinition(def);
295 }
296 }
297}
298
299bool RepositoryPrivate::loadSyntaxFolderFromIndex(Repository *repo, const QString &path)
300{
301 QFile indexFile(path + QLatin1String("/index.katesyntax"));
302 if (!indexFile.open(QFile::ReadOnly)) {
303 return false;
304 }
305
306 const auto indexDoc(QCborValue::fromCbor(indexFile.readAll()));
307 const auto index = indexDoc.toMap();
308 for (auto it = index.begin(); it != index.end(); ++it) {
309 if (!it.value().isMap()) {
310 continue;
311 }
312 const auto fileName = QString(path + QLatin1Char('/') + it.key().toString());
313 const auto defMap = it.value().toMap();
314 Definition def;
315 auto defData = DefinitionData::get(def);
316 defData->repo = repo;
317 if (defData->loadMetaData(fileName, defMap)) {
318 addDefinition(def);
319 }
320 }
321
322 return true;
323}
324
325void RepositoryPrivate::addDefinition(const Definition &def)
326{
327 const auto it = m_defs.constFind(def.name());
328 if (it == m_defs.constEnd()) {
329 m_defs.insert(def.name(), def);
330 return;
331 }
332
333 if (it.value().version() >= def.version()) {
334 return;
335 }
336 m_defs.insert(def.name(), def);
337}
338
339void RepositoryPrivate::loadThemeFolder(const QString &path)
340{
341 QDirIterator it(path, QStringList() << QLatin1String("*.theme"), QDir::Files);
342 while (it.hasNext()) {
343 auto themeData = std::unique_ptr<ThemeData>(new ThemeData);
344 if (themeData->load(it.next())) {
345 addTheme(Theme(themeData.release()));
346 }
347 }
348}
349
350static int themeRevision(const Theme &theme)
351{
352 auto data = ThemeData::get(theme);
353 return data->revision();
354}
355
356void RepositoryPrivate::addTheme(const Theme &theme)
357{
358 const auto &constThemes = m_themes;
359 const auto themeName = theme.name();
360 const auto constIt = lowerBoundTheme(constThemes, themeName);
361 const auto index = constIt - constThemes.begin();
362 if (constIt == constThemes.end() || (*constIt).name() != themeName) {
363 m_themes.insert(index, theme);
364 return;
365 }
366 if (themeRevision(*constIt) < themeRevision(theme)) {
367 m_themes[index] = theme;
368 }
369}
370
371int RepositoryPrivate::foldingRegionId(const QString &defName, const QString &foldName)
372{
373 const auto it = m_foldingRegionIds.constFind(qMakePair(defName, foldName));
374 if (it != m_foldingRegionIds.constEnd()) {
375 return it.value();
376 }
377 Q_ASSERT(m_foldingRegionId < std::numeric_limits<int>::max());
378 m_foldingRegionIds.insert(qMakePair(defName, foldName), ++m_foldingRegionId);
379 return m_foldingRegionId;
380}
381
382int RepositoryPrivate::nextFormatId()
383{
384 Q_ASSERT(m_formatId < std::numeric_limits<int>::max());
385 return ++m_formatId;
386}
387
389{
391
392 for (const auto &def : std::as_const(d->m_sortedDefs)) {
393 DefinitionData::get(def)->clear();
394 }
395 d->m_defs.clear();
396 d->m_sortedDefs.clear();
397 d->m_fullDefs.clear();
398
399 d->m_themes.clear();
400
401 d->m_foldingRegionId = 0;
402 d->m_foldingRegionIds.clear();
403
404 d->m_formatId = 0;
405
406 d->load(this);
407
409}
410
412{
413 d->m_customSearchPaths.append(path);
414 reload();
415}
416
418{
419 return d->m_customSearchPaths;
420}
421
422#include "moc_repository.cpp"
Represents a syntax definition.
Definition definition.h:83
int version() const
Returns the definition version.
Syntax highlighting repository.
Definition repository.h:122
DefaultTheme
Built-in default theme types.
Definition repository.h:213
@ DarkTheme
Theme with a dark background color.
Definition repository.h:217
@ LightTheme
Theme with a light background color.
Definition repository.h:215
Q_INVOKABLE QList< KSyntaxHighlighting::Definition > definitionsForMimeType(const QString &mimeType) const
Returns all Definitions to the type named mimeType sorted by priority.
void reload()
Reloads the repository.
void addCustomSearchPath(const QString &path)
Add a custom search path to the repository.
Q_INVOKABLE KSyntaxHighlighting::Theme defaultTheme(DefaultTheme t=LightTheme) const
Returns a default theme instance of the given type.
void aboutToReload()
This signal is emitted before the reload is started.
Theme themeForPalette(const QPalette &palette) const
Returns the best matching theme for the given palette.
Q_INVOKABLE KSyntaxHighlighting::Definition definitionForMimeType(const QString &mimeType) const
Returns the best matching Definition to the type named mimeType.
Q_INVOKABLE QList< KSyntaxHighlighting::Definition > definitionsForFileName(const QString &fileName) const
Returns all Definitions for the file named fileName sorted by priority.
Q_INVOKABLE KSyntaxHighlighting::Theme theme(const QString &themeName) const
Returns the theme called themeName.
Q_INVOKABLE KSyntaxHighlighting::Definition definitionForName(const QString &defName) const
Returns the Definition named defName.
QList< QString > customSearchPaths() const
Returns the list of custom search paths added to the repository.
void reloaded()
This signal is emitted when the reload is finished.
Q_INVOKABLE KSyntaxHighlighting::Definition definitionForFileName(const QString &fileName) const
Returns the best matching Definition for the file named fileName.
Color theme definition used for highlighting.
Definition theme.h:65
@ BackgroundColor
Background color for the editing area.
Definition theme.h:160
@ TextSelection
Background color for selected text.
Definition theme.h:162
QRgb editorColor(EditorColorRole role) const
Returns the editor color for the requested role.
Definition theme.cpp:102
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
Syntax highlighting engine for Kate syntax definitions.
QCborValue fromCbor(QCborStreamReader &reader)
QRgb rgb() const const
iterator begin()
iterator end()
const_iterator cbegin() const const
const_iterator cend() const const
Q_EMITQ_EMIT
const QColor & color(ColorGroup group, ColorRole role) const const
QString toLower() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:29 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.