File indexing completed on 2024-04-28 05:49:12

0001 /*
0002     SPDX-FileCopyrightText: 2011-21 Kåre Särs <kare.sars@iki.fi>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "SearchOpenFiles.h"
0008 
0009 SearchOpenFiles::SearchOpenFiles(QObject *parent)
0010     : QObject(parent)
0011 {
0012     m_nextRunTimer.setInterval(0);
0013     m_nextRunTimer.setSingleShot(true);
0014     connect(&m_nextRunTimer, &QTimer::timeout, this, [this]() {
0015         doSearchNextFile(m_nextLine);
0016     });
0017 }
0018 
0019 bool SearchOpenFiles::searching() const
0020 {
0021     return !m_cancelSearch;
0022 }
0023 
0024 void SearchOpenFiles::startSearch(const QList<KTextEditor::Document *> &list, const QRegularExpression &regexp)
0025 {
0026     if (m_nextFileIndex != -1) {
0027         return;
0028     }
0029 
0030     m_docList = list;
0031     m_nextFileIndex = 0;
0032     m_regExp = regexp;
0033     m_cancelSearch = false;
0034     m_terminateSearch = false;
0035     m_statusTime.restart();
0036     m_nextLine = 0;
0037     m_nextRunTimer.start(0);
0038 }
0039 
0040 void SearchOpenFiles::terminateSearch()
0041 {
0042     m_cancelSearch = true;
0043     m_terminateSearch = true;
0044     m_nextFileIndex = -1;
0045     m_nextLine = -1;
0046     m_nextRunTimer.stop();
0047 }
0048 
0049 void SearchOpenFiles::cancelSearch()
0050 {
0051     m_cancelSearch = true;
0052 }
0053 
0054 void SearchOpenFiles::doSearchNextFile(int startLine)
0055 {
0056     if (m_cancelSearch || m_nextFileIndex >= m_docList.size()) {
0057         m_nextFileIndex = -1;
0058         m_cancelSearch = true;
0059         m_nextLine = -1;
0060         return;
0061     }
0062 
0063     // NOTE The document managers signal documentWillBeDeleted() must be connected to
0064     // cancelSearch(). A closed file could lead to a crash if it is not handled.
0065     int line = searchOpenFile(m_docList[m_nextFileIndex], m_regExp, startLine);
0066     if (line == 0) {
0067         // file searched go to next
0068         m_nextFileIndex++;
0069         if (m_nextFileIndex == m_docList.size()) {
0070             m_nextFileIndex = -1;
0071             m_cancelSearch = true;
0072             Q_EMIT searchDone();
0073         } else {
0074             m_nextLine = 0;
0075         }
0076     } else {
0077         m_nextLine = line;
0078     }
0079     m_nextRunTimer.start();
0080 }
0081 
0082 int SearchOpenFiles::searchOpenFile(KTextEditor::Document *doc, const QRegularExpression &regExp, int startLine)
0083 {
0084     if (m_statusTime.elapsed() > 100) {
0085         m_statusTime.restart();
0086         Q_EMIT searching(doc->url().toString());
0087     }
0088 
0089     if (regExp.patternOptions().testFlag(QRegularExpression::MultilineOption) && regExp.pattern().contains(QLatin1String("\\n"))) {
0090         return searchMultiLineRegExp(doc, regExp, startLine);
0091     }
0092 
0093     return searchSingleLineRegExp(doc, regExp, startLine);
0094 }
0095 
0096 int SearchOpenFiles::searchSingleLineRegExp(KTextEditor::Document *doc, const QRegularExpression &regExp, int startLine)
0097 {
0098     int column;
0099     QElapsedTimer time;
0100 
0101     time.start();
0102     int resultLine = 0;
0103     QList<KateSearchMatch> matches;
0104     for (int line = startLine; line < doc->lines(); line++) {
0105         if (time.elapsed() > 100) {
0106             // qDebug() << "Search time exceeded" << time.elapsed() << line;
0107             resultLine = line;
0108             break;
0109         }
0110         QRegularExpressionMatch match;
0111         match = regExp.match(doc->line(line));
0112         column = match.capturedStart();
0113 
0114         while (column != -1 && !match.captured().isEmpty()) {
0115             int endColumn = column + match.capturedLength();
0116             const QString &lineStr = doc->line(line);
0117             const auto [preContextStart, postContextLen] = MatchModel::contextLengths(lineStr.size(), column, endColumn);
0118             QString preContext = lineStr.mid(preContextStart, column - preContextStart);
0119             QString postContext = lineStr.mid(endColumn, postContextLen);
0120 
0121             matches.push_back(KateSearchMatch{preContext,
0122                                               match.captured(),
0123                                               postContext,
0124                                               QString(),
0125                                               KTextEditor::Range{line, column, line, int(column + match.capturedLength())},
0126                                               true,
0127                                               true});
0128             match = regExp.match(doc->line(line), column + match.capturedLength());
0129             column = match.capturedStart();
0130         }
0131     }
0132 
0133     // Q_EMIT all matches batched
0134     Q_EMIT matchesFound(doc->url(), matches, doc);
0135 
0136     return resultLine;
0137 }
0138 
0139 int SearchOpenFiles::searchMultiLineRegExp(KTextEditor::Document *doc, const QRegularExpression &regExp, int inStartLine)
0140 {
0141     int column = 0;
0142     int startLine = 0;
0143     QElapsedTimer time;
0144     time.start();
0145     QRegularExpression tmpRegExp = regExp;
0146 
0147     if (inStartLine == 0) {
0148         // Copy the whole file to a temporary buffer to be able to search newlines
0149         m_fullDoc.clear();
0150         m_lineStart.clear();
0151         m_lineStart << 0;
0152         for (int i = 0; i < doc->lines(); i++) {
0153             m_fullDoc += doc->line(i) + QLatin1Char('\n');
0154             m_lineStart << m_fullDoc.size();
0155         }
0156         if (!regExp.pattern().endsWith(QLatin1Char('$'))) {
0157             // if regExp ends with '$' leave the extra newline at the end as
0158             // '$' will be replaced with (?=\\n), which needs the extra newline
0159             m_fullDoc.remove(m_fullDoc.size() - 1, 1);
0160         }
0161     } else {
0162         if (inStartLine > 0 && inStartLine < m_lineStart.size()) {
0163             column = m_lineStart[inStartLine];
0164             startLine = inStartLine;
0165         } else {
0166             return 0;
0167         }
0168     }
0169 
0170     if (regExp.pattern().endsWith(QLatin1Char('$'))) {
0171         QString newPatern = tmpRegExp.pattern();
0172         newPatern.replace(QStringLiteral("$"), QStringLiteral("(?=\\n)"));
0173         tmpRegExp.setPattern(newPatern);
0174     }
0175 
0176     QRegularExpressionMatch match;
0177     match = tmpRegExp.match(m_fullDoc, column);
0178     column = match.capturedStart();
0179     int resultLine = 0;
0180     QList<KateSearchMatch> matches;
0181     while (column != -1 && !match.captured().isEmpty()) {
0182         // search for the line number of the match
0183         int i;
0184         startLine = -1;
0185         for (i = 1; i < m_lineStart.size(); i++) {
0186             if (m_lineStart[i] > column) {
0187                 startLine = i - 1;
0188                 break;
0189             }
0190         }
0191         if (startLine == -1) {
0192             break;
0193         }
0194 
0195         int startColumn = (column - m_lineStart[startLine]);
0196         int endLine = startLine + match.captured().count(QLatin1Char('\n'));
0197         int lastNL = match.captured().lastIndexOf(QLatin1Char('\n'));
0198         int endColumn = lastNL == -1 ? startColumn + match.captured().length() : match.captured().length() - lastNL - 1;
0199 
0200         int preContextStart = qMax(0, startColumn - MatchModel::PreContextLen);
0201         QString preContext = doc->line(startLine).mid(preContextStart, startColumn - preContextStart);
0202         QString postContext = doc->line(endLine).mid(endColumn, MatchModel::PostContextLen);
0203 
0204         matches.push_back(
0205             KateSearchMatch{preContext, match.captured(), postContext, QString(), KTextEditor::Range{startLine, startColumn, endLine, endColumn}, true, true});
0206         match = tmpRegExp.match(m_fullDoc, column + match.capturedLength());
0207         column = match.capturedStart();
0208 
0209         if (time.elapsed() > 100) {
0210             // qDebug() << "Search time exceeded" << time.elapsed() << line;
0211             resultLine = startLine;
0212             break;
0213         }
0214     }
0215 
0216     // Q_EMIT all matches batched
0217     Q_EMIT matchesFound(doc->url(), matches, doc);
0218 
0219     return resultLine;
0220 }
0221 
0222 #include "moc_SearchOpenFiles.cpp"