File indexing completed on 2024-04-28 15:30:43

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