forked from qt-creator/qt-creator
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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -54,14 +54,12 @@ public:
|
|||||||
QFuture<SearchResultItems> executeSearch(const TextEditor::FileFindParameters ¶meters,
|
QFuture<SearchResultItems> executeSearch(const TextEditor::FileFindParameters ¶meters,
|
||||||
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*/,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user