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