File indexing completed on 2024-04-21 03:57:39

0001 /*
0002     SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0003     SPDX-FileCopyrightText: 2007 Sebastian Pipping <webmaster@hartwork.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 // BEGIN includes
0009 #include "kateplaintextsearch.h"
0010 
0011 #include "katepartdebug.h"
0012 #include "kateregexpsearch.h"
0013 #include <ktexteditor/document.h>
0014 
0015 #include <QRegularExpression>
0016 // END  includes
0017 
0018 // BEGIN d'tor, c'tor
0019 //
0020 // KateSearch Constructor
0021 //
0022 KatePlainTextSearch::KatePlainTextSearch(const KTextEditor::Document *document, Qt::CaseSensitivity caseSensitivity, bool wholeWords)
0023     : m_document(document)
0024     , m_caseSensitivity(caseSensitivity)
0025     , m_wholeWords(wholeWords)
0026 {
0027 }
0028 
0029 // END
0030 
0031 KTextEditor::Range KatePlainTextSearch::search(const QString &text, KTextEditor::Range inputRange, bool backwards)
0032 {
0033     // abuse regex for whole word plaintext search
0034     if (m_wholeWords) {
0035         // escape dot and friends
0036         const QString workPattern = QStringLiteral("\\b%1\\b").arg(QRegularExpression::escape(text));
0037 
0038         QRegularExpression::PatternOptions options;
0039         options |= m_caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption;
0040 
0041         return KateRegExpSearch(m_document).search(workPattern, inputRange, backwards, options).at(0);
0042     }
0043 
0044     if (text.isEmpty() || !inputRange.isValid() || (inputRange.start() == inputRange.end())) {
0045         return KTextEditor::Range::invalid();
0046     }
0047 
0048     // split multi-line needle into single lines
0049     const QList<QStringView> needleLines = QStringView(text).split(QLatin1Char('\n'));
0050 
0051     if (needleLines.count() > 1) {
0052         // multi-line plaintext search (both forwards or backwards)
0053         const int forMin = inputRange.start().line(); // first line in range
0054         const int forMax = inputRange.end().line() + 1 - needleLines.count(); // last line in range
0055         const int forInit = backwards ? forMax : forMin;
0056         const int forInc = backwards ? -1 : +1;
0057 
0058         for (int j = forInit; (forMin <= j) && (j <= forMax); j += forInc) {
0059             // try to match all lines
0060             const int startCol = m_document->lineLength(j) - needleLines[0].length();
0061             for (int k = 0; k < needleLines.count(); k++) {
0062                 // which lines to compare
0063                 const auto &needleLine = needleLines[k];
0064                 const QString &hayLine = m_document->line(j + k);
0065 
0066                 // position specific comparison (first, middle, last)
0067                 if (k == 0) {
0068                     // first line
0069                     if (forMin == j && startCol < inputRange.start().column()) {
0070                         break;
0071                     }
0072 
0073                     // NOTE: QString("")::endsWith("") is false in Qt, therefore we need the additional checks.
0074                     const bool endsWith = hayLine.endsWith(needleLine, m_caseSensitivity) || (hayLine.isEmpty() && needleLine.isEmpty());
0075                     if (!endsWith) {
0076                         break;
0077                     }
0078                 } else if (k == needleLines.count() - 1) {
0079                     // last line
0080                     const int maxRight = (j + k == inputRange.end().line()) ? inputRange.end().column() : hayLine.length();
0081 
0082                     // NOTE: QString("")::startsWith("") is false in Qt, therefore we need the additional checks.
0083                     const bool startsWith = hayLine.startsWith(needleLine, m_caseSensitivity) || (hayLine.isEmpty() && needleLine.isEmpty());
0084                     if (startsWith && needleLine.length() <= maxRight) {
0085                         return KTextEditor::Range(j, startCol, j + k, needleLine.length());
0086                     }
0087                 } else {
0088                     // mid lines
0089                     if (hayLine.compare(needleLine, m_caseSensitivity) != 0) {
0090                         break;
0091                     }
0092                 }
0093             }
0094         }
0095 
0096         // not found
0097         return KTextEditor::Range::invalid();
0098     } else {
0099         // single-line plaintext search (both forward of backward mode)
0100         const int startCol = inputRange.start().column();
0101         const int endCol = inputRange.end().column(); // first not included
0102         const int startLine = inputRange.start().line();
0103         const int endLine = inputRange.end().line();
0104         const int forInc = backwards ? -1 : +1;
0105 
0106         for (int line = backwards ? endLine : startLine; (startLine <= line) && (line <= endLine); line += forInc) {
0107             if ((line < 0) || (m_document->lines() <= line)) {
0108                 qCWarning(LOG_KTE) << "line " << line << " is not within interval [0.." << m_document->lines() << ") ... returning invalid range";
0109                 return KTextEditor::Range::invalid();
0110             }
0111 
0112             const QString textLine = m_document->line(line);
0113 
0114             const int offset = (line == startLine) ? startCol : 0;
0115             const int line_end = (line == endLine) ? endCol : textLine.length();
0116             const int foundAt =
0117                 backwards ? textLine.lastIndexOf(text, line_end - text.length(), m_caseSensitivity) : textLine.indexOf(text, offset, m_caseSensitivity);
0118 
0119             if ((offset <= foundAt) && (foundAt + text.length() <= line_end)) {
0120                 return KTextEditor::Range(line, foundAt, line, foundAt + text.length());
0121             }
0122         }
0123     }
0124     return KTextEditor::Range::invalid();
0125 }