From 14148261832186e30f264a14a6d76a92bb0e2f7f Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Wed, 5 Oct 2016 17:37:00 +0200 Subject: [PATCH] Implement diff on close, on revert and on external modification Task-number: QTCREATORBUG-1531 Change-Id: I8c9a740d66eb7836b3df6850ac243260fd282b32 Reviewed-by: Tobias Hunger --- src/libs/utils/reloadpromptutils.cpp | 18 ++++- src/libs/utils/reloadpromptutils.h | 6 +- src/plugins/coreplugin/coreplugin.pro | 3 +- src/plugins/coreplugin/coreplugin.qbs | 1 + .../coreplugin/dialogs/saveitemsdialog.cpp | 52 +++++++++--- .../coreplugin/dialogs/saveitemsdialog.h | 6 +- src/plugins/coreplugin/diffservice.h | 47 +++++++++++ src/plugins/coreplugin/documentmanager.cpp | 24 +++++- .../editormanager/editormanager.cpp | 11 +++ src/plugins/diffeditor/diffeditorplugin.cpp | 81 +++++++++++++++++++ src/plugins/diffeditor/diffeditorplugin.h | 11 +++ 11 files changed, 243 insertions(+), 17 deletions(-) create mode 100644 src/plugins/coreplugin/diffservice.h diff --git a/src/libs/utils/reloadpromptutils.cpp b/src/libs/utils/reloadpromptutils.cpp index 8eec9e92e1b..798a4876a70 100644 --- a/src/libs/utils/reloadpromptutils.cpp +++ b/src/libs/utils/reloadpromptutils.cpp @@ -35,6 +35,7 @@ namespace Utils { QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const FileName &fileName, bool modified, + bool enableDiffOption, QWidget *parent) { @@ -50,12 +51,13 @@ QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const FileName &fileName, "The file %1 has changed outside Qt Creator. Do you want to reload it?"); } msg = msg.arg(fileName.fileName()); - return reloadPrompt(title, msg, fileName.toUserOutput(), parent); + return reloadPrompt(title, msg, fileName.toUserOutput(), enableDiffOption, parent); } QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const QString &title, const QString &prompt, const QString &details, + bool enableDiffOption, QWidget *parent) { QMessageBox msg(parent); @@ -69,7 +71,19 @@ QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const QString &title, msg.button(QMessageBox::Close)->setText(QCoreApplication::translate("Utils::reloadPrompt", "&Close")); - switch (msg.exec()) { + QPushButton *diffButton = nullptr; + if (enableDiffOption) { + diffButton = msg.addButton(QCoreApplication::translate( + "Utils::reloadPrompt", "No to All && &Diff"), + QMessageBox::NoRole); + } + + const int result = msg.exec(); + + if (msg.clickedButton() == diffButton) + return ReloadNoneAndDiff; + + switch (result) { case QMessageBox::Yes: return ReloadCurrent; case QMessageBox::YesToAll: diff --git a/src/libs/utils/reloadpromptutils.h b/src/libs/utils/reloadpromptutils.h index b01bd25b53b..8991d52b59b 100644 --- a/src/libs/utils/reloadpromptutils.h +++ b/src/libs/utils/reloadpromptutils.h @@ -40,15 +40,19 @@ enum ReloadPromptAnswer { ReloadAll, ReloadSkipCurrent, ReloadNone, + ReloadNoneAndDiff, CloseCurrent }; QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const FileName &fileName, bool modified, + bool enableDiffOption, QWidget *parent); QTCREATOR_UTILS_EXPORT ReloadPromptAnswer reloadPrompt(const QString &title, const QString &prompt, - const QString &details, QWidget *parent); + const QString &details, + bool enableDiffOption, + QWidget *parent); enum FileDeletedPromptAnswer { FileDeletedClose, diff --git a/src/plugins/coreplugin/coreplugin.pro b/src/plugins/coreplugin/coreplugin.pro index 9c8be5cfa76..6de7837acfc 100644 --- a/src/plugins/coreplugin/coreplugin.pro +++ b/src/plugins/coreplugin/coreplugin.pro @@ -218,7 +218,8 @@ HEADERS += corejsextensions.h \ iwelcomepage.h \ systemsettings.h \ coreicons.h \ - editormanager/documentmodel_p.h + editormanager/documentmodel_p.h \ + diffservice.h FORMS += dialogs/newdialog.ui \ dialogs/saveitemsdialog.ui \ diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs index ed50f54985d..fe6b3cb75e9 100644 --- a/src/plugins/coreplugin/coreplugin.qbs +++ b/src/plugins/coreplugin/coreplugin.qbs @@ -41,6 +41,7 @@ Project { "corejsextensions.cpp", "corejsextensions.h", "coreplugin.cpp", "coreplugin.h", "designmode.cpp", "designmode.h", + "diffservice.h", "documentmanager.cpp", "documentmanager.h", "editmode.cpp", "editmode.h", "editortoolbar.cpp", "editortoolbar.h", diff --git a/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp b/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp index 885b08e9346..a36ecd9d485 100644 --- a/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp +++ b/src/plugins/coreplugin/dialogs/saveitemsdialog.cpp @@ -25,12 +25,15 @@ #include "saveitemsdialog.h" +#include #include #include #include #include +#include + #include #include #include @@ -51,6 +54,12 @@ SaveItemsDialog::SaveItemsDialog(QWidget *parent, // QDialogButtonBox's behavior for "destructive" is wrong, the "do not save" should be left-aligned const QDialogButtonBox::ButtonRole discardButtonRole = Utils::HostOsInfo::isMacHost() ? QDialogButtonBox::ResetRole : QDialogButtonBox::DestructiveRole; + + if (ExtensionSystem::PluginManager::getObject()) { + m_diffButton = m_ui.buttonBox->addButton(tr("&Diff"), discardButtonRole); + connect(m_diffButton, &QAbstractButton::clicked, this, &SaveItemsDialog::collectFilesToDiff); + } + QPushButton *discardButton = m_ui.buttonBox->addButton(tr("Do not Save"), discardButtonRole); m_ui.buttonBox->button(QDialogButtonBox::Save)->setDefault(true); m_ui.treeWidget->setFocus(); @@ -80,13 +89,13 @@ SaveItemsDialog::SaveItemsDialog(QWidget *parent, if (Utils::HostOsInfo::isMacHost()) m_ui.treeWidget->setAlternatingRowColors(true); adjustButtonWidths(); - updateSaveButton(); + updateButtons(); connect(m_ui.buttonBox->button(QDialogButtonBox::Save), &QAbstractButton::clicked, this, &SaveItemsDialog::collectItemsToSave); connect(discardButton, &QAbstractButton::clicked, this, &SaveItemsDialog::discardAll); connect(m_ui.treeWidget, &QTreeWidget::itemSelectionChanged, - this, &SaveItemsDialog::updateSaveButton); + this, &SaveItemsDialog::updateButtons); } void SaveItemsDialog::setMessage(const QString &msg) @@ -94,19 +103,27 @@ void SaveItemsDialog::setMessage(const QString &msg) m_ui.msgLabel->setText(msg); } -void SaveItemsDialog::updateSaveButton() +void SaveItemsDialog::updateButtons() { int count = m_ui.treeWidget->selectedItems().count(); - QPushButton *button = m_ui.buttonBox->button(QDialogButtonBox::Save); + QPushButton *saveButton = m_ui.buttonBox->button(QDialogButtonBox::Save); + bool buttonsEnabled = true; + QString saveText = tr("Save"); + QString diffText = tr("&Diff && Cancel"); if (count == m_ui.treeWidget->topLevelItemCount()) { - button->setEnabled(true); - button->setText(tr("Save All")); + saveText = tr("Save All"); + diffText = tr("&Diff All && Cancel"); } else if (count == 0) { - button->setEnabled(false); - button->setText(tr("Save")); + buttonsEnabled = false; } else { - button->setEnabled(true); - button->setText(tr("Save Selected")); + saveText = tr("Save Selected"); + diffText = tr("&Diff Selected && Cancel"); + } + saveButton->setEnabled(buttonsEnabled); + saveButton->setText(saveText); + if (m_diffButton) { + m_diffButton->setEnabled(buttonsEnabled); + m_diffButton->setText(diffText); } } @@ -145,6 +162,16 @@ void SaveItemsDialog::collectItemsToSave() accept(); } +void SaveItemsDialog::collectFilesToDiff() +{ + m_filesToDiff.clear(); + foreach (QTreeWidgetItem *item, m_ui.treeWidget->selectedItems()) { + if (IDocument *doc = item->data(0, Qt::UserRole).value()) + m_filesToDiff.append(doc->filePath().toString()); + } + reject(); +} + void SaveItemsDialog::discardAll() { m_ui.treeWidget->clearSelection(); @@ -156,6 +183,11 @@ QList SaveItemsDialog::itemsToSave() const return m_itemsToSave; } +QStringList SaveItemsDialog::filesToDiff() const +{ + return m_filesToDiff; +} + void SaveItemsDialog::setAlwaysSaveMessage(const QString &msg) { m_ui.saveBeforeBuildCheckBox->setText(msg); diff --git a/src/plugins/coreplugin/dialogs/saveitemsdialog.h b/src/plugins/coreplugin/dialogs/saveitemsdialog.h index 623cb2e85f7..1048ca03481 100644 --- a/src/plugins/coreplugin/dialogs/saveitemsdialog.h +++ b/src/plugins/coreplugin/dialogs/saveitemsdialog.h @@ -54,15 +54,19 @@ public: void setAlwaysSaveMessage(const QString &msg); bool alwaysSaveChecked(); QList itemsToSave() const; + QStringList filesToDiff() const; private: void collectItemsToSave(); + void collectFilesToDiff(); void discardAll(); - void updateSaveButton(); + void updateButtons(); void adjustButtonWidths(); Ui::SaveItemsDialog m_ui; QList m_itemsToSave; + QStringList m_filesToDiff; + QPushButton *m_diffButton = nullptr; }; } // namespace Internal diff --git a/src/plugins/coreplugin/diffservice.h b/src/plugins/coreplugin/diffservice.h new file mode 100644 index 00000000000..3b282170ddc --- /dev/null +++ b/src/plugins/coreplugin/diffservice.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "core_global.h" +#include + +QT_FORWARD_DECLARE_CLASS(QStringList) + +namespace Core { + +class CORE_EXPORT DiffService +{ +public: + virtual ~DiffService() {} + + virtual void diffModifiedFiles(const QStringList &fileNames) = 0; +}; + +} // namespace Core + +QT_BEGIN_NAMESPACE +Q_DECLARE_INTERFACE(Core::DiffService, "Core::DiffService") +QT_END_NAMESPACE diff --git a/src/plugins/coreplugin/documentmanager.cpp b/src/plugins/coreplugin/documentmanager.cpp index 6ba6d61ca24..5a936731fa0 100644 --- a/src/plugins/coreplugin/documentmanager.cpp +++ b/src/plugins/coreplugin/documentmanager.cpp @@ -29,6 +29,7 @@ #include "idocument.h" #include "coreconstants.h" +#include #include #include #include @@ -38,6 +39,8 @@ #include #include +#include + #include #include #include @@ -606,6 +609,11 @@ static bool saveModifiedFilesHelper(const QList &documents, (*alwaysSave) = dia.alwaysSaveChecked(); if (failedToSave) (*failedToSave) = modifiedDocuments; + const QStringList filesToDiff = dia.filesToDiff(); + if (!filesToDiff.isEmpty()) { + if (auto diffService = ExtensionSystem::PluginManager::getObject()) + diffService->diffModifiedFiles(filesToDiff); + } return false; } if (alwaysSave) @@ -978,6 +986,7 @@ void DocumentManager::checkForReload() // handle the IDocuments QStringList errorStrings; + QStringList filesToDiff; foreach (IDocument *document, changedIDocuments) { IDocument::ChangeTrigger trigger = IDocument::TriggerInternal; IDocument::ChangeType type = IDocument::TypePermissions; @@ -1067,7 +1076,7 @@ void DocumentManager::checkForReload() // IDocument wants us to ask } else if (type == IDocument::TypeContents) { // content change, IDocument wants to ask user - if (previousReloadAnswer == ReloadNone) { + if (previousReloadAnswer == ReloadNone || previousReloadAnswer == ReloadNoneAndDiff) { // answer already given, ignore success = document->reload(&errorString, IDocument::FlagIgnore, IDocument::TypeContents); } else if (previousReloadAnswer == ReloadAll) { @@ -1076,7 +1085,8 @@ void DocumentManager::checkForReload() } else { // Ask about content change previousReloadAnswer = reloadPrompt(document->filePath(), document->isModified(), - ICore::dialogParent()); + ExtensionSystem::PluginManager::getObject(), + ICore::dialogParent()); switch (previousReloadAnswer) { case ReloadAll: case ReloadCurrent: @@ -1084,6 +1094,7 @@ void DocumentManager::checkForReload() break; case ReloadSkipCurrent: case ReloadNone: + case ReloadNoneAndDiff: success = document->reload(&errorString, IDocument::FlagIgnore, IDocument::TypeContents); break; case CloseCurrent: @@ -1091,6 +1102,9 @@ void DocumentManager::checkForReload() break; } } + if (previousReloadAnswer == ReloadNoneAndDiff) + filesToDiff.append(document->filePath().toString()); + // IDocument wants us to ask, and it's the TypeRemoved case } else { // Ask about removed file @@ -1134,6 +1148,12 @@ void DocumentManager::checkForReload() d->m_blockedIDocument = 0; } + + if (!filesToDiff.isEmpty()) { + if (auto diffService = ExtensionSystem::PluginManager::getObject()) + diffService->diffModifiedFiles(filesToDiff); + } + if (!errorStrings.isEmpty()) QMessageBox::critical(ICore::dialogParent(), tr("File Error"), errorStrings.join(QLatin1Char('\n'))); diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp index 7f344ecd95f..4f13bcec2b3 100644 --- a/src/plugins/coreplugin/editormanager/editormanager.cpp +++ b/src/plugins/coreplugin/editormanager/editormanager.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -2168,11 +2169,21 @@ void EditorManagerPrivate::revertToSaved(IDocument *document) QMessageBox::Yes|QMessageBox::No, ICore::mainWindow()); msgBox.button(QMessageBox::Yes)->setText(tr("Proceed")); msgBox.button(QMessageBox::No)->setText(tr("Cancel")); + + QPushButton *diffButton = nullptr; + auto diffService = ExtensionSystem::PluginManager::getObject(); + if (diffService) + diffButton = msgBox.addButton(tr("Cancel && &Diff"), QMessageBox::RejectRole); + msgBox.setDefaultButton(QMessageBox::No); msgBox.setEscapeButton(QMessageBox::No); if (msgBox.exec() == QMessageBox::No) return; + if (diffService && msgBox.clickedButton() == diffButton) { + diffService->diffModifiedFiles(QStringList() << fileName); + return; + } } QString errorString; if (!document->reload(&errorString, IDocument::FlagReload, IDocument::TypeContents)) diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp index db7936f34af..a04b4b2b862 100644 --- a/src/plugins/diffeditor/diffeditorplugin.cpp +++ b/src/plugins/diffeditor/diffeditorplugin.cpp @@ -211,6 +211,66 @@ void DiffAllModifiedFilesController::reload() ///////////////// +class DiffModifiedFilesController : public DiffFilesController +{ + Q_OBJECT +public: + DiffModifiedFilesController(Core::IDocument *document, const QStringList &fileNames); + +protected: + void reload(); + +private: + QStringList m_fileNames; +}; + +DiffModifiedFilesController::DiffModifiedFilesController(Core::IDocument *document, const QStringList &fileNames) : + DiffFilesController(document), m_fileNames(fileNames) +{ } + +void DiffModifiedFilesController::reload() +{ + QList fileDataList; + + foreach (const QString fileName, m_fileNames) { + TextEditor::TextDocument *textDocument = qobject_cast( + Core::DocumentModel::documentForFilePath(fileName)); + + if (textDocument && textDocument->isModified()) { + QString errorString; + Utils::TextFileFormat format = textDocument->format(); + + QString leftText; + bool leftFileExists = true; + const QString fileName = textDocument->filePath().toString(); + if (Utils::TextFileFormat::readFile(fileName, + format.codec, + &leftText, &format, &errorString) + != Utils::TextFileFormat::ReadSuccess) { + leftFileExists = false; + } + + const QString rightText = textDocument->plainText(); + + FileData fileData = diffFiles(leftText, rightText); + fileData.leftFileInfo.fileName = fileName; + fileData.rightFileInfo.fileName = fileName; + fileData.leftFileInfo.typeInfo = tr("Saved"); + fileData.rightFileInfo.typeInfo = tr("Modified"); + + if (!leftFileExists) + fileData.fileOperation = FileData::NewFile; + + fileDataList << fileData; + } + } + + setDiffFiles(fileDataList); + reloadFinished(true); +} + +///////////////// + class DiffExternalFilesController : public DiffFilesController { Q_OBJECT @@ -273,6 +333,26 @@ void DiffExternalFilesController::reload() ///////////////// +DiffEditorServiceImpl::DiffEditorServiceImpl(QObject *parent) : + QObject(parent) +{ +} + +void DiffEditorServiceImpl::diffModifiedFiles(const QStringList &fileNames) +{ + const QString documentId = QLatin1String("Diff Modified Files"); + const QString title = tr("Diff Modified Files"); + auto const document = qobject_cast( + DiffEditorController::findOrCreateDocument(documentId, title)); + if (!document) + return; + + if (!DiffEditorController::controller(document)) + new DiffModifiedFilesController(document, fileNames); + Core::EditorManager::activateEditorForDocument(document); + document->reload(); +} + bool DiffEditorPlugin::initialize(const QStringList &arguments, QString *errorMessage) { Q_UNUSED(arguments) @@ -309,6 +389,7 @@ bool DiffEditorPlugin::initialize(const QStringList &arguments, QString *errorMe updateActions(); addAutoReleasedObject(new DiffEditorFactory(this)); + addAutoReleasedObject(new DiffEditorServiceImpl(this)); return true; } diff --git a/src/plugins/diffeditor/diffeditorplugin.h b/src/plugins/diffeditor/diffeditorplugin.h index ba0cab24337..66485660024 100644 --- a/src/plugins/diffeditor/diffeditorplugin.h +++ b/src/plugins/diffeditor/diffeditorplugin.h @@ -27,6 +27,7 @@ #include "diffeditor_global.h" +#include #include #include @@ -37,6 +38,16 @@ namespace Core { class IEditor; } namespace DiffEditor { namespace Internal { +class DiffEditorServiceImpl : public QObject, public Core::DiffService +{ + Q_OBJECT + Q_INTERFACES(Core::DiffService) +public: + explicit DiffEditorServiceImpl(QObject *parent = nullptr); + + void diffModifiedFiles(const QStringList &fileNames) override; +}; + class DiffEditorPlugin : public ExtensionSystem::IPlugin { Q_OBJECT