1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: MIT
5*/
6
7#include "syntaxhighlighter.h"
8#include "abstracthighlighter_p.h"
9#include "definition.h"
10#include "definition_p.h"
11#include "foldingregion.h"
12#include "format.h"
13#include "format_p.h"
14#include "state.h"
15#include "theme.h"
16#include "themedata_p.h"
17
18Q_DECLARE_METATYPE(QTextBlock)
19
20using namespace KSyntaxHighlighting;
21
22namespace KSyntaxHighlighting
23{
24class TextBlockUserData : public QTextBlockUserData
25{
26public:
27 State state;
28 QList<FoldingRegion> foldingRegions;
29};
30
31class SyntaxHighlighterPrivate : public AbstractHighlighterPrivate
32{
33public:
34 static FoldingRegion foldingRegion(const QTextBlock &startBlock);
35 void initTextFormat(QTextCharFormat &tf, const Format &format);
36 void computeTextFormats();
37
38 struct TextFormat {
39 QTextCharFormat tf;
40 /**
41 * id to check that the format belongs to the definition
42 */
43 std::intptr_t ptrId;
44 };
45
46 QList<FoldingRegion> foldingRegions;
47 std::vector<TextFormat> tfs;
48};
49
50}
51
52FoldingRegion SyntaxHighlighterPrivate::foldingRegion(const QTextBlock &startBlock)
53{
54 const auto data = dynamic_cast<TextBlockUserData *>(startBlock.userData());
55 if (!data) {
56 return FoldingRegion();
57 }
58 for (int i = data->foldingRegions.size() - 1; i >= 0; --i) {
59 if (data->foldingRegions.at(i).type() == FoldingRegion::Begin) {
60 return data->foldingRegions.at(i);
61 }
62 }
63 return FoldingRegion();
64}
65
66void SyntaxHighlighterPrivate::initTextFormat(QTextCharFormat &tf, const Format &format)
67{
68 // always set the foreground color to avoid palette issues
69 tf.setForeground(format.textColor(theme: m_theme));
70
71 if (format.hasBackgroundColor(theme: m_theme)) {
72 tf.setBackground(format.backgroundColor(theme: m_theme));
73 }
74 if (format.isBold(theme: m_theme)) {
75 tf.setFontWeight(QFont::Bold);
76 }
77 if (format.isItalic(theme: m_theme)) {
78 tf.setFontItalic(true);
79 }
80 if (format.isUnderline(theme: m_theme)) {
81 tf.setFontUnderline(true);
82 }
83 if (format.isStrikeThrough(theme: m_theme)) {
84 tf.setFontStrikeOut(true);
85 }
86}
87
88void SyntaxHighlighterPrivate::computeTextFormats()
89{
90 auto definitions = m_definition.includedDefinitions();
91 definitions.append(t: m_definition);
92
93 int maxId = 0;
94 for (const auto &definition : std::as_const(t&: definitions)) {
95 for (const auto &format : std::as_const(t&: DefinitionData::get(def: definition)->formats)) {
96 maxId = qMax(a: maxId, b: format.id());
97 }
98 }
99 tfs.clear();
100 tfs.resize(new_size: maxId + 1);
101
102 // initialize tfs
103 for (const auto &definition : std::as_const(t&: definitions)) {
104 for (const auto &format : std::as_const(t&: DefinitionData::get(def: definition)->formats)) {
105 auto &tf = tfs[format.id()];
106 tf.ptrId = FormatPrivate::ptrId(format);
107 initTextFormat(tf&: tf.tf, format);
108 }
109 }
110}
111
112SyntaxHighlighter::SyntaxHighlighter(QObject *parent)
113 : QSyntaxHighlighter(parent)
114 , AbstractHighlighter(new SyntaxHighlighterPrivate)
115{
116 qRegisterMetaType<QTextBlock>();
117}
118
119SyntaxHighlighter::SyntaxHighlighter(QTextDocument *document)
120 : QSyntaxHighlighter(document)
121 , AbstractHighlighter(new SyntaxHighlighterPrivate)
122{
123 qRegisterMetaType<QTextBlock>();
124}
125
126SyntaxHighlighter::~SyntaxHighlighter()
127{
128}
129
130void SyntaxHighlighter::setDefinition(const Definition &def)
131{
132 Q_D(SyntaxHighlighter);
133
134 const auto needsRehighlight = d->m_definition != def;
135 if (DefinitionData::get(def: d->m_definition) != DefinitionData::get(def)) {
136 d->m_definition = def;
137 d->tfs.clear();
138 }
139 if (needsRehighlight) {
140 rehighlight();
141 }
142}
143
144void SyntaxHighlighter::setTheme(const Theme &theme)
145{
146 Q_D(SyntaxHighlighter);
147 if (ThemeData::get(theme: d->m_theme) != ThemeData::get(theme)) {
148 d->m_theme = theme;
149 d->tfs.clear();
150 }
151}
152
153bool SyntaxHighlighter::startsFoldingRegion(const QTextBlock &startBlock) const
154{
155 return SyntaxHighlighterPrivate::foldingRegion(startBlock).type() == FoldingRegion::Begin;
156}
157
158QTextBlock SyntaxHighlighter::findFoldingRegionEnd(const QTextBlock &startBlock) const
159{
160 const auto region = SyntaxHighlighterPrivate::foldingRegion(startBlock);
161
162 auto block = startBlock;
163 int depth = 1;
164 while (block.isValid()) {
165 block = block.next();
166 const auto data = dynamic_cast<TextBlockUserData *>(block.userData());
167 if (!data) {
168 continue;
169 }
170 for (const auto &foldingRegion : std::as_const(t&: data->foldingRegions)) {
171 if (foldingRegion.id() != region.id()) {
172 continue;
173 }
174 if (foldingRegion.type() == FoldingRegion::End) {
175 --depth;
176 } else if (foldingRegion.type() == FoldingRegion::Begin) {
177 ++depth;
178 }
179 if (depth == 0) {
180 return block;
181 }
182 }
183 }
184
185 return QTextBlock();
186}
187
188void SyntaxHighlighter::highlightBlock(const QString &text)
189{
190 Q_D(SyntaxHighlighter);
191
192 static const State emptyState;
193 const State *previousState = &emptyState;
194 if (currentBlock().position() > 0) {
195 const auto prevBlock = currentBlock().previous();
196 const auto prevData = dynamic_cast<TextBlockUserData *>(prevBlock.userData());
197 if (prevData) {
198 previousState = &prevData->state;
199 }
200 }
201 d->foldingRegions.clear();
202 auto newState = highlightLine(text, state: *previousState);
203
204 auto data = dynamic_cast<TextBlockUserData *>(currentBlockUserData());
205 if (!data) { // first time we highlight this
206 data = new TextBlockUserData;
207 data->state = std::move(newState);
208 data->foldingRegions = d->foldingRegions;
209 setCurrentBlockUserData(data);
210 return;
211 }
212
213 if (data->state == newState && data->foldingRegions == d->foldingRegions) { // we ended up in the same state, so we are done here
214 return;
215 }
216 data->state = std::move(newState);
217 data->foldingRegions = d->foldingRegions;
218
219 const auto nextBlock = currentBlock().next();
220 if (nextBlock.isValid()) {
221 QMetaObject::invokeMethod(obj: this, member: "rehighlightBlock", c: Qt::QueuedConnection, Q_ARG(QTextBlock, nextBlock));
222 }
223}
224
225void SyntaxHighlighter::applyFormat(int offset, int length, const Format &format)
226{
227 if (length == 0) {
228 return;
229 }
230
231 Q_D(SyntaxHighlighter);
232
233 if (Q_UNLIKELY(d->tfs.empty())) {
234 d->computeTextFormats();
235 }
236
237 const auto id = static_cast<std::size_t>(format.id());
238 // This doesn't happen when format comes from the definition.
239 // But as the user can override the function to pass any format, this is a possible scenario.
240 if (id < d->tfs.size() && d->tfs[id].ptrId == FormatPrivate::ptrId(format)) {
241 QSyntaxHighlighter::setFormat(start: offset, count: length, format: d->tfs[id].tf);
242 } else {
243 QTextCharFormat tf;
244 d->initTextFormat(tf, format);
245 QSyntaxHighlighter::setFormat(start: offset, count: length, format: tf);
246 }
247}
248
249void SyntaxHighlighter::applyFolding(int offset, int length, FoldingRegion region)
250{
251 Q_UNUSED(offset);
252 Q_UNUSED(length);
253 Q_D(SyntaxHighlighter);
254
255 if (region.type() == FoldingRegion::Begin) {
256 d->foldingRegions.push_back(t: region);
257 }
258
259 if (region.type() == FoldingRegion::End) {
260 for (int i = d->foldingRegions.size() - 1; i >= 0; --i) {
261 if (d->foldingRegions.at(i).id() != region.id() || d->foldingRegions.at(i).type() != FoldingRegion::Begin) {
262 continue;
263 }
264 d->foldingRegions.remove(i);
265 return;
266 }
267 d->foldingRegions.push_back(t: region);
268 }
269}
270
271#include "moc_syntaxhighlighter.cpp"
272

source code of syntax-highlighting/src/lib/syntaxhighlighter.cpp