// 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 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(PatchAction patchAction, int fileIndex, int chunkIndex) { if (!m_document) return; if (!chunkExists(fileIndex, chunkIndex)) return; const FileData fileData = m_contextFileData.at(fileIndex); const QString fileName = patchAction == PatchAction::Apply ? fileData.fileInfo[LeftSide].fileName : fileData.fileInfo[RightSide].fileName; const DiffFileInfo::PatchBehaviour patchBehaviour = patchAction == PatchAction::Apply ? fileData.fileInfo[LeftSide].patchBehaviour : fileData.fileInfo[RightSide].patchBehaviour; const FilePath workingDirectory = m_document->baseDirectory().isEmpty() ? FilePath::fromString(fileName).absolutePath() : m_document->baseDirectory(); const FilePath absFilePath = workingDirectory.resolvePath(fileName).absoluteFilePath(); auto textDocument = qobject_cast( DocumentModel::documentForFilePath(absFilePath)); const bool isModified = patchBehaviour == DiffFileInfo::PatchFile && textDocument && textDocument->isModified(); if (!PatchTool::confirmPatching(m_diffEditorWidget, patchAction, isModified)) return; if (patchBehaviour == DiffFileInfo::PatchFile) { if (textDocument && !EditorManager::saveDocument(textDocument)) return; const int strip = m_document->baseDirectory().isEmpty() ? -1 : 0; const QString patch = m_document->makePatch(fileIndex, chunkIndex, {}, patchAction); if (patch.isEmpty()) return; FileChangeBlocker fileChangeBlocker(absFilePath); if (PatchTool::runPatch(EditorManager::defaultTextCodec()->fromUnicode(patch), workingDirectory, strip, patchAction)) m_document->reload(); } else { // PatchEditor 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, {}, patchAction, false, QFileInfo(contentsCopyFileName).fileName()); if (patch.isEmpty()) return; if (PatchTool::runPatch(EditorManager::defaultTextCodec()->fromUnicode(patch), FilePath::fromString(contentsCopyDir), 0, patchAction)) { 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_spanLineFormat = fontSettings.toTextCharFormat(C_LINE_NUMBER); m_lineFormat[LeftSide] = fontSettings.toTextCharFormat(C_DIFF_SOURCE_LINE); m_charFormat[LeftSide] = fontSettings.toTextCharFormat(C_DIFF_SOURCE_CHAR); m_lineFormat[RightSide] = fontSettings.toTextCharFormat(C_DIFF_DEST_LINE); m_charFormat[RightSide] = 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 || fileIndex < 0 || chunkIndex < 0 || fileIndex >= m_contextFileData.count()) return {}; const FileData fileData = m_contextFileData.at(fileIndex); if (chunkIndex >= fileData.chunks.count()) return {}; return fileData.chunks.at(chunkIndex); } bool DiffEditorWidgetController::fileNamesAreDifferent(int fileIndex) const { const FileData fileData = m_contextFileData.at(fileIndex); return fileData.fileInfo[LeftSide].fileName != fileData.fileInfo[RightSide].fileName; } void DiffEditorWidgetController::addPatchAction(QMenu *menu, int fileIndex, int chunkIndex, PatchAction patchAction) { const QString actionName = patchAction == PatchAction::Apply ? tr("Apply Chunk...") : tr("Revert Chunk..."); QAction *action = menu->addAction(actionName); connect(action, &QAction::triggered, this, [this, fileIndex, chunkIndex, patchAction] { patch(patchAction, fileIndex, chunkIndex); }); const bool enabled = chunkExists(fileIndex, chunkIndex) && (patchAction == PatchAction::Revert || fileNamesAreDifferent(fileIndex)); action->setEnabled(enabled); } 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, {}, PatchAction::Apply); 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_spanLineFormat(&controller->m_spanLineFormat) , m_lineFormat{&controller->m_lineFormat[LeftSide], &controller->m_lineFormat[RightSide]} , m_charFormat{&controller->m_charFormat[LeftSide], &controller->m_charFormat[RightSide]} { } } // namespace Internal } // namespace DiffEditor