diff --git a/src/plugins/diffeditor/diffeditorcontroller.cpp b/src/plugins/diffeditor/diffeditorcontroller.cpp index ad9e0a9c2d1..a762cbb333a 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.cpp +++ b/src/plugins/diffeditor/diffeditorcontroller.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -122,12 +123,22 @@ void DiffEditorController::requestReload() { m_isReloading = true; m_document->beginReload(); - QTC_ASSERT(m_reloader, reloadFinished(false); return); - m_reloader(); + if (m_reloader) { + m_reloader(); + return; + } + m_taskTree.reset(new TaskTree(reloadRecipe())); + connect(m_taskTree.get(), &TaskTree::done, this, [this] { reloadFinished(true); }); + connect(m_taskTree.get(), &TaskTree::errorOccurred, this, [this] { reloadFinished(false); }); + auto progress = new TaskProgress(m_taskTree.get()); + progress->setDisplayName(displayName()); + m_taskTree->start(); } void DiffEditorController::reloadFinished(bool success) { + if (m_taskTree) + m_taskTree.release()->deleteLater(); m_document->endReload(success); m_isReloading = false; } diff --git a/src/plugins/diffeditor/diffeditorcontroller.h b/src/plugins/diffeditor/diffeditorcontroller.h index b94fdc7314c..48a0f51df91 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.h +++ b/src/plugins/diffeditor/diffeditorcontroller.h @@ -7,6 +7,7 @@ #include "diffutils.h" #include +#include #include @@ -57,6 +58,9 @@ signals: const ChunkSelection &selection); protected: + void setDisplayName(const QString &name) { m_displayName = name; } + QString displayName() const { return m_displayName; } + // reloadFinished() should be called inside the reloader (for synchronous reload) // or later (for asynchronous reload) void setReloader(const std::function &reloader); @@ -72,7 +76,10 @@ protected: private: Internal::DiffEditorDocument *const m_document; bool m_isReloading = false; + QString m_displayName; std::function m_reloader; + std::unique_ptr m_taskTree; + virtual Utils::Tasking::Group reloadRecipe() { return {}; } // TODO: make pure abstract friend class Internal::DiffEditorDocument; }; diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index d4a1cd3f82e..b3eb3a57cf7 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -61,8 +62,6 @@ #include #include -#include - const char GIT_DIRECTORY[] = ".git"; const char HEAD[] = "HEAD"; const char CHERRY_PICK_HEAD[] = "CHERRY_PICK_HEAD"; @@ -424,131 +423,171 @@ class ShowController : public GitBaseDiffEditorController { Q_OBJECT public: - ShowController(IDocument *document, const QString &id) : - GitBaseDiffEditorController(document, {}, {}), - m_id(id), - m_state(Idle) + ShowController(IDocument *document, const QString &id) + : GitBaseDiffEditorController(document, {}, {}) + , m_id(id) { setDisplayName("Git Show"); - setReloader([this] { - m_state = GettingDescription; - const QStringList args = {"show", "-s", noColorOption, showFormatC, m_id}; - runCommand({args}, m_instance->encoding(workingDirectory(), "i18n.commitEncoding")); - setStartupFile(VcsBase::source(this->document())); - }); } - ~ShowController() - { - abortCommands(); - } - - void processCommandOutput(const QString &output) override; private: - void processDescription(const QString &output); - void updateDescription(); - void abortCommands(); + Tasking::Group reloadRecipe() final; const QString m_id; - enum State { Idle, GettingDescription, GettingDiff }; - State m_state; - QString m_header; - QString m_body; - QString m_precedes; - std::vector m_follows; - QList m_commands; }; -void ShowController::processCommandOutput(const QString &output) +Tasking::Group ShowController::reloadRecipe() { - QTC_ASSERT(m_state != Idle, return); - if (m_state == GettingDescription) { - processDescription(output); - // stage 2 - m_state = GettingDiff; - const QStringList args = {"show", "--format=format:", // omit header, already generated - noColorOption, decorateOption, m_id}; - runCommand(QList() << addConfigurationArguments(args)); - } else if (m_state == GettingDiff) { - m_state = Idle; - GitBaseDiffEditorController::processCommandOutput(output); - } -} + static const QString busyMessage = Tr::tr(""); + using namespace Tasking; -void ShowController::processDescription(const QString &output) -{ - abortCommands(); - if (!output.startsWith("commit ")) { - setDescription(output); - return; - } - QString modText = output; - int lastHeaderLine = modText.indexOf("\n\n") + 1; - m_header = output.left(lastHeaderLine) + Constants::EXPAND_BRANCHES + '\n'; - m_body = output.mid(lastHeaderLine + 1); - m_precedes = Tr::tr(""); - m_follows.push_back(m_precedes); - updateDescription(); - const QString commit = modText.mid(7, 8); + struct ReloadStorage { + bool m_postProcessDescription = false; + QString m_commit; - QtcProcess *precedesProcess = new QtcProcess(this); - m_commands.append(precedesProcess); - precedesProcess->setEnvironment(m_instance->processEnvironment()); - precedesProcess->setCommand({m_instance->vcsBinary(), {"describe", "--contains", commit}}); - precedesProcess->setWorkingDirectory(workingDirectory()); - connect(precedesProcess, &QtcProcess::done, this, [this, precedesProcess] { - m_precedes = precedesProcess->result() == ProcessResult::FinishedWithSuccess - ? precedesProcess->cleanedStdOut().trimmed() : QString(); - const int tilde = m_precedes.indexOf('~'); + QString m_header; + QString m_body; + QString m_precedes; + QStringList m_follows; + + QString m_diffOutput; + }; + + const auto updateDescription = [this](const ReloadStorage &storage) { + QString desc = storage.m_header; + if (!storage.m_precedes.isEmpty()) + desc.append("Precedes: " + storage.m_precedes + '\n'); + QStringList follows; + for (const QString &str : storage.m_follows) { + if (!str.isEmpty()) + follows.append(str); + } + if (!follows.isEmpty()) + desc.append("Follows: " + follows.join(", ") + '\n'); + desc.append('\n' + storage.m_body); + setDescription(desc); + }; + + const TreeStorage storage; + + const auto setupDescription = [this](QtcProcess &process) { + process.setCodec(m_instance->encoding(workingDirectory(), "i18n.commitEncoding")); + setStartupFile(VcsBase::source(this->document())); + setupCommand(process, {"show", "-s", noColorOption, showFormatC, m_id}); + VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); + setDescription(Tr::tr("Waiting for data...")); + }; + const auto onDescriptionDone = [this, storage, updateDescription](const QtcProcess &process) { + ReloadStorage *data = storage.activeStorage(); + const QString output = process.cleanedStdOut(); + data->m_postProcessDescription = output.startsWith("commit "); + if (!data->m_postProcessDescription) { + setDescription(output); + return; + } + const int lastHeaderLine = output.indexOf("\n\n") + 1; + data->m_commit = output.mid(7, 12); + data->m_header = output.left(lastHeaderLine) + Constants::EXPAND_BRANCHES + '\n'; + data->m_body = output.mid(lastHeaderLine + 1); + updateDescription(*data); + }; + + const auto desciptionDetailsSetup = [storage] { + if (!storage->m_postProcessDescription) + return GroupConfig{GroupAction::StopWithDone}; + return GroupConfig(); + }; + + const auto setupPrecedes = [this, storage](QtcProcess &process) { + storage->m_precedes = busyMessage; + setupCommand(process, {"describe", "--contains", storage->m_commit}); + }; + const auto onPrecedesDone = [storage, updateDescription](const QtcProcess &process) { + ReloadStorage *data = storage.activeStorage(); + data->m_precedes = process.cleanedStdOut().trimmed(); + const int tilde = data->m_precedes.indexOf('~'); if (tilde != -1) - m_precedes.truncate(tilde); - if (m_precedes.endsWith("^0")) - m_precedes.chop(2); - updateDescription(); - }); - precedesProcess->start(); + data->m_precedes.truncate(tilde); + if (data->m_precedes.endsWith("^0")) + data->m_precedes.chop(2); + updateDescription(*data); + }; + const auto onPrecedesError = [storage, updateDescription](const QtcProcess &) { + ReloadStorage *data = storage.activeStorage(); + data->m_precedes.clear(); + updateDescription(*data); + }; - QStringList parents; - QString errorMessage; - m_instance->synchronousParentRevisions(workingDirectory(), commit, &parents, &errorMessage); - m_follows.resize(parents.size()); - for (int i = 0, total = parents.size(); i < total; ++i) { - QtcProcess *followsProcess = new QtcProcess(this); - m_commands.append(followsProcess); - followsProcess->setEnvironment(m_instance->processEnvironment()); - followsProcess->setCommand({m_instance->vcsBinary(), - {"describe", "--tags", "--abbrev=0", parents[i]}}); - followsProcess->setWorkingDirectory(workingDirectory()); - connect(followsProcess, &QtcProcess::done, this, [this, followsProcess, i] { - if (followsProcess->result() != ProcessResult::FinishedWithSuccess) - return; - m_follows[i] = followsProcess->cleanedStdOut().trimmed(); - updateDescription(); - }); - followsProcess->start(); - } -} + const auto setupFollows = [this, storage, updateDescription](TaskTree &taskTree) { + ReloadStorage *data = storage.activeStorage(); + QStringList parents; + QString errorMessage; + // TODO: it's trivial now to call below asynchonously, too + m_instance->synchronousParentRevisions(workingDirectory(), data->m_commit, + &parents, &errorMessage); + data->m_follows = {busyMessage}; + data->m_follows.resize(parents.size()); -void ShowController::updateDescription() -{ - QString desc = m_header; - if (!m_precedes.isEmpty()) - desc.append("Precedes: " + m_precedes + '\n'); - QStringList follows; - for (const QString &str : m_follows) { - if (!str.isEmpty()) - follows.append(str); - } - if (!follows.isEmpty()) - desc.append("Follows: " + follows.join(", ") + '\n'); - desc.append('\n' + m_body); + const auto setupFollow = [this](QtcProcess &process, const QString &parent) { + setupCommand(process, {"describe", "--tags", "--abbrev=0", parent}); + }; + const auto onFollowDone = [data, updateDescription](const QtcProcess &process, int index) { + data->m_follows[index] = process.cleanedStdOut().trimmed(); + updateDescription(*data); + }; + const auto onFollowsError = [data, updateDescription] { + data->m_follows.clear(); + updateDescription(*data); + }; - setDescription(desc); -} + using namespace std::placeholders; + QList tasks {parallel, continueOnDone, OnGroupError(onFollowsError)}; + for (int i = 0, total = parents.size(); i < total; ++i) { + tasks.append(Process(std::bind(setupFollow, _1, parents.at(i)), + std::bind(onFollowDone, _1, i))); + } + taskTree.setupRoot(tasks); + }; -void ShowController::abortCommands() -{ - qDeleteAll(m_commands); - m_commands.clear(); + const auto setupDiff = [this](QtcProcess &process) { + setupCommand(process, addConfigurationArguments( + {"show", "--format=format:", // omit header, already generated + noColorOption, decorateOption, m_id})); + VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine()); + }; + const auto onDiffDone = [storage](const QtcProcess &process) { + storage->m_diffOutput = process.cleanedStdOut(); + }; + + const auto setupProcessDiff = [this, storage](AsyncTask> &async) { + setupDiffProcessor(async, storage->m_diffOutput); + }; + const auto onProcessDiffDone = [this, storage](const AsyncTask> &async) { + setDiffFiles(async.result(), workingDirectory(), startupFile()); + }; + const auto onProcessDiffError = [this, storage](const AsyncTask> &) { + setDiffFiles({}, workingDirectory(), startupFile()); + }; + + const Group root { + Storage(storage), + parallel, + Group { + optional, + Process(setupDescription, onDescriptionDone), + Group { + parallel, + optional, + DynamicSetup(desciptionDetailsSetup), + Process(setupPrecedes, onPrecedesDone, onPrecedesError), + Tree(setupFollows) + } + }, + Group { + Process(setupDiff, onDiffDone), + Async>(setupProcessDiff, onProcessDiffDone, onProcessDiffError) + } + }; + return root; } /////////////////////////////// diff --git a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp index b1c1ecc3bc2..ec5e8404fdb 100644 --- a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp +++ b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp @@ -4,13 +4,17 @@ #include "vcsbasediffeditorcontroller.h" #include "vcsbaseclient.h" #include "vcscommand.h" +#include "vcsplugin.h" #include #include +#include #include #include +#include #include +#include #include #include @@ -21,8 +25,7 @@ using namespace Utils; namespace VcsBase { -static void readPatch(QFutureInterface> &futureInterface, - const QString &patch) +static void readPatch(QFutureInterface> &futureInterface, const QString &patch) { bool ok; const QList &fileDataList = DiffUtils::readPatch(patch, &ok, &futureInterface); @@ -48,7 +51,6 @@ public: FilePath m_vcsBinary; int m_vscTimeoutS; QString m_startupFile; - QString m_displayName; QPointer m_command; QFutureWatcher> *m_processWatcher = nullptr; }; @@ -134,6 +136,20 @@ VcsBaseDiffEditorController::~VcsBaseDiffEditorController() delete d; } +void VcsBaseDiffEditorController::setupCommand(QtcProcess &process, const QStringList &args) const +{ + process.setEnvironment(d->m_processEnvironment); + process.setWorkingDirectory(workingDirectory()); + process.setCommand({d->m_vcsBinary, args}); +} + +void VcsBaseDiffEditorController::setupDiffProcessor(AsyncTask> &processor, + const QString &patch) const +{ + processor.setAsyncCallData(readPatch, patch); + processor.setFutureSynchronizer(Internal::VcsPlugin::futureSynchronizer()); +} + void VcsBaseDiffEditorController::runCommand(const QList &args, RunFlags flags, QTextCodec *codec) { // Cancel the possible ongoing reload without the commandFinished() nor @@ -144,7 +160,7 @@ void VcsBaseDiffEditorController::runCommand(const QList &args, Run d->cancelReload(); d->m_command = VcsBaseClient::createVcsCommand(workingDirectory(), d->m_processEnvironment); - d->m_command->setDisplayName(d->m_displayName); + d->m_command->setDisplayName(displayName()); d->m_command->setCodec(codec ? codec : EditorManager::defaultTextCodec()); connect(d->m_command.data(), &VcsCommand::done, this, [this] { d->commandFinished(d->m_command->result() == ProcessResult::FinishedWithSuccess); @@ -180,11 +196,6 @@ QString VcsBaseDiffEditorController::startupFile() const return d->m_startupFile; } -void VcsBaseDiffEditorController::setDisplayName(const QString &displayName) -{ - d->m_displayName = displayName; -} - void VcsBase::VcsBaseDiffEditorController::setWorkingDirectory(const FilePath &workingDir) { d->m_directory = workingDir; diff --git a/src/plugins/vcsbase/vcsbasediffeditorcontroller.h b/src/plugins/vcsbase/vcsbasediffeditorcontroller.h index b5c1f2b86bc..6e9d358bdea 100644 --- a/src/plugins/vcsbase/vcsbasediffeditorcontroller.h +++ b/src/plugins/vcsbase/vcsbasediffeditorcontroller.h @@ -15,8 +15,10 @@ QT_END_NAMESPACE namespace Core { class IDocument; } namespace Utils { +template class AsyncTask; class Environment; class FilePath; +class QtcProcess; } // Utils namespace VcsBase { @@ -37,13 +39,15 @@ public: void setWorkingDirectory(const Utils::FilePath &workingDir); protected: + void setupCommand(Utils::QtcProcess &process, const QStringList &args) const; + void setupDiffProcessor(Utils::AsyncTask> &processor, + const QString &patch) const; void runCommand(const QList &args, RunFlags flags, QTextCodec *codec = nullptr); virtual void processCommandOutput(const QString &output); Utils::FilePath workingDirectory() const; void setStartupFile(const QString &startupFile); QString startupFile() const; - void setDisplayName(const QString &displayName); private: friend class VcsBaseDiffEditorControllerPrivate; diff --git a/src/plugins/vcsbase/vcsplugin.cpp b/src/plugins/vcsbase/vcsplugin.cpp index 29f84088c03..69cab6a8e62 100644 --- a/src/plugins/vcsbase/vcsplugin.cpp +++ b/src/plugins/vcsbase/vcsplugin.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -39,6 +40,7 @@ class VcsPluginPrivate public: CommonOptionsPage m_settingsPage; QStandardItemModel *m_nickNameModel = nullptr; + FutureSynchronizer m_futureSynchronizer; }; static VcsPlugin *m_instance = nullptr; @@ -125,6 +127,12 @@ CommonVcsSettings &VcsPlugin::settings() const return d->m_settingsPage.settings(); } +FutureSynchronizer *VcsPlugin::futureSynchronizer() +{ + QTC_ASSERT(m_instance, return nullptr); + return &m_instance->d->m_futureSynchronizer; +} + /* Delayed creation/update of the nick name model. */ QStandardItemModel *VcsPlugin::nickNameModel() { diff --git a/src/plugins/vcsbase/vcsplugin.h b/src/plugins/vcsbase/vcsplugin.h index 162d090ed08..e420237974c 100644 --- a/src/plugins/vcsbase/vcsplugin.h +++ b/src/plugins/vcsbase/vcsplugin.h @@ -11,6 +11,8 @@ QT_BEGIN_NAMESPACE class QStandardItemModel; QT_END_NAMESPACE +namespace Utils { class FutureSynchronizer; } + namespace VcsBase { class VcsBaseSubmitEditor; @@ -34,6 +36,8 @@ public: CommonVcsSettings &settings() const; + static Utils::FutureSynchronizer *futureSynchronizer(); + // Model of user nick names used for the submit // editor. Stored centrally here to achieve delayed // initialization and updating on settings change.