DiffEditorController: Make it possible to setup task tree

Instead of reloader function. Use task tree for ShowController.
Make diff processing a part of task tree.

Change-Id: I732d0a14eaf8de7ce0d1be891fb4700b22ea24b7
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Jarek Kobus
2022-12-12 14:40:56 +01:00
parent 041c59e90f
commit eef9cb458b
7 changed files with 207 additions and 123 deletions

View File

@@ -8,6 +8,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/taskprogress.h>
#include <utils/qtcassert.h>
@@ -122,12 +123,22 @@ void DiffEditorController::requestReload()
{
m_isReloading = true;
m_document->beginReload();
QTC_ASSERT(m_reloader, reloadFinished(false); return);
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;
}

View File

@@ -7,6 +7,7 @@
#include "diffutils.h"
#include <utils/filepath.h>
#include <utils/tasktree.h>
#include <QObject>
@@ -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<void ()> &reloader);
@@ -72,7 +76,10 @@ protected:
private:
Internal::DiffEditorDocument *const m_document;
bool m_isReloading = false;
QString m_displayName;
std::function<void()> m_reloader;
std::unique_ptr<Utils::TaskTree> m_taskTree;
virtual Utils::Tasking::Group reloadRecipe() { return {}; } // TODO: make pure abstract
friend class Internal::DiffEditorDocument;
};

View File

@@ -19,6 +19,7 @@
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
#include <utils/asynctask.h>
#include <utils/algorithm.h>
#include <utils/checkablemessagebox.h>
#include <utils/commandline.h>
@@ -61,8 +62,6 @@
#include <QToolButton>
#include <QTextCodec>
#include <vector>
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;
};
Tasking::Group ShowController::reloadRecipe()
{
static const QString busyMessage = Tr::tr("<resolving>");
using namespace Tasking;
struct ReloadStorage {
bool m_postProcessDescription = false;
QString m_commit;
QString m_header;
QString m_body;
QString m_precedes;
std::vector<QString> m_follows;
QList<QtcProcess *> m_commands;
};
QStringList m_follows;
void ShowController::processCommandOutput(const QString &output)
{
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<QStringList>() << addConfigurationArguments(args));
} else if (m_state == GettingDiff) {
m_state = Idle;
GitBaseDiffEditorController::processCommandOutput(output);
}
}
QString m_diffOutput;
};
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("<resolving>");
m_follows.push_back(m_precedes);
updateDescription();
const QString commit = modText.mid(7, 8);
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('~');
if (tilde != -1)
m_precedes.truncate(tilde);
if (m_precedes.endsWith("^0"))
m_precedes.chop(2);
updateDescription();
});
precedesProcess->start();
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();
}
}
void ShowController::updateDescription()
{
QString desc = m_header;
if (!m_precedes.isEmpty())
desc.append("Precedes: " + m_precedes + '\n');
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 : m_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' + m_body);
desc.append('\n' + storage.m_body);
setDescription(desc);
}
};
void ShowController::abortCommands()
{
qDeleteAll(m_commands);
m_commands.clear();
const TreeStorage<ReloadStorage> 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)
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);
};
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());
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);
};
using namespace std::placeholders;
QList<TaskItem> 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);
};
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<QList<FileData>> &async) {
setupDiffProcessor(async, storage->m_diffOutput);
};
const auto onProcessDiffDone = [this, storage](const AsyncTask<QList<FileData>> &async) {
setDiffFiles(async.result(), workingDirectory(), startupFile());
};
const auto onProcessDiffError = [this, storage](const AsyncTask<QList<FileData>> &) {
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<QList<FileData>>(setupProcessDiff, onProcessDiffDone, onProcessDiffError)
}
};
return root;
}
///////////////////////////////

View File

@@ -4,13 +4,17 @@
#include "vcsbasediffeditorcontroller.h"
#include "vcsbaseclient.h"
#include "vcscommand.h"
#include "vcsplugin.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <utils/asynctask.h>
#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/futuresynchronizer.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/runextensions.h>
#include <QPointer>
@@ -21,8 +25,7 @@ using namespace Utils;
namespace VcsBase {
static void readPatch(QFutureInterface<QList<FileData>> &futureInterface,
const QString &patch)
static void readPatch(QFutureInterface<QList<FileData>> &futureInterface, const QString &patch)
{
bool ok;
const QList<FileData> &fileDataList = DiffUtils::readPatch(patch, &ok, &futureInterface);
@@ -48,7 +51,6 @@ public:
FilePath m_vcsBinary;
int m_vscTimeoutS;
QString m_startupFile;
QString m_displayName;
QPointer<VcsCommand> m_command;
QFutureWatcher<QList<FileData>> *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<QList<FileData>> &processor,
const QString &patch) const
{
processor.setAsyncCallData(readPatch, patch);
processor.setFutureSynchronizer(Internal::VcsPlugin::futureSynchronizer());
}
void VcsBaseDiffEditorController::runCommand(const QList<QStringList> &args, RunFlags flags, QTextCodec *codec)
{
// Cancel the possible ongoing reload without the commandFinished() nor
@@ -144,7 +160,7 @@ void VcsBaseDiffEditorController::runCommand(const QList<QStringList> &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;

View File

@@ -15,8 +15,10 @@ QT_END_NAMESPACE
namespace Core { class IDocument; }
namespace Utils {
template <typename R> 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<QList<DiffEditor::FileData>> &processor,
const QString &patch) const;
void runCommand(const QList<QStringList> &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;

View File

@@ -22,6 +22,7 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projecttree.h>
#include <utils/futuresynchronizer.h>
#include <utils/macroexpander.h>
#include <utils/qtcassert.h>
@@ -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()
{

View File

@@ -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.