forked from qt-creator/qt-creator
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:
@@ -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,27 +55,8 @@ static QStringView nextLine(QStringView *remainingInput)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GitGrepRunner
|
struct Match
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
GitGrepRunner(const TextEditor::FileFindParameters ¶meters)
|
|
||||||
: 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
|
|
||||||
{
|
|
||||||
Match() = default;
|
Match() = default;
|
||||||
Match(int start, int length) :
|
Match(int start, int length) :
|
||||||
matchStart(start), matchLength(length) {}
|
matchStart(start), matchLength(length) {}
|
||||||
@@ -83,10 +64,12 @@ public:
|
|||||||
int matchStart = 0;
|
int matchStart = 0;
|
||||||
int matchLength = 0;
|
int matchLength = 0;
|
||||||
QStringList regexpCapturedTexts;
|
QStringList regexpCapturedTexts;
|
||||||
};
|
};
|
||||||
|
|
||||||
void processLine(QStringView line, SearchResultItems *resultList) const
|
static void processLine(QStringView line, SearchResultItems *resultList,
|
||||||
{
|
const std::optional<QRegularExpression> ®Exp, const QString &ref,
|
||||||
|
const FilePath &directory)
|
||||||
|
{
|
||||||
if (line.isEmpty())
|
if (line.isEmpty())
|
||||||
return;
|
return;
|
||||||
static const QLatin1String boldRed("\x1b[1;31m");
|
static const QLatin1String boldRed("\x1b[1;31m");
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
@@ -124,15 +107,17 @@ public:
|
|||||||
result.setUseTextEditorFont(true);
|
result.setUseTextEditorFont(true);
|
||||||
resultList->append(result);
|
resultList->append(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int read(QPromise<SearchResultItems> &promise, const QString &input)
|
static SearchResultItems parse(const QFuture<void> &future, const QString &input,
|
||||||
{
|
const std::optional<QRegularExpression> ®Exp, 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())
|
|
||||||
promise.addResult(items);
|
|
||||||
return items.count();
|
|
||||||
}
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runGitGrep(QPromise<SearchResultItems> &promise, const FileFindParameters ¶meters)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
void operator()(QPromise<SearchResultItems> &promise)
|
|
||||||
{
|
|
||||||
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,101 +149,41 @@ 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.setWorkingDirectory(directory);
|
||||||
|
};
|
||||||
|
|
||||||
Process process;
|
const auto outputParser = [&ref, &directory](const QFuture<void> &future, const QString &input,
|
||||||
process.setEnvironment(m_environment);
|
const std::optional<QRegularExpression> ®Exp) {
|
||||||
process.setCommand({m_vcsBinary, arguments});
|
return parse(future, input, regExp, ref, directory);
|
||||||
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();
|
TextEditor::searchInProcessOutput(promise, parameters, setupProcess, outputParser);
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool isGitDirectory(const FilePath &path)
|
static bool isGitDirectory(const FilePath &path)
|
||||||
{
|
{
|
||||||
@@ -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 ¶meters,
|
QFuture<SearchResultItems> GitGrep::executeSearch(const FileFindParameters ¶meters,
|
||||||
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 ¶meters)
|
const FileFindParameters ¶meters)
|
||||||
{
|
{
|
||||||
const GitGrepParameters params = parameters.searchEngineParameters.value<GitGrepParameters>();
|
const GitGrepParameters params = parameters.searchEngineParameters.value<GitGrepParameters>();
|
||||||
const QStringList &itemPath = item.path();
|
const QStringList &itemPath = item.path();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user