KSyntaxHighlighting

rule.cpp
1/*
2 SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
3 SPDX-FileCopyrightText: 2018 Christoph Cullmann <cullmann@kde.org>
4 SPDX-FileCopyrightText: 2020 Jonathan Poelen <jonathan.poelen+kde@gmail.com>
5
6 SPDX-License-Identifier: MIT
7*/
8
9#include "context_p.h"
10#include "definition_p.h"
11#include "dynamicregexpcache_p.h"
12#include "ksyntaxhighlighting_logging.h"
13#include "rule_p.h"
14#include "worddelimiters_p.h"
15#include "xml_p.h"
16
17using namespace KSyntaxHighlighting;
18
19// QChar::isDigit() match any digit in unicode (romain numeral, etc)
20static bool isDigit(QChar c)
21{
22 return (c <= QLatin1Char('9') && QLatin1Char('0') <= c);
23}
24
25static bool isOctalChar(QChar c)
26{
27 return (c <= QLatin1Char('7') && QLatin1Char('0') <= c);
28}
29
30static bool isHexChar(QChar c)
31{
32 return isDigit(c) || (c <= QLatin1Char('f') && QLatin1Char('a') <= c) || (c <= QLatin1Char('F') && QLatin1Char('A') <= c);
33}
34
35static int matchEscapedChar(QStringView text, int offset)
36{
37 if (text.at(offset) != QLatin1Char('\\') || text.size() < offset + 2) {
38 return offset;
39 }
40
41 const auto c = text.at(offset + 1);
42 switch (c.unicode()) {
43 // control chars
44 case 'a':
45 case 'b':
46 case 'e':
47 case 'f':
48 case 'n':
49 case 'r':
50 case 't':
51 case 'v':
52 case '"':
53 case '\'':
54 case '?':
55 case '\\':
56 return offset + 2;
57
58 // hex encoded character
59 case 'x':
60 if (offset + 2 < text.size() && isHexChar(text.at(offset + 2))) {
61 if (offset + 3 < text.size() && isHexChar(text.at(offset + 3))) {
62 return offset + 4;
63 }
64 return offset + 3;
65 }
66 return offset;
67
68 // octal encoding, simple \0 is OK, too, unlike simple \x above
69 case '0':
70 case '1':
71 case '2':
72 case '3':
73 case '4':
74 case '5':
75 case '6':
76 case '7':
77 if (offset + 2 < text.size() && isOctalChar(text.at(offset + 2))) {
78 if (offset + 3 < text.size() && isOctalChar(text.at(offset + 3))) {
79 return offset + 4;
80 }
81 return offset + 3;
82 }
83 return offset + 2;
84 }
85
86 return offset;
87}
88
89static QString replaceCaptures(const QString &pattern, const QStringList &captures, bool quote)
90{
91 auto result = pattern;
92 for (int i = captures.size(); i >= 1; --i) {
93 result.replace(QLatin1Char('%') + QString::number(i), quote ? QRegularExpression::escape(captures.at(i - 1)) : captures.at(i - 1));
94 }
95 return result;
96}
97
98static MatchResult matchString(QStringView pattern, QStringView text, int offset, Qt::CaseSensitivity caseSensitivity)
99{
100 if (offset + pattern.size() <= text.size() && text.sliced(offset, pattern.size()).compare(pattern, caseSensitivity) == 0) {
101 return offset + pattern.size();
102 }
103 return offset;
104}
105
106static void resolveAdditionalWordDelimiters(WordDelimiters &wordDelimiters, const HighlightingContextData::Rule::WordDelimiters &delimiters)
107{
108 // cache for DefinitionData::wordDelimiters, is accessed VERY often
109 if (!delimiters.additionalDeliminator.isEmpty() || !delimiters.weakDeliminator.isEmpty()) {
110 wordDelimiters.append(QStringView(delimiters.additionalDeliminator));
111 wordDelimiters.remove(QStringView(delimiters.weakDeliminator));
112 }
113}
114
115Rule::~Rule() = default;
116
117const IncludeRules *Rule::castToIncludeRules() const
118{
119 if (m_type != Type::IncludeRules) {
120 return nullptr;
121 }
122 return static_cast<const IncludeRules *>(this);
123}
124
125bool Rule::resolveCommon(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
126{
127 switch (ruleData.type) {
128 // IncludeRules uses this with a different semantic
129 case HighlightingContextData::Rule::Type::IncludeRules:
130 m_type = Type::IncludeRules;
131 return true;
132 case HighlightingContextData::Rule::Type::LineContinue:
133 m_type = Type::LineContinue;
134 break;
135 default:
136 m_type = Type::OtherRule;
137 break;
138 }
139
140 /**
141 * try to get our format from the definition we stem from
142 */
143 if (!ruleData.common.attributeName.isEmpty()) {
144 m_attributeFormat = def.formatByName(ruleData.common.attributeName);
145 if (!m_attributeFormat.isValid()) {
146 qCWarning(Log) << "Rule: Unknown format" << ruleData.common.attributeName << "in context" << lookupContextName << "of definition" << def.name;
147 }
148 }
149
150 m_firstNonSpace = ruleData.common.firstNonSpace;
151 m_lookAhead = ruleData.common.lookAhead;
152 m_column = ruleData.common.column;
153
154 if (!ruleData.common.beginRegionName.isEmpty()) {
155 m_beginRegion = FoldingRegion(FoldingRegion::Begin, def.foldingRegionId(ruleData.common.beginRegionName));
156 }
157 if (!ruleData.common.endRegionName.isEmpty()) {
158 m_endRegion = FoldingRegion(FoldingRegion::End, def.foldingRegionId(ruleData.common.endRegionName));
159 }
160
161 m_context.resolve(def, ruleData.common.contextName);
162
163 return !(m_lookAhead && m_context.isStay());
164}
165
166static Rule::Ptr createRule(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
167{
168 using Type = HighlightingContextData::Rule::Type;
169
170 switch (ruleData.type) {
171 case Type::AnyChar:
172 return std::make_shared<AnyChar>(ruleData.data.anyChar);
173 case Type::DetectChar:
174 return std::make_shared<DetectChar>(ruleData.data.detectChar);
175 case Type::Detect2Chars:
176 return std::make_shared<Detect2Chars>(ruleData.data.detect2Chars);
177 case Type::IncludeRules:
178 return std::make_shared<IncludeRules>(ruleData.data.includeRules);
179 case Type::Int:
180 return std::make_shared<Int>(def, ruleData.data.detectInt);
181 case Type::Keyword:
182 return KeywordListRule::create(def, ruleData.data.keyword, lookupContextName);
183 case Type::LineContinue:
184 return std::make_shared<LineContinue>(ruleData.data.lineContinue);
185 case Type::RangeDetect:
186 return std::make_shared<RangeDetect>(ruleData.data.rangeDetect);
187 case Type::RegExpr:
188 if (!ruleData.data.regExpr.dynamic) {
189 return std::make_shared<RegExpr>(ruleData.data.regExpr);
190 } else {
191 return std::make_shared<DynamicRegExpr>(ruleData.data.regExpr);
192 }
193 case Type::StringDetect:
194 if (ruleData.data.stringDetect.dynamic) {
195 return std::make_shared<DynamicStringDetect>(ruleData.data.stringDetect);
196 }
197 return std::make_shared<StringDetect>(ruleData.data.stringDetect);
198 case Type::WordDetect:
199 return std::make_shared<WordDetect>(def, ruleData.data.wordDetect);
200 case Type::Float:
201 return std::make_shared<Float>(def, ruleData.data.detectFloat);
202 case Type::HlCOct:
203 return std::make_shared<HlCOct>(def, ruleData.data.hlCOct);
204 case Type::HlCStringChar:
205 return std::make_shared<HlCStringChar>();
206 case Type::DetectIdentifier:
207 return std::make_shared<DetectIdentifier>();
208 case Type::DetectSpaces:
209 return std::make_shared<DetectSpaces>();
210 case Type::HlCChar:
211 return std::make_shared<HlCChar>();
212 case Type::HlCHex:
213 return std::make_shared<HlCHex>(def, ruleData.data.hlCHex);
214
215 case Type::Unknown:;
216 }
217
218 return Rule::Ptr(nullptr);
219}
220
221Rule::Ptr Rule::create(DefinitionData &def, const HighlightingContextData::Rule &ruleData, QStringView lookupContextName)
222{
223 auto rule = createRule(def, ruleData, lookupContextName);
224 if (rule && !rule->resolveCommon(def, ruleData, lookupContextName)) {
225 rule.reset();
226 }
227 return rule;
228}
229
230AnyChar::AnyChar(const HighlightingContextData::Rule::AnyChar &data)
231 : m_chars(data.chars)
232{
233}
234
235MatchResult AnyChar::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
236{
237 if (m_chars.contains(text.at(offset))) {
238 return offset + 1;
239 }
240 return offset;
241}
242
243DetectChar::DetectChar(const HighlightingContextData::Rule::DetectChar &data)
244 : m_char(data.char1)
245 , m_captureIndex((data.dynamic ? data.char1.digitValue() : 0) - 1)
246{
247 m_dynamic = data.dynamic;
248}
249
250MatchResult DetectChar::doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &) const
251{
252 if (m_dynamic) {
253 if (m_captureIndex == -1 || captures.size() <= m_captureIndex || captures.at(m_captureIndex).isEmpty()) {
254 return offset;
255 }
256 if (text.at(offset) == captures.at(m_captureIndex).at(0)) {
257 return offset + 1;
258 }
259 return offset;
260 }
261
262 if (text.at(offset) == m_char) {
263 return offset + 1;
264 }
265 return offset;
266}
267
268Detect2Chars::Detect2Chars(const HighlightingContextData::Rule::Detect2Chars &data)
269 : m_char1(data.char1)
270 , m_char2(data.char2)
271{
272}
273
274MatchResult Detect2Chars::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
275{
276 if (text.size() - offset < 2) {
277 return offset;
278 }
279 if (text.at(offset) == m_char1 && text.at(offset + 1) == m_char2) {
280 return offset + 2;
281 }
282 return offset;
283}
284
285MatchResult DetectIdentifier::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
286{
287 if (!text.at(offset).isLetter() && text.at(offset) != QLatin1Char('_')) {
288 return offset;
289 }
290
291 for (int i = offset + 1; i < text.size(); ++i) {
292 const auto c = text.at(i);
293 if (!c.isLetterOrNumber() && c != QLatin1Char('_')) {
294 return i;
295 }
296 }
297
298 return text.size();
299}
300
301MatchResult DetectSpaces::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
302{
303 while (offset < text.size() && text.at(offset).isSpace()) {
304 ++offset;
305 }
306 return offset;
307}
308
309Float::Float(DefinitionData &def, const HighlightingContextData::Rule::Float &data)
310 : m_wordDelimiters(def.wordDelimiters)
311{
312 resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
313}
314
315MatchResult Float::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
316{
317 if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
318 return offset;
319 }
320
321 auto newOffset = offset;
322 while (newOffset < text.size() && isDigit(text.at(newOffset))) {
323 ++newOffset;
324 }
325
326 if (newOffset >= text.size() || text.at(newOffset) != QLatin1Char('.')) {
327 return offset;
328 }
329 ++newOffset;
330
331 while (newOffset < text.size() && isDigit(text.at(newOffset))) {
332 ++newOffset;
333 }
334
335 if (newOffset == offset + 1) { // we only found a decimal point
336 return offset;
337 }
338
339 auto expOffset = newOffset;
340 if (expOffset >= text.size() || (text.at(expOffset) != QLatin1Char('e') && text.at(expOffset) != QLatin1Char('E'))) {
341 return newOffset;
342 }
343 ++expOffset;
344
345 if (expOffset < text.size() && (text.at(expOffset) == QLatin1Char('+') || text.at(expOffset) == QLatin1Char('-'))) {
346 ++expOffset;
347 }
348 bool foundExpDigit = false;
349 while (expOffset < text.size() && isDigit(text.at(expOffset))) {
350 ++expOffset;
351 foundExpDigit = true;
352 }
353
354 if (!foundExpDigit) {
355 return newOffset;
356 }
357 return expOffset;
358}
359
360MatchResult HlCChar::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
361{
362 if (text.size() < offset + 3) {
363 return offset;
364 }
365
366 if (text.at(offset) != QLatin1Char('\'') || text.at(offset + 1) == QLatin1Char('\'')) {
367 return offset;
368 }
369
370 auto newOffset = matchEscapedChar(text, offset + 1);
371 if (newOffset == offset + 1) {
372 if (text.at(newOffset) == QLatin1Char('\\')) {
373 return offset;
374 } else {
375 ++newOffset;
376 }
377 }
378 if (newOffset >= text.size()) {
379 return offset;
380 }
381
382 if (text.at(newOffset) == QLatin1Char('\'')) {
383 return newOffset + 1;
384 }
385
386 return offset;
387}
388
389HlCHex::HlCHex(DefinitionData &def, const HighlightingContextData::Rule::HlCHex &data)
390 : m_wordDelimiters(def.wordDelimiters)
391{
392 resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
393}
394
395MatchResult HlCHex::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
396{
397 if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
398 return offset;
399 }
400
401 if (text.size() < offset + 3) {
402 return offset;
403 }
404
405 if (text.at(offset) != QLatin1Char('0') || (text.at(offset + 1) != QLatin1Char('x') && text.at(offset + 1) != QLatin1Char('X'))) {
406 return offset;
407 }
408
409 if (!isHexChar(text.at(offset + 2))) {
410 return offset;
411 }
412
413 offset += 3;
414 while (offset < text.size() && isHexChar(text.at(offset))) {
415 ++offset;
416 }
417
418 // TODO Kate matches U/L suffix, QtC does not?
419
420 return offset;
421}
422
423HlCOct::HlCOct(DefinitionData &def, const HighlightingContextData::Rule::HlCOct &data)
424 : m_wordDelimiters(def.wordDelimiters)
425{
426 resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
427}
428
429MatchResult HlCOct::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
430{
431 if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
432 return offset;
433 }
434
435 if (text.size() < offset + 2) {
436 return offset;
437 }
438
439 if (text.at(offset) != QLatin1Char('0')) {
440 return offset;
441 }
442
443 if (!isOctalChar(text.at(offset + 1))) {
444 return offset;
445 }
446
447 offset += 2;
448 while (offset < text.size() && isOctalChar(text.at(offset))) {
449 ++offset;
450 }
451
452 return offset;
453}
454
455MatchResult HlCStringChar::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
456{
457 return matchEscapedChar(text, offset);
458}
459
460IncludeRules::IncludeRules(const HighlightingContextData::Rule::IncludeRules &data)
461 : m_contextName(data.contextName)
462 , m_includeAttribute(data.includeAttribute)
463{
464}
465
466MatchResult IncludeRules::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
467{
468 Q_UNUSED(text);
469 qCWarning(Log) << "Unresolved include rule";
470 return offset;
471}
472
473Int::Int(DefinitionData &def, const HighlightingContextData::Rule::Int &data)
474 : m_wordDelimiters(def.wordDelimiters)
475{
476 resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
477}
478
479MatchResult Int::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
480{
481 if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1))) {
482 return offset;
483 }
484
485 while (offset < text.size() && isDigit(text.at(offset))) {
486 ++offset;
487 }
488 return offset;
489}
490
491Rule::Ptr KeywordListRule::create(DefinitionData &def, const HighlightingContextData::Rule::Keyword &data, QStringView lookupContextName)
492{
493 /**
494 * get our keyword list, if not found => bail out
495 */
496 auto *keywordList = def.keywordList(data.name);
497 if (!keywordList) {
498 qCWarning(Log) << "Rule: Unknown keyword list" << data.name << "in context" << lookupContextName << "of definition" << def.name;
499 return Rule::Ptr();
500 }
501
502 if (keywordList->isEmpty()) {
503 return Rule::Ptr();
504 }
505
506 /**
507 * we might overwrite the case sensitivity
508 * then we need to init the list for lookup of that sensitivity setting
509 */
510 if (data.hasCaseSensitivityOverride) {
511 keywordList->initLookupForCaseSensitivity(data.caseSensitivityOverride);
512 }
513
514 return std::make_shared<KeywordListRule>(*keywordList, def, data);
515}
516
517KeywordListRule::KeywordListRule(const KeywordList &keywordList, DefinitionData &def, const HighlightingContextData::Rule::Keyword &data)
518 : m_wordDelimiters(def.wordDelimiters)
519 , m_keywordList(keywordList)
520 , m_caseSensitivity(data.hasCaseSensitivityOverride ? data.caseSensitivityOverride : keywordList.caseSensitivity())
521{
522 resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
523 m_hasSkipOffset = true;
524}
525
526MatchResult KeywordListRule::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
527{
528 auto newOffset = offset;
529 while (text.size() > newOffset && !m_wordDelimiters.contains(text.at(newOffset))) {
530 ++newOffset;
531 }
532 if (newOffset == offset) {
533 return offset;
534 }
535
536 if (m_keywordList.contains(text.sliced(offset, newOffset - offset), m_caseSensitivity)) {
537 return newOffset;
538 }
539
540 // we don't match, but we can skip until newOffset as we can't start a keyword in-between
541 return MatchResult(offset, newOffset);
542}
543
544LineContinue::LineContinue(const HighlightingContextData::Rule::LineContinue &data)
545 : m_char(data.char1)
546{
547}
548
549MatchResult LineContinue::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
550{
551 if (offset == text.size() - 1 && text.at(offset) == m_char) {
552 return offset + 1;
553 }
554 return offset;
555}
556
557RangeDetect::RangeDetect(const HighlightingContextData::Rule::RangeDetect &data)
558 : m_begin(data.begin)
559 , m_end(data.end)
560{
561}
562
563MatchResult RangeDetect::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
564{
565 if (text.size() - offset < 2) {
566 return offset;
567 }
568 if (text.at(offset) != m_begin) {
569 return offset;
570 }
571
572 auto newOffset = offset + 1;
573 while (newOffset < text.size()) {
574 if (text.at(newOffset) == m_end) {
575 return newOffset + 1;
576 }
577 ++newOffset;
578 }
579 return offset;
580}
581
582static QRegularExpression::PatternOptions makePattenOptions(const HighlightingContextData::Rule::RegExpr &data)
583{
586 // DontCaptureOption is removed by resolve() when necessary
588 // ensure Unicode support is enabled
590}
591
592static void resolveRegex(QRegularExpression &regexp, Context *context)
593{
594 bool enableCapture = context && context->hasDynamicRule();
595
596 // disable DontCaptureOption when reference a context with dynamic rule or
597 // with invalid regex because DontCaptureOption with back reference capture is an error
598 if (enableCapture || !regexp.isValid()) {
600 }
601
602 if (!regexp.isValid()) {
603 qCDebug(Log) << "Invalid regexp:" << regexp.pattern();
604 }
605}
606
607static MatchResult regexMatch(const QRegularExpression &regexp, QStringView text, int offset)
608{
609 /**
610 * match the pattern
611 */
613 if (result.capturedStart() == offset) {
614 /**
615 * we only need to compute the captured texts if we have real capture groups
616 * highlightings should only address %1..%.., see e.g. replaceCaptures
617 * DetectChar ignores %0, too
618 */
619 int lastCapturedIndex = result.lastCapturedIndex();
620 if (lastCapturedIndex > 0) {
621 QStringList captures;
622 captures.reserve(lastCapturedIndex);
623 // ignore the capturing group number 0
624 for (int i = 1; i <= lastCapturedIndex; ++i)
625 captures.push_back(result.captured(i));
626 return MatchResult(offset + result.capturedLength(), std::move(captures));
627 }
628
629 /**
630 * else: ignore the implicit 0 group we always capture, no need to allocate stuff for that
631 */
632 return MatchResult(offset + result.capturedLength());
633 }
634
635 /**
636 * no match
637 * we can always compute the skip offset as the highlighter will invalidate the cache for changed captures for dynamic rules!
638 */
639 return MatchResult(offset, result.capturedStart());
640}
641
642RegExpr::RegExpr(const HighlightingContextData::Rule::RegExpr &data)
643 : m_regexp(data.pattern, makePattenOptions(data))
644{
645 m_hasSkipOffset = true;
646}
647
648void RegExpr::resolve()
649{
650 m_isResolved = true;
651
652 resolveRegex(m_regexp, context().context());
653}
654
655MatchResult RegExpr::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
656{
657 if (Q_UNLIKELY(!m_isResolved)) {
658 const_cast<RegExpr *>(this)->resolve();
659 }
660
661 return regexMatch(m_regexp, text, offset);
662}
663
664DynamicRegExpr::DynamicRegExpr(const HighlightingContextData::Rule::RegExpr &data)
665 : m_pattern(data.pattern)
666 , m_patternOptions(makePattenOptions(data))
667{
668 m_dynamic = true;
669 m_hasSkipOffset = true;
670}
671
672void DynamicRegExpr::resolve()
673{
674 m_isResolved = true;
675
676 QRegularExpression regexp(m_pattern, m_patternOptions);
677 resolveRegex(regexp, context().context());
678 m_patternOptions = regexp.patternOptions();
679}
680
681MatchResult DynamicRegExpr::doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &dynamicRegexpCache) const
682{
683 if (Q_UNLIKELY(!m_isResolved)) {
684 const_cast<DynamicRegExpr *>(this)->resolve();
685 }
686
687 /**
688 * create new pattern with right instantiation
689 */
690 auto pattern = replaceCaptures(m_pattern, captures, true);
691 auto &regexp = dynamicRegexpCache.compileRegexp(std::move(pattern), m_patternOptions);
692 return regexMatch(regexp, text, offset);
693}
694
695StringDetect::StringDetect(const HighlightingContextData::Rule::StringDetect &data)
696 : m_string(data.string)
697 , m_caseSensitivity(data.caseSensitivity)
698{
699}
700
701MatchResult StringDetect::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
702{
703 return matchString(m_string, text, offset, m_caseSensitivity);
704}
705
706DynamicStringDetect::DynamicStringDetect(const HighlightingContextData::Rule::StringDetect &data)
707 : m_string(data.string)
708 , m_caseSensitivity(data.caseSensitivity)
709{
710 m_dynamic = true;
711}
712
713MatchResult DynamicStringDetect::doMatch(QStringView text, int offset, const QStringList &captures, DynamicRegexpCache &) const
714{
715 /**
716 * for dynamic case: create new pattern with right instantiation
717 */
718 const auto pattern = replaceCaptures(m_string, captures, false);
719 return matchString(pattern, text, offset, m_caseSensitivity);
720}
721
722WordDetect::WordDetect(DefinitionData &def, const HighlightingContextData::Rule::WordDetect &data)
723 : m_wordDelimiters(def.wordDelimiters)
724 , m_word(data.word)
725 , m_caseSensitivity(data.caseSensitivity)
726{
727 resolveAdditionalWordDelimiters(m_wordDelimiters, data.wordDelimiters);
728}
729
730MatchResult WordDetect::doMatch(QStringView text, int offset, const QStringList &, DynamicRegexpCache &) const
731{
732 if (text.size() - offset < m_word.size()) {
733 return offset;
734 }
735
736 /**
737 * detect delimiter characters on the inner and outer boundaries of the string
738 * NOTE: m_word isn't empty
739 */
740 if (offset > 0 && !m_wordDelimiters.contains(text.at(offset - 1)) && !m_wordDelimiters.contains(text.at(offset))) {
741 return offset;
742 }
743
744 if (text.sliced(offset, m_word.size()).compare(m_word, m_caseSensitivity) != 0) {
745 return offset;
746 }
747
748 if (text.size() == offset + m_word.size() || m_wordDelimiters.contains(text.at(offset + m_word.size()))
749 || m_wordDelimiters.contains(text.at(offset + m_word.size() - 1))) {
750 return offset + m_word.size();
751 }
752
753 return offset;
754}
Represents a begin or end of a folding region.
@ Begin
Indicates the start of a FoldingRegion.
@ End
Indicates the end of a FoldingRegion.
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
Syntax highlighting engine for Kate syntax definitions.
bool isLetter(char32_t ucs4)
bool isLetterOrNumber(char32_t ucs4)
bool isSpace(char32_t ucs4)
char16_t & unicode()
const_reference at(qsizetype i) const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
QString escape(QStringView str)
bool isValid() const const
QRegularExpressionMatch matchView(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QString pattern() const const
PatternOptions patternOptions() const const
void setPatternOptions(PatternOptions options)
bool isEmpty() const const
QString number(double n, char format, int precision)
QChar at(qsizetype n) const const
int compare(QChar ch) const const
qsizetype size() const const
QStringView sliced(qsizetype pos) const const
CaseSensitivity
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:03:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.