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

KDE's Doxygen guidelines are available online.