// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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( 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()) { // 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("Error: 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(); 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