KTextEditor

katehighlight.cpp
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(1);
36 m_propertiesForFormat.push_back(&m_properties[0]);
37 m_formats.resize(1);
38 m_formatsIdToIndex.insert(std::make_pair(m_formats[0].id(), 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(definitions.size());
57 // first: handle only really included definitions
58 for (const auto &includedDefinition : std::as_const(definitions)) {
59 embeddedHighlightingModes.push_back(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(definition());
72 m_properties.resize(definitions.size());
73 size_t propertiesIndex = 0;
74 for (const auto &includedDefinition : std::as_const(definitions)) {
75 auto &properties = m_properties[propertiesIndex];
76 properties.definition = includedDefinition;
77 properties.emptyLines.reserve(includedDefinition.foldingIgnoreList().size());
78 const auto foldingIgnoreList = includedDefinition.foldingIgnoreList();
79 for (const auto &emptyLine : foldingIgnoreList) {
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(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(std::make_pair(format.id(), int(nextId)));
102 m_formats.push_back(format);
103 m_propertiesForFormat.push_back(&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
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(textLine->text(), 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(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(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(offset + ((region.type() == KSyntaxHighlighting::FoldingRegion::Begin) ? 0 : length), length, region);
191 }
192
193 // for each end region, decrement counter for that type, erase if count reaches 0!
195 QHash<int, int>::iterator end = m_foldingStartToCount.find(region.id());
196 if (end != m_foldingStartToCount.end()) {
197 if (end.value() > 1) {
198 --(end.value());
199 } else {
200 m_foldingStartToCount.erase(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(sanitizeFormatIndex(attrib))->characterEncodings;
226}
227
228const KatePrefixStore &KateHighlighting::getCharacterEncodingsPrefixStore(int attrib) const
229{
230 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->characterEncodingsPrefixStore;
231}
232
233const QHash<QChar, QString> &KateHighlighting::getReverseCharacterEncodings(int attrib) const
234{
235 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->reverseCharacterEncodings;
236}
237
238bool KateHighlighting::attributeRequiresSpellchecking(int attr)
239{
240 return m_formats[sanitizeFormatIndex(attr)].spellCheck();
241}
242
243KSyntaxHighlighting::Theme::TextStyle KateHighlighting::defaultStyleForAttribute(int attr) const
244{
245 return m_formats[sanitizeFormatIndex(attr)].textStyle();
246}
247
248QString KateHighlighting::nameForAttrib(int attrib) const
249{
250 const auto &format = m_formats.at(sanitizeFormatIndex(attrib));
251 return m_propertiesForFormat.at(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(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(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(sanitizeFormatIndex(attrib))->emptyLines;
269}
270
271bool KateHighlighting::canComment(int startAttrib, int endAttrib) const
272{
273 const auto startProperties = m_propertiesForFormat.at(sanitizeFormatIndex(startAttrib));
274 const auto endProperties = m_propertiesForFormat.at(sanitizeFormatIndex(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(sanitizeFormatIndex(attrib))->multiLineCommentStart;
283}
284
285QString KateHighlighting::getCommentEnd(int attrib) const
286{
287 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->multiLineCommentEnd;
288}
289
290QString KateHighlighting::getCommentSingleLineStart(int attrib) const
291{
292 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->singleLineCommentMarker;
293}
294
295KSyntaxHighlighting::CommentPosition KateHighlighting::getCommentSingleLinePosition(int attrib) const
296{
297 return m_propertiesForFormat.at(sanitizeFormatIndex(attrib))->singleLineCommentPosition;
298}
299
300const QHash<QString, QChar> &KateHighlighting::characterEncodings(int attrib) const
301{
302 return m_propertiesForFormat.at(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
315 array.reserve(m_formats.size());
316 const auto currentTheme = KateHlManager::self()->repository().theme(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(array.size()), format.textStyle()));
320
321 if (const auto color = format.textColor(currentTheme).rgba()) {
322 newAttribute->setForeground(QColor::fromRgba(color));
323 }
324
325 if (const auto color = format.selectedTextColor(currentTheme).rgba()) {
326 newAttribute->setSelectedForeground(QColor::fromRgba(color));
327 }
328
329 if (const auto color = format.backgroundColor(currentTheme).rgba()) {
330 newAttribute->setBackground(QColor::fromRgba(color));
331 } else {
332 newAttribute->clearBackground();
333 }
334
335 if (const auto color = format.selectedBackgroundColor(currentTheme).rgba()) {
336 newAttribute->setSelectedBackground(QColor::fromRgba(color));
337 } else {
338 newAttribute->clearProperty(SelectedBackground);
339 }
340
341 // Only set attributes if true, otherwise we waste memory
342 if (format.isBold(currentTheme)) {
343 newAttribute->setFontBold(true);
344 }
345 if (format.isItalic(currentTheme)) {
346 newAttribute->setFontItalic(true);
347 }
348 if (format.isUnderline(currentTheme)) {
349 newAttribute->setFontUnderline(true);
350 }
351 if (format.isStrikeThrough(currentTheme)) {
352 newAttribute->setFontStrikeOut(true);
353 }
354 if (format.spellCheck()) {
355 newAttribute->setSkipSpellChecking(true);
356 }
357 array.append(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(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(schema, 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(textline->attribute(0));
388 if (l.isEmpty()) {
389 return false;
390 }
391
392 for (const QRegularExpression &re : l) {
393 const QRegularExpressionMatch match = re.match(txt, 0, QRegularExpression::NormalMatch, 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(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(tl.attribute(cursor.column()));
415 } else if (cursor.column() >= tl.length()) {
416 if (!tl.attributesList().empty()) {
417 return sanitizeFormatIndex(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(attributeForLocation(doc, cursor))->definition;
427 QStringList keywords;
428 keywords.reserve(def.keywordLists().size());
429 const auto keyWordLists = def.keywordLists();
430 for (const auto &keylist : keyWordLists) {
431 keywords += def.keywordList(keylist);
432 }
433 return keywords;
434}
435
436bool KateHighlighting::spellCheckingRequiredForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor)
437{
438 return m_formats.at(attributeForLocation(doc, cursor)).spellCheck();
439}
440
441QString KateHighlighting::higlightingModeForLocation(KTextEditor::DocumentPrivate *doc, const KTextEditor::Cursor cursor)
442{
443 return m_propertiesForFormat.at(attributeForLocation(doc, cursor))->definition.name();
444}
445
446// END
virtual void setDefinition(const Definition &def)
State highlightLine(QStringView text, const State &state)
QStringList keywordList(const QString &name) const
bool indentationBasedFoldingEnabled() const
QList< Format > formats() const
QStringList keywordLists() const
QList< Definition > includedDefinitions() const
bool isUnderline(const Theme &theme) const
QColor textColor(const Theme &theme) const
QColor selectedBackgroundColor(const Theme &theme) const
Theme::TextStyle textStyle() const
bool isBold(const Theme &theme) const
QColor backgroundColor(const Theme &theme) const
QColor selectedTextColor(const Theme &theme) const
bool isItalic(const Theme &theme) const
bool isStrikeThrough(const Theme &theme) const
Q_INVOKABLE KSyntaxHighlighting::Theme theme(const QString &themeName) const
A class which provides customized text decorations.
Definition attribute.h:51
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
This class can be used to efficiently search for occurrences of strings in a given string.
Definition prefixstore.h:27
Attribute storage.
Class representing a single text line.
int attribute(int pos) const
Gets the attribute at the given position use KRenderer::attributes to get the KTextAttribute for this...
const QString & text() const
Accessor to the text contained in this line.
void setHighlightingState(const KSyntaxHighlighting::State &val)
Sets the syntax highlight context number.
void clearMarkedAsFoldingStartAndEnd()
Clear folding start and end status.
const KSyntaxHighlighting::State & highlightingState() const
context stack
void markAsFoldingStartAttribute()
Mark as folding start line of an attribute based folding.
void clearAttributes()
Clear attributes and foldings of this line.
void markAsFoldingEndAttribute()
Mark as folding end line of an attribute based folding.
void addAttribute(const Attribute &attribute)
Add attribute to this line.
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
KGuiItem properties()
const QList< QKeySequence > & end()
bool isSpace() const const
QColor fromRgba(QRgb rgba)
QRgb rgba() const const
void clear()
QHash::iterator end()
QHash::iterator erase(QHash::iterator pos)
QHash::iterator find(const Key &key)
bool isEmpty() const const
void append(const T &value)
void push_back(const T &value)
void reserve(int alloc)
int size() const const
bool isEmpty() const const
int length() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Feb 24 2024 20:00:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.