1 | /* |
2 | SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org> |
3 | SPDX-FileCopyrightText: 2018 Dominik Haumann <dhaumann@kde.org> |
4 | SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org> |
5 | SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com> |
6 | |
7 | SPDX-License-Identifier: MIT |
8 | */ |
9 | |
10 | #include "definition.h" |
11 | #include "definition_p.h" |
12 | |
13 | #include "context_p.h" |
14 | #include "format.h" |
15 | #include "format_p.h" |
16 | #include "highlightingdata_p.hpp" |
17 | #include "ksyntaxhighlighting_logging.h" |
18 | #include "ksyntaxhighlighting_version.h" |
19 | #include "repository.h" |
20 | #include "repository_p.h" |
21 | #include "rule_p.h" |
22 | #include "worddelimiters_p.h" |
23 | #include "xml_p.h" |
24 | |
25 | #include <QCborMap> |
26 | #include <QCoreApplication> |
27 | #include <QFile> |
28 | #include <QStringTokenizer> |
29 | #include <QXmlStreamReader> |
30 | |
31 | #include <algorithm> |
32 | #include <atomic> |
33 | |
34 | using namespace KSyntaxHighlighting; |
35 | |
36 | DefinitionData::DefinitionData() |
37 | : wordDelimiters() |
38 | , wordWrapDelimiters(wordDelimiters) |
39 | { |
40 | } |
41 | |
42 | DefinitionData::~DefinitionData() = default; |
43 | |
44 | Definition::Definition() |
45 | : d(std::make_shared<DefinitionData>()) |
46 | { |
47 | d->q = d; |
48 | } |
49 | |
50 | Definition::Definition(Definition &&other) noexcept = default; |
51 | Definition::Definition(const Definition &) = default; |
52 | Definition::~Definition() = default; |
53 | Definition &Definition::operator=(Definition &&other) noexcept = default; |
54 | Definition &Definition::operator=(const Definition &) = default; |
55 | |
56 | Definition::Definition(const DefinitionData &defData) |
57 | : d(defData.q.lock()) |
58 | { |
59 | } |
60 | |
61 | bool Definition::operator==(const Definition &other) const |
62 | { |
63 | return d->fileName == other.d->fileName; |
64 | } |
65 | |
66 | bool Definition::operator!=(const Definition &other) const |
67 | { |
68 | return d->fileName != other.d->fileName; |
69 | } |
70 | |
71 | bool Definition::isValid() const |
72 | { |
73 | return d->repo && !d->fileName.isEmpty() && !d->name.isEmpty(); |
74 | } |
75 | |
76 | QString Definition::filePath() const |
77 | { |
78 | return d->fileName; |
79 | } |
80 | |
81 | QString Definition::name() const |
82 | { |
83 | return d->name; |
84 | } |
85 | |
86 | QStringList Definition::alternativeNames() const |
87 | { |
88 | return d->alternativeNames; |
89 | } |
90 | |
91 | QString Definition::translatedName() const |
92 | { |
93 | if (d->translatedName.isEmpty()) { |
94 | d->translatedName = QCoreApplication::instance()->translate(context: "Language" , key: d->nameUtf8.isEmpty() ? d->name.toUtf8().constData() : d->nameUtf8.constData()); |
95 | } |
96 | return d->translatedName; |
97 | } |
98 | |
99 | QString Definition::section() const |
100 | { |
101 | return d->section; |
102 | } |
103 | |
104 | QString Definition::translatedSection() const |
105 | { |
106 | if (d->translatedSection.isEmpty()) { |
107 | d->translatedSection = QCoreApplication::instance()->translate(context: "Language Section" , |
108 | key: d->sectionUtf8.isEmpty() ? d->section.toUtf8().constData() : d->sectionUtf8.constData()); |
109 | } |
110 | return d->translatedSection; |
111 | } |
112 | |
113 | QList<QString> Definition::mimeTypes() const |
114 | { |
115 | return d->mimetypes; |
116 | } |
117 | |
118 | QList<QString> Definition::extensions() const |
119 | { |
120 | return d->extensions; |
121 | } |
122 | |
123 | int Definition::version() const |
124 | { |
125 | return d->version; |
126 | } |
127 | |
128 | int Definition::priority() const |
129 | { |
130 | return d->priority; |
131 | } |
132 | |
133 | bool Definition::isHidden() const |
134 | { |
135 | return d->hidden; |
136 | } |
137 | |
138 | QString Definition::style() const |
139 | { |
140 | return d->style; |
141 | } |
142 | |
143 | QString Definition::indenter() const |
144 | { |
145 | return d->indenter; |
146 | } |
147 | |
148 | QString Definition::author() const |
149 | { |
150 | return d->author; |
151 | } |
152 | |
153 | QString Definition::license() const |
154 | { |
155 | return d->license; |
156 | } |
157 | |
158 | bool Definition::isWordDelimiter(QChar c) const |
159 | { |
160 | d->load(); |
161 | return d->wordDelimiters.contains(c); |
162 | } |
163 | |
164 | bool Definition::isWordWrapDelimiter(QChar c) const |
165 | { |
166 | d->load(); |
167 | return d->wordWrapDelimiters.contains(c); |
168 | } |
169 | |
170 | bool Definition::foldingEnabled() const |
171 | { |
172 | if (d->foldingRegionsState == DefinitionData::FoldingRegionsState::Undetermined) { |
173 | d->load(); |
174 | } |
175 | |
176 | return d->foldingRegionsState == DefinitionData::FoldingRegionsState::ContainsFoldingRegions || d->indentationBasedFolding; |
177 | } |
178 | |
179 | bool Definition::indentationBasedFoldingEnabled() const |
180 | { |
181 | d->load(); |
182 | return d->indentationBasedFolding; |
183 | } |
184 | |
185 | QStringList Definition::foldingIgnoreList() const |
186 | { |
187 | d->load(); |
188 | return d->foldingIgnoreList; |
189 | } |
190 | |
191 | QStringList Definition::keywordLists() const |
192 | { |
193 | d->load(onlyKeywords: DefinitionData::OnlyKeywords(true)); |
194 | return d->keywordLists.keys(); |
195 | } |
196 | |
197 | QStringList Definition::keywordList(const QString &name) const |
198 | { |
199 | d->load(onlyKeywords: DefinitionData::OnlyKeywords(true)); |
200 | const auto list = d->keywordList(name); |
201 | return list ? list->keywords() : QStringList(); |
202 | } |
203 | |
204 | bool Definition::setKeywordList(const QString &name, const QStringList &content) |
205 | { |
206 | d->load(onlyKeywords: DefinitionData::OnlyKeywords(true)); |
207 | KeywordList *list = d->keywordList(name); |
208 | if (list) { |
209 | list->setKeywordList(content); |
210 | return true; |
211 | } else { |
212 | return false; |
213 | } |
214 | } |
215 | |
216 | QList<Format> Definition::formats() const |
217 | { |
218 | d->load(); |
219 | |
220 | // sort formats so that the order matches the order of the itemDatas in the xml files. |
221 | auto formatList = d->formats.values(); |
222 | std::sort(first: formatList.begin(), last: formatList.end(), comp: [](const KSyntaxHighlighting::Format &lhs, const KSyntaxHighlighting::Format &rhs) { |
223 | return lhs.id() < rhs.id(); |
224 | }); |
225 | |
226 | return formatList; |
227 | } |
228 | |
229 | QList<Definition> Definition::includedDefinitions() const |
230 | { |
231 | QList<Definition> definitions; |
232 | |
233 | if (isValid()) { |
234 | d->load(); |
235 | |
236 | // init worklist and result used as guard with this definition |
237 | QVarLengthArray<const DefinitionData *, 4> queue{d.get()}; |
238 | definitions.push_back(t: *this); |
239 | while (!queue.empty()) { |
240 | const auto *def = queue.back(); |
241 | queue.pop_back(); |
242 | for (const auto *defData : def->immediateIncludedDefinitions) { |
243 | auto pred = [defData](const Definition &def) { |
244 | return DefinitionData::get(def) == defData; |
245 | }; |
246 | if (std::find_if(first: definitions.begin(), last: definitions.end(), pred: pred) == definitions.end()) { |
247 | definitions.push_back(t: Definition(*defData)); |
248 | queue.push_back(t: defData); |
249 | } |
250 | } |
251 | } |
252 | |
253 | // remove the 1st entry, since it is this Definition |
254 | definitions.front() = std::move(definitions.back()); |
255 | definitions.pop_back(); |
256 | } |
257 | |
258 | return definitions; |
259 | } |
260 | |
261 | QString Definition::() const |
262 | { |
263 | d->load(); |
264 | return d->singleLineCommentMarker; |
265 | } |
266 | |
267 | CommentPosition Definition::() const |
268 | { |
269 | d->load(); |
270 | return d->singleLineCommentPosition; |
271 | } |
272 | |
273 | QPair<QString, QString> Definition::() const |
274 | { |
275 | d->load(); |
276 | return {d->multiLineCommentStartMarker, d->multiLineCommentEndMarker}; |
277 | } |
278 | |
279 | QList<QPair<QChar, QString>> Definition::characterEncodings() const |
280 | { |
281 | d->load(); |
282 | return d->characterEncodings; |
283 | } |
284 | |
285 | Context *DefinitionData::initialContext() |
286 | { |
287 | Q_ASSERT(!contexts.empty()); |
288 | return &contexts.front(); |
289 | } |
290 | |
291 | Context *DefinitionData::contextByName(QStringView wantedName) |
292 | { |
293 | for (auto &context : contexts) { |
294 | if (context.name() == wantedName) { |
295 | return &context; |
296 | } |
297 | } |
298 | return nullptr; |
299 | } |
300 | |
301 | KeywordList *DefinitionData::keywordList(const QString &wantedName) |
302 | { |
303 | auto it = keywordLists.find(key: wantedName); |
304 | return (it == keywordLists.end()) ? nullptr : &it.value(); |
305 | } |
306 | |
307 | Format DefinitionData::formatByName(QStringView wantedName) const |
308 | { |
309 | const auto it = formats.constFind(key: wantedName); |
310 | if (it != formats.constEnd()) { |
311 | return it.value(); |
312 | } |
313 | |
314 | return Format(); |
315 | } |
316 | |
317 | bool DefinitionData::isLoaded() const |
318 | { |
319 | return !contexts.empty(); |
320 | } |
321 | |
322 | namespace |
323 | { |
324 | std::atomic<uint64_t> definitionId{1}; |
325 | } |
326 | |
327 | bool DefinitionData::load(OnlyKeywords onlyKeywords) |
328 | { |
329 | if (isLoaded()) { |
330 | return true; |
331 | } |
332 | |
333 | if (fileName.isEmpty()) { |
334 | return false; |
335 | } |
336 | |
337 | if (bool(onlyKeywords) && keywordIsLoaded) { |
338 | return true; |
339 | } |
340 | |
341 | QFile file(fileName); |
342 | if (!file.open(flags: QFile::ReadOnly)) { |
343 | return false; |
344 | } |
345 | |
346 | QXmlStreamReader reader(&file); |
347 | while (!reader.atEnd()) { |
348 | const auto token = reader.readNext(); |
349 | if (token != QXmlStreamReader::StartElement) { |
350 | continue; |
351 | } |
352 | |
353 | if (reader.name() == QLatin1String("highlighting" )) { |
354 | loadHighlighting(reader, onlyKeywords); |
355 | if (bool(onlyKeywords)) { |
356 | return true; |
357 | } |
358 | } |
359 | |
360 | else if (reader.name() == QLatin1String("general" )) { |
361 | loadGeneral(reader); |
362 | } |
363 | } |
364 | |
365 | for (auto &kw : keywordLists) { |
366 | kw.setCaseSensitivity(caseSensitive); |
367 | } |
368 | |
369 | resolveContexts(); |
370 | |
371 | id = definitionId.fetch_add(i: 1, m: std::memory_order_relaxed); |
372 | |
373 | return true; |
374 | } |
375 | |
376 | void DefinitionData::clear() |
377 | { |
378 | // keep only name and repo, so we can re-lookup to make references persist over repo reloads |
379 | // see AbstractHighlighterPrivate::ensureDefinitionLoaded() |
380 | id = 0; |
381 | keywordLists.clear(); |
382 | contexts.clear(); |
383 | formats.clear(); |
384 | contextDatas.clear(); |
385 | immediateIncludedDefinitions.clear(); |
386 | wordDelimiters = WordDelimiters(); |
387 | wordWrapDelimiters = wordDelimiters; |
388 | keywordIsLoaded = false; |
389 | foldingRegionsState = FoldingRegionsState::Undetermined; |
390 | indentationBasedFolding = false; |
391 | foldingIgnoreList.clear(); |
392 | singleLineCommentMarker.clear(); |
393 | singleLineCommentPosition = CommentPosition::StartOfLine; |
394 | multiLineCommentStartMarker.clear(); |
395 | multiLineCommentEndMarker.clear(); |
396 | characterEncodings.clear(); |
397 | |
398 | fileName.clear(); |
399 | nameUtf8.clear(); |
400 | translatedName.clear(); |
401 | section.clear(); |
402 | sectionUtf8.clear(); |
403 | translatedSection.clear(); |
404 | style.clear(); |
405 | indenter.clear(); |
406 | author.clear(); |
407 | license.clear(); |
408 | mimetypes.clear(); |
409 | extensions.clear(); |
410 | caseSensitive = Qt::CaseSensitive; |
411 | version = 0.0f; |
412 | priority = 0; |
413 | hidden = false; |
414 | |
415 | // purge our cache that is used to unify states |
416 | unify.clear(); |
417 | } |
418 | |
419 | bool DefinitionData::loadMetaData(const QString &definitionFileName) |
420 | { |
421 | fileName = definitionFileName; |
422 | |
423 | QFile file(definitionFileName); |
424 | if (!file.open(flags: QFile::ReadOnly)) { |
425 | return false; |
426 | } |
427 | |
428 | QXmlStreamReader reader(&file); |
429 | while (!reader.atEnd()) { |
430 | const auto token = reader.readNext(); |
431 | if (token != QXmlStreamReader::StartElement) { |
432 | continue; |
433 | } |
434 | if (reader.name() == QLatin1String("language" )) { |
435 | return loadLanguage(reader); |
436 | } |
437 | } |
438 | |
439 | return false; |
440 | } |
441 | |
442 | bool DefinitionData::loadMetaData(const QString &file, const QCborMap &obj) |
443 | { |
444 | name = obj.value(key: QLatin1String("name" )).toString(); |
445 | if (name.isEmpty()) { |
446 | return false; |
447 | } |
448 | nameUtf8 = obj.value(key: QLatin1String("name" )).toByteArray(); |
449 | section = obj.value(key: QLatin1String("section" )).toString(); |
450 | sectionUtf8 = obj.value(key: QLatin1String("section" )).toByteArray(); |
451 | version = obj.value(key: QLatin1String("version" )).toInteger(); |
452 | priority = obj.value(key: QLatin1String("priority" )).toInteger(); |
453 | style = obj.value(key: QLatin1String("style" )).toString(); |
454 | author = obj.value(key: QLatin1String("author" )).toString(); |
455 | license = obj.value(key: QLatin1String("license" )).toString(); |
456 | indenter = obj.value(key: QLatin1String("indenter" )).toString(); |
457 | hidden = obj.value(key: QLatin1String("hidden" )).toBool(); |
458 | fileName = file; |
459 | |
460 | const auto names = obj.value(key: QLatin1String("alternativeNames" )).toString(); |
461 | alternativeNames = names.split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts); |
462 | |
463 | const auto exts = obj.value(key: QLatin1String("extensions" )).toString(); |
464 | extensions = exts.split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts); |
465 | |
466 | const auto mts = obj.value(key: QLatin1String("mimetype" )).toString(); |
467 | mimetypes = mts.split(sep: QLatin1Char(';'), behavior: Qt::SkipEmptyParts); |
468 | |
469 | return true; |
470 | } |
471 | |
472 | bool DefinitionData::loadLanguage(QXmlStreamReader &reader) |
473 | { |
474 | Q_ASSERT(reader.name() == QLatin1String("language" )); |
475 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
476 | |
477 | if (!checkKateVersion(verStr: reader.attributes().value(qualifiedName: QLatin1String("kateversion" )))) { |
478 | return false; |
479 | } |
480 | |
481 | name = reader.attributes().value(qualifiedName: QLatin1String("name" )).toString(); |
482 | if (name.isEmpty()) { |
483 | return false; |
484 | } |
485 | const auto names = reader.attributes().value(qualifiedName: QLatin1String("alternativeNames" )); |
486 | for (const auto &n : QStringTokenizer(names, u';', Qt::SkipEmptyParts)) { |
487 | alternativeNames.push_back(t: n.toString()); |
488 | } |
489 | section = reader.attributes().value(qualifiedName: QLatin1String("section" )).toString(); |
490 | // toFloat instead of toInt for backward compatibility with old Kate files |
491 | version = reader.attributes().value(qualifiedName: QLatin1String("version" )).toFloat(); |
492 | priority = reader.attributes().value(qualifiedName: QLatin1String("priority" )).toInt(); |
493 | hidden = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("hidden" ))); |
494 | style = reader.attributes().value(qualifiedName: QLatin1String("style" )).toString(); |
495 | indenter = reader.attributes().value(qualifiedName: QLatin1String("indenter" )).toString(); |
496 | author = reader.attributes().value(qualifiedName: QLatin1String("author" )).toString(); |
497 | license = reader.attributes().value(qualifiedName: QLatin1String("license" )).toString(); |
498 | const auto exts = reader.attributes().value(qualifiedName: QLatin1String("extensions" )); |
499 | for (const auto &ext : QStringTokenizer(exts, u';', Qt::SkipEmptyParts)) { |
500 | extensions.push_back(t: ext.toString()); |
501 | } |
502 | const auto mts = reader.attributes().value(qualifiedName: QLatin1String("mimetype" )); |
503 | for (const auto &mt : QStringTokenizer(mts, u';', Qt::SkipEmptyParts)) { |
504 | mimetypes.push_back(t: mt.toString()); |
505 | } |
506 | if (reader.attributes().hasAttribute(qualifiedName: QLatin1String("casesensitive" ))) { |
507 | caseSensitive = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("casesensitive" ))) ? Qt::CaseSensitive : Qt::CaseInsensitive; |
508 | } |
509 | return true; |
510 | } |
511 | |
512 | void DefinitionData::loadHighlighting(QXmlStreamReader &reader, OnlyKeywords onlyKeywords) |
513 | { |
514 | Q_ASSERT(reader.name() == QLatin1String("highlighting" )); |
515 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
516 | |
517 | // skip highlighting |
518 | reader.readNext(); |
519 | |
520 | while (!reader.atEnd()) { |
521 | switch (reader.tokenType()) { |
522 | case QXmlStreamReader::StartElement: |
523 | if (reader.name() == QLatin1String("list" )) { |
524 | if (!keywordIsLoaded) { |
525 | KeywordList keywords; |
526 | keywords.load(reader); |
527 | keywordLists.insert(key: keywords.name(), value: keywords); |
528 | } else { |
529 | reader.skipCurrentElement(); |
530 | reader.readNext(); // Skip </list> |
531 | } |
532 | } else if (bool(onlyKeywords)) { |
533 | resolveIncludeKeywords(); |
534 | return; |
535 | } else if (reader.name() == QLatin1String("contexts" )) { |
536 | resolveIncludeKeywords(); |
537 | loadContexts(reader); |
538 | reader.readNext(); |
539 | } else if (reader.name() == QLatin1String("itemDatas" )) { |
540 | loadItemData(reader); |
541 | } else { |
542 | reader.readNext(); |
543 | } |
544 | break; |
545 | case QXmlStreamReader::EndElement: |
546 | return; |
547 | default: |
548 | reader.readNext(); |
549 | break; |
550 | } |
551 | } |
552 | } |
553 | |
554 | void DefinitionData::resolveIncludeKeywords() |
555 | { |
556 | if (keywordIsLoaded) { |
557 | return; |
558 | } |
559 | |
560 | keywordIsLoaded = true; |
561 | |
562 | for (auto &kw : keywordLists) { |
563 | kw.resolveIncludeKeywords(def&: *this); |
564 | } |
565 | } |
566 | |
567 | void DefinitionData::loadContexts(QXmlStreamReader &reader) |
568 | { |
569 | Q_ASSERT(reader.name() == QLatin1String("contexts" )); |
570 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
571 | |
572 | contextDatas.reserve(n: 32); |
573 | |
574 | while (!reader.atEnd()) { |
575 | switch (reader.tokenType()) { |
576 | case QXmlStreamReader::StartElement: |
577 | if (reader.name() == QLatin1String("context" )) { |
578 | contextDatas.emplace_back().load(defName: name, reader); |
579 | } |
580 | reader.readNext(); |
581 | break; |
582 | case QXmlStreamReader::EndElement: |
583 | return; |
584 | default: |
585 | reader.readNext(); |
586 | break; |
587 | } |
588 | } |
589 | } |
590 | |
591 | void DefinitionData::resolveContexts() |
592 | { |
593 | contexts.reserve(n: contextDatas.size()); |
594 | |
595 | /** |
596 | * Transform all HighlightingContextData to Context. |
597 | * This is necessary so that Context::resolveContexts() can find the referenced contexts. |
598 | */ |
599 | for (const auto &contextData : std::as_const(t&: contextDatas)) { |
600 | contexts.emplace_back(args&: *this, args: contextData); |
601 | } |
602 | |
603 | /** |
604 | * Resolves contexts and rules. |
605 | */ |
606 | auto ctxIt = contexts.begin(); |
607 | for (const auto &contextData : std::as_const(t&: contextDatas)) { |
608 | ctxIt->resolveContexts(def&: *this, data: contextData); |
609 | ++ctxIt; |
610 | } |
611 | |
612 | /** |
613 | * To free the memory, constDatas is emptied because it is no longer used. |
614 | */ |
615 | contextDatas.clear(); |
616 | contextDatas.shrink_to_fit(); |
617 | |
618 | /** |
619 | * Resolved includeRules. |
620 | */ |
621 | for (auto &context : contexts) { |
622 | context.resolveIncludes(def&: *this); |
623 | } |
624 | |
625 | // when a context includes a folding region this value is Yes, otherwise it remains undetermined |
626 | if (foldingRegionsState == FoldingRegionsState::Undetermined) { |
627 | foldingRegionsState = FoldingRegionsState::NoFoldingRegions; |
628 | } |
629 | } |
630 | |
631 | void DefinitionData::loadItemData(QXmlStreamReader &reader) |
632 | { |
633 | Q_ASSERT(reader.name() == QLatin1String("itemDatas" )); |
634 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
635 | |
636 | while (!reader.atEnd()) { |
637 | switch (reader.tokenType()) { |
638 | case QXmlStreamReader::StartElement: |
639 | if (reader.name() == QLatin1String("itemData" )) { |
640 | Format f; |
641 | auto formatData = FormatPrivate::detachAndGet(format&: f); |
642 | formatData->definitionName = name; |
643 | formatData->load(reader); |
644 | formatData->id = RepositoryPrivate::get(repo)->nextFormatId(); |
645 | formats.insert(key: f.name(), value: f); |
646 | reader.readNext(); |
647 | } |
648 | reader.readNext(); |
649 | break; |
650 | case QXmlStreamReader::EndElement: |
651 | return; |
652 | default: |
653 | reader.readNext(); |
654 | break; |
655 | } |
656 | } |
657 | } |
658 | |
659 | void DefinitionData::loadGeneral(QXmlStreamReader &reader) |
660 | { |
661 | Q_ASSERT(reader.name() == QLatin1String("general" )); |
662 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
663 | reader.readNext(); |
664 | |
665 | // reference counter to count XML child elements, to not return too early |
666 | int elementRefCounter = 1; |
667 | |
668 | while (!reader.atEnd()) { |
669 | switch (reader.tokenType()) { |
670 | case QXmlStreamReader::StartElement: |
671 | ++elementRefCounter; |
672 | |
673 | if (reader.name() == QLatin1String("keywords" )) { |
674 | if (reader.attributes().hasAttribute(qualifiedName: QLatin1String("casesensitive" ))) { |
675 | caseSensitive = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("casesensitive" ))) ? Qt::CaseSensitive : Qt::CaseInsensitive; |
676 | } |
677 | |
678 | // adapt wordDelimiters |
679 | wordDelimiters.append(s: reader.attributes().value(qualifiedName: QLatin1String("additionalDeliminator" ))); |
680 | wordDelimiters.remove(c: reader.attributes().value(qualifiedName: QLatin1String("weakDeliminator" ))); |
681 | |
682 | // adapt WordWrapDelimiters |
683 | auto wordWrapDeliminatorAttr = reader.attributes().value(qualifiedName: QLatin1String("wordWrapDeliminator" )); |
684 | if (wordWrapDeliminatorAttr.isEmpty()) { |
685 | wordWrapDelimiters = wordDelimiters; |
686 | } else { |
687 | wordWrapDelimiters.append(s: wordWrapDeliminatorAttr); |
688 | } |
689 | } else if (reader.name() == QLatin1String("folding" )) { |
690 | if (reader.attributes().hasAttribute(qualifiedName: QLatin1String("indentationsensitive" ))) { |
691 | indentationBasedFolding = Xml::attrToBool(str: reader.attributes().value(qualifiedName: QLatin1String("indentationsensitive" ))); |
692 | } |
693 | } else if (reader.name() == QLatin1String("emptyLines" )) { |
694 | loadFoldingIgnoreList(reader); |
695 | } else if (reader.name() == QLatin1String("comments" )) { |
696 | loadComments(reader); |
697 | } else if (reader.name() == QLatin1String("spellchecking" )) { |
698 | loadSpellchecking(reader); |
699 | } else { |
700 | reader.skipCurrentElement(); |
701 | } |
702 | reader.readNext(); |
703 | break; |
704 | case QXmlStreamReader::EndElement: |
705 | --elementRefCounter; |
706 | if (elementRefCounter == 0) { |
707 | return; |
708 | } |
709 | reader.readNext(); |
710 | break; |
711 | default: |
712 | reader.readNext(); |
713 | break; |
714 | } |
715 | } |
716 | } |
717 | |
718 | void DefinitionData::(QXmlStreamReader &reader) |
719 | { |
720 | Q_ASSERT(reader.name() == QLatin1String("comments" )); |
721 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
722 | reader.readNext(); |
723 | |
724 | // reference counter to count XML child elements, to not return too early |
725 | int elementRefCounter = 1; |
726 | |
727 | while (!reader.atEnd()) { |
728 | switch (reader.tokenType()) { |
729 | case QXmlStreamReader::StartElement: |
730 | ++elementRefCounter; |
731 | if (reader.name() == QLatin1String("comment" )) { |
732 | const bool isSingleLine = reader.attributes().value(qualifiedName: QLatin1String("name" )) == QLatin1String("singleLine" ); |
733 | if (isSingleLine) { |
734 | singleLineCommentMarker = reader.attributes().value(qualifiedName: QLatin1String("start" )).toString(); |
735 | const bool afterWhiteSpace = reader.attributes().value(qualifiedName: QLatin1String("position" )) == QLatin1String("afterwhitespace" ); |
736 | singleLineCommentPosition = afterWhiteSpace ? CommentPosition::AfterWhitespace : CommentPosition::StartOfLine; |
737 | } else { |
738 | multiLineCommentStartMarker = reader.attributes().value(qualifiedName: QLatin1String("start" )).toString(); |
739 | multiLineCommentEndMarker = reader.attributes().value(qualifiedName: QLatin1String("end" )).toString(); |
740 | } |
741 | } |
742 | reader.readNext(); |
743 | break; |
744 | case QXmlStreamReader::EndElement: |
745 | --elementRefCounter; |
746 | if (elementRefCounter == 0) { |
747 | return; |
748 | } |
749 | reader.readNext(); |
750 | break; |
751 | default: |
752 | reader.readNext(); |
753 | break; |
754 | } |
755 | } |
756 | } |
757 | |
758 | void DefinitionData::loadFoldingIgnoreList(QXmlStreamReader &reader) |
759 | { |
760 | Q_ASSERT(reader.name() == QLatin1String("emptyLines" )); |
761 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
762 | reader.readNext(); |
763 | |
764 | // reference counter to count XML child elements, to not return too early |
765 | int elementRefCounter = 1; |
766 | |
767 | while (!reader.atEnd()) { |
768 | switch (reader.tokenType()) { |
769 | case QXmlStreamReader::StartElement: |
770 | ++elementRefCounter; |
771 | if (reader.name() == QLatin1String("emptyLine" )) { |
772 | foldingIgnoreList << reader.attributes().value(qualifiedName: QLatin1String("regexpr" )).toString(); |
773 | } |
774 | reader.readNext(); |
775 | break; |
776 | case QXmlStreamReader::EndElement: |
777 | --elementRefCounter; |
778 | if (elementRefCounter == 0) { |
779 | return; |
780 | } |
781 | reader.readNext(); |
782 | break; |
783 | default: |
784 | reader.readNext(); |
785 | break; |
786 | } |
787 | } |
788 | } |
789 | |
790 | void DefinitionData::loadSpellchecking(QXmlStreamReader &reader) |
791 | { |
792 | Q_ASSERT(reader.name() == QLatin1String("spellchecking" )); |
793 | Q_ASSERT(reader.tokenType() == QXmlStreamReader::StartElement); |
794 | reader.readNext(); |
795 | |
796 | // reference counter to count XML child elements, to not return too early |
797 | int elementRefCounter = 1; |
798 | |
799 | while (!reader.atEnd()) { |
800 | switch (reader.tokenType()) { |
801 | case QXmlStreamReader::StartElement: |
802 | ++elementRefCounter; |
803 | if (reader.name() == QLatin1String("encoding" )) { |
804 | const auto charRef = reader.attributes().value(qualifiedName: QLatin1String("char" )); |
805 | if (!charRef.isEmpty()) { |
806 | const auto str = reader.attributes().value(qualifiedName: QLatin1String("string" )); |
807 | characterEncodings.push_back(t: {charRef[0], str.toString()}); |
808 | } |
809 | } |
810 | reader.readNext(); |
811 | break; |
812 | case QXmlStreamReader::EndElement: |
813 | --elementRefCounter; |
814 | if (elementRefCounter == 0) { |
815 | return; |
816 | } |
817 | reader.readNext(); |
818 | break; |
819 | default: |
820 | reader.readNext(); |
821 | break; |
822 | } |
823 | } |
824 | } |
825 | |
826 | bool DefinitionData::checkKateVersion(QStringView verStr) |
827 | { |
828 | const auto idx = verStr.indexOf(c: QLatin1Char('.')); |
829 | if (idx <= 0) { |
830 | qCWarning(Log) << "Skipping" << fileName << "due to having no valid kateversion attribute:" << verStr; |
831 | return false; |
832 | } |
833 | const auto major = verStr.sliced(pos: 0, n: idx).toInt(); |
834 | const auto minor = verStr.sliced(pos: idx + 1).toInt(); |
835 | |
836 | if (major > KSYNTAXHIGHLIGHTING_VERSION_MAJOR || (major == KSYNTAXHIGHLIGHTING_VERSION_MAJOR && minor > KSYNTAXHIGHLIGHTING_VERSION_MINOR)) { |
837 | qCWarning(Log) << "Skipping" << fileName << "due to being too new, version:" << verStr; |
838 | return false; |
839 | } |
840 | |
841 | return true; |
842 | } |
843 | |
844 | quint16 DefinitionData::foldingRegionId(const QString &foldName) |
845 | { |
846 | foldingRegionsState = FoldingRegionsState::ContainsFoldingRegions; |
847 | return RepositoryPrivate::get(repo)->foldingRegionId(defName: name, foldName); |
848 | } |
849 | |
850 | DefinitionData::ResolvedContext DefinitionData::resolveIncludedContext(QStringView defName, QStringView contextName) |
851 | { |
852 | if (defName.isEmpty()) { |
853 | return {.def: this, .context: contextByName(wantedName: contextName)}; |
854 | } |
855 | |
856 | auto d = repo->definitionForName(defName: defName.toString()); |
857 | if (d.isValid()) { |
858 | auto *resolvedDef = get(def: d); |
859 | if (resolvedDef != this) { |
860 | if (std::find(first: immediateIncludedDefinitions.begin(), last: immediateIncludedDefinitions.end(), val: resolvedDef) == immediateIncludedDefinitions.end()) { |
861 | immediateIncludedDefinitions.push_back(t: resolvedDef); |
862 | resolvedDef->load(); |
863 | if (resolvedDef->foldingRegionsState == FoldingRegionsState::ContainsFoldingRegions) { |
864 | foldingRegionsState = FoldingRegionsState::ContainsFoldingRegions; |
865 | } |
866 | } |
867 | } |
868 | if (contextName.isEmpty()) { |
869 | return {.def: resolvedDef, .context: resolvedDef->initialContext()}; |
870 | } else { |
871 | return {.def: resolvedDef, .context: resolvedDef->contextByName(wantedName: contextName)}; |
872 | } |
873 | } |
874 | |
875 | return {.def: nullptr, .context: nullptr}; |
876 | } |
877 | |
878 | #include "moc_definition.cpp" |
879 | |