GitGrep: Reuse searchInProcessOutput()

Change-Id: I5e97f23c0e2a06ccd3d204977ac1abc986b84e5c
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Jarek Kobus
2023-06-01 21:42:18 +02:00
parent 64d209c24b
commit f10581ee31
2 changed files with 112 additions and 185 deletions

View File

@@ -21,12 +21,12 @@
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <QCheckBox> #include <QCheckBox>
#include <QFuture>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QRegularExpressionValidator> #include <QRegularExpressionValidator>
#include <QSettings> #include <QSettings>
using namespace Core; using namespace Core;
using namespace TextEditor;
using namespace Utils; using namespace Utils;
using namespace VcsBase; using namespace VcsBase;
@@ -55,25 +55,6 @@ static QStringView nextLine(QStringView *remainingInput)
return ret; return ret;
} }
class GitGrepRunner
{
public:
GitGrepRunner(const TextEditor::FileFindParameters &parameters)
: m_parameters(parameters)
{
m_directory = FilePath::fromString(parameters.additionalParameters.toString());
m_vcsBinary = GitClient::instance()->vcsBinary();
m_environment = GitClient::instance()->processEnvironment();
if (m_parameters.flags & FindRegularExpression) {
const QRegularExpression::PatternOptions patternOptions
= m_parameters.flags & FindCaseSensitively
? QRegularExpression::NoPatternOption
: QRegularExpression::CaseInsensitiveOption;
m_regexp.setPattern(m_parameters.text);
m_regexp.setPatternOptions(patternOptions);
}
}
struct Match struct Match
{ {
Match() = default; Match() = default;
@@ -85,7 +66,9 @@ public:
QStringList regexpCapturedTexts; QStringList regexpCapturedTexts;
}; };
void processLine(QStringView line, SearchResultItems *resultList) const static void processLine(QStringView line, SearchResultItems *resultList,
const std::optional<QRegularExpression> &regExp, const QString &ref,
const FilePath &directory)
{ {
if (line.isEmpty()) if (line.isEmpty())
return; return;
@@ -94,9 +77,9 @@ public:
SearchResultItem result; SearchResultItem result;
const int lineSeparator = line.indexOf(QChar::Null); const int lineSeparator = line.indexOf(QChar::Null);
QStringView filePath = line.left(lineSeparator); QStringView filePath = line.left(lineSeparator);
if (!m_ref.isEmpty() && filePath.startsWith(m_ref)) if (!ref.isEmpty() && filePath.startsWith(ref))
filePath = filePath.mid(m_ref.length()); filePath = filePath.mid(ref.length());
result.setFilePath(m_directory.pathAppended(filePath.toString())); result.setFilePath(directory.pathAppended(filePath.toString()));
const int textSeparator = line.indexOf(QChar::Null, lineSeparator + 1); const int textSeparator = line.indexOf(QChar::Null, lineSeparator + 1);
const int lineNumber = line.mid(lineSeparator + 1, textSeparator - lineSeparator - 1).toInt(); const int lineNumber = line.mid(lineSeparator + 1, textSeparator - lineSeparator - 1).toInt();
QString text = line.mid(textSeparator + 1).toString(); QString text = line.mid(textSeparator + 1).toString();
@@ -111,8 +94,8 @@ public:
const int matchLength = matchEnd - matchTextStart; const int matchLength = matchEnd - matchTextStart;
Match match(matchStart, matchLength); Match match(matchStart, matchLength);
const QString matchText = text.mid(matchTextStart, matchLength); const QString matchText = text.mid(matchTextStart, matchLength);
if (m_parameters.flags & FindRegularExpression) if (regExp)
match.regexpCapturedTexts = m_regexp.match(matchText).capturedTexts(); match.regexpCapturedTexts = regExp->match(matchText).capturedTexts();
matches.append(match); matches.append(match);
text = text.left(matchStart) + matchText + text.mid(matchEnd + resetColor.size()); text = text.left(matchStart) + matchText + text.mid(matchEnd + resetColor.size());
} }
@@ -126,13 +109,15 @@ public:
} }
} }
int read(QPromise<SearchResultItems> &promise, const QString &input) static SearchResultItems parse(const QFuture<void> &future, const QString &input,
const std::optional<QRegularExpression> &regExp, const QString &ref,
const FilePath &directory)
{ {
SearchResultItems items; SearchResultItems items;
QStringView remainingInput(input); QStringView remainingInput(input);
while (true) { while (true) {
if (promise.isCanceled()) if (future.isCanceled())
return 0; return {};
if (remainingInput.isEmpty()) if (remainingInput.isEmpty())
break; break;
@@ -141,15 +126,22 @@ public:
if (line.isEmpty()) if (line.isEmpty())
continue; continue;
processLine(line, &items); processLine(line, &items, regExp, ref, directory);
} }
if (!items.isEmpty()) return items;
promise.addResult(items);
return items.count();
} }
void operator()(QPromise<SearchResultItems> &promise) static void runGitGrep(QPromise<SearchResultItems> &promise, const FileFindParameters &parameters)
{ {
const FilePath directory = FilePath::fromString(parameters.additionalParameters.toString());
const GitGrepParameters gitParameters
= parameters.searchEngineParameters.value<GitGrepParameters>();
const QString ref = gitParameters.ref.isEmpty() ? QString() : gitParameters.ref + ':';
const auto setupProcess = [&](Process &process) {
const FilePath vcsBinary = GitClient::instance()->vcsBinary();
const Environment environment = GitClient::instance()->processEnvironment();
QStringList arguments = { QStringList arguments = {
"-c", "color.grep.match=bold red", "-c", "color.grep.match=bold red",
"-c", "color.grep=always", "-c", "color.grep=always",
@@ -157,102 +149,42 @@ public:
"-c", "color.grep.lineNumber=", "-c", "color.grep.lineNumber=",
"grep", "-zn", "--no-full-name" "grep", "-zn", "--no-full-name"
}; };
if (!(m_parameters.flags & FindCaseSensitively)) if (!(parameters.flags & FindCaseSensitively))
arguments << "-i"; arguments << "-i";
if (m_parameters.flags & FindWholeWords) if (parameters.flags & FindWholeWords)
arguments << "-w"; arguments << "-w";
if (m_parameters.flags & FindRegularExpression) if (parameters.flags & FindRegularExpression)
arguments << "-P"; arguments << "-P";
else else
arguments << "-F"; arguments << "-F";
arguments << "-e" << m_parameters.text; arguments << "-e" << parameters.text;
GitGrepParameters params = m_parameters.searchEngineParameters.value<GitGrepParameters>(); if (gitParameters.recurseSubmodules)
if (params.recurseSubmodules)
arguments << "--recurse-submodules"; arguments << "--recurse-submodules";
if (!params.ref.isEmpty()) { if (!gitParameters.ref.isEmpty()) {
arguments << params.ref; arguments << gitParameters.ref;
m_ref = params.ref + ':';
} }
const QStringList filterArgs = const QStringList filterArgs =
m_parameters.nameFilters.isEmpty() ? QStringList("*") // needed for exclusion filters parameters.nameFilters.isEmpty() ? QStringList("*") // needed for exclusion filters
: m_parameters.nameFilters; : parameters.nameFilters;
const QStringList exclusionArgs = const QStringList exclusionArgs =
Utils::transform(m_parameters.exclusionFilters, [](const QString &filter) { Utils::transform(parameters.exclusionFilters, [](const QString &filter) {
return QString(":!" + filter); return QString(":!" + filter);
}); });
arguments << "--" << filterArgs << exclusionArgs; arguments << "--" << filterArgs << exclusionArgs;
QEventLoop loop; process.setEnvironment(environment);
process.setCommand({vcsBinary, arguments});
Process process; process.setWorkingDirectory(directory);
process.setEnvironment(m_environment);
process.setCommand({m_vcsBinary, arguments});
process.setWorkingDirectory(m_directory);
QStringList outputBuffer;
// The states transition exactly in this order:
enum State { BelowLimit, AboveLimit, Paused, Resumed };
State state = BelowLimit;
int reportedResultsCount = 0;
process.setStdOutCallback([this, &process, &loop, &promise, &state, &reportedResultsCount,
&outputBuffer](const QString &output) {
if (promise.isCanceled()) {
process.close();
loop.quit();
return;
}
// The SearchResultWidget is going to pause the search anyway, so start buffering
// the output.
if (state == AboveLimit || state == Paused) {
outputBuffer.append(output);
} else {
reportedResultsCount += read(promise, output);
if (state == BelowLimit && reportedResultsCount > 200000)
state = AboveLimit;
}
});
QObject::connect(&process, &Process::done, &loop, [&loop, &promise, &state] {
if (state == BelowLimit || state == Resumed || promise.isCanceled())
loop.quit();
});
process.start();
if (process.state() == QProcess::NotRunning)
return;
QFutureWatcher<void> watcher;
QFuture<void> future(promise.future());
QObject::connect(&watcher, &QFutureWatcherBase::canceled, &loop, [&process, &loop] {
process.close();
loop.quit();
});
QObject::connect(&watcher, &QFutureWatcherBase::paused, &loop, [&state] { state = Paused; });
QObject::connect(&watcher, &QFutureWatcherBase::resumed, &loop,
[this, &process, &loop, &promise, &state, &outputBuffer] {
state = Resumed;
for (const QString &output : outputBuffer) {
if (promise.isCanceled()) {
process.close();
loop.quit();
}
read(promise, output);
}
outputBuffer.clear();
if (process.state() == QProcess::NotRunning)
loop.quit();
});
watcher.setFuture(future);
loop.exec(QEventLoop::ExcludeUserInputEvents);
}
private:
FilePath m_vcsBinary;
FilePath m_directory;
QString m_ref;
TextEditor::FileFindParameters m_parameters;
Environment m_environment;
QRegularExpression m_regexp;
}; };
const auto outputParser = [&ref, &directory](const QFuture<void> &future, const QString &input,
const std::optional<QRegularExpression> &regExp) {
return parse(future, input, regExp, ref, directory);
};
TextEditor::searchInProcessOutput(promise, parameters, setupProcess, outputParser);
}
static bool isGitDirectory(const FilePath &path) static bool isGitDirectory(const FilePath &path)
{ {
static IVersionControl *gitVc = VcsManager::versionControl(VcsBase::Constants::VCS_ID_GIT); static IVersionControl *gitVc = VcsManager::versionControl(VcsBase::Constants::VCS_ID_GIT);
@@ -274,18 +206,16 @@ GitGrep::GitGrep(GitClient *client)
m_treeLineEdit->setValidator(new QRegularExpressionValidator(refExpression, this)); m_treeLineEdit->setValidator(new QRegularExpressionValidator(refExpression, this));
layout->addWidget(m_treeLineEdit); layout->addWidget(m_treeLineEdit);
// asynchronously check git version, add "recurse submodules" option if available // asynchronously check git version, add "recurse submodules" option if available
Utils::onResultReady(client->gitVersion(), Utils::onResultReady(client->gitVersion(), this,
this,
[this, pLayout = QPointer<QHBoxLayout>(layout)](unsigned version) { [this, pLayout = QPointer<QHBoxLayout>(layout)](unsigned version) {
if (version >= 0x021300 && pLayout) { if (version >= 0x021300 && pLayout) {
m_recurseSubmodules = new QCheckBox(Tr::tr("Recurse submodules")); m_recurseSubmodules = new QCheckBox(Tr::tr("Recurse submodules"));
pLayout->addWidget(m_recurseSubmodules); pLayout->addWidget(m_recurseSubmodules);
} }
}); });
TextEditor::FindInFiles *findInFiles = TextEditor::FindInFiles::instance(); FindInFiles *findInFiles = FindInFiles::instance();
QTC_ASSERT(findInFiles, return); QTC_ASSERT(findInFiles, return);
connect(findInFiles, &TextEditor::FindInFiles::pathChanged, connect(findInFiles, &FindInFiles::pathChanged, m_widget, [this](const FilePath &path) {
m_widget, [this](const FilePath &path) {
setEnabled(isGitDirectory(path)); setEnabled(isGitDirectory(path));
}); });
connect(this, &SearchEngine::enabledChanged, m_widget, &QWidget::setEnabled); connect(this, &SearchEngine::enabledChanged, m_widget, &QWidget::setEnabled);
@@ -334,14 +264,14 @@ void GitGrep::writeSettings(QSettings *settings) const
settings->setValue(GitGrepRef, m_treeLineEdit->text()); settings->setValue(GitGrepRef, m_treeLineEdit->text());
} }
QFuture<SearchResultItems> GitGrep::executeSearch(const TextEditor::FileFindParameters &parameters, QFuture<SearchResultItems> GitGrep::executeSearch(const FileFindParameters &parameters,
TextEditor::BaseFileFind * /*baseFileFind*/) BaseFileFind *)
{ {
return Utils::asyncRun(GitGrepRunner(parameters)); return Utils::asyncRun(runGitGrep, parameters);
} }
IEditor *GitGrep::openEditor(const SearchResultItem &item, IEditor *GitGrep::openEditor(const SearchResultItem &item,
const TextEditor::FileFindParameters &parameters) const FileFindParameters &parameters)
{ {
const GitGrepParameters params = parameters.searchEngineParameters.value<GitGrepParameters>(); const GitGrepParameters params = parameters.searchEngineParameters.value<GitGrepParameters>();
const QStringList &itemPath = item.path(); const QStringList &itemPath = item.path();

View File

@@ -9,10 +9,7 @@ QT_BEGIN_NAMESPACE
class QCheckBox; class QCheckBox;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace Utils { namespace Utils { class FancyLineEdit; }
class FancyLineEdit;
class SearchResultItem;
}
namespace Git::Internal { namespace Git::Internal {