KTextEditor

kateregexpsearch.cpp
1/*
2 SPDX-FileCopyrightText: 2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
3 SPDX-FileCopyrightText: 2007 Sebastian Pipping <webmaster@hartwork.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8// BEGIN includes
9#include "kateregexpsearch.h"
10
11#include "katepartdebug.h" // for LOG_KTE
12
13#include <ktexteditor/document.h>
14// END includes
15
16// Turn debug messages on/off here
17// #define FAST_DEBUG_ENABLE
18
19#ifdef FAST_DEBUG_ENABLE
20#define FAST_DEBUG(x) qCDebug(LOG_KTE) << x
21#else
22#define FAST_DEBUG(x)
23#endif
24
25class KateRegExpSearch::ReplacementStream
26{
27public:
28 struct counter {
29 counter(int value, int minWidth)
30 : value(value)
31 , minWidth(minWidth)
32 {
33 }
34
35 const int value;
36 const int minWidth;
37 };
38
39 struct cap {
40 cap(int n)
41 : n(n)
42 {
43 }
44
45 const int n;
46 };
47
48 enum CaseConversion {
49 upperCase, ///< \U ... uppercase from now on
50 upperCaseFirst, ///< \u ... uppercase the first letter
51 lowerCase, ///< \L ... lowercase from now on
52 lowerCaseFirst, ///< \l ... lowercase the first letter
53 keepCase ///< \E ... back to original case
54 };
55
56public:
57 ReplacementStream(const QStringList &capturedTexts);
58
59 QString str() const
60 {
61 return m_str;
62 }
63
64 ReplacementStream &operator<<(const QString &);
65 ReplacementStream &operator<<(const counter &);
66 ReplacementStream &operator<<(const cap &);
67 ReplacementStream &operator<<(CaseConversion);
68
69private:
70 const QStringList m_capturedTexts;
71 CaseConversion m_caseConversion;
72 QString m_str;
73};
74
75KateRegExpSearch::ReplacementStream::ReplacementStream(const QStringList &capturedTexts)
76 : m_capturedTexts(capturedTexts)
77 , m_caseConversion(keepCase)
78{
79}
80
81KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(const QString &str)
82{
83 switch (m_caseConversion) {
84 case upperCase:
85 // Copy as uppercase
86 m_str.append(str.toUpper());
87 break;
88
89 case upperCaseFirst:
90 if (str.length() > 0) {
91 m_str.append(str.at(0).toUpper());
92 m_str.append(QStringView(str).mid(1));
93 m_caseConversion = keepCase;
94 }
95 break;
96
97 case lowerCase:
98 // Copy as lowercase
99 m_str.append(str.toLower());
100 break;
101
102 case lowerCaseFirst:
103 if (str.length() > 0) {
104 m_str.append(str.at(0).toLower());
105 m_str.append(QStringView(str).mid(1));
106 m_caseConversion = keepCase;
107 }
108 break;
109
110 case keepCase: // FALLTHROUGH
111 default:
112 // Copy unmodified
113 m_str.append(str);
114 break;
115 }
116
117 return *this;
118}
119
120KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(const counter &c)
121{
122 // Zero padded counter value
123 m_str.append(QStringLiteral("%1").arg(c.value, c.minWidth, 10, QLatin1Char('0')));
124
125 return *this;
126}
127
128KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(const cap &cap)
129{
130 if (0 <= cap.n && cap.n < m_capturedTexts.size()) {
131 (*this) << m_capturedTexts[cap.n];
132 } else {
133 // Insert just the number to be consistent with QRegExp ("\c" becomes "c")
134 m_str.append(QString::number(cap.n));
135 }
136
137 return *this;
138}
139
140KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(CaseConversion caseConversion)
141{
142 m_caseConversion = caseConversion;
143
144 return *this;
145}
146
147// BEGIN d'tor, c'tor
148//
149// KateSearch Constructor
150//
151KateRegExpSearch::KateRegExpSearch(const KTextEditor::Document *document)
152 : m_document(document)
153{
154}
155
156// helper structs for captures re-construction
157struct TwoViewCursor {
158 int index;
159 int line;
160 int col;
161};
162
163struct IndexPair {
164 int openIndex;
165 int closeIndex;
166};
167
170{
171 // Save regexes to avoid reconstructing regexes all the time
172 static QRegularExpression preRegex;
173 static QRegularExpression repairedRegex;
174
175 // Returned if no matches are found
177
178 // Note that some methods in vimode (e.g. Searcher::findPatternWorker) rely on the
179 // this method returning here if 'pattern' is empty.
180 if (pattern.isEmpty() || inputRange.isEmpty() || !inputRange.isValid()) {
181 return noResult;
182 }
183
184 // Always enable Unicode support
186
187 if (preRegex.pattern() != pattern || preRegex.patternOptions() != options) {
188 preRegex = QRegularExpression(pattern, options);
189 }
190
191 // If repairPattern() is called on an invalid regex pattern it may cause asserts
192 // in QString (e.g. if the pattern is just '\\', pattern.size() is 1, and repaierPattern
193 // expects at least one character after a '\')
194 if (!preRegex.isValid()) {
195 return noResult;
196 }
197
198 // detect pattern type (single- or mutli-line)
199 bool stillMultiLine;
200 const QString repairedPattern = repairPattern(pattern, stillMultiLine);
201
202 // Enable multiline mode, so that the ^ and $ metacharacters in the pattern
203 // are allowed to match, respectively, immediately after and immediately
204 // before any newline in the subject string, as well as at the very beginning
205 // and at the very end of the subject string (see QRegularExpression docs).
206 //
207 // Whole lines are passed to QRegularExpression, so that e.g. if the inputRange
208 // ends in the middle of a line, then a '$' won't match at that position. And
209 // matches that are out of the inputRange are rejected.
210 if (stillMultiLine) {
212 }
213
214 // check if anything changed at all
215 if (repairedRegex.pattern() != repairedPattern || repairedRegex.patternOptions() != options) {
216 repairedRegex.setPattern(repairedPattern);
217 repairedRegex.setPatternOptions(options);
218 }
219 if (!repairedRegex.isValid()) {
220 return noResult;
221 }
222
223 const int rangeStartLine = inputRange.start().line();
224 const int rangeStartCol = inputRange.start().column();
225
226 const int rangeEndLine = inputRange.end().line();
227 const int rangeEndCol = inputRange.end().column();
228
229 if (stillMultiLine) {
230 const int rangeLineCount = rangeEndLine - rangeStartLine + 1;
231 FAST_DEBUG("regular expression search (lines " << rangeStartLine << ".." << rangeEndLine << ")");
232
233 const int docLineCount = m_document->lines();
234 // nothing to do...
235 if (rangeStartLine >= docLineCount) {
236 return noResult;
237 }
238
239 QList<int> lineLens(rangeLineCount);
240 int maxMatchOffset = 0;
241
242 // all lines in the input range
243 QString wholeRange;
244 for (int i = 0; i < rangeLineCount; ++i) {
245 const int docLineIndex = rangeStartLine + i;
246 if (docLineIndex < 0 || docLineCount <= docLineIndex) { // invalid index
247 return noResult;
248 }
249
250 const QString textLine = m_document->line(docLineIndex);
251 lineLens[i] = textLine.length();
252 wholeRange.append(textLine);
253
254 // This check is needed as some parts in vimode rely on this behaviour.
255 // We add an '\n' as a delimiter between lines in the range; but never after the
256 // last line as that would add an '\n' that isn't there in the original text,
257 // and can skew search results or hit an assert when accessing lineLens later
258 // in the code.
259 if (i != (rangeLineCount - 1)) {
260 wholeRange.append(QLatin1Char('\n'));
261 }
262
263 // lineLens.at(i) + 1, because '\n' was added
264 maxMatchOffset += (i == rangeEndLine) ? rangeEndCol : lineLens.at(i) + 1;
265
266 FAST_DEBUG(" line" << i << "has length" << lineLens.at(i));
267 }
268
269 FAST_DEBUG("Max. match offset" << maxMatchOffset);
270
272 bool found = false;
273 QRegularExpressionMatchIterator iter = repairedRegex.globalMatch(wholeRange, rangeStartCol);
274
275 if (backwards) {
276 while (iter.hasNext()) {
277 QRegularExpressionMatch curMatch = iter.next();
278 if (curMatch.capturedEnd() <= maxMatchOffset) {
279 match.swap(curMatch);
280 found = true;
281 }
282 }
283 } else { /* forwards */
285 if (iter.hasNext()) {
286 curMatch = iter.next();
287 }
288 if (curMatch.capturedEnd() <= maxMatchOffset) {
289 match.swap(curMatch);
290 found = true;
291 }
292 }
293
294 if (!found) {
295 // no match
296 FAST_DEBUG("not found");
297 return noResult;
298 }
299
300 // Capture groups: save opening and closing indices and build a map,
301 // the correct values will be written into it later
302 QMap<int, TwoViewCursor *> indicesToCursors;
303 const int numCaptures = repairedRegex.captureCount();
304 QList<IndexPair> indexPairs(numCaptures + 1);
305 for (int c = 0; c <= numCaptures; ++c) {
306 const int openIndex = match.capturedStart(c);
307 IndexPair &pair = indexPairs[c];
308 if (openIndex == -1) {
309 // An invalid index indicates an empty capture group
310 pair.openIndex = -1;
311 pair.closeIndex = -1;
312 FAST_DEBUG("capture []");
313 } else {
314 const int closeIndex = match.capturedEnd(c);
315 pair.openIndex = openIndex;
316 pair.closeIndex = closeIndex;
317 FAST_DEBUG("capture [" << pair.openIndex << ".." << pair.closeIndex << "]");
318
319 // each key no more than once
320 if (!indicesToCursors.contains(openIndex)) {
321 TwoViewCursor *twoViewCursor = new TwoViewCursor;
322 twoViewCursor->index = openIndex;
323 indicesToCursors.insert(openIndex, twoViewCursor);
324 FAST_DEBUG(" capture group start index added: " << openIndex);
325 }
326 if (!indicesToCursors.contains(closeIndex)) {
327 TwoViewCursor *twoViewCursor = new TwoViewCursor;
328 twoViewCursor->index = closeIndex;
329 indicesToCursors.insert(closeIndex, twoViewCursor);
330 FAST_DEBUG(" capture group end index added: " << closeIndex);
331 }
332 }
333 }
334
335 // find out where they belong
336 int curRelLine = 0;
337 int curRelCol = 0;
338 int curRelIndex = 0;
339
340 for (TwoViewCursor *twoViewCursor : std::as_const(indicesToCursors)) {
341 // forward to index, save line/col
342 const int index = twoViewCursor->index;
343 FAST_DEBUG("resolving position" << index);
344
345 while (curRelIndex <= index) {
346 FAST_DEBUG("walk pos (" << curRelLine << "," << curRelCol << ") = " << curRelIndex << "relative, steps more to go" << index - curRelIndex);
347
348 const int curRelLineLen = lineLens.at(curRelLine);
349 const int curLineRemainder = curRelLineLen - curRelCol;
350 const int lineFeedIndex = curRelIndex + curLineRemainder;
351 if (index <= lineFeedIndex) {
352 if (index == lineFeedIndex) {
353 // on this line _at_ line feed
354 FAST_DEBUG(" on line feed");
355 const int absLine = curRelLine + rangeStartLine;
356 twoViewCursor->line = absLine;
357 twoViewCursor->col = curRelLineLen;
358
359 // advance to next line
360 const int advance = (index - curRelIndex) + 1;
361 ++curRelLine;
362 curRelCol = 0;
363 curRelIndex += advance;
364 } else { // index < lineFeedIndex
365 // on this line _before_ line feed
366 FAST_DEBUG(" before line feed");
367 const int diff = (index - curRelIndex);
368 const int absLine = curRelLine + rangeStartLine;
369 const int absCol = curRelCol + diff;
370 twoViewCursor->line = absLine;
371 twoViewCursor->col = absCol;
372
373 // advance on same line
374 const int advance = diff + 1;
375 curRelCol += advance;
376 curRelIndex += advance;
377 }
378 FAST_DEBUG("position(" << twoViewCursor->line << "," << twoViewCursor->col << ")");
379 } else { // if (index > lineFeedIndex)
380 // not on this line
381 // advance to next line
382 FAST_DEBUG(" not on this line");
383 ++curRelLine;
384 curRelCol = 0;
385 const int advance = curLineRemainder + 1;
386 curRelIndex += advance;
387 }
388 }
389 }
390
391 // build result array
393 for (int y = 0; y <= numCaptures; y++) {
394 IndexPair &pair = indexPairs[y];
395 if (!(pair.openIndex == -1 || pair.closeIndex == -1)) {
396 const TwoViewCursor *const openCursors = indicesToCursors.value(pair.openIndex);
397 const TwoViewCursor *const closeCursors = indicesToCursors.value(pair.closeIndex);
398 const int startLine = openCursors->line;
399 const int startCol = openCursors->col;
400 const int endLine = closeCursors->line;
401 const int endCol = closeCursors->col;
402 FAST_DEBUG("range " << y << ": (" << startLine << ", " << startCol << ")..(" << endLine << ", " << endCol << ")");
403 result[y] = KTextEditor::Range(startLine, startCol, endLine, endCol);
404 }
405 }
406
407 // free structs allocated for indicesToCursors
408 qDeleteAll(indicesToCursors);
409
410 return result;
411 } else {
412 // single-line regex search (forwards and backwards)
413 const int rangeStartCol = inputRange.start().column();
414 const uint rangeEndCol = inputRange.end().column();
415
416 const int rangeStartLine = inputRange.start().line();
417 const int rangeEndLine = inputRange.end().line();
418
419 const int forInit = backwards ? rangeEndLine : rangeStartLine;
420
421 const int forInc = backwards ? -1 : +1;
422
423 FAST_DEBUG("single line " << (backwards ? rangeEndLine : rangeStartLine) << ".." << (backwards ? rangeStartLine : rangeEndLine));
424
425 for (int j = forInit; (rangeStartLine <= j) && (j <= rangeEndLine); j += forInc) {
426 if (j < 0 || m_document->lines() <= j) {
427 FAST_DEBUG("searchText | line " << j << ": no");
428 return noResult;
429 }
430
431 const QString textLine = m_document->line(j);
432
433 const int offset = (j == rangeStartLine) ? rangeStartCol : 0;
434 const int endLineMaxOffset = (j == rangeEndLine) ? rangeEndCol : textLine.length();
435
436 bool found = false;
437
439
440 if (backwards) {
441 QRegularExpressionMatchIterator iter = repairedRegex.globalMatch(textLine, offset);
442 while (iter.hasNext()) {
443 QRegularExpressionMatch curMatch = iter.next();
444 if (curMatch.capturedEnd() <= endLineMaxOffset) {
445 match.swap(curMatch);
446 found = true;
447 }
448 }
449 } else {
450 match = repairedRegex.match(textLine, offset);
451 if (match.hasMatch() && match.capturedEnd() <= endLineMaxOffset) {
452 found = true;
453 }
454 }
455
456 if (found) {
457 FAST_DEBUG("line " << j << ": yes");
458
459 // build result array
460 const int numCaptures = repairedRegex.captureCount();
461 QList<KTextEditor::Range> result(numCaptures + 1);
462 result[0] = KTextEditor::Range(j, match.capturedStart(), j, match.capturedEnd());
463
464 FAST_DEBUG("result range " << 0 << ": (" << j << ", " << match.capturedStart() << ")..(" << j << ", " << match.capturedEnd() << ")");
465
466 for (int y = 1; y <= numCaptures; ++y) {
467 const int openIndex = match.capturedStart(y);
468
469 if (openIndex == -1) {
470 result[y] = KTextEditor::Range::invalid();
471
472 FAST_DEBUG("capture []");
473 } else {
474 const int closeIndex = match.capturedEnd(y);
475
476 FAST_DEBUG("result range " << y << ": (" << j << ", " << openIndex << ")..(" << j << ", " << closeIndex << ")");
477
478 result[y] = KTextEditor::Range(j, openIndex, j, closeIndex);
479 }
480 }
481 return result;
482 } else {
483 FAST_DEBUG("searchText | line " << j << ": no");
484 }
485 }
486 }
487 return noResult;
488}
489
491{
492 return buildReplacement(text, QStringList(), 0, false);
493}
494
495/*static*/ QString KateRegExpSearch::buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter)
496{
497 return buildReplacement(text, capturedTexts, replacementCounter, true);
498}
499
500/*static*/ QString KateRegExpSearch::buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter, bool replacementGoodies)
501{
502 // get input
503 const int inputLen = text.length();
504 int input = 0; // walker index
505
506 // prepare output
507 ReplacementStream out(capturedTexts);
508
509 while (input < inputLen) {
510 switch (text[input].unicode()) {
511 case L'\n':
512 out << text[input];
513 input++;
514 break;
515
516 case L'\\':
517 if (input + 1 >= inputLen) {
518 // copy backslash
519 out << text[input];
520 input++;
521 break;
522 }
523
524 switch (text[input + 1].unicode()) {
525 case L'0': // "\0000".."\0377"
526 if (input + 4 >= inputLen) {
527 out << ReplacementStream::cap(0);
528 input += 2;
529 } else {
530 bool stripAndSkip = false;
531 const ushort text_2 = text[input + 2].unicode();
532 if ((text_2 >= L'0') && (text_2 <= L'3')) {
533 const ushort text_3 = text[input + 3].unicode();
534 if ((text_3 >= L'0') && (text_3 <= L'7')) {
535 const ushort text_4 = text[input + 4].unicode();
536 if ((text_4 >= L'0') && (text_4 <= L'7')) {
537 int digits[3];
538 for (int i = 0; i < 3; i++) {
539 digits[i] = 7 - (L'7' - text[input + 2 + i].unicode());
540 }
541 const int ch = 64 * digits[0] + 8 * digits[1] + digits[2];
542 out << QChar(ch);
543 input += 5;
544 } else {
545 stripAndSkip = true;
546 }
547 } else {
548 stripAndSkip = true;
549 }
550 } else {
551 stripAndSkip = true;
552 }
553
554 if (stripAndSkip) {
555 out << ReplacementStream::cap(0);
556 input += 2;
557 }
558 }
559 break;
560
561 // single letter captures \x
562 case L'1':
563 case L'2':
564 case L'3':
565 case L'4':
566 case L'5':
567 case L'6':
568 case L'7':
569 case L'8':
570 case L'9':
571 out << ReplacementStream::cap(9 - (L'9' - text[input + 1].unicode()));
572 input += 2;
573 break;
574
575 // multi letter captures \{xxxx}
576 case L'{': {
577 // allow {1212124}.... captures, see bug 365124 + testReplaceManyCapturesBug365124
578 int capture = 0;
579 int captureSize = 2;
580 while ((input + captureSize) < inputLen) {
581 const ushort nextDigit = text[input + captureSize].unicode();
582 if ((nextDigit >= L'0') && (nextDigit <= L'9')) {
583 capture = (10 * capture) + (9 - (L'9' - nextDigit));
584 ++captureSize;
585 continue;
586 }
587 if (nextDigit == L'}') {
588 ++captureSize;
589 break;
590 }
591 break;
592 }
593 out << ReplacementStream::cap(capture);
594 input += captureSize;
595 break;
596 }
597
598 case L'E': // FALLTHROUGH
599 case L'L': // FALLTHROUGH
600 case L'l': // FALLTHROUGH
601 case L'U': // FALLTHROUGH
602 case L'u':
603 if (!replacementGoodies) {
604 // strip backslash ("\?" -> "?")
605 out << text[input + 1];
606 } else {
607 // handle case switcher
608 switch (text[input + 1].unicode()) {
609 case L'L':
610 out << ReplacementStream::lowerCase;
611 break;
612
613 case L'l':
614 out << ReplacementStream::lowerCaseFirst;
615 break;
616
617 case L'U':
618 out << ReplacementStream::upperCase;
619 break;
620
621 case L'u':
622 out << ReplacementStream::upperCaseFirst;
623 break;
624
625 case L'E': // FALLTHROUGH
626 default:
627 out << ReplacementStream::keepCase;
628 }
629 }
630 input += 2;
631 break;
632
633 case L'#':
634 if (!replacementGoodies) {
635 // strip backslash ("\?" -> "?")
636 out << text[input + 1];
637 input += 2;
638 } else {
639 // handle replacement counter
640 // eat and count all following hash marks
641 // each hash stands for a leading zero: \### will produces 001, 002, ...
642 int minWidth = 1;
643 while ((input + minWidth + 1 < inputLen) && (text[input + minWidth + 1].unicode() == L'#')) {
644 minWidth++;
645 }
646 out << ReplacementStream::counter(replacementCounter, minWidth);
647 input += 1 + minWidth;
648 }
649 break;
650
651 case L'a':
652 out << QChar(0x07);
653 input += 2;
654 break;
655
656 case L'f':
657 out << QChar(0x0c);
658 input += 2;
659 break;
660
661 case L'n':
662 out << QChar(0x0a);
663 input += 2;
664 break;
665
666 case L'r':
667 out << QChar(0x0d);
668 input += 2;
669 break;
670
671 case L't':
672 out << QChar(0x09);
673 input += 2;
674 break;
675
676 case L'v':
677 out << QChar(0x0b);
678 input += 2;
679 break;
680
681 case L'x': // "\x0000".."\xffff"
682 if (input + 5 >= inputLen) {
683 // strip backslash ("\x" -> "x")
684 out << text[input + 1];
685 input += 2;
686 } else {
687 bool stripAndSkip = false;
688 const ushort text_2 = text[input + 2].unicode();
689 if (((text_2 >= L'0') && (text_2 <= L'9')) || ((text_2 >= L'a') && (text_2 <= L'f')) || ((text_2 >= L'A') && (text_2 <= L'F'))) {
690 const ushort text_3 = text[input + 3].unicode();
691 if (((text_3 >= L'0') && (text_3 <= L'9')) || ((text_3 >= L'a') && (text_3 <= L'f')) || ((text_3 >= L'A') && (text_3 <= L'F'))) {
692 const ushort text_4 = text[input + 4].unicode();
693 if (((text_4 >= L'0') && (text_4 <= L'9')) || ((text_4 >= L'a') && (text_4 <= L'f')) || ((text_4 >= L'A') && (text_4 <= L'F'))) {
694 const ushort text_5 = text[input + 5].unicode();
695 if (((text_5 >= L'0') && (text_5 <= L'9')) || ((text_5 >= L'a') && (text_5 <= L'f'))
696 || ((text_5 >= L'A') && (text_5 <= L'F'))) {
697 int digits[4];
698 for (int i = 0; i < 4; i++) {
699 const ushort cur = text[input + 2 + i].unicode();
700 if ((cur >= L'0') && (cur <= L'9')) {
701 digits[i] = 9 - (L'9' - cur);
702 } else if ((cur >= L'a') && (cur <= L'f')) {
703 digits[i] = 15 - (L'f' - cur);
704 } else { // if ((cur >= L'A') && (cur <= L'F')))
705 digits[i] = 15 - (L'F' - cur);
706 }
707 }
708
709 const int ch = 4096 * digits[0] + 256 * digits[1] + 16 * digits[2] + digits[3];
710 out << QChar(ch);
711 input += 6;
712 } else {
713 stripAndSkip = true;
714 }
715 } else {
716 stripAndSkip = true;
717 }
718 } else {
719 stripAndSkip = true;
720 }
721 }
722
723 if (stripAndSkip) {
724 // strip backslash ("\x" -> "x")
725 out << text[input + 1];
726 input += 2;
727 }
728 }
729 break;
730
731 default:
732 // strip backslash ("\?" -> "?")
733 out << text[input + 1];
734 input += 2;
735 }
736 break;
737
738 default:
739 out << text[input];
740 input++;
741 }
742 }
743
744 return out.str();
745}
746
747QString KateRegExpSearch::repairPattern(const QString &pattern, bool &stillMultiLine)
748{
749 // '\s' can make a pattern multi-line, it's replaced here with '[ \t]';
750 // besides \s, the following characters can make a pattern multi-line:
751 // \n, \x000A (Line Feed), \x????-\x????, \0012, \0???-\0???
752 // a multi-line pattern must not pass as single-line, the other
753 // way around will just result in slower searches and is therefore
754 // not as critical
755
756 const int inputLen = pattern.length();
757 const QStringView patternView{pattern};
758
759 // prepare output
760 QString output;
761 output.reserve(2 * inputLen + 1); // twice should be enough for the average case
762
763 // parser state
764 bool insideClass = false;
765
766 stillMultiLine = false;
767 int input = 0;
768 while (input < inputLen) {
769 if (insideClass) {
770 // wait for closing, unescaped ']'
771 switch (pattern[input].unicode()) {
772 case L'\\':
773 switch (pattern[input + 1].unicode()) {
774 case L'x':
775 if (input + 5 < inputLen) {
776 // copy "\x????" unmodified
777 output.append(patternView.mid(input, 6));
778 input += 6;
779 } else {
780 // copy "\x" unmodified
781 output.append(patternView.mid(input, 2));
782 input += 2;
783 }
784 stillMultiLine = true;
785 break;
786
787 case L'0':
788 if (input + 4 < inputLen) {
789 // copy "\0???" unmodified
790 output.append(patternView.mid(input, 5));
791 input += 5;
792 } else {
793 // copy "\0" unmodified
794 output.append(patternView.mid(input, 2));
795 input += 2;
796 }
797 stillMultiLine = true;
798 break;
799
800 case L's':
801 // replace "\s" with "[ \t]"
802 output.append(QLatin1String(" \\t"));
803 input += 2;
804 break;
805
806 case L'n':
807 stillMultiLine = true;
808 // FALLTROUGH
809 Q_FALLTHROUGH();
810
811 default:
812 // copy "\?" unmodified
813 output.append(patternView.mid(input, 2));
814 input += 2;
815 }
816 break;
817
818 case L']':
819 // copy "]" unmodified
820 insideClass = false;
821 output.append(pattern[input]);
822 ++input;
823 break;
824
825 default:
826 // copy "?" unmodified
827 output.append(pattern[input]);
828 ++input;
829 }
830 } else {
831 switch (pattern[input].unicode()) {
832 case L'\\':
833 switch (pattern[input + 1].unicode()) {
834 case L'x':
835 if (input + 5 < inputLen) {
836 // copy "\x????" unmodified
837 output.append(patternView.mid(input, 6));
838 input += 6;
839 } else {
840 // copy "\x" unmodified
841 output.append(patternView.mid(input, 2));
842 input += 2;
843 }
844 stillMultiLine = true;
845 break;
846
847 case L'0':
848 if (input + 4 < inputLen) {
849 // copy "\0???" unmodified
850 output.append(patternView.mid(input, 5));
851 input += 5;
852 } else {
853 // copy "\0" unmodified
854 output.append(patternView.mid(input, 2));
855 input += 2;
856 }
857 stillMultiLine = true;
858 break;
859
860 case L's':
861 // replace "\s" with "[ \t]"
862 output.append(QLatin1String("[ \\t]"));
863 input += 2;
864 break;
865
866 case L'n':
867 stillMultiLine = true;
868 // FALLTROUGH
869 Q_FALLTHROUGH();
870 default:
871 // copy "\?" unmodified
872 output.append(patternView.mid(input, 2));
873 input += 2;
874 }
875 break;
876
877 case L'[':
878 // copy "[" unmodified
879 insideClass = true;
880 output.append(pattern[input]);
881 ++input;
882 break;
883
884 default:
885 // copy "?" unmodified
886 output.append(pattern[input]);
887 ++input;
888 }
889 }
890 }
891 return output;
892}
893
894// Kill our helpers again
895#ifdef FAST_DEBUG_ENABLE
896#undef FAST_DEBUG_ENABLE
897#endif
898#undef FAST_DEBUG
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
A KParts derived class representing a text document.
Definition document.h:284
virtual QString line(int line) const =0
Get a single text line.
virtual int lines() const =0
Get the count of lines of the document.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
QList< KTextEditor::Range > search(const QString &pattern, KTextEditor::Range inputRange, bool backwards=false, QRegularExpression::PatternOptions options=QRegularExpression::NoPatternOption)
Search for the regular expression pattern inside the range inputRange.
static QString escapePlaintext(const QString &text)
Returns a modified version of text where escape sequences are resolved, e.g.
static QString buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter)
Returns a modified version of text where.
Q_SCRIPTABLE Q_NOREPLY void capture(double settleTime=0.0)
char32_t toLower(char32_t ucs4)
char32_t toUpper(char32_t ucs4)
const_reference at(qsizetype i) const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
T value(const Key &key, const T &defaultValue) const const
QRegularExpressionMatchIterator globalMatch(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
int captureCount() const const
bool isValid() const const
QString pattern() const const
PatternOptions patternOptions() const const
void setPattern(const QString &pattern)
void setPatternOptions(PatternOptions options)
qsizetype capturedEnd(QStringView name) const const
QRegularExpressionMatch next()
QString & append(QChar ch)
const QChar at(qsizetype position) const const
bool isEmpty() const const
qsizetype length() const const
QString number(double n, char format, int precision)
void reserve(qsizetype size)
QString toLower() const const
QString toUpper() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:56:21 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.