SideBySideDiffEditor: Move showing diff into separate thread

Change-Id: I8b0a4835cf6f51e4acfd483dcfc7b94585c64bf5
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Jarek Kobus
2022-09-28 11:32:13 +02:00
parent 83be3443de
commit 53fc68bc08
3 changed files with 134 additions and 28 deletions

View File

@@ -5,6 +5,7 @@
#include "selectabletexteditorwidget.h" #include "selectabletexteditorwidget.h"
#include "diffeditorconstants.h" #include "diffeditorconstants.h"
#include "diffeditordocument.h" #include "diffeditordocument.h"
#include "diffeditorplugin.h"
#include "diffutils.h" #include "diffutils.h"
#include <QMenu> #include <QMenu>
@@ -22,7 +23,9 @@
#include <coreplugin/icore.h> #include <coreplugin/icore.h>
#include <coreplugin/find/highlightscrollbarcontroller.h> #include <coreplugin/find/highlightscrollbarcontroller.h>
#include <coreplugin/minisplitter.h> #include <coreplugin/minisplitter.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <utils/runextensions.h>
#include <utils/tooltip/tooltip.h> #include <utils/tooltip/tooltip.h>
using namespace Core; using namespace Core;
@@ -876,6 +879,14 @@ SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent)
Core::ICore::addContextObject(rightContext); Core::ICore::addContextObject(rightContext);
} }
SideBySideDiffEditorWidget::~SideBySideDiffEditorWidget()
{
if (m_watcher) {
m_watcher->cancel();
DiffEditorPlugin::addFuture(m_watcher->future());
}
}
TextEditorWidget *SideBySideDiffEditorWidget::leftEditorWidget() const TextEditorWidget *SideBySideDiffEditorWidget::leftEditorWidget() const
{ {
return m_leftEditor; return m_leftEditor;
@@ -907,13 +918,19 @@ void SideBySideDiffEditorWidget::clear(const QString &message)
setDiff({}); setDiff({});
m_leftEditor->clearAll(message); m_leftEditor->clearAll(message);
m_rightEditor->clearAll(message); m_rightEditor->clearAll(message);
if (m_watcher) {
m_watcher->cancel();
DiffEditorPlugin::addFuture(m_watcher->future());
m_watcher.reset();
m_controller.setBusyShowing(false);
}
} }
void SideBySideDiffEditorWidget::setDiff(const QList<FileData> &diffFileList) void SideBySideDiffEditorWidget::setDiff(const QList<FileData> &diffFileList)
{ {
const GuardLocker locker(m_controller.m_ignoreChanges); const GuardLocker locker(m_controller.m_ignoreChanges);
m_leftEditor->clear(); m_leftEditor->clearAll(tr("Waiting for data..."));
m_rightEditor->clear(); m_rightEditor->clearAll(tr("Waiting for data..."));
m_controller.m_contextFileData = diffFileList; m_controller.m_contextFileData = diffFileList;
if (m_controller.m_contextFileData.isEmpty()) { if (m_controller.m_contextFileData.isEmpty()) {
@@ -966,29 +983,104 @@ void SideBySideDiffEditorWidget::restoreState()
void SideBySideDiffEditorWidget::showDiff() void SideBySideDiffEditorWidget::showDiff()
{ {
QFutureInterface<void> fi; m_watcher.reset(new QFutureWatcher<ShowResults>());
const SideBySideDiffOutput output = SideDiffData::diffOutput(fi, 0, 100, {&m_controller}); m_controller.setBusyShowing(true);
m_leftEditor->setDiffData(output.side[LeftSide].diffData);
m_rightEditor->setDiffData(output.side[RightSide].diffData);
connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] {
if (m_watcher->isCanceled()) {
m_leftEditor->clearAll(tr("Retrieving data failed."));
m_rightEditor->clearAll(tr("Retrieving data failed."));
} else {
const ShowResults results = m_watcher->result();
m_leftEditor->setDiffData(results[LeftSide].diffData);
m_rightEditor->setDiffData(results[RightSide].diffData);
TextDocumentPtr leftDoc(results[LeftSide].textDocument);
TextDocumentPtr rightDoc(results[RightSide].textDocument);
{ {
const GuardLocker locker(m_controller.m_ignoreChanges); const GuardLocker locker(m_controller.m_ignoreChanges);
m_leftEditor->clear(); // TextDocument was living in no thread, so it's safe to pull it
m_leftEditor->setPlainText(output.side[LeftSide].diffText); leftDoc->moveToThread(thread());
m_rightEditor->clear(); rightDoc->moveToThread(thread());
m_rightEditor->setPlainText(output.side[RightSide].diffText); m_leftEditor->setTextDocument(leftDoc);
m_rightEditor->setTextDocument(rightDoc);
m_leftEditor->setReadOnly(true);
m_rightEditor->setReadOnly(true);
}
m_leftEditor->setSelections(results[LeftSide].selections);
m_rightEditor->setSelections(results[RightSide].selections);
}
m_watcher.release()->deleteLater();
m_controller.setBusyShowing(false);
});
const DiffEditorInput input(&m_controller);
auto getDocument = [input](QFutureInterface<ShowResults> &futureInterface) {
auto cleanup = qScopeGuard([&futureInterface] {
if (futureInterface.isCanceled())
futureInterface.reportCanceled();
});
const int firstPartMax = 20; // showDiff is about 4 times quicker than filling document
const int leftPartMax = 60;
const int rightPartMax = 100;
futureInterface.setProgressRange(0, rightPartMax);
futureInterface.setProgressValue(0);
QFutureInterface<void> fi = futureInterface;
const SideBySideDiffOutput output = SideDiffData::diffOutput(fi, 0, firstPartMax, input);
if (futureInterface.isCanceled())
return;
const ShowResult leftResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")),
output.side[LeftSide].diffData, output.side[LeftSide].selections};
const ShowResult rightResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")),
output.side[RightSide].diffData, output.side[RightSide].selections};
const ShowResults result{leftResult, rightResult};
auto propagateDocument = [&output, &fi](DiffSide side, const ShowResult &result,
int progressMin, int progressMax) {
// No need to store the change history
result.textDocument->document()->setUndoRedoEnabled(false);
// We could do just:
// result.textDocument->setPlainText(output.diffText);
// but this would freeze the thread for couple of seconds without progress reporting
// and without checking for canceled.
const int diffSize = output.side[side].diffText.size();
const int packageSize = 10000;
int currentPos = 0;
QTextCursor cursor(result.textDocument->document());
while (currentPos < diffSize) {
const QString package = output.side[side].diffText.mid(currentPos, packageSize);
cursor.insertText(package);
currentPos += package.size();
fi.setProgressValue(DiffUtils::interpolate(currentPos, 0, diffSize, progressMin, progressMax));
if (fi.isCanceled())
return;
} }
QTextBlock block = m_leftEditor->document()->firstBlock(); QTextBlock block = result.textDocument->document()->firstBlock();
for (int b = 0; block.isValid(); block = block.next(), ++b)
SelectableTextEditorWidget::setFoldingIndent(block, output.foldingIndent.value(b, 3));
block = m_rightEditor->document()->firstBlock();
for (int b = 0; block.isValid(); block = block.next(), ++b) for (int b = 0; block.isValid(); block = block.next(), ++b)
SelectableTextEditorWidget::setFoldingIndent(block, output.foldingIndent.value(b, 3)); SelectableTextEditorWidget::setFoldingIndent(block, output.foldingIndent.value(b, 3));
m_leftEditor->setSelections(output.side[LeftSide].selections); // If future was canceled, the destructor runs in this thread, so we can't move it
m_rightEditor->setSelections(output.side[RightSide].selections); // to caller's thread. We push it to no thread (make object to have no thread affinity),
// and later, in the caller's thread, we pull it back to the caller's thread.
result.textDocument->moveToThread(nullptr);
};
propagateDocument(LeftSide, leftResult, firstPartMax, leftPartMax);
if (fi.isCanceled())
return;
propagateDocument(RightSide, rightResult, leftPartMax, rightPartMax);
if (fi.isCanceled())
return;
futureInterface.reportResult(result);
};
m_watcher->setFuture(runAsync(getDocument));
ProgressManager::addTask(m_watcher->future(), tr("Rendering diff"), "DiffEditor");
} }
void SideBySideDiffEditorWidget::setFontSettings(const FontSettings &fontSettings) void SideBySideDiffEditorWidget::setFontSettings(const FontSettings &fontSettings)
@@ -1143,10 +1235,13 @@ void SideBySideDiffEditorWidget::handlePositionChange(SideDiffEditorWidget *sour
if (m_controller.m_ignoreChanges.isLocked()) if (m_controller.m_ignoreChanges.isLocked())
return; return;
const int fileIndex = source->diffData().fileIndexForBlockNumber(source->textCursor().blockNumber());
if (fileIndex < 0)
return;
const GuardLocker locker(m_controller.m_ignoreChanges); const GuardLocker locker(m_controller.m_ignoreChanges);
syncCursor(source, dest); syncCursor(source, dest);
emit currentDiffFileIndexChanged( emit currentDiffFileIndexChanged(fileIndex);
source->diffData().fileIndexForBlockNumber(source->textCursor().blockNumber()));
} }
void SideBySideDiffEditorWidget::syncCursor(SideDiffEditorWidget *source, SideDiffEditorWidget *dest) void SideBySideDiffEditorWidget::syncCursor(SideDiffEditorWidget *source, SideDiffEditorWidget *dest)

View File

@@ -94,6 +94,7 @@ class SideBySideDiffEditorWidget : public QWidget
Q_OBJECT Q_OBJECT
public: public:
explicit SideBySideDiffEditorWidget(QWidget *parent = nullptr); explicit SideBySideDiffEditorWidget(QWidget *parent = nullptr);
~SideBySideDiffEditorWidget();
TextEditor::TextEditorWidget *leftEditorWidget() const; TextEditor::TextEditorWidget *leftEditorWidget() const;
TextEditor::TextEditorWidget *rightEditorWidget() const; TextEditor::TextEditorWidget *rightEditorWidget() const;
@@ -143,6 +144,16 @@ private:
DiffEditorWidgetController m_controller; DiffEditorWidgetController m_controller;
bool m_horizontalSync = false; bool m_horizontalSync = false;
struct ShowResult
{
QSharedPointer<TextEditor::TextDocument> textDocument{};
SideDiffData diffData;
DiffSelections selections;
};
using ShowResults = std::array<ShowResult, SideCount>;
std::unique_ptr<QFutureWatcher<ShowResults>> m_watcher;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -124,11 +124,11 @@ void UnifiedDiffEditorWidget::setFontSettings(const FontSettings &fontSettings)
void UnifiedDiffEditorWidget::slotCursorPositionChangedInEditor() void UnifiedDiffEditorWidget::slotCursorPositionChangedInEditor()
{ {
const int fileIndex = fileIndexForBlockNumber(textCursor().blockNumber()); if (m_controller.m_ignoreChanges.isLocked())
if (fileIndex < 0)
return; return;
if (m_controller.m_ignoreChanges.isLocked()) const int fileIndex = fileIndexForBlockNumber(textCursor().blockNumber());
if (fileIndex < 0)
return; return;
const GuardLocker locker(m_controller.m_ignoreChanges); const GuardLocker locker(m_controller.m_ignoreChanges);
@@ -515,7 +515,7 @@ void UnifiedDiffEditorWidget::showDiff()
const DiffEditorInput input(&m_controller); const DiffEditorInput input(&m_controller);
auto getDocument = [=](QFutureInterface<ShowResult> &futureInterface) { auto getDocument = [input](QFutureInterface<ShowResult> &futureInterface) {
auto cleanup = qScopeGuard([&futureInterface] { auto cleanup = qScopeGuard([&futureInterface] {
if (futureInterface.isCanceled()) if (futureInterface.isCanceled())
futureInterface.reportCanceled(); futureInterface.reportCanceled();
@@ -540,7 +540,7 @@ void UnifiedDiffEditorWidget::showDiff()
// but this would freeze the thread for couple of seconds without progress reporting // but this would freeze the thread for couple of seconds without progress reporting
// and without checking for canceled. // and without checking for canceled.
const int diffSize = output.diffText.size(); const int diffSize = output.diffText.size();
const int packageSize = 100000; const int packageSize = 10000;
int currentPos = 0; int currentPos = 0;
QTextCursor cursor(result.textDocument->document()); QTextCursor cursor(result.textDocument->document());
while (currentPos < diffSize) { while (currentPos < diffSize) {