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