1 | /* |
2 | SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org> |
3 | SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen@gmail.com> |
4 | |
5 | SPDX-License-Identifier: MIT |
6 | */ |
7 | |
8 | #include "context_p.h" |
9 | #include "definition_p.h" |
10 | #include "format.h" |
11 | #include "ksyntaxhighlighting_logging.h" |
12 | #include "repository.h" |
13 | #include "rule_p.h" |
14 | #include "xml_p.h" |
15 | |
16 | #include <QString> |
17 | #include <QXmlStreamReader> |
18 | |
19 | using namespace KSyntaxHighlighting; |
20 | |
21 | Context::Context(const DefinitionData &def, const HighlightingContextData &data) |
22 | : m_name(data.name) |
23 | , m_attributeFormat(data.attribute.isEmpty() ? Format() : def.formatByName(name: data.attribute)) |
24 | , m_indentationBasedFolding(!data.noIndentationBasedFolding && def.indentationBasedFolding) |
25 | { |
26 | if (!data.attribute.isEmpty() && !m_attributeFormat.isValid()) { |
27 | qCWarning(Log) << "Context: Unknown format" << data.attribute << "in context" << m_name << "of definition" << def.name; |
28 | } |
29 | } |
30 | |
31 | bool Context::indentationBasedFoldingEnabled() const |
32 | { |
33 | return m_indentationBasedFolding; |
34 | } |
35 | |
36 | void Context::resolveContexts(DefinitionData &def, const HighlightingContextData &data) |
37 | { |
38 | m_lineEndContext.resolve(def, contextInstr: data.lineEndContext); |
39 | m_lineEmptyContext.resolve(def, contextInstr: data.lineEmptyContext); |
40 | m_fallthroughContext.resolve(def, contextInstr: data.fallthroughContext); |
41 | m_stopEmptyLineContextSwitchLoop = data.stopEmptyLineContextSwitchLoop; |
42 | |
43 | /** |
44 | * line end context switches only when lineEmptyContext is #stay. This avoids |
45 | * skipping empty lines after a line continuation character (see bug 405903) |
46 | */ |
47 | if (m_lineEmptyContext.isStay()) { |
48 | m_lineEmptyContext = m_lineEndContext; |
49 | } |
50 | |
51 | m_rules.reserve(n: data.rules.size()); |
52 | for (const auto &ruleData : data.rules) { |
53 | m_rules.push_back(x: Rule::create(def, ruleData, lookupContextName: m_name)); |
54 | if (!m_rules.back()) { |
55 | m_rules.pop_back(); |
56 | } |
57 | } |
58 | } |
59 | |
60 | void Context::resolveIncludes(DefinitionData &def) |
61 | { |
62 | if (m_resolveState == Resolved) { |
63 | return; |
64 | } |
65 | if (m_resolveState == Resolving) { |
66 | qCWarning(Log) << "Cyclic dependency!" ; |
67 | return; |
68 | } |
69 | |
70 | Q_ASSERT(m_resolveState == Unresolved); |
71 | m_resolveState = Resolving; // cycle guard |
72 | |
73 | for (auto it = m_rules.begin(); it != m_rules.end();) { |
74 | const IncludeRules *includeRules = it->get()->castToIncludeRules(); |
75 | if (!includeRules) { |
76 | m_hasDynamicRule = m_hasDynamicRule || it->get()->isDynamic(); |
77 | ++it; |
78 | continue; |
79 | } |
80 | |
81 | Context *context = nullptr; |
82 | DefinitionData *defData = &def; |
83 | |
84 | const auto &contextName = includeRules->contextName(); |
85 | const int idx = contextName.indexOf(s: QLatin1String("##" )); |
86 | |
87 | if (idx == -1) { // local include |
88 | context = def.contextByName(name: contextName); |
89 | } else { |
90 | auto definitionName = contextName.mid(position: idx + 2); |
91 | auto includedDef = def.repo->definitionForName(defName: definitionName); |
92 | if (!includedDef.isValid()) { |
93 | qCWarning(Log) << "Unable to resolve external include rule for definition" << definitionName << "in" << def.name; |
94 | ++it; |
95 | continue; |
96 | } |
97 | defData = DefinitionData::get(def: includedDef); |
98 | def.addImmediateIncludedDefinition(def: includedDef); |
99 | defData->load(); |
100 | if (idx == 0) { |
101 | context = defData->initialContext(); |
102 | } else { |
103 | context = defData->contextByName(name: QStringView(contextName).left(n: idx)); |
104 | } |
105 | } |
106 | |
107 | if (!context) { |
108 | qCWarning(Log) << "Unable to resolve include rule for definition" << contextName << "in" << def.name; |
109 | ++it; |
110 | continue; |
111 | } |
112 | |
113 | if (context == this) { |
114 | qCWarning(Log) << "Unable to resolve self include rule for definition" << contextName << "in" << def.name; |
115 | ++it; |
116 | continue; |
117 | } |
118 | |
119 | if (context->m_resolveState != Resolved) { |
120 | context->resolveIncludes(def&: *defData); |
121 | } |
122 | |
123 | m_hasDynamicRule = m_hasDynamicRule || context->m_hasDynamicRule; |
124 | |
125 | /** |
126 | * handle included attribute |
127 | * transitive closure: we might include attributes included from somewhere else |
128 | */ |
129 | if (includeRules->includeAttribute()) { |
130 | m_attributeFormat = context->m_attributeFormat; |
131 | } |
132 | |
133 | it = m_rules.erase(position: it); |
134 | it = m_rules.insert(position: it, first: context->rules().begin(), last: context->rules().end()); |
135 | it += context->rules().size(); |
136 | } |
137 | |
138 | m_resolveState = Resolved; |
139 | } |
140 | |