1/*
2 SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7// BEGIN Includes
8#include "katemodemanager.h"
9#include "katemodemenulist.h"
10#include "katestatusbar.h"
11
12#include "document.h"
13#include "kateconfig.h"
14#include "kateglobal.h"
15#include "katepartdebug.h"
16#include "katesyntaxmanager.h"
17#include "kateview.h"
18
19#include <KConfigGroup>
20#include <KSyntaxHighlighting/WildcardMatcher>
21
22#include <QFileInfo>
23#include <QMimeDatabase>
24
25#include <algorithm>
26#include <limits>
27// END Includes
28
29static QStringList vectorToList(const QList<QString> &v)
30{
31 QStringList l;
32 l.reserve(asize: v.size());
33 std::copy(first: v.begin(), last: v.end(), result: std::back_inserter(x&: l));
34 return l;
35}
36
37KateModeManager::KateModeManager()
38{
39 update();
40}
41
42KateModeManager::~KateModeManager()
43{
44 qDeleteAll(c: m_types);
45}
46
47bool compareKateFileType(const KateFileType *const left, const KateFileType *const right)
48{
49 int comparison = left->translatedSection.compare(s: right->translatedSection, cs: Qt::CaseInsensitive);
50 if (comparison == 0) {
51 comparison = left->translatedName.compare(s: right->translatedName, cs: Qt::CaseInsensitive);
52 }
53 return comparison < 0;
54}
55
56//
57// read the types from config file and update the internal list
58//
59void KateModeManager::update()
60{
61 KConfig config(QStringLiteral("katemoderc"), KConfig::NoGlobals);
62
63 QStringList g(config.groupList());
64
65 KateFileType *normalType = nullptr;
66 qDeleteAll(c: m_types);
67 m_types.clear();
68 m_name2Type.clear();
69 for (int z = 0; z < g.count(); z++) {
70 KConfigGroup cg(&config, g[z]);
71
72 KateFileType *type = new KateFileType();
73 type->name = g[z];
74 type->wildcards = cg.readXdgListEntry(QStringLiteral("Wildcards"));
75 type->mimetypes = cg.readXdgListEntry(QStringLiteral("Mimetypes"));
76 type->priority = cg.readEntry(QStringLiteral("Priority"), aDefault: 0);
77 type->varLine = cg.readEntry(QStringLiteral("Variables"));
78 type->indenter = cg.readEntry(QStringLiteral("Indenter"));
79
80 type->hl = cg.readEntry(QStringLiteral("Highlighting"));
81
82 // only for generated types...
83 type->hlGenerated = cg.readEntry(QStringLiteral("Highlighting Generated"), aDefault: false);
84
85 // the "Normal" mode will be added later
86 if (type->name == QLatin1String("Normal")) {
87 if (!normalType) {
88 normalType = type;
89 }
90 } else {
91 type->section = cg.readEntry(QStringLiteral("Section"));
92 type->version = cg.readEntry(QStringLiteral("Highlighting Version"));
93
94 // already add all non-highlighting generated user types
95 if (!type->hlGenerated) {
96 m_types.append(t: type);
97 }
98 }
99
100 // insert into the hash...
101 // NOTE: "katemoderc" could have modes that do not exist or are invalid (for example, custom
102 // XML files that were deleted or renamed), so they will be added to the list "m_types" later
103 m_name2Type.insert(key: type->name, value: type);
104 }
105
106 // try if the hl stuff is up to date...
107 const auto modes = KateHlManager::self()->modeList();
108 for (int i = 0; i < modes.size(); ++i) {
109 // filter out hidden languages; and
110 // filter out "None" hl, we add that later as "Normal" mode.
111 // Hl with empty names will also be filtered. The
112 // KTextEditor::DocumentPrivate::updateFileType() function considers
113 // hl with empty names as invalid.
114 if (modes[i].isHidden() || modes[i].name().isEmpty() || modes[i].name() == QLatin1String("None")) {
115 continue;
116 }
117
118 KateFileType *type = nullptr;
119 bool newType = false;
120 if (m_name2Type.contains(key: modes[i].name())) {
121 type = m_name2Type[modes[i].name()];
122
123 // add if hl generated, we skipped that stuff above
124 if (type->hlGenerated) {
125 m_types.append(t: type);
126 }
127 } else {
128 newType = true;
129 type = new KateFileType();
130 type->name = modes[i].name();
131 type->priority = 0;
132 type->hlGenerated = true;
133 m_types.append(t: type);
134 m_name2Type.insert(key: type->name, value: type);
135 }
136
137 if (newType || type->version != QString::number(modes[i].version())) {
138 type->name = modes[i].name();
139 type->section = modes[i].section();
140 type->wildcards = vectorToList(v: modes[i].extensions());
141 type->mimetypes = vectorToList(v: modes[i].mimeTypes());
142 type->priority = modes[i].priority();
143 type->version = QString::number(modes[i].version());
144 type->indenter = modes[i].indenter();
145 type->hl = modes[i].name();
146 }
147
148 type->translatedName = modes[i].translatedName();
149 type->translatedSection = modes[i].translatedSection();
150 }
151
152 // sort the list...
153 std::sort(first: m_types.begin(), last: m_types.end(), comp: compareKateFileType);
154
155 // add the none type...
156 if (!normalType) {
157 normalType = new KateFileType();
158 }
159
160 // marked by hlGenerated
161 normalType->name = QStringLiteral("Normal");
162 normalType->translatedName = i18n("Normal");
163 normalType->hl = QStringLiteral("None");
164 normalType->hlGenerated = true;
165
166 m_types.prepend(t: normalType);
167
168 // update the mode menu of the status bar, for all views.
169 // this menu uses the KateFileType objects
170 for (auto *view : KTextEditor::EditorPrivate::self()->views()) {
171 if (view->statusBar() && view->statusBar()->modeMenu()) {
172 view->statusBar()->modeMenu()->reloadItems();
173 }
174 }
175}
176
177//
178// save the given list to config file + update
179//
180void KateModeManager::save(const QList<KateFileType *> &v)
181{
182 KConfig katerc(QStringLiteral("katemoderc"), KConfig::NoGlobals);
183
184 QStringList newg;
185 newg.reserve(asize: v.size());
186 for (const KateFileType *type : v) {
187 KConfigGroup config(&katerc, type->name);
188
189 config.writeEntry(key: "Section", value: type->section);
190 config.writeXdgListEntry(key: "Wildcards", value: type->wildcards);
191 config.writeXdgListEntry(key: "Mimetypes", value: type->mimetypes);
192 config.writeEntry(key: "Priority", value: type->priority);
193 config.writeEntry(key: "Indenter", value: type->indenter);
194
195 QString varLine = type->varLine;
196 if (!varLine.contains(s: QLatin1String("kate:"))) {
197 varLine.prepend(s: QLatin1String("kate: "));
198 }
199
200 config.writeEntry(key: "Variables", value: varLine);
201
202 config.writeEntry(key: "Highlighting", value: type->hl);
203
204 // only for generated types...
205 config.writeEntry(key: "Highlighting Generated", value: type->hlGenerated);
206 config.writeEntry(key: "Highlighting Version", value: type->version);
207
208 newg << type->name;
209 }
210
211 const auto groupNames = katerc.groupList();
212 for (const QString &groupName : groupNames) {
213 if (newg.indexOf(str: groupName) == -1) {
214 katerc.deleteGroup(group: groupName);
215 }
216 }
217
218 katerc.sync();
219
220 update();
221}
222
223QString KateModeManager::fileType(KTextEditor::Document *doc, const QString &fileToReadFrom)
224{
225 if (!doc) {
226 return QString();
227 }
228
229 if (m_types.isEmpty()) {
230 return QString();
231 }
232
233 QString fileName = doc->url().toString();
234 int length = doc->url().toString().length();
235
236 QString result;
237
238 // Try wildcards
239 if (!fileName.isEmpty()) {
240 static const QLatin1String commonSuffixes[] = {
241 QLatin1String(".orig"),
242 QLatin1String(".new"),
243 QLatin1String("~"),
244 QLatin1String(".bak"),
245 QLatin1String(".BAK"),
246 };
247
248 if (!(result = wildcardsFind(fileName)).isEmpty()) {
249 return result;
250 }
251
252 QString backupSuffix = KateDocumentConfig::global()->backupSuffix();
253 if (fileName.endsWith(s: backupSuffix)) {
254 if (!(result = wildcardsFind(fileName: fileName.left(n: length - backupSuffix.length()))).isEmpty()) {
255 return result;
256 }
257 }
258
259 for (auto &commonSuffix : commonSuffixes) {
260 if (commonSuffix != backupSuffix && fileName.endsWith(s: commonSuffix)) {
261 if (!(result = wildcardsFind(fileName: fileName.left(n: length - commonSuffix.size()))).isEmpty()) {
262 return result;
263 }
264 }
265 }
266 }
267
268 // either read the file passed to this function (pre-load) or use the normal mimeType() KF KTextEditor API
269 QString mtName;
270 if (!fileToReadFrom.isEmpty()) {
271 mtName = QMimeDatabase().mimeTypeForFile(fileName: fileToReadFrom).name();
272 } else {
273 mtName = doc->mimeType();
274 }
275 return mimeTypesFind(mimeTypeName: mtName);
276}
277
278template<typename UnaryStringPredicate>
279static QString findHighestPriorityTypeNameIf(const QList<KateFileType *> &types, QStringList KateFileType::*list, UnaryStringPredicate anyOfCondition)
280{
281 const KateFileType *match = nullptr;
282 auto matchPriority = std::numeric_limits<int>::lowest();
283 for (const KateFileType *type : types) {
284 if (type->priority > matchPriority && std::any_of((type->*list).cbegin(), (type->*list).cend(), anyOfCondition)) {
285 match = type;
286 matchPriority = type->priority;
287 }
288 }
289 return match == nullptr ? QString() : match->name;
290}
291
292QString KateModeManager::wildcardsFind(const QString &fileName) const
293{
294 const auto fileNameNoPath = QFileInfo{fileName}.fileName();
295 return findHighestPriorityTypeNameIf(types: m_types, list: &KateFileType::wildcards, anyOfCondition: [&fileNameNoPath](const QString &wildcard) {
296 return KSyntaxHighlighting::WildcardMatcher::exactMatch(candidate: fileNameNoPath, wildcard);
297 });
298}
299
300QString KateModeManager::mimeTypesFind(const QString &mimeTypeName) const
301{
302 return findHighestPriorityTypeNameIf(types: m_types, list: &KateFileType::mimetypes, anyOfCondition: [&mimeTypeName](const QString &name) {
303 return mimeTypeName == name;
304 });
305}
306
307const KateFileType &KateModeManager::fileType(const QString &name) const
308{
309 for (int i = 0; i < m_types.size(); ++i) {
310 if (m_types[i]->name == name) {
311 return *m_types[i];
312 }
313 }
314
315 static KateFileType notype;
316 return notype;
317}
318

source code of ktexteditor/src/mode/katemodemanager.cpp