KSyntaxHighlighting

repository.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Volker Krause <[email protected]>
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 
33 using namespace KSyntaxHighlighting;
34 
35 namespace
36 {
37 QString fileNameFromFilePath(const QString &filePath)
38 {
39  return QFileInfo{filePath}.fileName();
40 }
41 
42 auto 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 
52 auto 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 
64 template<typename UnaryPredicate>
65 Definition 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 
79 template<typename UnaryPredicate>
80 QVector<Definition> findDefinitionsIf(const QMap<QString, Definition> &defs, UnaryPredicate predicate)
81 {
82  QVector<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 
91 static void initResource()
92 {
93 #ifdef HAS_SYNTAX_RESOURCE
94  Q_INIT_RESOURCE(syntax_data);
95 #endif
96  Q_INIT_RESOURCE(theme_data);
97 }
98 
99 RepositoryPrivate *RepositoryPrivate::get(Repository *repo)
100 {
101  return repo->d.get();
102 }
103 
105  : d(new RepositoryPrivate)
106 {
107  initResource();
108  d->load(this);
109 }
110 
111 Repository::~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_defs.value(defName);
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 
146 {
147  return d->m_sortedDefs;
148 }
149 
151 {
152  return d->m_themes;
153 }
154 
155 static auto lowerBoundTheme(const QVector<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 
162 Theme 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  return std::as_const(*this).defaultTheme(t);
183 }
184 
186 {
187  const auto base = palette.color(QPalette::Base);
188 
189  // find themes with matching background colors
191  for (const auto &theme : std::as_const(d->m_themes)) {
192  const auto background = theme.editorColor(KSyntaxHighlighting::Theme::EditorColorRole::BackgroundColor);
193  if (background == base.rgb()) {
194  matchingThemes.append(&theme);
195  }
196  }
197  if (!matchingThemes.empty()) {
198  // if there's multiple, search for one with a matching highlight color
199  const auto highlight = palette.color(QPalette::Highlight);
200  for (const auto *theme : std::as_const(matchingThemes)) {
201  auto selection = theme->editorColor(KSyntaxHighlighting::Theme::EditorColorRole::TextSelection);
202  if (selection == highlight.rgb()) {
203  return *theme;
204  }
205  }
206  return *matchingThemes.first();
207  }
208 
209  // fallback to just use the default light or dark theme
211 }
212 
214 {
215  return std::as_const(*this).themeForPalette(palette);
216 }
217 
218 void RepositoryPrivate::load(Repository *repo)
219 {
220  // always add invalid default "None" highlighting
221  addDefinition(Definition());
222 
223  // do lookup in standard paths, if not disabled
224 #ifndef NO_STANDARD_PATHS
225  // do lookup in installed path when has no syntax resource
226 #ifndef HAS_SYNTAX_RESOURCE
228  QStringLiteral("org.kde.syntax-highlighting/syntax-bundled"),
230  if (!loadSyntaxFolderFromIndex(repo, dir)) {
231  loadSyntaxFolder(repo, dir);
232  }
233  }
234 #endif
235 
237  QStringLiteral("org.kde.syntax-highlighting/syntax"),
239  loadSyntaxFolder(repo, dir);
240  }
241 
242  // backward compatibility with Kate
243  for (const auto &dir :
245  loadSyntaxFolder(repo, dir);
246  }
247 #endif
248 
249  // default resources are always used, this is the one location that has a index cbor file
250  loadSyntaxFolderFromIndex(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax"));
251 
252  // extra resources provided by 3rdparty libraries/applications
253  loadSyntaxFolder(repo, QStringLiteral(":/org.kde.syntax-highlighting/syntax-addons"));
254 
255  // user given extra paths
256  for (const auto &path : std::as_const(m_customSearchPaths)) {
257  loadSyntaxFolder(repo, path + QStringLiteral("/syntax"));
258  }
259 
260  m_sortedDefs.reserve(m_defs.size());
261  for (auto it = m_defs.constBegin(); it != m_defs.constEnd(); ++it) {
262  m_sortedDefs.push_back(it.value());
263  }
264  std::sort(m_sortedDefs.begin(), m_sortedDefs.end(), [](const Definition &left, const Definition &right) {
265  auto comparison = left.translatedSection().compare(right.translatedSection(), Qt::CaseInsensitive);
266  if (comparison == 0) {
267  comparison = left.translatedName().compare(right.translatedName(), Qt::CaseInsensitive);
268  }
269  return comparison < 0;
270  });
271 
272  // load themes
273 
274  // do lookup in standard paths, if not disabled
275 #ifndef NO_STANDARD_PATHS
277  QStringLiteral("org.kde.syntax-highlighting/themes"),
279  loadThemeFolder(dir);
280  }
281 #endif
282 
283  // default resources are always used
284  loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes"));
285 
286  // extra resources provided by 3rdparty libraries/applications
287  loadThemeFolder(QStringLiteral(":/org.kde.syntax-highlighting/themes-addons"));
288 
289  // user given extra paths
290  for (const auto &path : std::as_const(m_customSearchPaths)) {
291  loadThemeFolder(path + QStringLiteral("/themes"));
292  }
293 }
294 
295 void 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(def);
304  }
305  }
306 }
307 
308 bool 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(def);
328  }
329  }
330 
331  return true;
332 }
333 
334 void RepositoryPrivate::addDefinition(const Definition &def)
335 {
336  const auto it = m_defs.constFind(def.name());
337  if (it == m_defs.constEnd()) {
338  m_defs.insert(def.name(), def);
339  return;
340  }
341 
342  if (it.value().version() >= def.version()) {
343  return;
344  }
345  m_defs.insert(def.name(), def);
346 }
347 
348 void RepositoryPrivate::loadThemeFolder(const QString &path)
349 {
350  QDirIterator it(path, QStringList() << QLatin1String("*.theme"), QDir::Files);
351  while (it.hasNext()) {
352  auto themeData = std::unique_ptr<ThemeData>(new ThemeData);
353  if (themeData->load(it.next())) {
354  addTheme(Theme(themeData.release()));
355  }
356  }
357 }
358 
359 static int themeRevision(const Theme &theme)
360 {
361  auto data = ThemeData::get(theme);
362  return data->revision();
363 }
364 
365 void RepositoryPrivate::addTheme(const Theme &theme)
366 {
367  const auto &constThemes = m_themes;
368  const auto themeName = theme.name();
369  const auto constIt = lowerBoundTheme(constThemes, themeName);
370  const auto index = constIt - constThemes.begin();
371  if (constIt == constThemes.end() || (*constIt).name() != themeName) {
372  m_themes.insert(index, theme);
373  return;
374  }
375  if (themeRevision(*constIt) < themeRevision(theme)) {
376  m_themes[index] = theme;
377  }
378 }
379 
380 quint16 RepositoryPrivate::foldingRegionId(const QString &defName, const QString &foldName)
381 {
382  const auto it = m_foldingRegionIds.constFind(qMakePair(defName, foldName));
383  if (it != m_foldingRegionIds.constEnd()) {
384  return it.value();
385  }
386  m_foldingRegionIds.insert(qMakePair(defName, foldName), ++m_foldingRegionId);
387  return m_foldingRegionId;
388 }
389 
390 quint16 RepositoryPrivate::nextFormatId()
391 {
392  Q_ASSERT(m_formatId < std::numeric_limits<quint16>::max());
393  return ++m_formatId;
394 }
395 
396 void Repository::reload()
397 {
398  qCDebug(Log) << "Reloading syntax definitions!";
399  for (const auto &def : std::as_const(d->m_sortedDefs)) {
400  DefinitionData::get(def)->clear();
401  }
402  d->m_defs.clear();
403  d->m_sortedDefs.clear();
404 
405  d->m_themes.clear();
406 
407  d->m_foldingRegionId = 0;
408  d->m_foldingRegionIds.clear();
409 
410  d->m_formatId = 0;
411 
412  d->load(this);
413 }
414 
416 {
417  d->m_customSearchPaths.append(path);
418  reload();
419 }
420 
422 {
423  return d->m_customSearchPaths;
424 }
const QColor & color(QPalette::ColorGroup group, QPalette::ColorRole role) const const
Definition definitionForMimeType(const QString &mimeType) const
Returns the best matching Definition to the type named mimeType.
Definition: repository.cpp:135
QVector::iterator begin()
QVector< Theme > themes() const
Returns all available color themes.
Definition: repository.cpp:150
void append(const T &value)
@ LightTheme
Theme with a light background color.
Definition: repository.h:216
Theme defaultTheme(DefaultTheme t=LightTheme) const
Returns a default theme instance of the given type.
Definition: repository.cpp:172
Theme theme(const QString &themeName) const
Returns the theme called themeName.
Definition: repository.cpp:162
QRgb editorColor(EditorColorRole role) const
Returns the editor color for the requested role.
Definition: theme.cpp:102
T & first()
Definition definitionForName(const QString &defName) const
Returns the Definition named defName.
Definition: repository.cpp:120
QMap::const_iterator cbegin() const const
QVector< Definition > definitions() const
Returns all available Definitions.
Definition: repository.cpp:145
Syntax highlighting repository.
Definition: repository.h:127
Repository()
Create a new syntax definition repository.
Definition: repository.cpp:104
QCborValue fromCbor(QCborStreamReader &reader)
QMap::const_iterator cend() const const
void addCustomSearchPath(const QString &path)
Add a custom search path to the repository.
Definition: repository.cpp:415
void clear()
int version() const
Returns the definition version.
Definition: definition.cpp:118
void reload()
Reloads the repository.
Definition: repository.cpp:396
QVector< QString > customSearchPaths() const
Returns the list of custom search paths added to the repository.
Definition: repository.cpp:421
QVector::iterator end()
QVector< Definition > definitionsForFileName(const QString &fileName) const
Returns all Definitions for the file named fileName sorted by priority.
Definition: repository.cpp:130
Definition definitionForFileName(const QString &fileName) const
Returns the best matching Definition for the file named fileName.
Definition: repository.cpp:125
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QStringList locateAll(QStandardPaths::StandardLocation type, const QString &fileName, QStandardPaths::LocateOptions options)
Theme themeForPalette(const QPalette &palette) const
Returns the best matching theme for the given palette.
Definition: repository.cpp:185
QVector< Definition > definitionsForMimeType(const QString &mimeType) const
Returns all Definitions to the type named mimeType sorted by priority.
Definition: repository.cpp:140
@ DarkTheme
Theme with a dark background color.
Definition: repository.h:218
Represents a syntax definition.
Definition: definition.h:86
bool empty() const const
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
Color theme definition used for highlighting.
Definition: theme.h:64
DefaultTheme
Built-in default theme types.
Definition: repository.h:214
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Mar 26 2023 04:09:17 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.