KSyntaxHighlighting

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

KDE's Doxygen guidelines are available online.