FileSearch: Reuse searchInContents() inside findInFiles()

Get rid of findInFilesRegExp().

Change-Id: Iae47b023a1428a66fff165c0a0a4fa38d55ba132
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Jarek Kobus
2023-05-22 16:28:45 +02:00
parent b9e316e8d1
commit 61f0bf479d
4 changed files with 22 additions and 257 deletions

View File

@@ -12,7 +12,6 @@
#include "utilstr.h" #include "utilstr.h"
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMutex>
#include <QRegularExpression> #include <QRegularExpression>
#include <QTextCodec> #include <QTextCodec>
@@ -218,7 +217,6 @@ static inline QString msgFound(const QString &searchTerm, int numMatches, int nu
namespace { namespace {
// returns success
static bool getFileContent(const FilePath &filePath, static bool getFileContent(const FilePath &filePath,
QTextCodec *encoding, QTextCodec *encoding,
QString *tempString, QString *tempString,
@@ -239,57 +237,13 @@ static bool getFileContent(const FilePath &filePath,
class FileSearch class FileSearch
{ {
public: public:
FileSearch(const QString &searchTerm,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap);
void operator()(QFutureInterface<SearchResultItems> &futureInterface, void operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const; const FileIterator::Item &item) const;
const QString m_searchTerm;
private: const FindFlags m_flags;
QMap<FilePath, QString> fileToContentsMap; const QMap<FilePath, QString> m_fileToContentsMap;
QString searchTermLower;
QString searchTermUpper;
int termMaxIndex;
const QChar *termData;
const QChar *termDataLower;
const QChar *termDataUpper;
bool caseSensitive;
bool wholeWord;
}; };
class FileSearchRegExp
{
public:
FileSearchRegExp(const QString &searchTerm,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap);
FileSearchRegExp(const FileSearchRegExp &other);
void operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const;
private:
QRegularExpressionMatch doGuardedMatch(const QString &line, int offset) const;
QMap<FilePath, QString> fileToContentsMap;
QRegularExpression expression;
mutable QMutex mutex;
};
FileSearch::FileSearch(const QString &searchTerm,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap)
{
this->fileToContentsMap = fileToContentsMap;
caseSensitive = (flags & QTextDocument::FindCaseSensitively);
wholeWord = (flags & QTextDocument::FindWholeWords);
searchTermLower = searchTerm.toLower();
searchTermUpper = searchTerm.toUpper();
termMaxIndex = searchTerm.length() - 1;
termData = searchTerm.constData();
termDataLower = searchTermLower.constData();
termDataUpper = searchTermUpper.constData();
}
void FileSearch::operator()(QFutureInterface<SearchResultItems> &futureInterface, void FileSearch::operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const const FileIterator::Item &item) const
{ {
@@ -298,169 +252,16 @@ void FileSearch::operator()(QFutureInterface<SearchResultItems> &futureInterface
qCDebug(searchLog) << "Searching in" << item.filePath; qCDebug(searchLog) << "Searching in" << item.filePath;
futureInterface.setProgressRange(0, 1); futureInterface.setProgressRange(0, 1);
futureInterface.setProgressValue(0); futureInterface.setProgressValue(0);
SearchResultItems results; QString contents;
QString tempString; if (!getFileContent(item.filePath, item.encoding, &contents, m_fileToContentsMap)) {
if (!getFileContent(item.filePath, item.encoding, &tempString, fileToContentsMap)) {
qCDebug(searchLog) << "- failed to get content for" << item.filePath; qCDebug(searchLog) << "- failed to get content for" << item.filePath;
futureInterface.cancel(); // failure futureInterface.cancel(); // failure
return; return;
} }
QTextStream stream(&tempString);
int lineNr = 0;
while (!stream.atEnd()) { const QFuture<void> future(futureInterface.future());
++lineNr; const SearchResultItems results = searchInContents(future, m_searchTerm, m_flags, item.filePath,
const QString chunk = stream.readLine(); contents);
const int chunkLength = chunk.length();
const QChar *chunkPtr = chunk.constData();
const QChar *chunkEnd = chunkPtr + chunkLength - 1;
for (const QChar *regionPtr = chunkPtr; regionPtr + termMaxIndex <= chunkEnd; ++regionPtr) {
const QChar *regionEnd = regionPtr + termMaxIndex;
if ( /* optimization check for start and end of region */
// case sensitive
(caseSensitive && *regionPtr == termData[0]
&& *regionEnd == termData[termMaxIndex])
||
// case insensitive
(!caseSensitive && (*regionPtr == termDataLower[0]
|| *regionPtr == termDataUpper[0])
&& (*regionEnd == termDataLower[termMaxIndex]
|| *regionEnd == termDataUpper[termMaxIndex]))
) {
bool equal = true;
// whole word check
const QChar *beforeRegion = regionPtr - 1;
const QChar *afterRegion = regionEnd + 1;
if (wholeWord
&& (((beforeRegion >= chunkPtr)
&& (beforeRegion->isLetterOrNumber()
|| ((*beforeRegion) == QLatin1Char('_'))))
||
((afterRegion <= chunkEnd)
&& (afterRegion->isLetterOrNumber()
|| ((*afterRegion) == QLatin1Char('_'))))
)) {
equal = false;
} else {
// check all chars
int regionIndex = 1;
for (const QChar *regionCursor = regionPtr + 1;
regionCursor < regionEnd;
++regionCursor, ++regionIndex) {
if ( // case sensitive
(caseSensitive
&& *regionCursor != termData[regionIndex])
||
// case insensitive
(!caseSensitive
&& *regionCursor != termDataLower[regionIndex]
&& *regionCursor != termDataUpper[regionIndex])
) {
equal = false;
break;
}
}
}
if (equal) {
SearchResultItem result;
result.setFilePath(item.filePath);
result.setMainRange(lineNr, regionPtr - chunkPtr, termMaxIndex + 1);
result.setDisplayText(clippedText(chunk, MAX_LINE_SIZE));
result.setUserData(QStringList());
result.setUseTextEditorFont(true);
results << result;
regionPtr += termMaxIndex; // another +1 done by for-loop
}
}
}
if (futureInterface.isPaused())
futureInterface.waitForResume();
if (futureInterface.isCanceled())
break;
}
if (!futureInterface.isCanceled()) {
futureInterface.reportResult(results);
futureInterface.setProgressValue(1);
}
qCDebug(searchLog) << "- finished searching in" << item.filePath;
}
FileSearchRegExp::FileSearchRegExp(const QString &searchTerm,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap)
{
this->fileToContentsMap = fileToContentsMap;
QString term = searchTerm;
if (flags & QTextDocument::FindWholeWords)
term = QString::fromLatin1("\\b%1\\b").arg(term);
const QRegularExpression::PatternOptions patternOptions = (flags & QTextDocument::FindCaseSensitively)
? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption;
expression = QRegularExpression(term, patternOptions);
}
FileSearchRegExp::FileSearchRegExp(const FileSearchRegExp &other)
: fileToContentsMap(other.fileToContentsMap),
expression(other.expression)
{
}
QRegularExpressionMatch FileSearchRegExp::doGuardedMatch(const QString &line, int offset) const
{
QMutexLocker lock(&mutex);
return expression.match(line, offset);
}
void FileSearchRegExp::operator()(QFutureInterface<SearchResultItems> &futureInterface,
const FileIterator::Item &item) const
{
if (!expression.isValid()) {
futureInterface.cancel();
return;
}
if (futureInterface.isCanceled())
return;
qCDebug(searchLog) << "Searching in" << item.filePath;
futureInterface.setProgressRange(0, 1);
futureInterface.setProgressValue(0);
SearchResultItems results;
QString tempString;
if (!getFileContent(item.filePath, item.encoding, &tempString, fileToContentsMap)) {
qCDebug(searchLog) << "- failed to get content for" << item.filePath;
futureInterface.cancel(); // failure
return;
}
QTextStream stream(&tempString);
int lineNr = 0;
QString line;
QRegularExpressionMatch match;
while (!stream.atEnd()) {
++lineNr;
line = stream.readLine();
const QString resultItemText = clippedText(line, MAX_LINE_SIZE);
int lengthOfLine = line.size();
int pos = 0;
while ((match = doGuardedMatch(line, pos)).hasMatch()) {
pos = match.capturedStart();
SearchResultItem result;
result.setFilePath(item.filePath);
result.setMainRange(lineNr, pos, match.capturedLength());
result.setDisplayText(resultItemText);
result.setUserData(match.capturedTexts());
result.setUseTextEditorFont(true);
results << result;
if (match.capturedLength() == 0)
break;
pos += match.capturedLength();
if (pos >= lengthOfLine)
break;
}
if (futureInterface.isPaused())
futureInterface.waitForResume();
if (futureInterface.isCanceled())
break;
}
if (!futureInterface.isCanceled()) { if (!futureInterface.isCanceled()) {
futureInterface.reportResult(results); futureInterface.reportResult(results);
futureInterface.setProgressValue(1); futureInterface.setProgressValue(1);
@@ -530,31 +331,15 @@ void cleanUpFileSearch(QFutureInterface<SearchResultItems> &futureInterface,
} // namespace } // namespace
QFuture<SearchResultItems> Utils::findInFiles(const QString &searchTerm, QFuture<SearchResultItems> Utils::findInFiles(const QString &searchTerm, FileIterator *files,
FileIterator *files, FindFlags flags,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap) const QMap<FilePath, QString> &fileToContentsMap)
{ {
return mapReduce(files->begin(), files->end(), return mapReduce(files->begin(), files->end(),
[searchTerm, files](QFutureInterface<SearchResultItems> &futureInterface) { [searchTerm, files](QFutureInterface<SearchResultItems> &futureInterface) {
return initFileSearch(futureInterface, searchTerm, files); return initFileSearch(futureInterface, searchTerm, files);
}, },
FileSearch(searchTerm, flags, fileToContentsMap), FileSearch{searchTerm, flags, fileToContentsMap},
&collectSearchResults,
&cleanUpFileSearch);
}
QFuture<SearchResultItems> Utils::findInFilesRegExp(
const QString &searchTerm,
FileIterator *files,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap)
{
return mapReduce(files->begin(), files->end(),
[searchTerm, files](QFutureInterface<SearchResultItems> &futureInterface) {
return initFileSearch(futureInterface, searchTerm, files);
},
FileSearchRegExp(searchTerm, flags, fileToContentsMap),
&collectSearchResults, &collectSearchResults,
&cleanUpFileSearch); &cleanUpFileSearch);
} }

View File

@@ -171,15 +171,7 @@ private:
}; };
QTCREATOR_UTILS_EXPORT QFuture<SearchResultItems> findInFiles(const QString &searchTerm, QTCREATOR_UTILS_EXPORT QFuture<SearchResultItems> findInFiles(const QString &searchTerm,
FileIterator *files, FileIterator *files, FindFlags flags, const QMap<FilePath, QString> &fileToContentsMap);
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap = {});
QTCREATOR_UTILS_EXPORT QFuture<SearchResultItems> findInFilesRegExp(
const QString &searchTerm,
FileIterator *files,
QTextDocument::FindFlags flags,
const QMap<FilePath, QString> &fileToContentsMap = {});
QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText, QTCREATOR_UTILS_EXPORT QString expandRegExpReplacement(const QString &replaceText,
const QStringList &capturedTexts); const QStringList &capturedTexts);

View File

@@ -54,14 +54,12 @@ public:
QFuture<SearchResultItems> executeSearch(const TextEditor::FileFindParameters &parameters, QFuture<SearchResultItems> executeSearch(const TextEditor::FileFindParameters &parameters,
BaseFileFind *baseFileFind) override BaseFileFind *baseFileFind) override
{ {
const auto func = parameters.flags & FindRegularExpression ? Utils::findInFilesRegExp return Utils::findInFiles(parameters.text,
: Utils::findInFiles; baseFileFind->files(parameters.nameFilters,
parameters.exclusionFilters,
return func(parameters.text, parameters.additionalParameters),
baseFileFind->files(parameters.nameFilters, parameters.exclusionFilters, parameters.flags,
parameters.additionalParameters), TextDocument::openedTextDocumentContents());
Utils::textDocumentFlagsForFindFlags(parameters.flags),
TextDocument::openedTextDocumentContents());
} }
Core::IEditor *openEditor(const SearchResultItem &/*item*/, Core::IEditor *openEditor(const SearchResultItem &/*item*/,

View File

@@ -11,12 +11,6 @@ using namespace Utils;
class tst_FileSearch : public QObject class tst_FileSearch : public QObject
{ {
Q_OBJECT Q_OBJECT
public:
enum RegExpFlag {
NoRegExp,
RegExp
};
private slots: private slots:
void multipleResults(); void multipleResults();
void caseSensitive(); void caseSensitive();
@@ -41,16 +35,12 @@ SearchResultItem searchResult(const FilePath &fileName, const QString &matchingL
} }
void test_helper(const FilePath &filePath, const SearchResultItems &expectedResults, void test_helper(const FilePath &filePath, const SearchResultItems &expectedResults,
const QString &term, QTextDocument::FindFlags flags = {}, const QString &term, Utils::FindFlags flags = {})
tst_FileSearch::RegExpFlag regexp = tst_FileSearch::NoRegExp)
{ {
FileIterator *it = new FileListIterator({filePath}, {QTextCodec::codecForLocale()}); FileIterator *it = new FileListIterator({filePath}, {QTextCodec::codecForLocale()});
QFutureWatcher<SearchResultItems> watcher; QFutureWatcher<SearchResultItems> watcher;
QSignalSpy ready(&watcher, &QFutureWatcherBase::resultsReadyAt); QSignalSpy ready(&watcher, &QFutureWatcherBase::resultsReadyAt);
if (regexp == tst_FileSearch::NoRegExp) watcher.setFuture(Utils::findInFiles(term, it, flags, {}));
watcher.setFuture(Utils::findInFiles(term, it, flags));
else
watcher.setFuture(Utils::findInFilesRegExp(term, it, flags));
watcher.future().waitForFinished(); watcher.future().waitForFinished();
QTest::qWait(100); // process events QTest::qWait(100); // process events
QCOMPARE(ready.count(), 1); QCOMPARE(ready.count(), 1);
@@ -84,7 +74,7 @@ void tst_FileSearch::multipleResults()
expectedResults << searchResult(m_filePath, expectedResults << searchResult(m_filePath,
"aaaaaaaa this line has 2 results for four a in a row", "aaaaaaaa this line has 2 results for four a in a row",
5, 4, 4, {"aaaa"}); 5, 4, 4, {"aaaa"});
test_helper(m_filePath, expectedResults, "aaaa", {}, RegExp); test_helper(m_filePath, expectedResults, "aaaa", FindRegularExpression);
} }
void tst_FileSearch::caseSensitive() void tst_FileSearch::caseSensitive()
@@ -92,7 +82,7 @@ void tst_FileSearch::caseSensitive()
SearchResultItems expectedResults; SearchResultItems expectedResults;
expectedResults << searchResult(m_filePath, "search CaseSensitively for casesensitive", expectedResults << searchResult(m_filePath, "search CaseSensitively for casesensitive",
3, 7, 13); 3, 7, 13);
test_helper(m_filePath, expectedResults, "CaseSensitive", QTextDocument::FindCaseSensitively); test_helper(m_filePath, expectedResults, "CaseSensitive", FindCaseSensitively);
} }
void tst_FileSearch::caseInSensitive() void tst_FileSearch::caseInSensitive()