1/*
2 SPDX-FileCopyrightText: 2009-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 "kateplaintextsearch.h"
10
11#include "katepartdebug.h"
12#include "kateregexpsearch.h"
13#include <ktexteditor/document.h>
14
15#include <QRegularExpression>
16// END includes
17
18// BEGIN d'tor, c'tor
19//
20// KateSearch Constructor
21//
22KatePlainTextSearch::KatePlainTextSearch(const KTextEditor::Document *document, Qt::CaseSensitivity caseSensitivity, bool wholeWords)
23 : m_document(document)
24 , m_caseSensitivity(caseSensitivity)
25 , m_wholeWords(wholeWords)
26{
27}
28
29// END
30
31KTextEditor::Range KatePlainTextSearch::search(const QString &text, KTextEditor::Range inputRange, bool backwards)
32{
33 // abuse regex for whole word plaintext search
34 if (m_wholeWords) {
35 // escape dot and friends
36 const QString workPattern = QStringLiteral("\\b%1\\b").arg(a: QRegularExpression::escape(str: text));
37
38 QRegularExpression::PatternOptions options;
39 options |= m_caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption;
40
41 return KateRegExpSearch(m_document).search(pattern: workPattern, inputRange, backwards, options).at(i: 0);
42 }
43
44 if (text.isEmpty() || !inputRange.isValid() || (inputRange.start() == inputRange.end())) {
45 return KTextEditor::Range::invalid();
46 }
47
48 // split multi-line needle into single lines
49 const QList<QStringView> needleLines = QStringView(text).split(sep: QLatin1Char('\n'));
50
51 if (needleLines.count() > 1) {
52 // multi-line plaintext search (both forwards or backwards)
53 const int forMin = inputRange.start().line(); // first line in range
54 const int forMax = inputRange.end().line() + 1 - needleLines.count(); // last line in range
55 const int forInit = backwards ? forMax : forMin;
56 const int forInc = backwards ? -1 : +1;
57
58 for (int j = forInit; (forMin <= j) && (j <= forMax); j += forInc) {
59 // try to match all lines
60 const int startCol = m_document->lineLength(line: j) - needleLines[0].length();
61 for (int k = 0; k < needleLines.count(); k++) {
62 // which lines to compare
63 const auto &needleLine = needleLines[k];
64 const QString &hayLine = m_document->line(line: j + k);
65
66 // position specific comparison (first, middle, last)
67 if (k == 0) {
68 // first line
69 if (forMin == j && startCol < inputRange.start().column()) {
70 break;
71 }
72
73 // NOTE: QString("")::endsWith("") is false in Qt, therefore we need the additional checks.
74 const bool endsWith = hayLine.endsWith(s: needleLine, cs: m_caseSensitivity) || (hayLine.isEmpty() && needleLine.isEmpty());
75 if (!endsWith) {
76 break;
77 }
78 } else if (k == needleLines.count() - 1) {
79 // last line
80 const int maxRight = (j + k == inputRange.end().line()) ? inputRange.end().column() : hayLine.length();
81
82 // NOTE: QString("")::startsWith("") is false in Qt, therefore we need the additional checks.
83 const bool startsWith = hayLine.startsWith(s: needleLine, cs: m_caseSensitivity) || (hayLine.isEmpty() && needleLine.isEmpty());
84 if (startsWith && needleLine.length() <= maxRight) {
85 return KTextEditor::Range(j, startCol, j + k, needleLine.length());
86 }
87 } else {
88 // mid lines
89 if (hayLine.compare(s: needleLine, cs: m_caseSensitivity) != 0) {
90 break;
91 }
92 }
93 }
94 }
95
96 // not found
97 return KTextEditor::Range::invalid();
98 } else {
99 // single-line plaintext search (both forward of backward mode)
100 const int startCol = inputRange.start().column();
101 const int endCol = inputRange.end().column(); // first not included
102 const int startLine = inputRange.start().line();
103 const int endLine = inputRange.end().line();
104 const int forInc = backwards ? -1 : +1;
105
106 for (int line = backwards ? endLine : startLine; (startLine <= line) && (line <= endLine); line += forInc) {
107 if ((line < 0) || (m_document->lines() <= line)) {
108 qCWarning(LOG_KTE) << "line " << line << " is not within interval [0.." << m_document->lines() << ") ... returning invalid range";
109 return KTextEditor::Range::invalid();
110 }
111
112 const QString textLine = m_document->line(line);
113
114 const int offset = (line == startLine) ? startCol : 0;
115 const int line_end = (line == endLine) ? endCol : textLine.length();
116 const int foundAt =
117 backwards ? textLine.lastIndexOf(s: text, from: line_end - text.length(), cs: m_caseSensitivity) : textLine.indexOf(s: text, from: offset, cs: m_caseSensitivity);
118
119 if ((offset <= foundAt) && (foundAt + text.length() <= line_end)) {
120 return KTextEditor::Range(line, foundAt, line, foundAt + text.length());
121 }
122 }
123 }
124 return KTextEditor::Range::invalid();
125}
126

source code of ktexteditor/src/search/kateplaintextsearch.cpp