9#include "kateregexpsearch.h"
11#include "katepartdebug.h"
13#include <ktexteditor/document.h>
19#ifdef FAST_DEBUG_ENABLE
20#define FAST_DEBUG(x) qCDebug(LOG_KTE) << x
25class KateRegExpSearch::ReplacementStream
29 counter(
int value,
int minWidth)
57 ReplacementStream(
const QStringList &capturedTexts);
64 ReplacementStream &operator<<(
const QString &);
65 ReplacementStream &operator<<(
const counter &);
66 ReplacementStream &operator<<(
const cap &);
67 ReplacementStream &operator<<(CaseConversion);
71 CaseConversion m_caseConversion;
75KateRegExpSearch::ReplacementStream::ReplacementStream(
const QStringList &capturedTexts)
76 : m_capturedTexts(capturedTexts)
77 , m_caseConversion(keepCase)
81KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(
const QString &str)
83 switch (m_caseConversion) {
93 m_caseConversion = keepCase;
106 m_caseConversion = keepCase;
120KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(
const counter &c)
123 m_str.append(QStringLiteral(
"%1").arg(c.value, c.minWidth, 10,
QLatin1Char(
'0')));
128KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(
const cap &cap)
130 if (0 <= cap.n && cap.n < m_capturedTexts.size()) {
131 (*this) << m_capturedTexts[cap.n];
140KateRegExpSearch::ReplacementStream &KateRegExpSearch::ReplacementStream::operator<<(CaseConversion caseConversion)
142 m_caseConversion = caseConversion;
152 : m_document(document)
157struct TwoViewCursor {
200 const QString repairedPattern = repairPattern(pattern, stillMultiLine);
210 if (stillMultiLine) {
219 if (!repairedRegex.
isValid()) {
223 const int rangeStartLine = inputRange.
start().
line();
224 const int rangeStartCol = inputRange.
start().
column();
226 const int rangeEndLine = inputRange.
end().
line();
227 const int rangeEndCol = inputRange.
end().
column();
229 if (stillMultiLine) {
230 const int rangeLineCount = rangeEndLine - rangeStartLine + 1;
231 FAST_DEBUG(
"regular expression search (lines " << rangeStartLine <<
".." << rangeEndLine <<
")");
233 const int docLineCount = m_document->
lines();
235 if (rangeStartLine >= docLineCount) {
240 int maxMatchOffset = 0;
244 for (
int i = 0; i < rangeLineCount; ++i) {
245 const int docLineIndex = rangeStartLine + i;
246 if (docLineIndex < 0 || docLineCount <= docLineIndex) {
250 const QString textLine = m_document->
line(docLineIndex);
251 lineLens[i] = textLine.
length();
252 wholeRange.
append(textLine);
259 if (i != (rangeLineCount - 1)) {
264 maxMatchOffset += (i == rangeEndLine) ? rangeEndCol : lineLens.
at(i) + 1;
266 FAST_DEBUG(
" line" << i <<
"has length" << lineLens.
at(i));
269 FAST_DEBUG(
"Max. match offset" << maxMatchOffset);
279 match.swap(curMatch);
286 curMatch = iter.
next();
289 match.swap(curMatch);
296 FAST_DEBUG(
"not found");
305 for (
int c = 0; c <= numCaptures; ++c) {
306 const int openIndex = match.capturedStart(c);
307 IndexPair &pair = indexPairs[c];
308 if (openIndex == -1) {
311 pair.closeIndex = -1;
312 FAST_DEBUG(
"capture []");
314 const int closeIndex = match.capturedEnd(c);
315 pair.openIndex = openIndex;
316 pair.closeIndex = closeIndex;
317 FAST_DEBUG(
"capture [" << pair.openIndex <<
".." << pair.closeIndex <<
"]");
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);
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);
340 for (TwoViewCursor *twoViewCursor : std::as_const(indicesToCursors)) {
342 const int index = twoViewCursor->index;
343 FAST_DEBUG(
"resolving position" << index);
345 while (curRelIndex <= index) {
346 FAST_DEBUG(
"walk pos (" << curRelLine <<
"," << curRelCol <<
") = " << curRelIndex <<
"relative, steps more to go" << index - curRelIndex);
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) {
354 FAST_DEBUG(
" on line feed");
355 const int absLine = curRelLine + rangeStartLine;
356 twoViewCursor->line = absLine;
357 twoViewCursor->col = curRelLineLen;
360 const int advance = (index - curRelIndex) + 1;
363 curRelIndex += advance;
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;
374 const int advance = diff + 1;
375 curRelCol += advance;
376 curRelIndex += advance;
378 FAST_DEBUG(
"position(" << twoViewCursor->line <<
"," << twoViewCursor->col <<
")");
382 FAST_DEBUG(
" not on this line");
385 const int advance = curLineRemainder + 1;
386 curRelIndex += advance;
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 <<
")");
408 qDeleteAll(indicesToCursors);
413 const int rangeStartCol = inputRange.
start().
column();
414 const uint rangeEndCol = inputRange.
end().
column();
416 const int rangeStartLine = inputRange.
start().
line();
417 const int rangeEndLine = inputRange.
end().
line();
419 const int forInit = backwards ? rangeEndLine : rangeStartLine;
421 const int forInc = backwards ? -1 : +1;
423 FAST_DEBUG(
"single line " << (backwards ? rangeEndLine : rangeStartLine) <<
".." << (backwards ? rangeStartLine : rangeEndLine));
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");
433 const int offset = (j == rangeStartLine) ? rangeStartCol : 0;
434 const int endLineMaxOffset = (j == rangeEndLine) ? rangeEndCol : textLine.
length();
445 match.swap(curMatch);
450 match = repairedRegex.
match(textLine, offset);
451 if (match.hasMatch() && match.capturedEnd() <= endLineMaxOffset) {
457 FAST_DEBUG(
"line " << j <<
": yes");
464 FAST_DEBUG(
"result range " << 0 <<
": (" << j <<
", " << match.capturedStart() <<
")..(" << j <<
", " << match.capturedEnd() <<
")");
466 for (
int y = 1; y <= numCaptures; ++y) {
467 const int openIndex = match.capturedStart(y);
469 if (openIndex == -1) {
472 FAST_DEBUG(
"capture []");
474 const int closeIndex = match.capturedEnd(y);
476 FAST_DEBUG(
"result range " << y <<
": (" << j <<
", " << openIndex <<
")..(" << j <<
", " << closeIndex <<
")");
483 FAST_DEBUG(
"searchText | line " << j <<
": no");
503 const int inputLen = text.
length();
507 ReplacementStream out(capturedTexts);
509 while (input < inputLen) {
510 switch (text[input].unicode()) {
517 if (input + 1 >= inputLen) {
524 switch (text[input + 1].unicode()) {
526 if (input + 4 >= inputLen) {
527 out << ReplacementStream::cap(0);
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')) {
538 for (
int i = 0; i < 3; i++) {
539 digits[i] = 7 - (L
'7' - text[input + 2 + i].unicode());
541 const int ch = 64 * digits[0] + 8 * digits[1] + digits[2];
555 out << ReplacementStream::cap(0);
571 out << ReplacementStream::cap(9 - (L
'9' - text[input + 1].unicode()));
580 while ((input + captureSize) < inputLen) {
581 const ushort nextDigit = text[input + captureSize].unicode();
582 if ((nextDigit >= L
'0') && (nextDigit <= L
'9')) {
587 if (nextDigit == L
'}') {
593 out << ReplacementStream::cap(
capture);
594 input += captureSize;
603 if (!replacementGoodies) {
605 out << text[input + 1];
608 switch (text[input + 1].unicode()) {
610 out << ReplacementStream::lowerCase;
614 out << ReplacementStream::lowerCaseFirst;
618 out << ReplacementStream::upperCase;
622 out << ReplacementStream::upperCaseFirst;
627 out << ReplacementStream::keepCase;
634 if (!replacementGoodies) {
636 out << text[input + 1];
643 while ((input + minWidth + 1 < inputLen) && (text[input + minWidth + 1].unicode() == L
'#')) {
646 out << ReplacementStream::counter(replacementCounter, minWidth);
647 input += 1 + minWidth;
682 if (input + 5 >= inputLen) {
684 out << text[input + 1];
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'))) {
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);
705 digits[i] = 15 - (L
'F' - cur);
709 const int ch = 4096 * digits[0] + 256 * digits[1] + 16 * digits[2] + digits[3];
725 out << text[input + 1];
733 out << text[input + 1];
747QString KateRegExpSearch::repairPattern(
const QString &pattern,
bool &stillMultiLine)
756 const int inputLen = pattern.
length();
761 output.
reserve(2 * inputLen + 1);
764 bool insideClass =
false;
766 stillMultiLine =
false;
768 while (input < inputLen) {
771 switch (pattern[input].unicode()) {
773 switch (pattern[input + 1].unicode()) {
775 if (input + 5 < inputLen) {
777 output.
append(patternView.mid(input, 6));
781 output.
append(patternView.mid(input, 2));
784 stillMultiLine =
true;
788 if (input + 4 < inputLen) {
790 output.
append(patternView.mid(input, 5));
794 output.
append(patternView.mid(input, 2));
797 stillMultiLine =
true;
807 stillMultiLine =
true;
813 output.
append(patternView.mid(input, 2));
821 output.
append(pattern[input]);
827 output.
append(pattern[input]);
831 switch (pattern[input].unicode()) {
833 switch (pattern[input + 1].unicode()) {
835 if (input + 5 < inputLen) {
837 output.
append(patternView.mid(input, 6));
841 output.
append(patternView.mid(input, 2));
844 stillMultiLine =
true;
848 if (input + 4 < inputLen) {
850 output.
append(patternView.mid(input, 5));
854 output.
append(patternView.mid(input, 2));
857 stillMultiLine =
true;
867 stillMultiLine =
true;
872 output.
append(patternView.mid(input, 2));
880 output.
append(pattern[input]);
886 output.
append(pattern[input]);
895#ifdef FAST_DEBUG_ENABLE
896#undef FAST_DEBUG_ENABLE
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
A KParts derived class representing a text document.
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
bool hasNext() 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