1/*
2 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
3 SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
4 SPDX-FileCopyrightText: 2003, 2004 Anders Lund <anders@alweb.dk>
5 SPDX-FileCopyrightText: 2003 Hamish Rodda <rodda@kde.org>
6 SPDX-FileCopyrightText: 2001, 2002 Joseph Wenninger <jowenn@kde.org>
7 SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
8 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
9
10 SPDX-License-Identifier: LGPL-2.0-or-later
11*/
12
13// BEGIN INCLUDES
14#include "katehighlight.h"
15
16#include "katedocument.h"
17#include "kateextendedattribute.h"
18#include "katesyntaxmanager.h"
19// END
20
21// BEGIN KateHighlighting
22KateHighlighting::KateHighlighting(const KSyntaxHighlighting::Definition &def)
23{
24 // get name and section, always works
25 iName = def.name();
26 iSection = def.translatedSection();
27
28 // get all included definitions, e.g. PHP for HTML highlighting
29 auto definitions = def.includedDefinitions();
30
31 // handle the "no highlighting" case
32 // it's possible to not have any definitions with malformed file
33 if (!def.isValid() || (definitions.isEmpty() && def.formats().isEmpty())) {
34 // dummy properties + formats
35 m_properties.resize(new_size: 1);
36 m_propertiesForFormat.push_back(x: &m_properties[0]);
37 m_formats.resize(new_size: 1);
38 m_formatsIdToIndex.insert(x: std::make_pair(x: m_formats[0].id(), y: 0));
39
40 // be done, all below is just for the real highlighting variants
41 return;
42 }
43
44 // handle the real highlighting case
45 noHl = false;
46 iHidden = def.isHidden();
47 identifier = def.filePath();
48 iStyle = def.style();
49 m_indentation = def.indenter();
50 folding = def.foldingEnabled();
51 m_foldingIndentationSensitive = def.indentationBasedFoldingEnabled();
52
53 // tell the AbstractHighlighter the definition it shall use
54 setDefinition(def);
55
56 embeddedHighlightingModes.reserve(asize: definitions.size());
57 // first: handle only really included definitions
58 for (const auto &includedDefinition : std::as_const(t&: definitions)) {
59 embeddedHighlightingModes.push_back(t: includedDefinition.name());
60 }
61
62 // now: handle all, including this definition itself
63 // create the format => attributes mapping
64 // collect embedded highlightings, too
65 //
66 // we start with our definition as we want to have the default format
67 // of the initial definition as attribute with index == 0
68 //
69 // we collect additional properties in the m_properties and
70 // map the formats to the right properties in m_propertiesForFormat
71 definitions.push_front(t: definition());
72 m_properties.resize(new_size: definitions.size());
73 size_t propertiesIndex = 0;
74 for (const auto &includedDefinition : std::as_const(t&: definitions)) {
75 auto &properties = m_properties[propertiesIndex];
76 properties.definition = includedDefinition;
77 properties.emptyLines.reserve(asize: includedDefinition.foldingIgnoreList().size());
78 const auto foldingIgnoreList = includedDefinition.foldingIgnoreList();
79 for (const auto &emptyLine : foldingIgnoreList) {
80 properties.emptyLines.push_back(t: QRegularExpression(emptyLine, QRegularExpression::UseUnicodePropertiesOption));
81 }
82 properties.singleLineCommentMarker = includedDefinition.singleLineCommentMarker();
83 properties.singleLineCommentPosition = includedDefinition.singleLineCommentPosition();
84 const auto multiLineComment = includedDefinition.multiLineCommentMarker();
85 properties.multiLineCommentStart = multiLineComment.first;
86 properties.multiLineCommentEnd = multiLineComment.second;
87
88 // collect character characters
89 const auto encodings = includedDefinition.characterEncodings();
90 for (const auto &enc : encodings) {
91 properties.characterEncodingsPrefixStore.addPrefix(prefix: enc.second);
92 properties.characterEncodings[enc.second] = enc.first;
93 properties.reverseCharacterEncodings[enc.first] = enc.second;
94 }
95
96 // collect formats
97 const auto formats = includedDefinition.formats();
98 for (const auto &format : formats) {
99 // register format id => internal attributes, we want no clashs
100 const auto nextId = m_formats.size();
101 m_formatsIdToIndex.insert(x: std::make_pair(x: format.id(), y: int(nextId)));
102 m_formats.push_back(x: format);
103 m_propertiesForFormat.push_back(x: &properties);
104 }
105
106 // advance to next properties
107 ++propertiesIndex;
108 }
109}
110
111void KateHighlighting::doHighlight(const Kate::TextLine *prevLine, Kate::TextLine *textLine, bool &ctxChanged, Foldings *foldings)
112{
113 // default: no context change
114 ctxChanged = false;
115
116 // no text line => nothing to do
117 if (!textLine) {
118 return;
119 }
120
121 // in all cases, remove old hl, or we will grow to infinite ;)
122 textLine->clearAttributes();
123
124 // reset folding start
125 textLine->clearMarkedAsFoldingStartAndEnd();
126 if (foldings) {
127 foldings->clear();
128 }
129
130 // no hl set, nothing to do more than the above cleaning ;)
131 if (noHl) {
132 return;
133 }
134
135 // ensure we arrive in clean state
136 Q_ASSERT(!m_textLineToHighlight);
137 Q_ASSERT(!m_foldings);
138 Q_ASSERT(m_foldingStartToCount.isEmpty());
139
140 // highlight the given line via the abstract highlighter
141 // a bit ugly: we set the line to highlight as member to be able to update its stats in the applyFormat and applyFolding member functions
142 m_textLineToHighlight = textLine;
143 m_foldings = foldings;
144 const KSyntaxHighlighting::State initialState(!prevLine ? KSyntaxHighlighting::State() : prevLine->highlightingState());
145 const KSyntaxHighlighting::State endOfLineState = highlightLine(text: textLine->text(), state: initialState);
146 m_textLineToHighlight = nullptr;
147 m_foldings = nullptr;
148
149 // update highlighting state if needed
150 if (textLine->highlightingState() != endOfLineState) {
151 textLine->setHighlightingState(endOfLineState);
152 ctxChanged = true;
153 }
154
155 // handle folding info computed and cleanup hash again, if there
156 // check if folding is not balanced and we have more starts then ends
157 // then this line is a possible folding start!
158 if (!m_foldingStartToCount.isEmpty()) {
159 // possible folding start, if imbalanced, aka hash not empty!
160 textLine->markAsFoldingStartAttribute();
161
162 // clear hash for next doHighlight
163 m_foldingStartToCount.clear();
164 }
165}
166
167void KateHighlighting::applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format)
168{
169 Q_ASSERT(m_textLineToHighlight);
170 if (!format.isValid()) {
171 return;
172 }
173
174 // get internal attribute, must exist
175 const auto it = m_formatsIdToIndex.find(x: format.id());
176 Q_ASSERT(it != m_formatsIdToIndex.end());
177
178 // WE ATM assume ascending offset order
179 // remember highlighting info in our textline
180 m_textLineToHighlight->addAttribute(attribute: Kate::TextLine::Attribute(offset, length, it->second));
181}
182
183void KateHighlighting::applyFolding(int offset, int length, KSyntaxHighlighting::FoldingRegion region)
184{
185 Q_ASSERT(m_textLineToHighlight);
186 Q_ASSERT(region.isValid());
187
188 // WE ATM assume ascending offset order, we add the length to the offset for the folding ends to have ranges spanning the full folding region
189 if (m_foldings) {
190 m_foldings->emplace_back(args: offset + ((region.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 0 : length), args&: length, args&: region);
191 }
192
193 // for each end region, decrement counter for that type, erase if count reaches 0!
194 if (region.type() == KSyntaxHighlighting::FoldingRegion::End) {
195 QHash<int, int>::iterator end = m_foldingStartToCount.find(key: region.id());
196 if (end != m_foldingStartToCount.end()) {
197 if (end.value() > 1) {
198 --(end.value());
199 } else {
200 m_foldingStartToCount.erase(it: end);
201 }
202 } else {
203 // if we arrive here, we might have some folding end in this line for previous lines
204 m_textLineToHighlight->markAsFoldingEndAttribute();
205 }
206 }
207
208 // increment counter for each begin region!
209 else {
210 ++m_foldingStartToCount[region.id()];
211 }
212}
213
214int KateHighlighting::sanitizeFormatIndex(int attrib) const
215{
216 // sanitize, e.g. one could have old hl info with now invalid attribs
217 if (attrib < 0 || size_t(attrib) >= m_formats.size()) {
218 return 0;
219 }
220 return attrib;
221}
222
223const QHash<QString, QChar> &KateHighlighting::getCharacterEncodings(int attrib) const
224{
225 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->characterEncodings;
226}
227
228const KatePrefixStore &KateHighlighting::getCharacterEncodingsPrefixStore(int attrib) const
229{
230 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->characterEncodingsPrefixStore;
231}
232
233const QHash<QChar, QString> &KateHighlighting::getReverseCharacterEncodings(int attrib) const
234{
235 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->reverseCharacterEncodings;
236}
237
238bool KateHighlighting::attributeRequiresSpellchecking(int attr)
239{
240 return m_formats[sanitizeFormatIndex(attrib: attr)].spellCheck();
241}
242
243KSyntaxHighlighting::Theme::TextStyle KateHighlighting::defaultStyleForAttribute(int attr) const
244{
245 return m_formats[sanitizeFormatIndex(attrib: attr)].textStyle();
246}
247
248QString KateHighlighting::nameForAttrib(int attrib) const
249{
250 const auto &format = m_formats.at(n: sanitizeFormatIndex(attrib));
251 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->definition.name() + QLatin1Char(':')
252 + QString(format.isValid() ? format.name() : QStringLiteral("Normal"));
253}
254
255bool KateHighlighting::isInWord(QChar c, int attrib) const
256{
257 return !m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->definition.isWordDelimiter(c) && !c.isSpace() && c != QLatin1Char('"')
258 && c != QLatin1Char('\'') && c != QLatin1Char('`');
259}
260
261bool KateHighlighting::canBreakAt(QChar c, int attrib) const
262{
263 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->definition.isWordWrapDelimiter(c) && c != QLatin1Char('"') && c != QLatin1Char('\'');
264}
265
266const QList<QRegularExpression> &KateHighlighting::emptyLines(int attrib) const
267{
268 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->emptyLines;
269}
270
271bool KateHighlighting::canComment(int startAttrib, int endAttrib) const
272{
273 const auto startProperties = m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib: startAttrib));
274 const auto endProperties = m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib: endAttrib));
275 return (startProperties == endProperties
276 && ((!startProperties->multiLineCommentStart.isEmpty() && !startProperties->multiLineCommentEnd.isEmpty())
277 || !startProperties->singleLineCommentMarker.isEmpty()));
278}
279
280QString KateHighlighting::getCommentStart(int attrib) const
281{
282 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->multiLineCommentStart;
283}
284
285QString KateHighlighting::getCommentEnd(int attrib) const
286{
287 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->multiLineCommentEnd;
288}
289
290QString KateHighlighting::getCommentSingleLineStart(int attrib) const
291{
292 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->singleLineCommentMarker;
293}
294
295KSyntaxHighlighting::CommentPosition KateHighlighting::getCommentSingleLinePosition(int attrib) const
296{
297 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->singleLineCommentPosition;
298}
299
300const QHash<QString, QChar> &KateHighlighting::characterEncodings(int attrib) const
301{
302 return m_propertiesForFormat.at(n: sanitizeFormatIndex(attrib))->characterEncodings;
303}
304
305void KateHighlighting::clearAttributeArrays()
306{
307 // just clear the hashed attributes, we create them lazy again
308 m_attributeArrays.clear();
309}
310
311QList<KTextEditor::Attribute::Ptr> KateHighlighting::attributesForDefinition(const QString &schema) const
312{
313 // create list of known attributes based on highlighting format & wanted theme
314 QList<KTextEditor::Attribute::Ptr> array;
315 array.reserve(asize: m_formats.size());
316 const auto currentTheme = KateHlManager::self()->repository().theme(themeName: schema);
317 for (const auto &format : m_formats) {
318 // create a KTextEditor attribute matching the given format
319 KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(nameForAttrib(attrib: array.size()), format.textStyle()));
320
321 if (const auto color = format.textColor(theme: currentTheme).rgba()) {
322 newAttribute->setForeground(QColor::fromRgba(rgba: color));
323 }
324
325 if (const auto color = format.selectedTextColor(theme: currentTheme).rgba()) {
326 newAttribute->setSelectedForeground(QColor::fromRgba(rgba: color));
327 }
328
329 if (const auto color = format.backgroundColor(theme: currentTheme).rgba()) {
330 newAttribute->setBackground(QColor::fromRgba(rgba: color));
331 } else {
332 newAttribute->clearBackground();
333 }
334
335 if (const auto color = format.selectedBackgroundColor(theme: currentTheme).rgba()) {
336 newAttribute->setSelectedBackground(QColor::fromRgba(rgba: color));
337 } else {
338 newAttribute->clearProperty(propertyId: SelectedBackground);
339 }
340
341 // Only set attributes if true, otherwise we waste memory
342 if (format.isBold(theme: currentTheme)) {
343 newAttribute->setFontBold(true);
344 }
345 if (format.isItalic(theme: currentTheme)) {
346 newAttribute->setFontItalic(true);
347 }
348 if (format.isUnderline(theme: currentTheme)) {
349 newAttribute->setFontUnderline(true);
350 }
351 if (format.isStrikeThrough(theme: currentTheme)) {
352 newAttribute->setFontStrikeOut(true);
353 }
354 if (format.spellCheck()) {
355 newAttribute->setSkipSpellChecking(true);
356 }
357 array.append(t: newAttribute);
358 }
359 return array;
360}
361
362QList<KTextEditor::Attribute::Ptr> KateHighlighting::attributes(const QString &schema)
363{
364 // query cache first
365 if (m_attributeArrays.contains(key: schema)) {
366 return m_attributeArrays[schema];
367 }
368
369 // create new attributes array for wanted theme and cache it
370 const auto array = attributesForDefinition(schema);
371 m_attributeArrays.insert(key: schema, value: array);
372 return array;
373}
374
375QStringList KateHighlighting::getEmbeddedHighlightingModes() const
376{
377 return embeddedHighlightingModes;
378}
379
380bool KateHighlighting::isEmptyLine(const Kate::TextLine *textline) const
381{
382 const QString &txt = textline->text();
383 if (txt.isEmpty()) {
384 return true;
385 }
386
387 const auto &l = emptyLines(attrib: textline->attribute(pos: 0));
388 if (l.isEmpty()) {
389 return false;
390 }
391
392 for (const QRegularExpression &re : l) {
393 const QRegularExpressionMatch match = re.match(subject: txt, offset: 0, matchType: QRegularExpression::NormalMatch, matchOptions: QRegularExpression::AnchorAtOffsetMatchOption);
394 if (match.hasMatch() && match.capturedLength() == txt.length()) {
395 return true;
396 }
397 }
398
399 return false;
400}
401
402int KateHighlighting::attributeForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor)
403{
404 // Validate parameters to prevent out of range access
405 if (cursor.line() < 0 || cursor.line() >= doc->lines() || cursor.column() < 0) {
406 return 0;
407 }
408
409 // get highlighted line
410 const auto tl = doc->kateTextLine(i: cursor.line());
411
412 // either get char attribute or attribute of context still active at end of line
413 if (cursor.column() < tl.length()) {
414 return sanitizeFormatIndex(attrib: tl.attribute(pos: cursor.column()));
415 } else if (cursor.column() >= tl.length()) {
416 if (!tl.attributesList().empty()) {
417 return sanitizeFormatIndex(attrib: tl.attributesList().back().attributeValue);
418 }
419 }
420 return 0;
421}
422
423QStringList KateHighlighting::keywordsForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor)
424{
425 // FIXME-SYNTAX: was before more precise, aka context level
426 const auto &def = m_propertiesForFormat.at(n: attributeForLocation(doc, cursor))->definition;
427 QStringList keywords;
428 keywords.reserve(asize: def.keywordLists().size());
429 const auto keyWordLists = def.keywordLists();
430 for (const auto &keylist : keyWordLists) {
431 keywords += def.keywordList(name: keylist);
432 }
433 return keywords;
434}
435
436bool KateHighlighting::spellCheckingRequiredForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor)
437{
438 return m_formats.at(n: attributeForLocation(doc, cursor)).spellCheck();
439}
440
441QString KateHighlighting::higlightingModeForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor)
442{
443 return m_propertiesForFormat.at(n: attributeForLocation(doc, cursor))->definition.name();
444}
445
446// END
447

source code of ktexteditor/src/syntax/katehighlight.cpp