diff --git a/src/plugins/texteditor/basefilefind.cpp b/src/plugins/texteditor/basefilefind.cpp index 8a75a27910c..99f37d05eac 100644 --- a/src/plugins/texteditor/basefilefind.cpp +++ b/src/plugins/texteditor/basefilefind.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,98 @@ using namespace Utils; using namespace Core; namespace TextEditor { + +static std::optional regExpFromParameters(const FileFindParameters ¶meters) +{ + if (!(parameters.flags & FindRegularExpression)) + return {}; + + const QRegularExpression::PatternOptions options = parameters.flags & FindCaseSensitively + ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption; + QRegularExpression regExp; + regExp.setPattern(parameters.text); + regExp.setPatternOptions(options); + return regExp; +} + +void searchInProcessOutput(QPromise &promise, + const FileFindParameters ¶meters, + const ProcessSetupHandler &processSetupHandler, + const ProcessOutputParser &processOutputParser) +{ + if (promise.isCanceled()) + return; + + QEventLoop loop; + + Process process; + processSetupHandler(process); + + const std::optional regExp = regExpFromParameters(parameters); + QStringList outputBuffer; + // The states transition exactly in this order: + enum State { BelowLimit, AboveLimit, Paused, Resumed }; + State state = BelowLimit; + int reportedItemsCount = 0; + QFuture future(promise.future()); + process.setStdOutCallback([&](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 { + const SearchResultItems items = processOutputParser(future, output, regExp); + if (!items.isEmpty()) + promise.addResult(items); + reportedItemsCount += items.size(); + if (state == BelowLimit && reportedItemsCount > 200000) + state = AboveLimit; + } + }); + QObject::connect(&process, &Process::done, &loop, [&loop, &promise, &state] { + if (state == BelowLimit || state == Resumed || promise.isCanceled()) + loop.quit(); + }); + + if (promise.isCanceled()) + return; + + process.start(); + if (process.state() == QProcess::NotRunning) + return; + + QFutureWatcher watcher; + 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, [&] { + state = Resumed; + for (const QString &output : outputBuffer) { + if (promise.isCanceled()) { + process.close(); + loop.quit(); + } + const SearchResultItems items = processOutputParser(future, output, regExp); + if (!items.isEmpty()) + promise.addResult(items); + } + outputBuffer.clear(); + if (process.state() == QProcess::NotRunning) + loop.quit(); + }); + watcher.setFuture(future); + if (promise.isCanceled()) + return; + loop.exec(QEventLoop::ExcludeUserInputEvents); +} + namespace Internal { class InternalEngine : public TextEditor::SearchEngine diff --git a/src/plugins/texteditor/basefilefind.h b/src/plugins/texteditor/basefilefind.h index b564b35c0c1..c8555f03deb 100644 --- a/src/plugins/texteditor/basefilefind.h +++ b/src/plugins/texteditor/basefilefind.h @@ -20,6 +20,7 @@ class SearchResult; namespace Utils { class FileIterator; +class Process; } namespace TextEditor { @@ -41,6 +42,16 @@ public: Core::FindFlags flags; }; +using ProcessSetupHandler = std::function; +using ProcessOutputParser = std::function &, const QString &, const std::optional &)>; + +// Call it from a non-main thread only, it's a blocking invocation. +void TEXTEDITOR_EXPORT searchInProcessOutput(QPromise &promise, + const FileFindParameters ¶meters, + const ProcessSetupHandler &processSetupHandler, + const ProcessOutputParser &processOutputParser); + class BaseFileFind; class TEXTEDITOR_EXPORT SearchEngine : public QObject