Coverage Report

Created: 2026-05-31 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/syntax-highlighting/src/lib/themedata.cpp
Line
Count
Source
1
/*
2
    SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3
    SPDX-FileCopyrightText: 2016 Dominik Haumann <dhaumann@kde.org>
4
    SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com>
5
6
    SPDX-License-Identifier: MIT
7
*/
8
9
#include "ksyntaxhighlighting_logging.h"
10
#include "themedata_p.h"
11
12
#include <QFile>
13
#include <QFileInfo>
14
#include <QJsonDocument>
15
#include <QJsonObject>
16
#include <QJsonValue>
17
#include <QMetaEnum>
18
19
using namespace KSyntaxHighlighting;
20
21
ThemeData::ThemeData()
22
560k
{
23
560k
    memset(m_editorColors, 0, sizeof(m_editorColors));
24
560k
    m_textStyles.resize(QMetaEnum::fromType<Theme::TextStyle>().keyCount());
25
560k
}
26
27
/**
28
 * Convert QJsonValue @p val into a color, if possible. Valid colors are only
29
 * in hex format: #aarrggbb. On error, returns 0x00000000.
30
 */
31
static inline QRgb readColor(const QJsonValue &val)
32
8.01M
{
33
8.01M
    const QRgb unsetColor = 0;
34
8.01M
    if (!val.isString()) {
35
3.70M
        return unsetColor;
36
3.70M
    }
37
4.30M
    const QString str = val.toString();
38
4.30M
    if (str.isEmpty() || str[0] != QLatin1Char('#')) {
39
0
        return unsetColor;
40
0
    }
41
4.30M
    const QColor color(str);
42
4.30M
    return color.isValid() ? color.rgba() : unsetColor;
43
4.30M
}
44
45
static inline TextStyleData readThemeData(const QJsonObject &obj)
46
1.87M
{
47
1.87M
    TextStyleData td;
48
49
1.87M
    td.textColor = readColor(obj.value(QLatin1String("text-color")));
50
1.87M
    td.backgroundColor = readColor(obj.value(QLatin1String("background-color")));
51
1.87M
    td.selectedTextColor = readColor(obj.value(QLatin1String("selected-text-color")));
52
1.87M
    td.selectedBackgroundColor = readColor(obj.value(QLatin1String("selected-background-color")));
53
54
1.87M
    auto val = obj.value(QLatin1String("bold"));
55
1.87M
    if (val.isBool()) {
56
203k
        td.bold = val.toBool();
57
203k
        td.hasBold = true;
58
203k
    }
59
1.87M
    val = obj.value(QLatin1String("italic"));
60
1.87M
    if (val.isBool()) {
61
18.5k
        td.italic = val.toBool();
62
18.5k
        td.hasItalic = true;
63
18.5k
    }
64
1.87M
    val = obj.value(QLatin1String("underline"));
65
1.87M
    if (val.isBool()) {
66
37.0k
        td.underline = val.toBool();
67
37.0k
        td.hasUnderline = true;
68
37.0k
    }
69
1.87M
    val = obj.value(QLatin1String("strike-through"));
70
1.87M
    if (val.isBool()) {
71
18.5k
        td.strikeThrough = val.toBool();
72
18.5k
        td.hasStrikeThrough = true;
73
18.5k
    }
74
75
1.87M
    return td;
76
1.87M
}
77
78
bool ThemeData::load(const QString &filePath)
79
560k
{
80
    // flag first as done for the error cases
81
560k
    m_completelyLoaded = true;
82
83
560k
    QFile loadFile(filePath);
84
560k
    if (!loadFile.open(QIODevice::ReadOnly)) {
85
0
        return false;
86
0
    }
87
560k
    const QByteArray jsonData = loadFile.readAll();
88
    // look for metadata object
89
560k
    int metaDataStart = jsonData.indexOf("\"metadata\"");
90
560k
    int start = jsonData.indexOf('{', metaDataStart);
91
560k
    int end = jsonData.indexOf("}", metaDataStart);
92
560k
    if (start < 0 || end < 0) {
93
0
        qCWarning(Log) << "Failed to parse theme file" << filePath << ":"
94
0
                       << "no metadata object found";
95
0
        return false;
96
0
    }
97
98
560k
    QJsonParseError parseError;
99
560k
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData.sliced(start, (end + 1) - start), &parseError);
100
560k
    if (parseError.error != QJsonParseError::NoError) {
101
0
        qCWarning(Log) << "Failed to parse theme file" << filePath << ":" << parseError.errorString();
102
0
        return false;
103
0
    }
104
105
560k
    m_filePath = filePath;
106
107
    // we need more data later
108
560k
    m_completelyLoaded = false;
109
110
    // read metadata
111
560k
    QJsonObject metadata = jsonDoc.object();
112
560k
    m_name = metadata.value(QLatin1String("name")).toString();
113
560k
    m_revision = metadata.value(QLatin1String("revision")).toInt();
114
560k
    return true;
115
560k
}
116
117
void ThemeData::loadComplete()
118
18.5k
{
119
18.5k
    if (m_completelyLoaded) {
120
0
        return;
121
0
    }
122
18.5k
    m_completelyLoaded = true;
123
124
18.5k
    QFile loadFile(m_filePath);
125
18.5k
    if (!loadFile.open(QIODevice::ReadOnly)) {
126
0
        return;
127
0
    }
128
18.5k
    const QByteArray jsonData = loadFile.readAll();
129
130
18.5k
    QJsonParseError parseError;
131
18.5k
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &parseError);
132
18.5k
    if (parseError.error != QJsonParseError::NoError) {
133
0
        qCWarning(Log) << "Failed to parse theme file" << m_filePath << ":" << parseError.errorString();
134
0
        return;
135
0
    }
136
137
18.5k
    QJsonObject obj = jsonDoc.object();
138
    // read text styles
139
18.5k
    const auto metaEnumStyle = QMetaEnum::fromType<Theme::TextStyle>();
140
18.5k
    const QJsonObject textStyles = obj.value(QLatin1String("text-styles")).toObject();
141
593k
    for (int i = 0; i < metaEnumStyle.keyCount(); ++i) {
142
574k
        Q_ASSERT(i == metaEnumStyle.value(i));
143
574k
        m_textStyles[i] = readThemeData(textStyles.value(QLatin1String(metaEnumStyle.key(i))).toObject());
144
574k
    }
145
146
    // read editor colors
147
18.5k
    const auto metaEnumColor = QMetaEnum::fromType<Theme::EditorColorRole>();
148
18.5k
    const QJsonObject editorColors = obj.value(QLatin1String("editor-colors")).toObject();
149
537k
    for (int i = 0; i < metaEnumColor.keyCount(); ++i) {
150
519k
        Q_ASSERT(i == metaEnumColor.value(i));
151
519k
        m_editorColors[i] = readColor(editorColors.value(QLatin1String(metaEnumColor.key(i))));
152
519k
    }
153
154
    // if we have no new key around for Theme::BackgroundColor => use old variants to be compatible
155
18.5k
    if (!editorColors.contains(QLatin1String(metaEnumColor.key(Theme::BackgroundColor)))) {
156
0
        m_editorColors[Theme::BackgroundColor] = readColor(editorColors.value(QLatin1String("background-color")));
157
0
        m_editorColors[Theme::TextSelection] = readColor(editorColors.value(QLatin1String("selection")));
158
0
        m_editorColors[Theme::CurrentLine] = readColor(editorColors.value(QLatin1String("current-line")));
159
0
        m_editorColors[Theme::SearchHighlight] = readColor(editorColors.value(QLatin1String("search-highlight")));
160
0
        m_editorColors[Theme::ReplaceHighlight] = readColor(editorColors.value(QLatin1String("replace-highlight")));
161
0
        m_editorColors[Theme::BracketMatching] = readColor(editorColors.value(QLatin1String("bracket-matching")));
162
0
        m_editorColors[Theme::TabMarker] = readColor(editorColors.value(QLatin1String("tab-marker")));
163
0
        m_editorColors[Theme::SpellChecking] = readColor(editorColors.value(QLatin1String("spell-checking")));
164
0
        m_editorColors[Theme::IndentationLine] = readColor(editorColors.value(QLatin1String("indentation-line")));
165
0
        m_editorColors[Theme::IconBorder] = readColor(editorColors.value(QLatin1String("icon-border")));
166
0
        m_editorColors[Theme::CodeFolding] = readColor(editorColors.value(QLatin1String("code-folding")));
167
0
        m_editorColors[Theme::LineNumbers] = readColor(editorColors.value(QLatin1String("line-numbers")));
168
0
        m_editorColors[Theme::CurrentLineNumber] = readColor(editorColors.value(QLatin1String("current-line-number")));
169
0
        m_editorColors[Theme::WordWrapMarker] = readColor(editorColors.value(QLatin1String("word-wrap-marker")));
170
0
        m_editorColors[Theme::ModifiedLines] = readColor(editorColors.value(QLatin1String("modified-lines")));
171
0
        m_editorColors[Theme::SavedLines] = readColor(editorColors.value(QLatin1String("saved-lines")));
172
0
        m_editorColors[Theme::Separator] = readColor(editorColors.value(QLatin1String("separator")));
173
0
        m_editorColors[Theme::MarkBookmark] = readColor(editorColors.value(QLatin1String("mark-bookmark")));
174
0
        m_editorColors[Theme::MarkBreakpointActive] = readColor(editorColors.value(QLatin1String("mark-breakpoint-active")));
175
0
        m_editorColors[Theme::MarkBreakpointReached] = readColor(editorColors.value(QLatin1String("mark-breakpoint-reached")));
176
0
        m_editorColors[Theme::MarkBreakpointDisabled] = readColor(editorColors.value(QLatin1String("mark-breakpoint-disabled")));
177
0
        m_editorColors[Theme::MarkExecution] = readColor(editorColors.value(QLatin1String("mark-execution")));
178
0
        m_editorColors[Theme::MarkWarning] = readColor(editorColors.value(QLatin1String("mark-warning")));
179
0
        m_editorColors[Theme::MarkError] = readColor(editorColors.value(QLatin1String("mark-error")));
180
0
        m_editorColors[Theme::TemplateBackground] = readColor(editorColors.value(QLatin1String("template-background")));
181
0
        m_editorColors[Theme::TemplatePlaceholder] = readColor(editorColors.value(QLatin1String("template-placeholder")));
182
0
        m_editorColors[Theme::TemplateFocusedPlaceholder] = readColor(editorColors.value(QLatin1String("template-focused-placeholder")));
183
0
        m_editorColors[Theme::TemplateReadOnlyPlaceholder] = readColor(editorColors.value(QLatin1String("template-read-only-placeholder")));
184
0
    }
185
186
    // read per-definition style overrides
187
18.5k
    const auto customStyles = obj.value(QLatin1String("custom-styles")).toObject();
188
111k
    for (auto it = customStyles.begin(); it != customStyles.end(); ++it) {
189
92.7k
        const auto obj = it.value().toObject();
190
92.7k
        auto &overrideStyle = m_textStyleOverrides[it.key()];
191
1.39M
        for (auto it2 = obj.begin(); it2 != obj.end(); ++it2) {
192
1.29M
            overrideStyle.insert(it2.key(), readThemeData(it2.value().toObject()));
193
1.29M
        }
194
92.7k
    }
195
196
18.5k
    return;
197
18.5k
}
198
199
QString ThemeData::name() const
200
3.28M
{
201
3.28M
    return m_name;
202
3.28M
}
203
204
int ThemeData::revision() const
205
0
{
206
0
    return m_revision;
207
0
}
208
209
bool ThemeData::isReadOnly() const
210
0
{
211
0
    return !QFileInfo(m_filePath).isWritable();
212
0
}
213
214
QString ThemeData::filePath() const
215
0
{
216
0
    return m_filePath;
217
0
}
218
219
TextStyleData ThemeData::textStyle(Theme::TextStyle style) const
220
1.77M
{
221
1.77M
    if (!m_completelyLoaded) {
222
0
        const_cast<ThemeData *>(this)->loadComplete();
223
0
    }
224
1.77M
    return m_textStyles[style];
225
1.77M
}
226
227
QRgb ThemeData::textColor(Theme::TextStyle style) const
228
254k
{
229
254k
    return textStyle(style).textColor;
230
254k
}
231
232
QRgb ThemeData::selectedTextColor(Theme::TextStyle style) const
233
0
{
234
0
    return textStyle(style).selectedTextColor;
235
0
}
236
237
QRgb ThemeData::backgroundColor(Theme::TextStyle style) const
238
508k
{
239
508k
    return textStyle(style).backgroundColor;
240
508k
}
241
242
QRgb ThemeData::selectedBackgroundColor(Theme::TextStyle style) const
243
0
{
244
0
    return textStyle(style).selectedBackgroundColor;
245
0
}
246
247
bool ThemeData::isBold(Theme::TextStyle style) const
248
254k
{
249
254k
    return textStyle(style).bold;
250
254k
}
251
252
bool ThemeData::isItalic(Theme::TextStyle style) const
253
254k
{
254
254k
    return textStyle(style).italic;
255
254k
}
256
257
bool ThemeData::isUnderline(Theme::TextStyle style) const
258
254k
{
259
254k
    return textStyle(style).underline;
260
254k
}
261
262
bool ThemeData::isStrikeThrough(Theme::TextStyle style) const
263
254k
{
264
254k
    return textStyle(style).strikeThrough;
265
254k
}
266
267
QRgb ThemeData::editorColor(Theme::EditorColorRole role) const
268
0
{
269
0
    if (!m_completelyLoaded) {
270
0
        const_cast<ThemeData *>(this)->loadComplete();
271
0
    }
272
0
    Q_ASSERT(static_cast<int>(role) >= 0 && static_cast<int>(role) <= static_cast<int>(Theme::TemplateReadOnlyPlaceholder));
273
0
    return m_editorColors[role];
274
0
}
275
276
TextStyleData ThemeData::textStyleOverride(const QString &definitionName, const QString &attributeName) const
277
1.52M
{
278
1.52M
    if (!m_completelyLoaded) {
279
18.5k
        const_cast<ThemeData *>(this)->loadComplete();
280
18.5k
    }
281
1.52M
    auto it = m_textStyleOverrides.find(definitionName);
282
1.52M
    if (it != m_textStyleOverrides.end()) {
283
0
        return it->value(attributeName);
284
0
    }
285
1.52M
    return TextStyleData();
286
1.52M
}