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();
446 match.swap(curMatch);
452 match = repairedRegex.
matchView(textLine, offset);
453 if (match.hasMatch() && match.capturedEnd() <= endLineMaxOffset) {
459 FAST_DEBUG(
"line " << j <<
": yes");
466 FAST_DEBUG(
"result range " << 0 <<
": (" << j <<
", " << match.capturedStart() <<
")..(" << j <<
", " << match.capturedEnd() <<
")");
468 for (
int y = 1; y <= numCaptures; ++y) {
469 const int openIndex = match.capturedStart(y);
471 if (openIndex == -1) {
474 FAST_DEBUG(
"capture []");
476 const int closeIndex = match.capturedEnd(y);
478 FAST_DEBUG(
"result range " << y <<
": (" << j <<
", " << openIndex <<
")..(" << j <<
", " << closeIndex <<
")");
485 FAST_DEBUG(
"searchText | line " << j <<
": no");
505 const int inputLen = text.
length();
509 ReplacementStream out(capturedTexts);
511 while (input < inputLen) {
512 switch (text[input].unicode()) {
519 if (input + 1 >= inputLen) {
526 switch (text[input + 1].unicode()) {
528 if (input + 4 >= inputLen) {
529 out << ReplacementStream::cap(0);
532 bool stripAndSkip =
false;
533 const ushort text_2 = text[input + 2].unicode();
534 if ((text_2 >= L
'0') && (text_2 <= L
'3')) {
535 const ushort text_3 = text[input + 3].unicode();
536 if ((text_3 >= L
'0') && (text_3 <= L
'7')) {
537 const ushort text_4 = text[input + 4].unicode();
538 if ((text_4 >= L
'0') && (text_4 <= L
'7')) {
540 for (
int i = 0; i < 3; i++) {
541 digits[i] = 7 - (L
'7' - text[input + 2 + i].unicode());
543 const int ch = 64 * digits[0] + 8 * digits[1] + digits[2];
557 out << ReplacementStream::cap(0);
573 out << ReplacementStream::cap(9 - (L
'9' - text[input + 1].unicode()));
582 while ((input + captureSize) < inputLen) {
583 const ushort nextDigit = text[input + captureSize].unicode();
584 if ((nextDigit >= L
'0') && (nextDigit <= L
'9')) {
589 if (nextDigit == L
'}') {
595 out << ReplacementStream::cap(
capture);
596 input += captureSize;
605 if (!replacementGoodies) {
607 out << text[input + 1];
610 switch (text[input + 1].unicode()) {
612 out << ReplacementStream::lowerCase;
616 out << ReplacementStream::lowerCaseFirst;
620 out << ReplacementStream::upperCase;
624 out << ReplacementStream::upperCaseFirst;
629 out << ReplacementStream::keepCase;
636 if (!replacementGoodies) {
638 out << text[input + 1];
645 while ((input + minWidth + 1 < inputLen) && (text[input + minWidth + 1].unicode() == L
'#')) {
648 out << ReplacementStream::counter(replacementCounter, minWidth);
649 input += 1 + minWidth;
684 if (input + 5 >= inputLen) {
686 out << text[input + 1];
689 bool stripAndSkip =
false;
690 const ushort text_2 = text[input + 2].unicode();
691 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'))) {
692 const ushort text_3 = text[input + 3].unicode();
693 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'))) {
694 const ushort text_4 = text[input + 4].unicode();
695 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'))) {
696 const ushort text_5 = text[input + 5].unicode();
697 if (((text_5 >= L
'0') && (text_5 <= L
'9')) || ((text_5 >= L
'a') && (text_5 <= L
'f'))
698 || ((text_5 >= L
'A') && (text_5 <= L
'F'))) {
700 for (
int i = 0; i < 4; i++) {
701 const ushort cur = text[input + 2 + i].unicode();
702 if ((cur >= L
'0') && (cur <= L
'9')) {
703 digits[i] = 9 - (L
'9' - cur);
704 }
else if ((cur >= L
'a') && (cur <= L
'f')) {
705 digits[i] = 15 - (L
'f' - cur);
707 digits[i] = 15 - (L
'F' - cur);
711 const int ch = 4096 * digits[0] + 256 * digits[1] + 16 * digits[2] + digits[3];
727 out << text[input + 1];
735 out << text[input + 1];
749QString KateRegExpSearch::repairPattern(
const QString &pattern,
bool &stillMultiLine)
758 const int inputLen = pattern.
length();
763 output.
reserve(2 * inputLen + 1);
766 bool insideClass =
false;
768 stillMultiLine =
false;
770 while (input < inputLen) {
773 switch (pattern[input].unicode()) {
775 switch (pattern[input + 1].unicode()) {
777 if (input + 5 < inputLen) {
779 output.
append(patternView.mid(input, 6));
783 output.
append(patternView.mid(input, 2));
786 stillMultiLine =
true;
790 if (input + 4 < inputLen) {
792 output.
append(patternView.mid(input, 5));
796 output.
append(patternView.mid(input, 2));
799 stillMultiLine =
true;
809 stillMultiLine =
true;
815 output.
append(patternView.mid(input, 2));
823 output.
append(pattern[input]);
829 output.
append(pattern[input]);
833 switch (pattern[input].unicode()) {
835 switch (pattern[input + 1].unicode()) {
837 if (input + 5 < inputLen) {
839 output.
append(patternView.mid(input, 6));
843 output.
append(patternView.mid(input, 2));
846 stillMultiLine =
true;
850 if (input + 4 < inputLen) {
852 output.
append(patternView.mid(input, 5));
856 output.
append(patternView.mid(input, 2));
859 stillMultiLine =
true;
869 stillMultiLine =
true;
874 output.
append(patternView.mid(input, 2));
882 output.
append(pattern[input]);
888 output.
append(pattern[input]);
897#ifdef FAST_DEBUG_ENABLE
898#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
int captureCount() const const
QRegularExpressionMatchIterator globalMatchView(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
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 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