Files
qt-creator/src/plugins/diffeditor/diffeditorwidgetcontroller.cpp
Jarek Kobus 054d7d65d2 UnifiedDiffEditor: Move showing diff into separate thread
Before, when all the data was finished, we called showDiff()
in main thread. This consisted of 2 parts:

1. Calculating some extra data and generating actual text
   for UnifiedDiffEditor out of input data.
2. Calling setPlainText() with generated text.

For a really big diffs this could freeze the main thread for a
couple of seconds. Like e.g. 05c35356ab
(initial Creator import) - it contained 7 million characters,
part 1. took about 500 ms and part 2. took about 2.5 seconds.

This two tasks are now done in separate thread.
However, since we can't call TextEditorWidget::setPlainText()
directly from non-GUI thread, we create a separate
TextDocument object in the worker thread, fill it with
generated diff input and move the TextDocument object
into the main thread as a result of async computation.
In main thread we replace TextDocument object of the
TextEditorWidget with the one generated in other thread.
This replacement is very fast.

Change-Id: I49a717ced1dc2d5b8946e0fd6bee244b25071f35
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
2022-09-28 15:47:39 +00:00

353 lines
12 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "diffeditorwidgetcontroller.h"
#include "diffeditorconstants.h"
#include "diffeditorcontroller.h"
#include "diffeditordocument.h"
#include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/patchtool.h>
#include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h>
#include <extensionsystem/pluginmanager.h>
#include <cpaster/codepasterservice.h>
#include <utils/infobar.h>
#include <utils/progressindicator.h>
#include <utils/qtcassert.h>
#include <utils/temporaryfile.h>
#include <QDir>
#include <QMenu>
#include <QMessageBox>
#include <QTextCodec>
using namespace Core;
using namespace TextEditor;
using namespace Utils;
namespace DiffEditor {
namespace Internal {
DiffEditorWidgetController::DiffEditorWidgetController(QWidget *diffEditorWidget)
: QObject(diffEditorWidget)
, m_diffEditorWidget(diffEditorWidget)
{
m_timer.setSingleShot(true);
m_timer.setInterval(100);
connect(&m_timer, &QTimer::timeout, this, &DiffEditorWidgetController::showProgress);
}
bool DiffEditorWidgetController::isInProgress() const
{
return m_isBusyShowing || (m_document && m_document->state() == DiffEditorDocument::Reloading);
}
void DiffEditorWidgetController::toggleProgress(bool wasInProgress)
{
const bool inProgress = isInProgress();
if (wasInProgress == inProgress)
return;
if (inProgress)
scheduleShowProgress();
else
hideProgress();
}
void DiffEditorWidgetController::setDocument(DiffEditorDocument *document)
{
if (!m_progressIndicator) {
m_progressIndicator = new ProgressIndicator(ProgressIndicatorSize::Large);
m_progressIndicator->attachToWidget(m_diffEditorWidget);
m_progressIndicator->hide();
}
if (m_document == document)
return;
if (m_document) {
disconnect(m_document, &IDocument::aboutToReload, this, &DiffEditorWidgetController::scheduleShowProgress);
disconnect(m_document, &IDocument::reloadFinished, this, &DiffEditorWidgetController::onDocumentReloadFinished);
}
const bool wasInProgress = isInProgress();
m_document = document;
if (m_document) {
connect(m_document, &IDocument::aboutToReload, this, &DiffEditorWidgetController::scheduleShowProgress);
connect(m_document, &IDocument::reloadFinished, this, &DiffEditorWidgetController::onDocumentReloadFinished);
updateCannotDecodeInfo();
}
toggleProgress(wasInProgress);
}
DiffEditorDocument *DiffEditorWidgetController::document() const
{
return m_document;
}
void DiffEditorWidgetController::setBusyShowing(bool busy)
{
if (m_isBusyShowing == busy)
return;
const bool wasInProgress = isInProgress();
m_isBusyShowing = busy;
toggleProgress(wasInProgress);
}
void DiffEditorWidgetController::scheduleShowProgress()
{
m_timer.start();
}
void DiffEditorWidgetController::showProgress()
{
m_timer.stop();
if (m_progressIndicator)
m_progressIndicator->show();
}
void DiffEditorWidgetController::hideProgress()
{
m_timer.stop();
if (m_progressIndicator)
m_progressIndicator->hide();
}
void DiffEditorWidgetController::onDocumentReloadFinished()
{
updateCannotDecodeInfo();
if (!isInProgress())
hideProgress();
}
void DiffEditorWidgetController::patch(bool revert, int fileIndex, int chunkIndex)
{
if (!m_document)
return;
if (!chunkExists(fileIndex, chunkIndex))
return;
const QString title = revert ? tr("Revert Chunk") : tr("Apply Chunk");
const QString question = revert
? tr("Would you like to revert the chunk?")
: tr("Would you like to apply the chunk?");
if (QMessageBox::No == QMessageBox::question(m_diffEditorWidget, title,
question,
QMessageBox::Yes
| QMessageBox::No)) {
return;
}
const FileData fileData = m_contextFileData.at(fileIndex);
const QString fileName = revert
? fileData.rightFileInfo.fileName
: fileData.leftFileInfo.fileName;
const DiffFileInfo::PatchBehaviour patchBehaviour = revert
? fileData.rightFileInfo.patchBehaviour
: fileData.leftFileInfo.patchBehaviour;
const FilePath workingDirectory = m_document->baseDirectory().isEmpty()
? FilePath::fromString(fileName).absolutePath()
: m_document->baseDirectory();
const FilePath absFilePath = workingDirectory.resolvePath(fileName).absoluteFilePath();
if (patchBehaviour == DiffFileInfo::PatchFile) {
const int strip = m_document->baseDirectory().isEmpty() ? -1 : 0;
const QString patch = m_document->makePatch(fileIndex, chunkIndex, ChunkSelection(), revert);
if (patch.isEmpty())
return;
FileChangeBlocker fileChangeBlocker(absFilePath);
if (PatchTool::runPatch(EditorManager::defaultTextCodec()->fromUnicode(patch),
workingDirectory, strip, revert))
m_document->reload();
} else { // PatchEditor
auto textDocument = qobject_cast<TextEditor::TextDocument *>(
DocumentModel::documentForFilePath(absFilePath));
if (!textDocument)
return;
TemporaryFile contentsCopy("diff");
if (!contentsCopy.open())
return;
contentsCopy.write(textDocument->contents());
contentsCopy.close();
const QString contentsCopyFileName = contentsCopy.fileName();
const QString contentsCopyDir = QFileInfo(contentsCopyFileName).absolutePath();
const QString patch = m_document->makePatch(fileIndex, chunkIndex,
ChunkSelection(), revert, false,
QFileInfo(contentsCopyFileName).fileName());
if (patch.isEmpty())
return;
if (PatchTool::runPatch(EditorManager::defaultTextCodec()->fromUnicode(patch),
FilePath::fromString(contentsCopyDir), 0, revert)) {
QString errorString;
if (textDocument->reload(&errorString, FilePath::fromString(contentsCopyFileName)))
m_document->reload();
}
}
}
void DiffEditorWidgetController::jumpToOriginalFile(const QString &fileName,
int lineNumber,
int columnNumber)
{
if (!m_document)
return;
const FilePath filePath = m_document->baseDirectory().resolvePath(fileName);
if (filePath.exists() && !filePath.isDir())
EditorManager::openEditorAt({filePath, lineNumber, columnNumber});
}
void DiffEditorWidgetController::setFontSettings(const FontSettings &fontSettings)
{
m_fileLineFormat = fontSettings.toTextCharFormat(C_DIFF_FILE_LINE);
m_chunkLineFormat = fontSettings.toTextCharFormat(C_DIFF_CONTEXT_LINE);
m_leftLineFormat = fontSettings.toTextCharFormat(C_DIFF_SOURCE_LINE);
m_leftCharFormat = fontSettings.toTextCharFormat(C_DIFF_SOURCE_CHAR);
m_rightLineFormat = fontSettings.toTextCharFormat(C_DIFF_DEST_LINE);
m_rightCharFormat = fontSettings.toTextCharFormat(C_DIFF_DEST_CHAR);
}
void DiffEditorWidgetController::addCodePasterAction(QMenu *menu, int fileIndex, int chunkIndex)
{
if (ExtensionSystem::PluginManager::getObject<CodePaster::Service>()) {
// optional code pasting service
QAction *sendChunkToCodePasterAction = menu->addAction(tr("Send Chunk to CodePaster..."));
connect(sendChunkToCodePasterAction, &QAction::triggered, this, [this, fileIndex, chunkIndex] {
sendChunkToCodePaster(fileIndex, chunkIndex);
});
}
}
bool DiffEditorWidgetController::chunkExists(int fileIndex, int chunkIndex) const
{
if (!m_document)
return false;
if (DiffEditorController *controller = m_document->controller())
return controller->chunkExists(fileIndex, chunkIndex);
return false;
}
ChunkData DiffEditorWidgetController::chunkData(int fileIndex, int chunkIndex) const
{
if (!m_document)
return ChunkData();
if (fileIndex < 0 || chunkIndex < 0)
return ChunkData();
if (fileIndex >= m_contextFileData.count())
return ChunkData();
const FileData fileData = m_contextFileData.at(fileIndex);
if (chunkIndex >= fileData.chunks.count())
return ChunkData();
return fileData.chunks.at(chunkIndex);
}
bool DiffEditorWidgetController::fileNamesAreDifferent(int fileIndex) const
{
const FileData fileData = m_contextFileData.at(fileIndex);
return fileData.leftFileInfo.fileName != fileData.rightFileInfo.fileName;
}
void DiffEditorWidgetController::addApplyAction(QMenu *menu, int fileIndex, int chunkIndex)
{
QAction *applyAction = menu->addAction(tr("Apply Chunk..."));
connect(applyAction, &QAction::triggered, this, [this, fileIndex, chunkIndex] {
patch(false, fileIndex, chunkIndex);
});
applyAction->setEnabled(chunkExists(fileIndex, chunkIndex) && fileNamesAreDifferent(fileIndex));
}
void DiffEditorWidgetController::addRevertAction(QMenu *menu, int fileIndex, int chunkIndex)
{
QAction *revertAction = menu->addAction(tr("Revert Chunk..."));
connect(revertAction, &QAction::triggered, this, [this, fileIndex, chunkIndex] {
patch(true, fileIndex, chunkIndex);
});
revertAction->setEnabled(chunkExists(fileIndex, chunkIndex));
}
void DiffEditorWidgetController::addExtraActions(QMenu *menu, int fileIndex, int chunkIndex,
const ChunkSelection &selection)
{
if (DiffEditorController *controller = m_document->controller())
controller->requestChunkActions(menu, fileIndex, chunkIndex, selection);
}
void DiffEditorWidgetController::updateCannotDecodeInfo()
{
if (!m_document)
return;
InfoBar *infoBar = m_document->infoBar();
Id selectEncodingId(Constants::SELECT_ENCODING);
if (m_document->hasDecodingError()) {
if (!infoBar->canInfoBeAdded(selectEncodingId))
return;
InfoBarEntry info(selectEncodingId,
tr("<b>Error:</b> Could not decode \"%1\" with \"%2\"-encoding.")
.arg(m_document->displayName(),
QString::fromLatin1(m_document->codec()->name())));
info.addCustomButton(tr("Select Encoding"), [this] { m_document->selectEncoding(); });
infoBar->addInfo(info);
} else {
infoBar->removeInfo(selectEncodingId);
}
}
void DiffEditorWidgetController::sendChunkToCodePaster(int fileIndex, int chunkIndex)
{
if (!m_document)
return;
// Retrieve service by soft dependency.
auto pasteService = ExtensionSystem::PluginManager::getObject<CodePaster::Service>();
QTC_ASSERT(pasteService, return);
const QString patch = m_document->makePatch(fileIndex, chunkIndex,
ChunkSelection(), false);
if (patch.isEmpty())
return;
pasteService->postText(patch, Constants::DIFF_EDITOR_MIMETYPE);
}
DiffEditorInput::DiffEditorInput(DiffEditorWidgetController *controller)
: m_contextFileData(controller->m_contextFileData)
, m_fileLineFormat(&controller->m_fileLineFormat)
, m_chunkLineFormat(&controller->m_chunkLineFormat)
, m_leftLineFormat(&controller->m_leftLineFormat)
, m_rightLineFormat(&controller->m_rightLineFormat)
, m_leftCharFormat(&controller->m_leftCharFormat)
, m_rightCharFormat(&controller->m_rightCharFormat)
{ }
} // namespace Internal
} // namespace DiffEditor