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 ®exp) 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 ®Exp, 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 ®Exp, 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 ®Exp, 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"