From 8cad94534f97849081bd595da458670d168d6b63 Mon Sep 17 00:00:00 2001 From: jkobus Date: Thu, 13 Feb 2014 16:43:28 +0100 Subject: Implement unified diff editor Change-Id: I93e0bfd71a8a650afbe2ca9e0f1f3dbfc9d57db0 Reviewed-by: Jarek Kobus --- src/plugins/coreplugin/core.qrc | 3 - src/plugins/coreplugin/coreconstants.h | 3 - src/plugins/coreplugin/images/sidebysidediff.png | Bin 257 -> 0 bytes src/plugins/coreplugin/images/textdiff.png | Bin 258 -> 0 bytes src/plugins/coreplugin/images/topbaricon.png | Bin 379 -> 0 bytes src/plugins/coreplugin/patchtool.cpp | 4 +- src/plugins/diffeditor/diffeditor.cpp | 272 ++++- src/plugins/diffeditor/diffeditor.h | 24 +- src/plugins/diffeditor/diffeditor.pro | 12 +- src/plugins/diffeditor/diffeditor.qbs | 5 + src/plugins/diffeditor/diffeditor.qrc | 8 + src/plugins/diffeditor/diffeditorconstants.h | 6 + src/plugins/diffeditor/diffeditorcontroller.cpp | 105 +- src/plugins/diffeditor/diffeditorcontroller.h | 46 +- src/plugins/diffeditor/diffeditorfactory.cpp | 1 + src/plugins/diffeditor/diffeditorguicontroller.cpp | 73 +- src/plugins/diffeditor/diffeditorguicontroller.h | 11 +- src/plugins/diffeditor/diffeditorplugin.cpp | 496 ++++++++- src/plugins/diffeditor/diffeditorplugin.h | 9 +- src/plugins/diffeditor/diffeditorreloader.cpp | 98 ++ src/plugins/diffeditor/diffeditorreloader.h | 72 ++ src/plugins/diffeditor/differ.cpp | 141 ++- src/plugins/diffeditor/differ.h | 4 + src/plugins/diffeditor/diffutils.cpp | 785 +++++++++++-- src/plugins/diffeditor/diffutils.h | 78 +- src/plugins/diffeditor/images/reload.png | Bin 0 -> 250 bytes src/plugins/diffeditor/images/sidebysidediff.png | Bin 0 -> 257 bytes src/plugins/diffeditor/images/topbar.png | Bin 0 -> 379 bytes src/plugins/diffeditor/images/unifieddiff.png | Bin 0 -> 258 bytes .../diffeditor/selectabletexteditorwidget.cpp | 125 +++ .../diffeditor/selectabletexteditorwidget.h | 80 ++ .../diffeditor/sidebysidediffeditorwidget.cpp | 837 ++++++++------ .../diffeditor/sidebysidediffeditorwidget.h | 53 +- src/plugins/diffeditor/unifieddiffeditorwidget.cpp | 783 +++++++++++++ src/plugins/diffeditor/unifieddiffeditorwidget.h | 146 +++ src/plugins/git/branchdialog.cpp | 2 +- src/plugins/git/gerrit/gerritplugin.cpp | 2 +- src/plugins/git/gitclient.cpp | 1171 +++++++------------- src/plugins/git/gitclient.h | 16 +- src/plugins/git/gitsettings.cpp | 2 - src/plugins/git/gitsettings.h | 1 - src/plugins/vcsbase/basevcseditorfactory.cpp | 8 +- 42 files changed, 4058 insertions(+), 1424 deletions(-) delete mode 100644 src/plugins/coreplugin/images/sidebysidediff.png delete mode 100644 src/plugins/coreplugin/images/textdiff.png delete mode 100644 src/plugins/coreplugin/images/topbaricon.png create mode 100644 src/plugins/diffeditor/diffeditor.qrc create mode 100644 src/plugins/diffeditor/diffeditorreloader.cpp create mode 100644 src/plugins/diffeditor/diffeditorreloader.h create mode 100644 src/plugins/diffeditor/images/reload.png create mode 100644 src/plugins/diffeditor/images/sidebysidediff.png create mode 100644 src/plugins/diffeditor/images/topbar.png create mode 100644 src/plugins/diffeditor/images/unifieddiff.png create mode 100644 src/plugins/diffeditor/selectabletexteditorwidget.cpp create mode 100644 src/plugins/diffeditor/selectabletexteditorwidget.h create mode 100644 src/plugins/diffeditor/unifieddiffeditorwidget.cpp create mode 100644 src/plugins/diffeditor/unifieddiffeditorwidget.h diff --git a/src/plugins/coreplugin/core.qrc b/src/plugins/coreplugin/core.qrc index 9ce5a5bf5b..8032e35c7d 100644 --- a/src/plugins/coreplugin/core.qrc +++ b/src/plugins/coreplugin/core.qrc @@ -42,7 +42,6 @@ images/replace.png images/reset.png images/sidebaricon.png - images/topbaricon.png images/splitbutton_horizontal.png images/splitbutton_horizontal@2x.png images/statusbar.png @@ -89,8 +88,6 @@ images/splitbutton_vertical.png images/splitbutton_vertical@2x.png images/panel_manage_button.png - images/sidebysidediff.png - images/textdiff.png images/pause.png diff --git a/src/plugins/coreplugin/coreconstants.h b/src/plugins/coreplugin/coreconstants.h index 33b5febb4a..9d531139d4 100644 --- a/src/plugins/coreplugin/coreconstants.h +++ b/src/plugins/coreplugin/coreconstants.h @@ -197,7 +197,6 @@ const char ICON_CLEAR[] = ":/core/images/clear.png"; const char ICON_RESET[] = ":/core/images/reset.png"; const char ICON_MAGNIFIER[] = ":/core/images/magnifier.png"; const char ICON_TOGGLE_SIDEBAR[] = ":/core/images/sidebaricon.png"; -const char ICON_TOGGLE_TOPBAR[] = ":/core/images/topbaricon.png"; const char ICON_CLOSE_DOCUMENT[] = ":/core/images/button_close.png"; const char ICON_CLOSE[] = ":/core/images/closebutton.png"; const char ICON_CLOSE_DARK[] = ":/core/images/darkclosebutton.png"; @@ -207,8 +206,6 @@ const char ICON_CLOSE_SPLIT_TOP[] = ":/core/images/splitbutton_closetop.png"; const char ICON_CLOSE_SPLIT_BOTTOM[] = ":/core/images/splitbutton_closebottom.png"; const char ICON_CLOSE_SPLIT_LEFT[] = ":/core/images/splitbutton_closeleft.png"; const char ICON_CLOSE_SPLIT_RIGHT[] = ":/core/images/splitbutton_closeright.png"; -const char ICON_SIDE_BY_SIDE_DIFF[] = ":/core/images/sidebysidediff.png"; -const char ICON_TEXT_DIFF[] = ":/core/images/textdiff.png"; const char ICON_FILTER[] = ":/core/images/filtericon.png"; const char ICON_LINK[] = ":/core/images/linkicon.png"; const char ICON_PAUSE[] = ":/core/images/pause.png"; diff --git a/src/plugins/coreplugin/images/sidebysidediff.png b/src/plugins/coreplugin/images/sidebysidediff.png deleted file mode 100644 index 55c6f2802d..0000000000 Binary files a/src/plugins/coreplugin/images/sidebysidediff.png and /dev/null differ diff --git a/src/plugins/coreplugin/images/textdiff.png b/src/plugins/coreplugin/images/textdiff.png deleted file mode 100644 index 254b187744..0000000000 Binary files a/src/plugins/coreplugin/images/textdiff.png and /dev/null differ diff --git a/src/plugins/coreplugin/images/topbaricon.png b/src/plugins/coreplugin/images/topbaricon.png deleted file mode 100644 index 40d0fd842e..0000000000 Binary files a/src/plugins/coreplugin/images/topbaricon.png and /dev/null differ diff --git a/src/plugins/coreplugin/patchtool.cpp b/src/plugins/coreplugin/patchtool.cpp index 90c38d7102..83e2ce7104 100644 --- a/src/plugins/coreplugin/patchtool.cpp +++ b/src/plugins/coreplugin/patchtool.cpp @@ -93,7 +93,9 @@ bool PatchTool::runPatch(const QByteArray &input, const QString &workingDirector QProcess patchProcess; if (!workingDirectory.isEmpty()) patchProcess.setWorkingDirectory(workingDirectory); - QStringList args(QLatin1String("-p") + QString::number(strip)); + QStringList args; + if (strip >= 0) + args << (QLatin1String("-p") + QString::number(strip)); if (reverse) args << QLatin1String("-R"); MessageManager::write(QApplication::translate("Core::PatchTool", "Executing in %1: %2 %3"). diff --git a/src/plugins/diffeditor/diffeditor.cpp b/src/plugins/diffeditor/diffeditor.cpp index 875af4ddb6..b586aeeec7 100644 --- a/src/plugins/diffeditor/diffeditor.cpp +++ b/src/plugins/diffeditor/diffeditor.cpp @@ -32,6 +32,7 @@ #include "diffeditordocument.h" #include "diffeditorguicontroller.h" #include "sidebysidediffeditorwidget.h" +#include "unifieddiffeditorwidget.h" #include #include @@ -41,6 +42,7 @@ #include #include +#include #include #include #include @@ -49,6 +51,15 @@ #include #include #include +#include + +static const char settingsGroupC[] = "DiffEditor"; +static const char diffEditorTypeKeyC[] = "DiffEditorType"; +static const char sideBySideDiffEditorValueC[] = "SideBySide"; +static const char unifiedDiffEditorValueC[] = "Unified"; + +static const char legacySettingsGroupC[] = "Git"; +static const char useDiffEditorKeyC[] = "UseDiffEditor"; using namespace TextEditor; @@ -115,12 +126,16 @@ DiffEditor::DiffEditor() : IEditor(0) , m_document(new DiffEditorDocument()) , m_descriptionWidget(0) - , m_diffWidget(0) + , m_stackedWidget(0) + , m_sideBySideEditor(0) + , m_unifiedEditor(0) + , m_currentEditor(0) , m_controller(0) , m_guiController(0) , m_toolBar(0) , m_entriesComboBox(0) , m_toggleDescriptionAction(0) + , m_diffEditorSwitcher(0) { ctor(); } @@ -129,45 +144,64 @@ DiffEditor::DiffEditor(DiffEditor *other) : IEditor(0) , m_document(other->m_document) , m_descriptionWidget(0) - , m_diffWidget(0) + , m_stackedWidget(0) + , m_sideBySideEditor(0) + , m_unifiedEditor(0) + , m_currentEditor(0) , m_controller(0) , m_guiController(0) , m_toolBar(0) , m_entriesComboBox(0) , m_toggleDescriptionAction(0) + , m_diffEditorSwitcher(0) { ctor(); } void DiffEditor::ctor() { + setDuplicateSupported(true); + QSplitter *splitter = new Core::MiniSplitter(Qt::Vertical); m_descriptionWidget = new Internal::DescriptionEditorWidget(splitter); m_descriptionWidget->setReadOnly(true); splitter->addWidget(m_descriptionWidget); - m_diffWidget = new SideBySideDiffEditorWidget(splitter); - splitter->addWidget(m_diffWidget); + m_stackedWidget = new QStackedWidget(splitter); + splitter->addWidget(m_stackedWidget); + + m_sideBySideEditor = new SideBySideDiffEditorWidget(m_stackedWidget); + m_stackedWidget->addWidget(m_sideBySideEditor); + + m_unifiedEditor = new UnifiedDiffEditorWidget(m_stackedWidget); + m_stackedWidget->addWidget(m_unifiedEditor); setWidget(splitter); - connect(TextEditorSettings::instance(), SIGNAL(displaySettingsChanged(TextEditor::DisplaySettings)), - m_descriptionWidget, SLOT(setDisplaySettings(TextEditor::DisplaySettings))); - connect(TextEditorSettings::instance(), SIGNAL(fontSettingsChanged(TextEditor::FontSettings)), - m_descriptionWidget->baseTextDocument(), SLOT(setFontSettings(TextEditor::FontSettings))); - m_descriptionWidget->setDisplaySettings(TextEditorSettings::displaySettings()); - m_descriptionWidget->setCodeStyle(TextEditorSettings::codeStyle()); - m_descriptionWidget->baseTextDocument()->setFontSettings(TextEditorSettings::fontSettings()); + connect(TextEditorSettings::instance(), + SIGNAL(displaySettingsChanged(TextEditor::DisplaySettings)), + m_descriptionWidget, + SLOT(setDisplaySettings(TextEditor::DisplaySettings))); + connect(TextEditorSettings::instance(), + SIGNAL(fontSettingsChanged(TextEditor::FontSettings)), + m_descriptionWidget->baseTextDocument(), + SLOT(setFontSettings(TextEditor::FontSettings))); + + m_descriptionWidget->setDisplaySettings( + TextEditorSettings::displaySettings()); + m_descriptionWidget->setCodeStyle( + TextEditorSettings::codeStyle()); + m_descriptionWidget->baseTextDocument()->setFontSettings( + TextEditorSettings::fontSettings()); m_controller = m_document->controller(); m_guiController = new DiffEditorGuiController(m_controller, this); - m_diffWidget->setDiffEditorGuiController(m_guiController); connect(m_controller, SIGNAL(cleared(QString)), this, SLOT(slotCleared(QString))); - connect(m_controller, SIGNAL(diffContentsChanged(QList,QString)), - this, SLOT(slotDiffContentsChanged(QList,QString))); + connect(m_controller, SIGNAL(diffFilesChanged(QList,QString)), + this, SLOT(slotDiffFilesChanged(QList,QString))); connect(m_controller, SIGNAL(descriptionChanged(QString)), this, SLOT(slotDescriptionChanged(QString))); connect(m_controller, SIGNAL(descriptionEnablementChanged(bool)), @@ -179,6 +213,10 @@ void DiffEditor::ctor() slotDescriptionChanged(m_controller->description()); slotDescriptionVisibilityChanged(); + + showDiffEditor(readCurrentDiffEditorSetting()); + + toolBar(); } DiffEditor::~DiffEditor() @@ -193,11 +231,36 @@ Core::IEditor *DiffEditor::duplicate() return new DiffEditor(this); } -bool DiffEditor::open(QString *errorString, const QString &fileName, const QString &realFileName) +bool DiffEditor::open(QString *errorString, + const QString &fileName, + const QString &realFileName) { - Q_UNUSED(errorString) - Q_UNUSED(fileName) Q_UNUSED(realFileName) + + if (!m_controller) + return false; + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + *errorString = tr("Could not open patch file \"%1\".").arg(fileName); + return false; + } + + const QString patch = Core::EditorManager::defaultTextCodec()->toUnicode(file.readAll()); + + bool ok = false; + QList fileDataList + = DiffUtils::readPatch(patch, + m_controller->isIgnoreWhitespace(), + &ok); + if (!ok) { + *errorString = tr("Could not parse patch file \"%1\". " + "The contents is not of unified diff format.") + .arg(fileName); + return false; + } + + m_controller->setDiffFiles(fileDataList, QFileInfo(fileName).absolutePath()); return true; } @@ -223,7 +286,7 @@ QWidget *DiffEditor::toolBar() return m_toolBar; // Create - m_toolBar = createToolBar(m_diffWidget); + m_toolBar = createToolBar(m_sideBySideEditor); m_entriesComboBox = new QComboBox; m_entriesComboBox->setMinimumContentsLength(20); @@ -238,7 +301,7 @@ QWidget *DiffEditor::toolBar() QToolButton *whitespaceButton = new QToolButton(m_toolBar); whitespaceButton->setText(tr("Ignore Whitespace")); whitespaceButton->setCheckable(true); - whitespaceButton->setChecked(true); + whitespaceButton->setChecked(m_controller->isIgnoreWhitespace()); m_toolBar->addWidget(whitespaceButton); QLabel *contextLabel = new QLabel(m_toolBar); @@ -247,40 +310,58 @@ QWidget *DiffEditor::toolBar() m_toolBar->addWidget(contextLabel); QSpinBox *contextSpinBox = new QSpinBox(m_toolBar); - contextSpinBox->setRange(-1, 100); - contextSpinBox->setValue(3); + contextSpinBox->setRange(1, 100); + contextSpinBox->setValue(m_controller->contextLinesNumber()); contextSpinBox->setFrame(false); - contextSpinBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); // Mac Qt5 + contextSpinBox->setSizePolicy(QSizePolicy::Minimum, + QSizePolicy::Expanding); // Mac Qt5 m_toolBar->addWidget(contextSpinBox); QToolButton *toggleSync = new QToolButton(m_toolBar); toggleSync->setIcon(QIcon(QLatin1String(Core::Constants::ICON_LINK))); toggleSync->setCheckable(true); - toggleSync->setChecked(true); + toggleSync->setChecked(m_guiController->horizontalScrollBarSynchronization()); toggleSync->setToolTip(tr("Synchronize Horizontal Scroll Bars")); m_toolBar->addWidget(toggleSync); QToolButton *toggleDescription = new QToolButton(m_toolBar); - toggleDescription->setIcon(QIcon(QLatin1String(Core::Constants::ICON_TOGGLE_TOPBAR))); + toggleDescription->setIcon( + QIcon(QLatin1String(Constants::ICON_TOP_BAR))); toggleDescription->setCheckable(true); - toggleDescription->setChecked(true); + toggleDescription->setChecked(m_guiController->isDescriptionVisible()); m_toggleDescriptionAction = m_toolBar->addWidget(toggleDescription); slotDescriptionVisibilityChanged(); + QToolButton *reloadButton = new QToolButton(m_toolBar); + reloadButton->setIcon(QIcon(QLatin1String(Constants::ICON_RELOAD))); + reloadButton->setToolTip(tr("Reload Editor")); + m_toolBar->addWidget(reloadButton); + + m_diffEditorSwitcher = new QToolButton(m_toolBar); + m_toolBar->addWidget(m_diffEditorSwitcher); + updateDiffEditorSwitcher(); + connect(whitespaceButton, SIGNAL(clicked(bool)), - m_guiController, SLOT(setIgnoreWhitespaces(bool))); + m_controller, SLOT(setIgnoreWhitespace(bool))); + connect(m_controller, SIGNAL(ignoreWhitespaceChanged(bool)), + whitespaceButton, SLOT(setChecked(bool))); connect(contextSpinBox, SIGNAL(valueChanged(int)), - m_guiController, SLOT(setContextLinesNumber(int))); + m_controller, SLOT(setContextLinesNumber(int))); + connect(m_controller, SIGNAL(contextLinesNumberChanged(int)), + contextSpinBox, SLOT(setValue(int))); connect(toggleSync, SIGNAL(clicked(bool)), m_guiController, SLOT(setHorizontalScrollBarSynchronization(bool))); connect(toggleDescription, SIGNAL(clicked(bool)), m_guiController, SLOT(setDescriptionVisible(bool))); - // TODO: synchronize in opposite direction too + connect(m_diffEditorSwitcher, SIGNAL(clicked()), + this, SLOT(slotDiffEditorSwitched())); + connect(reloadButton, SIGNAL(clicked()), + m_controller, SLOT(requestReload())); return m_toolBar; } -DiffEditorController * DiffEditor::controller() const +DiffEditorController *DiffEditor::controller() const { return m_controller; } @@ -306,16 +387,16 @@ void DiffEditor::slotCleared(const QString &message) updateEntryToolTip(); } -void DiffEditor::slotDiffContentsChanged(const QList &diffFileList, - const QString &workingDirectory) +void DiffEditor::slotDiffFilesChanged(const QList &diffFileList, + const QString &workingDirectory) { Q_UNUSED(workingDirectory) m_entriesComboBox->clear(); const int count = diffFileList.count(); for (int i = 0; i < count; i++) { - const DiffEditorController::DiffFileInfo leftEntry = diffFileList.at(i).leftFileInfo; - const DiffEditorController::DiffFileInfo rightEntry = diffFileList.at(i).rightFileInfo; + const DiffFileInfo leftEntry = diffFileList.at(i).leftFileInfo; + const DiffFileInfo rightEntry = diffFileList.at(i).rightFileInfo; const QString leftShortFileName = QFileInfo(leftEntry.fileName).fileName(); const QString rightShortFileName = QFileInfo(rightEntry.fileName).fileName(); QString itemText; @@ -327,26 +408,34 @@ void DiffEditor::slotDiffContentsChanged(const QListaddItem(itemText); - m_entriesComboBox->setItemData(m_entriesComboBox->count() - 1, itemToolTip, Qt::ToolTipRole); + m_entriesComboBox->setItemData(m_entriesComboBox->count() - 1, + itemToolTip, Qt::ToolTipRole); } updateEntryToolTip(); } @@ -381,7 +470,114 @@ void DiffEditor::slotDescriptionVisibilityChanged() toggle->setToolTip(tr("Show Change Description")); m_toggleDescriptionAction->setVisible(enabled); +} + +void DiffEditor::slotDiffEditorSwitched() +{ + QWidget *oldEditor = m_currentEditor; + QWidget *newEditor = 0; + if (oldEditor == m_sideBySideEditor) + newEditor = m_unifiedEditor; + else if (oldEditor == m_unifiedEditor) + newEditor = m_sideBySideEditor; + else + newEditor = readCurrentDiffEditorSetting(); + + showDiffEditor(newEditor); +} + +void DiffEditor::updateDiffEditorSwitcher() +{ + if (!m_diffEditorSwitcher) + return; + + QIcon actionIcon; + QString actionToolTip; + if (m_currentEditor == m_unifiedEditor) { + actionIcon = QIcon(QLatin1String(Constants::ICON_SIDE_BY_SIDE_DIFF)); + actionToolTip = tr("Switch to Side By Side Diff Editor"); + } else if (m_currentEditor == m_sideBySideEditor) { + actionIcon = QIcon(QLatin1String(Constants::ICON_UNIFIED_DIFF)); + actionToolTip = tr("Switch to Unified Diff Editor"); + } + m_diffEditorSwitcher->setIcon(actionIcon); + m_diffEditorSwitcher->setToolTip(actionToolTip); +} + +void DiffEditor::showDiffEditor(QWidget *newEditor) +{ + if (m_currentEditor == newEditor) + return; + + if (m_currentEditor == m_sideBySideEditor) + m_sideBySideEditor->setDiffEditorGuiController(0); + else if (m_currentEditor == m_unifiedEditor) + m_unifiedEditor->setDiffEditorGuiController(0); + + m_currentEditor = newEditor; + + if (m_currentEditor == m_unifiedEditor) + m_unifiedEditor->setDiffEditorGuiController(m_guiController); + else if (m_currentEditor == m_sideBySideEditor) + m_sideBySideEditor->setDiffEditorGuiController(m_guiController); + + m_stackedWidget->setCurrentWidget(m_currentEditor); + + writeCurrentDiffEditorSetting(m_currentEditor); + updateDiffEditorSwitcher(); +} + +QWidget *DiffEditor::readLegacyCurrentDiffEditorSetting() +{ + QSettings *s = Core::ICore::settings(); + + s->beginGroup(QLatin1String(legacySettingsGroupC)); + const bool legacyExists = s->contains(QLatin1String(useDiffEditorKeyC)); + const bool legacyEditor = s->value( + QLatin1String(useDiffEditorKeyC), true).toBool(); + if (legacyExists) + s->remove(QLatin1String(useDiffEditorKeyC)); + s->endGroup(); + + QWidget *currentEditor = m_sideBySideEditor; + if (!legacyEditor) + currentEditor = m_unifiedEditor; + + if (legacyExists && currentEditor == m_unifiedEditor) + writeCurrentDiffEditorSetting(currentEditor); + + return currentEditor; +} + +QWidget *DiffEditor::readCurrentDiffEditorSetting() +{ + // replace it with m_sideBySideEditor when dropping legacy stuff + QWidget *defaultEditor = readLegacyCurrentDiffEditorSetting(); + + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + const QString editorString = s->value( + QLatin1String(diffEditorTypeKeyC)).toString(); + s->endGroup(); + if (editorString == QLatin1String(unifiedDiffEditorValueC)) + return m_unifiedEditor; + + if (editorString == QLatin1String(sideBySideDiffEditorValueC)) + return m_sideBySideEditor; + + return defaultEditor; +} + +void DiffEditor::writeCurrentDiffEditorSetting(QWidget *currentEditor) +{ + const QString editorString = currentEditor == m_unifiedEditor + ? QLatin1String(unifiedDiffEditorValueC) + : QLatin1String(sideBySideDiffEditorValueC); + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(diffEditorTypeKeyC), editorString); + s->endGroup(); } } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffeditor.h b/src/plugins/diffeditor/diffeditor.h index 4c982ab13d..cd09ee1e46 100644 --- a/src/plugins/diffeditor/diffeditor.h +++ b/src/plugins/diffeditor/diffeditor.h @@ -37,9 +37,10 @@ #include QT_BEGIN_NAMESPACE -class QToolBar; class QComboBox; +class QToolBar; class QToolButton; +class QStackedWidget; QT_END_NAMESPACE namespace TextEditor { class BaseTextEditorWidget; } @@ -49,6 +50,7 @@ namespace DiffEditor { class DiffEditorDocument; class DiffEditorGuiController; class SideBySideDiffEditorWidget; +class UnifiedDiffEditorWidget; class DIFFEDITOR_EXPORT DiffEditor : public Core::IEditor { @@ -64,7 +66,9 @@ public: // Core::IEditor Core::IEditor *duplicate(); - bool open(QString *errorString, const QString &fileName, const QString &realFileName); + bool open(QString *errorString, + const QString &fileName, + const QString &realFileName); Core::IDocument *document(); QWidget *toolBar(); @@ -74,24 +78,34 @@ public slots: private slots: void slotCleared(const QString &message); - void slotDiffContentsChanged(const QList &diffFileList, - const QString &workingDirectory); + void slotDiffFilesChanged(const QList &diffFileList, + const QString &workingDirectory); void entryActivated(int index); void slotDescriptionChanged(const QString &description); void slotDescriptionVisibilityChanged(); + void slotDiffEditorSwitched(); private: void ctor(); void updateEntryToolTip(); + void showDiffEditor(QWidget *newEditor); + void updateDiffEditorSwitcher(); + QWidget *readLegacyCurrentDiffEditorSetting(); + QWidget *readCurrentDiffEditorSetting(); + void writeCurrentDiffEditorSetting(QWidget *currentEditor); QSharedPointer m_document; TextEditor::BaseTextEditorWidget *m_descriptionWidget; - SideBySideDiffEditorWidget *m_diffWidget; + QStackedWidget *m_stackedWidget; + SideBySideDiffEditorWidget *m_sideBySideEditor; + UnifiedDiffEditorWidget *m_unifiedEditor; + QWidget *m_currentEditor; DiffEditorController *m_controller; DiffEditorGuiController *m_guiController; QToolBar *m_toolBar; QComboBox *m_entriesComboBox; QAction *m_toggleDescriptionAction; + QToolButton *m_diffEditorSwitcher; }; } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffeditor.pro b/src/plugins/diffeditor/diffeditor.pro index d015f3d1b4..801abc25c4 100644 --- a/src/plugins/diffeditor/diffeditor.pro +++ b/src/plugins/diffeditor/diffeditor.pro @@ -10,9 +10,12 @@ HEADERS += diffeditor_global.h \ diffeditorguicontroller.h \ diffeditormanager.h \ diffeditorplugin.h \ + diffeditorreloader.h \ differ.h \ diffutils.h \ - sidebysidediffeditorwidget.h + selectabletexteditorwidget.h \ + sidebysidediffeditorwidget.h \ + unifieddiffeditorwidget.h SOURCES += diffeditor.cpp \ diffeditorcontroller.cpp \ @@ -21,8 +24,11 @@ SOURCES += diffeditor.cpp \ diffeditorguicontroller.cpp \ diffeditormanager.cpp \ diffeditorplugin.cpp \ + diffeditorreloader.cpp \ differ.cpp \ diffutils.cpp \ - sidebysidediffeditorwidget.cpp + selectabletexteditorwidget.cpp \ + sidebysidediffeditorwidget.cpp \ + unifieddiffeditorwidget.cpp -RESOURCES += +RESOURCES += diffeditor.qrc diff --git a/src/plugins/diffeditor/diffeditor.qbs b/src/plugins/diffeditor/diffeditor.qbs index e561e1cd9c..4746e3346c 100644 --- a/src/plugins/diffeditor/diffeditor.qbs +++ b/src/plugins/diffeditor/diffeditor.qbs @@ -15,6 +15,7 @@ QtcPlugin { files: [ "diffeditor.cpp", "diffeditor.h", + "diffeditor.qrc", "diffeditor_global.h", "diffeditorconstants.h", "diffeditorcontroller.cpp", @@ -29,12 +30,16 @@ QtcPlugin { "diffeditormanager.h", "diffeditorplugin.cpp", "diffeditorplugin.h", + "diffeditorreloader.cpp", + "diffeditorreloader.h", "differ.cpp", "differ.h", "diffutils.cpp", "diffutils.h", "sidebysidediffeditorwidget.cpp", "sidebysidediffeditorwidget.h", + "unifieddiffeditorwidget.cpp", + "unifieddiffeditorwidget.h", ] } diff --git a/src/plugins/diffeditor/diffeditor.qrc b/src/plugins/diffeditor/diffeditor.qrc new file mode 100644 index 0000000000..e7c06f78ee --- /dev/null +++ b/src/plugins/diffeditor/diffeditor.qrc @@ -0,0 +1,8 @@ + + + images/reload.png + images/sidebysidediff.png + images/unifieddiff.png + images/topbar.png + + diff --git a/src/plugins/diffeditor/diffeditorconstants.h b/src/plugins/diffeditor/diffeditorconstants.h index a19528ce15..ef07b4c168 100644 --- a/src/plugins/diffeditor/diffeditorconstants.h +++ b/src/plugins/diffeditor/diffeditorconstants.h @@ -37,8 +37,14 @@ namespace Constants { const char DIFF_EDITOR_ID[] = "Diff Editor"; const char DIFF_EDITOR_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("DiffEditor", "Diff Editor"); +const char DIFF_EDITOR_MIMETYPE[] = "text/x-patch"; const char G_TOOLS_DIFF[] = "QtCreator.Group.Tools.Options"; +const char ICON_SIDE_BY_SIDE_DIFF[] = ":/diffeditor/images/sidebysidediff.png"; +const char ICON_UNIFIED_DIFF[] = ":/diffeditor/images/unifieddiff.png"; +const char ICON_RELOAD[] = ":/diffeditor/images/reload.png"; +const char ICON_TOP_BAR[] = ":/diffeditor/images/topbar.png"; + } // namespace Constants } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffeditorcontroller.cpp b/src/plugins/diffeditor/diffeditorcontroller.cpp index fae78efbcb..b815961cf1 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.cpp +++ b/src/plugins/diffeditor/diffeditorcontroller.cpp @@ -29,12 +29,28 @@ #include "diffeditorcontroller.h" +#include + +static const char settingsGroupC[] = "DiffEditor"; +static const char contextLineNumbersKeyC[] = "ContextLineNumbers"; +static const char ignoreWhitespaceKeyC[] = "IgnoreWhitespace"; + namespace DiffEditor { DiffEditorController::DiffEditorController(QObject *parent) : QObject(parent), - m_descriptionEnabled(false) + m_descriptionEnabled(false), + m_contextLinesNumber(3), + m_ignoreWhitespace(true) { + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + m_contextLinesNumber = s->value(QLatin1String(contextLineNumbersKeyC), + m_contextLinesNumber).toInt(); + m_ignoreWhitespace = s->value(QLatin1String(ignoreWhitespaceKeyC), + m_ignoreWhitespace).toBool(); + s->endGroup(); + clear(); } @@ -48,9 +64,9 @@ QString DiffEditorController::clearMessage() const return m_clearMessage; } -QList DiffEditorController::diffContents() const +QList DiffEditorController::diffFiles() const { - return m_diffFileList; + return m_diffFiles; } QString DiffEditorController::workingDirectory() const @@ -68,6 +84,43 @@ bool DiffEditorController::isDescriptionEnabled() const return m_descriptionEnabled; } +int DiffEditorController::contextLinesNumber() const +{ + return m_contextLinesNumber; +} + +bool DiffEditorController::isIgnoreWhitespace() const +{ + return m_ignoreWhitespace; +} + +QString DiffEditorController::makePatch(int diffFileIndex, + int chunkIndex, + bool revert) const +{ + if (diffFileIndex < 0 || chunkIndex < 0) + return QString(); + + if (diffFileIndex >= m_diffFiles.count()) + return QString(); + + const FileData fileData = m_diffFiles.at(diffFileIndex); + if (chunkIndex >= fileData.chunks.count()) + return QString(); + + const ChunkData chunkData = fileData.chunks.at(chunkIndex); + const bool lastChunk = (chunkIndex == fileData.chunks.count() - 1); + + const QString fileName = revert + ? fileData.rightFileInfo.fileName + : fileData.leftFileInfo.fileName; + + return DiffUtils::makePatch(chunkData, + fileName, + fileName, + lastChunk && fileData.lastChunkAtTheEndOfFile); +} + void DiffEditorController::clear() { clear(tr("No difference")); @@ -75,16 +128,18 @@ void DiffEditorController::clear() void DiffEditorController::clear(const QString &message) { + setDescription(QString()); + setDiffFiles(QList()); m_clearMessage = message; emit cleared(message); } -void DiffEditorController::setDiffContents(const QList &diffFileList, - const QString &workingDirectory) +void DiffEditorController::setDiffFiles(const QList &diffFileList, + const QString &workingDirectory) { - m_diffFileList = diffFileList; + m_diffFiles = diffFileList; m_workingDirectory = workingDirectory; - emit diffContentsChanged(diffFileList, workingDirectory); + emit diffFilesChanged(diffFileList, workingDirectory); } void DiffEditorController::setDescription(const QString &description) @@ -105,4 +160,40 @@ void DiffEditorController::setDescriptionEnabled(bool on) emit descriptionEnablementChanged(on); } +void DiffEditorController::setContextLinesNumber(int lines) +{ + const int l = qMax(lines, 1); + if (m_contextLinesNumber == l) + return; + + m_contextLinesNumber = l; + + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(contextLineNumbersKeyC), m_contextLinesNumber); + s->endGroup(); + + emit contextLinesNumberChanged(l); +} + +void DiffEditorController::setIgnoreWhitespace(bool ignore) +{ + if (m_ignoreWhitespace == ignore) + return; + + m_ignoreWhitespace = ignore; + + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(ignoreWhitespaceKeyC), m_ignoreWhitespace); + s->endGroup(); + + emit ignoreWhitespaceChanged(ignore); +} + +void DiffEditorController::requestReload() +{ + emit reloadRequested(); +} + } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffeditorcontroller.h b/src/plugins/diffeditor/diffeditorcontroller.h index 97aee8c118..cd632cafef 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.h +++ b/src/plugins/diffeditor/diffeditorcontroller.h @@ -31,6 +31,7 @@ #define DIFFEDITORCONTROLLER_H #include "diffeditor_global.h" +#include "diffutils.h" #include @@ -40,54 +41,55 @@ class DIFFEDITOR_EXPORT DiffEditorController : public QObject { Q_OBJECT public: - class DiffFileInfo { - public: - DiffFileInfo() {} - DiffFileInfo(const QString &file) : fileName(file) {} - DiffFileInfo(const QString &file, const QString &type) : fileName(file), typeInfo(type) {} - QString fileName; - QString typeInfo; - }; - - class DiffFilesContents { - public: - DiffFileInfo leftFileInfo; - QString leftText; - DiffFileInfo rightFileInfo; - QString rightText; - }; - DiffEditorController(QObject *parent = 0); ~DiffEditorController(); QString clearMessage() const; - QList diffContents() const; + QList diffFiles() const; QString workingDirectory() const; QString description() const; bool isDescriptionEnabled() const; + int contextLinesNumber() const; + bool isIgnoreWhitespace() const; + + QString makePatch(int diffFileIndex, int chunkIndex, bool revert) const; + +signals: + void chunkActionsRequested(QMenu *menu, + int diffFileIndex, + int chunkIndex); public slots: void clear(); void clear(const QString &message); - void setDiffContents(const QList &diffFileList, - const QString &workingDirectory = QString()); + void setDiffFiles(const QList &diffFileList, + const QString &workingDirectory = QString()); void setDescription(const QString &description); void setDescriptionEnabled(bool on); + void setContextLinesNumber(int lines); + void setIgnoreWhitespace(bool ignore); + void requestReload(); signals: void cleared(const QString &message); - void diffContentsChanged(const QList &diffFileList, const QString &workingDirectory); + void diffFilesChanged(const QList &diffFileList, + const QString &workingDirectory); void descriptionChanged(const QString &description); void descriptionEnablementChanged(bool on); + void contextLinesNumberChanged(int lines); + void ignoreWhitespaceChanged(bool ignore); + void reloadRequested(); private: QString m_clearMessage; - QList m_diffFileList; + QList m_diffFiles; QString m_workingDirectory; QString m_description; bool m_descriptionEnabled; + int m_contextLinesNumber; + bool m_ignoreWhitespace; }; } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffeditorfactory.cpp b/src/plugins/diffeditor/diffeditorfactory.cpp index 25c873654e..b0b23b2771 100644 --- a/src/plugins/diffeditor/diffeditorfactory.cpp +++ b/src/plugins/diffeditor/diffeditorfactory.cpp @@ -43,6 +43,7 @@ DiffEditorFactory::DiffEditorFactory(QObject *parent) { setId(Constants::DIFF_EDITOR_ID); setDisplayName(qApp->translate("DiffEditorFactory", Constants::DIFF_EDITOR_DISPLAY_NAME)); + addMimeType(Constants::DIFF_EDITOR_MIMETYPE); } Core::IEditor *DiffEditorFactory::createEditor() diff --git a/src/plugins/diffeditor/diffeditorguicontroller.cpp b/src/plugins/diffeditor/diffeditorguicontroller.cpp index a24cd8ae98..026feb7a95 100644 --- a/src/plugins/diffeditor/diffeditorguicontroller.cpp +++ b/src/plugins/diffeditor/diffeditorguicontroller.cpp @@ -30,19 +30,35 @@ #include "diffeditorguicontroller.h" #include "diffeditorcontroller.h" +#include + +static const char settingsGroupC[] = "DiffEditor"; +static const char descriptionVisibleKeyC[] = "DescriptionVisible"; +static const char horizontalScrollBarSynchronizationKeyC[] = + "HorizontalScrollBarSynchronization"; + namespace DiffEditor { -DiffEditorGuiController::DiffEditorGuiController(DiffEditorController *controller, QObject *parent) +DiffEditorGuiController::DiffEditorGuiController( + DiffEditorController *controller, + QObject *parent) : QObject(parent), m_controller(controller), m_descriptionVisible(true), - m_contextLinesNumber(3), - m_ignoreWhitespaces(true), m_syncScrollBars(true), m_currentDiffFileIndex(-1) { - connect(m_controller, SIGNAL(cleared(QString)), this, SLOT(slotUpdateDiffFileIndex())); - connect(m_controller, SIGNAL(diffContentsChanged(QList,QString)), + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + m_descriptionVisible = s->value(QLatin1String(descriptionVisibleKeyC), + m_descriptionVisible).toBool(); + m_syncScrollBars = s->value(QLatin1String(horizontalScrollBarSynchronizationKeyC), + m_syncScrollBars).toBool(); + s->endGroup(); + + connect(m_controller, SIGNAL(cleared(QString)), + this, SLOT(slotUpdateDiffFileIndex())); + connect(m_controller, SIGNAL(diffFilesChanged(QList,QString)), this, SLOT(slotUpdateDiffFileIndex())); slotUpdateDiffFileIndex(); } @@ -62,16 +78,6 @@ bool DiffEditorGuiController::isDescriptionVisible() const return m_descriptionVisible; } -int DiffEditorGuiController::contextLinesNumber() const -{ - return m_contextLinesNumber; -} - -bool DiffEditorGuiController::isIgnoreWhitespaces() const -{ - return m_ignoreWhitespaces; -} - bool DiffEditorGuiController::horizontalScrollBarSynchronization() const { return m_syncScrollBars; @@ -84,7 +90,7 @@ int DiffEditorGuiController::currentDiffFileIndex() const void DiffEditorGuiController::slotUpdateDiffFileIndex() { - m_currentDiffFileIndex = (m_controller->diffContents().isEmpty() ? -1 : 0); + m_currentDiffFileIndex = (m_controller->diffFiles().isEmpty() ? -1 : 0); } void DiffEditorGuiController::setDescriptionVisible(bool on) @@ -93,26 +99,13 @@ void DiffEditorGuiController::setDescriptionVisible(bool on) return; m_descriptionVisible = on; - emit descriptionVisibilityChanged(on); -} -void DiffEditorGuiController::setContextLinesNumber(int lines) -{ - const int l = qMax(lines, -1); - if (m_contextLinesNumber == l) - return; - - m_contextLinesNumber = l; - emit contextLinesNumberChanged(l); -} - -void DiffEditorGuiController::setIgnoreWhitespaces(bool ignore) -{ - if (m_ignoreWhitespaces == ignore) - return; + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(descriptionVisibleKeyC), m_descriptionVisible); + s->endGroup(); - m_ignoreWhitespaces = ignore; - emit ignoreWhitespacesChanged(ignore); + emit descriptionVisibilityChanged(on); } void DiffEditorGuiController::setHorizontalScrollBarSynchronization(bool on) @@ -121,15 +114,23 @@ void DiffEditorGuiController::setHorizontalScrollBarSynchronization(bool on) return; m_syncScrollBars = on; + + QSettings *s = Core::ICore::settings(); + s->beginGroup(QLatin1String(settingsGroupC)); + s->setValue(QLatin1String(horizontalScrollBarSynchronizationKeyC), + m_syncScrollBars); + s->endGroup(); + emit horizontalScrollBarSynchronizationChanged(on); } void DiffEditorGuiController::setCurrentDiffFileIndex(int diffFileIndex) { - if (m_controller->diffContents().isEmpty()) + if (m_controller->diffFiles().isEmpty()) return; // -1 is the only valid value in this case - const int newIndex = qBound(0, diffFileIndex, m_controller->diffContents().count() - 1); + const int newIndex = qBound(0, diffFileIndex, + m_controller->diffFiles().count() - 1); if (m_currentDiffFileIndex == newIndex) return; diff --git a/src/plugins/diffeditor/diffeditorguicontroller.h b/src/plugins/diffeditor/diffeditorguicontroller.h index a140a829b7..098ca0ef4a 100644 --- a/src/plugins/diffeditor/diffeditorguicontroller.h +++ b/src/plugins/diffeditor/diffeditorguicontroller.h @@ -42,28 +42,23 @@ class DIFFEDITOR_EXPORT DiffEditorGuiController : public QObject { Q_OBJECT public: - DiffEditorGuiController(DiffEditorController *controller, QObject *parent = 0); + DiffEditorGuiController(DiffEditorController *controller, + QObject *parent = 0); ~DiffEditorGuiController(); DiffEditorController *controller() const; bool isDescriptionVisible() const; - int contextLinesNumber() const; - bool isIgnoreWhitespaces() const; bool horizontalScrollBarSynchronization() const; int currentDiffFileIndex() const; public slots: void setDescriptionVisible(bool on); - void setContextLinesNumber(int lines); - void setIgnoreWhitespaces(bool ignore); void setHorizontalScrollBarSynchronization(bool on); void setCurrentDiffFileIndex(int diffFileIndex); signals: void descriptionVisibilityChanged(bool on); - void contextLinesNumberChanged(int lines); - void ignoreWhitespacesChanged(bool ignore); void horizontalScrollBarSynchronizationChanged(bool on); void currentDiffFileIndexChanged(int diffFileIndex); @@ -73,8 +68,6 @@ private slots: private: DiffEditorController *m_controller; bool m_descriptionVisible; - int m_contextLinesNumber; - bool m_ignoreWhitespaces; bool m_syncScrollBars; int m_currentDiffFileIndex; }; diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp index 1a9f5c2bc7..d15ab4a8b7 100644 --- a/src/plugins/diffeditor/diffeditorplugin.cpp +++ b/src/plugins/diffeditor/diffeditorplugin.cpp @@ -30,8 +30,11 @@ #include "diffeditorplugin.h" #include "diffeditor.h" #include "diffeditorconstants.h" +#include "diffeditordocument.h" #include "diffeditorfactory.h" #include "diffeditormanager.h" +#include "diffeditorreloader.h" +#include "differ.h" #include #include @@ -46,6 +49,87 @@ namespace DiffEditor { namespace Internal { +class SimpleDiffEditorReloader : public DiffEditorReloader +{ + Q_OBJECT +public: + SimpleDiffEditorReloader(QObject *parent, + const QString &leftFileName, + const QString &rightFileName); + +protected: + void reload(); + +private: + QString getFileContents(const QString &fileName) const; + + QString m_leftFileName; + QString m_rightFileName; +}; + +SimpleDiffEditorReloader::SimpleDiffEditorReloader(QObject *parent, + const QString &leftFileName, + const QString &rightFileName) + : DiffEditorReloader(parent), + m_leftFileName(leftFileName), + m_rightFileName(rightFileName) +{ +} + +void SimpleDiffEditorReloader::reload() +{ + const QString leftText = getFileContents(m_leftFileName); + const QString rightText = getFileContents(m_rightFileName); + + Differ differ; + QList diffList = differ.cleanupSemantics( + differ.diff(leftText, rightText)); + + QList leftDiffList; + QList rightDiffList; + Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList); + QList outputLeftDiffList; + QList outputRightDiffList; + + if (diffEditorController()->isIgnoreWhitespace()) { + const QList leftIntermediate = + Differ::moveWhitespaceIntoEqualities(leftDiffList); + const QList rightIntermediate = + Differ::moveWhitespaceIntoEqualities(rightDiffList); + Differ::ignoreWhitespaceBetweenEqualities(leftIntermediate, + rightIntermediate, + &outputLeftDiffList, + &outputRightDiffList); + } else { + outputLeftDiffList = leftDiffList; + outputRightDiffList = rightDiffList; + } + + const ChunkData chunkData = DiffUtils::calculateOriginalData( + outputLeftDiffList, outputRightDiffList); + FileData fileData = DiffUtils::calculateContextData( + chunkData, diffEditorController()->contextLinesNumber(), 0); + fileData.leftFileInfo.fileName = m_leftFileName; + fileData.rightFileInfo.fileName = m_rightFileName; + + QList fileDataList; + fileDataList << fileData; + + diffEditorController()->setDiffFiles(fileDataList); + + reloadFinished(); +} + +QString SimpleDiffEditorReloader::getFileContents(const QString &fileName) const +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + return Core::EditorManager::defaultTextCodec()->toUnicode(file.readAll()); + return QString(); +} + +///////////////// + DiffEditorPlugin::DiffEditorPlugin() { } @@ -62,7 +146,8 @@ bool DiffEditorPlugin::initialize(const QStringList &arguments, QString *errorMe //register actions Core::ActionContainer *toolsContainer = Core::ActionManager::actionContainer(Core::Constants::M_TOOLS); - toolsContainer->insertGroup(Core::Constants::G_TOOLS_OPTIONS, Constants::G_TOOLS_DIFF); + toolsContainer->insertGroup(Core::Constants::G_TOOLS_OPTIONS, + Constants::G_TOOLS_DIFF); Core::Context globalcontext(Core::Constants::C_GLOBAL); @@ -98,39 +183,396 @@ void DiffEditorPlugin::diff() return; - const Core::Id editorId = Constants::DIFF_EDITOR_ID; - //: Editor title - QString title = tr("Diff \"%1\", \"%2\"").arg(fileName1).arg(fileName2); - DiffEditor *editor = qobject_cast - (Core::EditorManager::openEditorWithContents(editorId, &title, QByteArray(), - (Core::EditorManager::OpenInOtherSplit - | Core::EditorManager::NoNewSplits))); - if (!editor) - return; + const QString documentId = QLatin1String("Diff ") + fileName1 + + QLatin1String(", ") + fileName2; + DiffEditorDocument *document = DiffEditorManager::find(documentId); + if (!document) { + QString title = tr("Diff \"%1\", \"%2\"").arg(fileName1).arg(fileName2); + document = DiffEditorManager::findOrCreate(documentId, title); + if (!document) + return; - const QString text1 = getFileContents(fileName1); - const QString text2 = getFileContents(fileName2); + DiffEditorController *controller = document->controller(); + SimpleDiffEditorReloader *reloader = + new SimpleDiffEditorReloader(controller, fileName1, fileName2); + reloader->setDiffEditorController(controller); + } - DiffEditorController::DiffFilesContents dfc; - dfc.leftFileInfo = fileName1; - dfc.leftText = text1; - dfc.rightFileInfo = fileName2; - dfc.rightText = text2; - QList list; - list.append(dfc); + Core::EditorManager::activateEditorForDocument(document); - editor->controller()->setDiffContents(list); + document->controller()->requestReload(); } -QString DiffEditorPlugin::getFileContents(const QString &fileName) const +} // namespace Internal +} // namespace DiffEditor + +#ifdef WITH_TESTS + +#include + +#include "diffutils.h" + +Q_DECLARE_METATYPE(DiffEditor::ChunkData) +Q_DECLARE_METATYPE(QList) + +void DiffEditor::Internal::DiffEditorPlugin::testMakePatch_data() { - QFile file(fileName); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) - return Core::EditorManager::defaultTextCodec()->toUnicode(file.readAll()); - return QString(); + QTest::addColumn("sourceChunk"); + QTest::addColumn("leftFileName"); + QTest::addColumn("rightFileName"); + QTest::addColumn("lastChunk"); + QTest::addColumn("patchText"); + + const QString fileName = QLatin1String("a.txt"); + const QString header = QLatin1String("--- ") + fileName + + QLatin1String("\n+++ ") + fileName + QLatin1String("\n"); + + QList rows; + rows << RowData(TextLineData(QLatin1String("ABCD")), + TextLineData(TextLineData::Separator)); + rows << RowData(TextLineData(QLatin1String("EFGH"))); + rows << RowData(TextLineData(QLatin1String(""))); + ChunkData chunk; + chunk.rows = rows; + QString patchText = header + QLatin1String("@@ -1,2 +1,1 @@\n" + "-ABCD\n" + " EFGH\n"); + QTest::newRow("Simple") << chunk + << fileName + << fileName + << true + << patchText; + + /////////// + + rows.clear(); + rows << RowData(TextLineData(QLatin1String("ABCD")), + TextLineData(QLatin1String("ABCD"))); + rows << RowData(TextLineData(QLatin1String("")), + TextLineData(TextLineData::Separator)); + chunk.rows = rows; + patchText = header + QLatin1String("@@ -1,1 +1,1 @@\n" + "-ABCD\n" + "+ABCD\n" + "\\ No newline at end of file\n"); + + QTest::newRow("Last newline removed") << chunk + << fileName + << fileName + << true + << patchText; + + /////////// + + // chunk the same here + patchText = header + QLatin1String("@@ -1,2 +1,1 @@\n" + "-ABCD\n" + "-\n" + "+ABCD\n"); + + QTest::newRow("Not a last newline removed") << chunk + << fileName + << fileName + << false + << patchText; + + /////////// + + rows.clear(); + rows << RowData(TextLineData(QLatin1String("ABCD")), + TextLineData(QLatin1String("ABCD"))); + rows << RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String(""))); + chunk.rows = rows; + patchText = header + QLatin1String("@@ -1,1 +1,1 @@\n" + "-ABCD\n" + "\\ No newline at end of file\n" + "+ABCD\n"); + + QTest::newRow("Last newline added") << chunk + << fileName + << fileName + << true + << patchText; + + /////////// + + // chunk the same here + patchText = header + QLatin1String("@@ -1,1 +1,2 @@\n" + "-ABCD\n" + "+ABCD\n" + "+\n"); + + QTest::newRow("Not a last newline added") << chunk + << fileName + << fileName + << false + << patchText; + + /////////// + + rows.clear(); + rows << RowData(TextLineData(QLatin1String("ABCD")), + TextLineData(QLatin1String("EFGH"))); + rows << RowData(TextLineData(QLatin1String(""))); + chunk.rows = rows; + patchText = header + QLatin1String("@@ -1,1 +1,1 @@\n" + "-ABCD\n" + "+EFGH\n"); + + QTest::newRow("Last line with a newline modified") << chunk + << fileName + << fileName + << true + << patchText; + + /////////// + + // chunk the same here + patchText = header + QLatin1String("@@ -1,2 +1,2 @@\n" + "-ABCD\n" + "+EFGH\n" + " \n"); + QTest::newRow("Not a last line with a newline modified") << chunk + << fileName + << fileName + << false + << patchText; + + /////////// + + rows.clear(); + rows << RowData(TextLineData(QLatin1String("ABCD")), + TextLineData(QLatin1String("EFGH"))); + chunk.rows = rows; + patchText = header + QLatin1String("@@ -1,1 +1,1 @@\n" + "-ABCD\n" + "\\ No newline at end of file\n" + "+EFGH\n" + "\\ No newline at end of file\n"); + + QTest::newRow("Last line without a newline modified") << chunk + << fileName + << fileName + << true + << patchText; + + /////////// + + // chunk the same here + patchText = header + QLatin1String("@@ -1,1 +1,1 @@\n" + "-ABCD\n" + "+EFGH\n"); + QTest::newRow("Not a last line without a newline modified") << chunk + << fileName + << fileName + << false + << patchText; + + /////////// + + rows.clear(); + rows << RowData(TextLineData(QLatin1String("ABCD")), + TextLineData(QLatin1String("EFGH"))); + rows << RowData(TextLineData(QLatin1String("IJKL"))); + chunk.rows = rows; + patchText = header + QLatin1String("@@ -1,2 +1,2 @@\n" + "-ABCD\n" + "+EFGH\n" + " IJKL\n" + "\\ No newline at end of file\n"); + + QTest::newRow("Last but one line modified, last line without a newline") + << chunk + << fileName + << fileName + << true + << patchText; + + /////////// + + // chunk the same here + patchText = header + QLatin1String("@@ -1,2 +1,2 @@\n" + "-ABCD\n" + "+EFGH\n" + " IJKL\n"); + + QTest::newRow("Last but one line modified, last line with a newline") + << chunk + << fileName + << fileName + << false + << patchText; } -} // namespace Internal -} // namespace DiffEditor +void DiffEditor::Internal::DiffEditorPlugin::testMakePatch() +{ + QFETCH(ChunkData, sourceChunk); + QFETCH(QString, leftFileName); + QFETCH(QString, rightFileName); + QFETCH(bool, lastChunk); + QFETCH(QString, patchText); + + QString result = DiffUtils::makePatch(sourceChunk, leftFileName, rightFileName, lastChunk); + + QCOMPARE(patchText, result); +} + +void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data() +{ + QTest::addColumn("sourcePatch"); + QTest::addColumn >("fileDataList"); + + QString patch = QLatin1String("diff --git a/src/plugins/diffeditor/diffeditor.cpp b/src/plugins/diffeditor/diffeditor.cpp\n" + "index eab9e9b..082c135 100644\n" + "--- a/src/plugins/diffeditor/diffeditor.cpp\n" + "+++ b/src/plugins/diffeditor/diffeditor.cpp\n" + "@@ -187,9 +187,6 @@ void DiffEditor::ctor()\n" + " m_controller = m_document->controller();\n" + " m_guiController = new DiffEditorGuiController(m_controller, this);\n" + " \n" + "-// m_sideBySideEditor->setDiffEditorGuiController(m_guiController);\n" + "-// m_unifiedEditor->setDiffEditorGuiController(m_guiController);\n" + "-\n" + " connect(m_controller, SIGNAL(cleared(QString)),\n" + " this, SLOT(slotCleared(QString)));\n" + " connect(m_controller, SIGNAL(diffContentsChanged(QList,QString)),\n" + "diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp\n" + "index 2f641c9..f8ff795 100644\n" + "--- a/src/plugins/diffeditor/diffutils.cpp\n" + "+++ b/src/plugins/diffeditor/diffutils.cpp\n" + "@@ -464,5 +464,12 @@ QString DiffUtils::makePatch(const ChunkData &chunkData,\n" + " return diffText;\n" + " }\n" + " \n" + "+FileData DiffUtils::makeFileData(const QString &patch)\n" + "+{\n" + "+ FileData fileData;\n" + "+\n" + "+ return fileData;\n" + "+}\n" + "+\n" + " } // namespace Internal\n" + " } // namespace DiffEditor"); + + FileData fileData1; + fileData1.leftFileInfo = DiffFileInfo(QLatin1String("src/plugins/diffeditor/diffeditor.cpp"), + QLatin1String("eab9e9b")); + fileData1.rightFileInfo = DiffFileInfo(QLatin1String("src/plugins/diffeditor/diffeditor.cpp"), + QLatin1String("082c135")); + ChunkData chunkData1; + chunkData1.leftStartingLineNumber = 187; + chunkData1.rightStartingLineNumber = 187; + QList rows1; + rows1.append(RowData(TextLineData(QLatin1String(" m_controller = m_document->controller();")))); + rows1.append(RowData(TextLineData(QLatin1String(" m_guiController = new DiffEditorGuiController(m_controller, this);")))); + rows1.append(RowData(TextLineData(QLatin1String("")))); + rows1.append(RowData(TextLineData(QLatin1String("// m_sideBySideEditor->setDiffEditorGuiController(m_guiController);")), + TextLineData(TextLineData::Separator))); + rows1.append(RowData(TextLineData(QLatin1String("// m_unifiedEditor->setDiffEditorGuiController(m_guiController);")), + TextLineData(TextLineData::Separator))); + rows1.append(RowData(TextLineData(QLatin1String("")), + TextLineData(TextLineData::Separator))); + rows1.append(RowData(TextLineData(QLatin1String(" connect(m_controller, SIGNAL(cleared(QString)),")))); + rows1.append(RowData(TextLineData(QLatin1String(" this, SLOT(slotCleared(QString)));")))); + rows1.append(RowData(TextLineData(QLatin1String(" connect(m_controller, SIGNAL(diffContentsChanged(QList,QString)),")))); + chunkData1.rows = rows1; + fileData1.chunks.append(chunkData1); + + FileData fileData2; + fileData2.leftFileInfo = DiffFileInfo(QLatin1String("src/plugins/diffeditor/diffutils.cpp"), + QLatin1String("2f641c9")); + fileData2.rightFileInfo = DiffFileInfo(QLatin1String("src/plugins/diffeditor/diffutils.cpp"), + QLatin1String("f8ff795")); + ChunkData chunkData2; + chunkData2.leftStartingLineNumber = 464; + chunkData2.rightStartingLineNumber = 464; + QList rows2; + rows2.append(RowData(TextLineData(QLatin1String(" return diffText;")))); + rows2.append(RowData(TextLineData(QLatin1String("}")))); + rows2.append(RowData(TextLineData(QLatin1String("")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String("FileData DiffUtils::makeFileData(const QString &patch)")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String("{")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String(" FileData fileData;")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String("")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String(" return fileData;")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String("}")))); + rows2.append(RowData(TextLineData(TextLineData::Separator), + TextLineData(QLatin1String("")))); + rows2.append(RowData(TextLineData(QLatin1String("} // namespace Internal")))); + rows2.append(RowData(TextLineData(QLatin1String("} // namespace DiffEditor")))); + chunkData2.rows = rows2; + fileData2.chunks.append(chunkData2); + + QList fileDataList; + fileDataList.append(fileData1); + fileDataList.append(fileData2); + + QTest::newRow("Git patch") << patch + << fileDataList; +} + +void DiffEditor::Internal::DiffEditorPlugin::testReadPatch() +{ + QFETCH(QString, sourcePatch); + QFETCH(QList, fileDataList); + + bool ok; + QList result = DiffUtils::readPatch(sourcePatch, false, &ok); + + QVERIFY(ok); + QCOMPARE(fileDataList.count(), result.count()); + for (int i = 0; i < fileDataList.count(); i++) { + const FileData &origFileData = fileDataList.at(i); + const FileData &resultFileData = result.at(i); + QCOMPARE(origFileData.leftFileInfo.fileName, + resultFileData.leftFileInfo.fileName); + QCOMPARE(origFileData.leftFileInfo.typeInfo, + resultFileData.leftFileInfo.typeInfo); + QCOMPARE(origFileData.rightFileInfo.fileName, + resultFileData.rightFileInfo.fileName); + QCOMPARE(origFileData.rightFileInfo.typeInfo, + resultFileData.rightFileInfo.typeInfo); + QCOMPARE(origFileData.chunks.count(), + resultFileData.chunks.count()); + for (int j = 0; j < origFileData.chunks.count(); j++) { + const ChunkData &origChunkData = origFileData.chunks.at(j); + const ChunkData &resultChunkData = resultFileData.chunks.at(j); + QCOMPARE(origChunkData.leftStartingLineNumber, + resultChunkData.leftStartingLineNumber); + QCOMPARE(origChunkData.rightStartingLineNumber, + resultChunkData.rightStartingLineNumber); + QCOMPARE(origChunkData.contextChunk, + resultChunkData.contextChunk); + QCOMPARE(origChunkData.rows.count(), + resultChunkData.rows.count()); + for (int k = 0; k < origChunkData.rows.count(); k++) { + const RowData &origRowData = origChunkData.rows.at(k); + const RowData &resultRowData = resultChunkData.rows.at(k); + QCOMPARE(origRowData.equal, + resultRowData.equal); + QCOMPARE(origRowData.leftLine.text, + resultRowData.leftLine.text); + QCOMPARE(origRowData.leftLine.textLineType, + resultRowData.leftLine.textLineType); + QCOMPARE(origRowData.rightLine.text, + resultRowData.rightLine.text); + QCOMPARE(origRowData.rightLine.textLineType, + resultRowData.rightLine.textLineType); + } + } + } +} + +#endif // WITH_TESTS + Q_EXPORT_PLUGIN(DiffEditor::Internal::DiffEditorPlugin) + +#include "diffeditorplugin.moc" diff --git a/src/plugins/diffeditor/diffeditorplugin.h b/src/plugins/diffeditor/diffeditorplugin.h index ba347213af..40eccd47d6 100644 --- a/src/plugins/diffeditor/diffeditorplugin.h +++ b/src/plugins/diffeditor/diffeditorplugin.h @@ -53,9 +53,12 @@ public: private slots: void diff(); -private: - QString getFileContents(const QString &fileName) const; - +#ifdef WITH_TESTS + void testMakePatch_data(); + void testMakePatch(); + void testReadPatch_data(); + void testReadPatch(); +#endif // WITH_TESTS }; } // namespace Internal diff --git a/src/plugins/diffeditor/diffeditorreloader.cpp b/src/plugins/diffeditor/diffeditorreloader.cpp new file mode 100644 index 0000000000..77087c1bda --- /dev/null +++ b/src/plugins/diffeditor/diffeditorreloader.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "diffeditorreloader.h" +#include "diffeditorcontroller.h" + +namespace DiffEditor { + +DiffEditorReloader::DiffEditorReloader(QObject *parent) + : QObject(parent), + m_controller(0), + m_reloading(false) +{ +} + +DiffEditorReloader::~DiffEditorReloader() +{ + +} + +DiffEditorController *DiffEditorReloader::diffEditorController() const +{ + return m_controller; +} + +void DiffEditorReloader::setDiffEditorController(DiffEditorController *controller) +{ + if (m_controller) { + disconnect(m_controller, SIGNAL(ignoreWhitespaceChanged(bool)), + this, SLOT(requestReload())); + disconnect(m_controller, SIGNAL(contextLinesNumberChanged(int)), + this, SLOT(requestReload())); + disconnect(m_controller, SIGNAL(reloadRequested()), + this, SLOT(requestReload())); + } + + m_controller = controller; + + if (m_controller) { + connect(m_controller, SIGNAL(ignoreWhitespaceChanged(bool)), + this, SLOT(requestReload())); + connect(m_controller, SIGNAL(contextLinesNumberChanged(int)), + this, SLOT(requestReload())); + connect(m_controller, SIGNAL(reloadRequested()), + this, SLOT(requestReload())); + } +} + +void DiffEditorReloader::requestReload() +{ + if (m_reloading) + return; + + if (!m_controller) + return; + + m_reloading = true; + + reload(); +} + +bool DiffEditorReloader::isReloading() const +{ + return m_reloading; +} + +void DiffEditorReloader::reloadFinished() +{ + m_reloading = false; +} + +} // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffeditorreloader.h b/src/plugins/diffeditor/diffeditorreloader.h new file mode 100644 index 0000000000..962902fb9d --- /dev/null +++ b/src/plugins/diffeditor/diffeditorreloader.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef DIFFEDITORRELOADER_H +#define DIFFEDITORRELOADER_H + +#include "diffeditor_global.h" + +#include + +namespace DiffEditor { + +class DiffEditorController; + +class DIFFEDITOR_EXPORT DiffEditorReloader : public QObject +{ + Q_OBJECT +public: + DiffEditorReloader(QObject *parent = 0); + ~DiffEditorReloader(); + + DiffEditorController *diffEditorController() const; + void setDiffEditorController(DiffEditorController *controller); + + bool isReloading() const; + +protected: + // reloadFinished() should be called + // inside reload() (for synchronous reload) + // or later (for asynchronous reload) + virtual void reload() = 0; + +protected slots: + void reloadFinished(); + +private slots: + void requestReload(); + +private: + DiffEditorController *m_controller; + bool m_reloading; +}; + +} // namespace DiffEditor + +#endif // DIFFEDITORRELOADER_H diff --git a/src/plugins/diffeditor/differ.cpp b/src/plugins/diffeditor/differ.cpp index e6c435bef9..aa396dfe8c 100644 --- a/src/plugins/diffeditor/differ.cpp +++ b/src/plugins/diffeditor/differ.cpp @@ -297,14 +297,16 @@ QList Differ::moveWhitespaceIntoEqualities(const QList &input) const int previousDiffCount = previousDiff.text.count(); if (previousDiff.command == Diff::Equal && previousDiffCount - && isWhitespace(previousDiff.text.at(previousDiffCount - 1))) { // previous diff ends with whitespace + && isWhitespace(previousDiff.text.at(previousDiffCount - 1))) { + // previous diff ends with whitespace int j = 0; while (j < diff.text.count()) { if (!isWhitespace(diff.text.at(j))) break; ++j; } - if (j > 0) { // diff starts with j whitespaces, move them to the previous diff + if (j > 0) { + // diff starts with j whitespaces, move them to the previous diff previousDiff.text.append(diff.text.left(j)); diff.text = diff.text.mid(j); } @@ -316,14 +318,16 @@ QList Differ::moveWhitespaceIntoEqualities(const QList &input) const int nextDiffCount = nextDiff.text.count(); if (nextDiff.command == Diff::Equal && nextDiffCount - && (isWhitespace(nextDiff.text.at(0)) || isNewLine(nextDiff.text.at(0)))) { // next diff starts with whitespace or with newline + && (isWhitespace(nextDiff.text.at(0)) || isNewLine(nextDiff.text.at(0)))) { + // next diff starts with whitespace or with newline int j = 0; while (j < diffCount) { if (!isWhitespace(diff.text.at(diffCount - j - 1))) break; ++j; } - if (j > 0) { // diff ends with j whitespaces, move them to the next diff + if (j > 0) { + // diff ends with j whitespaces, move them to the next diff nextDiff.text.prepend(diff.text.mid(diffCount - j)); diff.text = diff.text.left(diffCount - j); } @@ -559,15 +563,18 @@ static QString encodeExpandedWhitespace(const QString &leftEquality, if ((leftWhitespaces.count() && !rightWhitespaces.count()) || (!leftWhitespaces.count() && rightWhitespaces.count())) { - return QString(); // there must be at least 1 corresponding whitespace, equalities broken + // there must be at least 1 corresponding whitespace, equalities broken + return QString(); } if (leftWhitespaces.count() && rightWhitespaces.count()) { const int replacementPosition = output.count(); const int replacementSize = qMax(leftWhitespaces.count(), rightWhitespaces.count()); const QString replacement(replacementSize, QLatin1Char(' ')); - leftCodeMap->insert(replacementPosition, qMakePair(replacementSize, leftWhitespaces)); - rightCodeMap->insert(replacementPosition, qMakePair(replacementSize, rightWhitespaces)); + leftCodeMap->insert(replacementPosition, + qMakePair(replacementSize, leftWhitespaces)); + rightCodeMap->insert(replacementPosition, + qMakePair(replacementSize, rightWhitespaces)); output.append(replacement); } @@ -612,7 +619,8 @@ static QList decodeExpandedWhitespace(const QList &input, return QList(); // replacement exceeds one Diff const QString replacement = it.value().second; const int updatedDiffCount = diff.text.count(); - diff.text.replace(updatedDiffCount - reversePosition, replacementSize, replacement); + diff.text.replace(updatedDiffCount - reversePosition, + replacementSize, replacement); ++it; } output.append(diff); @@ -680,12 +688,14 @@ static bool diffWithWhitespaceExpandedInEqualities(const QList &leftInput, QMapIterator > itLeft(leftCodeMap); while (itLeft.hasNext()) { itLeft.next(); - commonLeftCodeMap.insert(leftText.count() + itLeft.key(), itLeft.value()); + commonLeftCodeMap.insert(leftText.count() + itLeft.key(), + itLeft.value()); } QMapIterator > itRight(rightCodeMap); while (itRight.hasNext()) { itRight.next(); - commonRightCodeMap.insert(rightText.count() + itRight.key(), itRight.value()); + commonRightCodeMap.insert(rightText.count() + itRight.key(), + itRight.value()); } leftText.append(commonEquality); @@ -706,7 +716,8 @@ static bool diffWithWhitespaceExpandedInEqualities(const QList &leftInput, } Differ differ; - QList diffList = differ.cleanupSemantics(differ.diff(leftText, rightText)); + QList diffList = differ.cleanupSemantics( + differ.diff(leftText, rightText)); QList leftDiffList; QList rightDiffList; @@ -716,10 +727,12 @@ static bool diffWithWhitespaceExpandedInEqualities(const QList &leftInput, rightDiffList = Differ::moveWhitespaceIntoEqualities(rightDiffList); bool ok = false; - *leftOutput = decodeExpandedWhitespace(leftDiffList, commonLeftCodeMap, &ok); + *leftOutput = decodeExpandedWhitespace(leftDiffList, + commonLeftCodeMap, &ok); if (!ok) return false; - *rightOutput = decodeExpandedWhitespace(rightDiffList, commonRightCodeMap, &ok); + *rightOutput = decodeExpandedWhitespace(rightDiffList, + commonRightCodeMap, &ok); if (!ok) return false; return true; @@ -755,11 +768,13 @@ static void appendWithEqualitiesSquashed(const QList &leftInput, * Deletions and insertions need to be merged. * * For each corresponding insertion / deletion pair: - * - diffWithWhitespaceReduced(): rediff them separately with whitespace reduced (new equalities may appear) + * - diffWithWhitespaceReduced(): rediff them separately with whitespace reduced + * (new equalities may appear) * - moveWhitespaceIntoEqualities(): move whitespace into new equalities - * - diffWithWhitespaceExpandedInEqualities(): expand whitespace inside new equalities only and rediff with cleanup + * - diffWithWhitespaceExpandedInEqualities(): expand whitespace inside new + * equalities only and rediff with cleanup */ -void Differ::diffBetweenEqualities(const QList &leftInput, +void Differ::ignoreWhitespaceBetweenEqualities(const QList &leftInput, const QList &rightInput, QList *leftOutput, QList *rightOutput) @@ -848,6 +863,88 @@ void Differ::diffBetweenEqualities(const QList &leftInput, } } +/* + * Prerequisites: + * leftInput cannot contain insertions, while right input cannot contain deletions. + * The number of equalities on leftInput and rightInput lists should be the same. + * Deletions and insertions need to be merged. + * + * For each corresponding insertion / deletion pair do just diff and merge equalities + */ +void Differ::diffBetweenEqualities(const QList &leftInput, + const QList &rightInput, + QList *leftOutput, + QList *rightOutput) +{ + if (!leftOutput || !rightOutput) + return; + + leftOutput->clear(); + rightOutput->clear(); + + const int leftCount = leftInput.count(); + const int rightCount = rightInput.count(); + int l = 0; + int r = 0; + + while (l <= leftCount && r <= rightCount) { + Diff leftDiff = l < leftCount + ? leftInput.at(l) + : Diff(Diff::Equal); + Diff rightDiff = r < rightCount + ? rightInput.at(r) + : Diff(Diff::Equal); + + if (leftDiff.command == Diff::Equal && rightDiff.command == Diff::Equal) { + Diff previousLeftDiff = l > 0 ? leftInput.at(l - 1) : Diff(Diff::Equal); + Diff previousRightDiff = r > 0 ? rightInput.at(r - 1) : Diff(Diff::Equal); + + if (previousLeftDiff.command == Diff::Delete + && previousRightDiff.command == Diff::Insert) { + Differ differ; + differ.setDiffMode(Differ::CharMode); + QList commonOutput = differ.cleanupSemantics( + differ.diff(previousLeftDiff.text, previousRightDiff.text)); + + QList outputLeftDiffList; + QList outputRightDiffList; + + Differ::splitDiffList(commonOutput, &outputLeftDiffList, + &outputRightDiffList); + + appendWithEqualitiesSquashed(outputLeftDiffList, + outputRightDiffList, + leftOutput, + rightOutput); + } else if (previousLeftDiff.command == Diff::Delete) { + leftOutput->append(previousLeftDiff); + } else if (previousRightDiff.command == Diff::Insert) { + rightOutput->append(previousRightDiff); + } + + QList leftEquality; + QList rightEquality; + if (l < leftCount) + leftEquality.append(leftDiff); + if (r < rightCount) + rightEquality.append(rightDiff); + + appendWithEqualitiesSquashed(leftEquality, + rightEquality, + leftOutput, + rightOutput); + + ++l; + ++r; + } + + if (leftDiff.command != Diff::Equal) + ++l; + if (rightDiff.command != Diff::Equal) + ++r; + } +} + /////////////// @@ -989,7 +1086,8 @@ QList Differ::preprocess2AndDiff(const QString &text1, const QString &text const QString shorttext = text1.count() > text2.count() ? text2 : text1; const int i = longtext.indexOf(shorttext); if (i != -1) { - const Diff::Command command = (text1.count() > text2.count()) ? Diff::Delete : Diff::Insert; + const Diff::Command command = (text1.count() > text2.count()) + ? Diff::Delete : Diff::Insert; diffList.append(Diff(command, longtext.left(i))); diffList.append(Diff(Diff::Equal, shorttext)); diffList.append(Diff(command, longtext.mid(i + shorttext.count()))); @@ -1146,7 +1244,8 @@ QList Differ::diffNonCharMode(const QString &text1, const QString &text2) for (int i = 0; i <= diffList.count(); i++) { const Diff diffItem = i < diffList.count() ? diffList.at(i) - : Diff(Diff::Equal); // dummy, ensure we process to the end even when diffList doesn't end with equality + : Diff(Diff::Equal); // dummy, ensure we process to the end + // even when diffList doesn't end with equality if (diffItem.command == Diff::Delete) { lastDelete += diffItem.text; } else if (diffItem.command == Diff::Insert) { @@ -1235,7 +1334,8 @@ QList Differ::merge(const QList &diffList) for (int i = 0; i <= diffList.count(); i++) { Diff diff = i < diffList.count() ? diffList.at(i) - : Diff(Diff::Equal); // dummy, ensure we process to the end even when diffList doesn't end with equality + : Diff(Diff::Equal); // dummy, ensure we process to the end + // even when diffList doesn't end with equality if (diff.command == Diff::Delete) { lastDelete += diff.text; } else if (diff.command == Diff::Insert) { @@ -1315,7 +1415,8 @@ QList Differ::cleanupSemantics(const QList &diffList) for (int i = 0; i <= diffList.count(); i++) { Diff diff = i < diffList.count() ? diffList.at(i) - : Diff(Diff::Equal); // dummy, ensure we process to the end even when diffList doesn't end with equality + : Diff(Diff::Equal); // dummy, ensure we process to the end + // even when diffList doesn't end with equality if (diff.command == Diff::Equal) { if (!equalities.isEmpty()) { EqualityData &previousData = equalities.last(); diff --git a/src/plugins/diffeditor/differ.h b/src/plugins/diffeditor/differ.h index 5ec2679ba7..9c9054ff02 100644 --- a/src/plugins/diffeditor/differ.h +++ b/src/plugins/diffeditor/differ.h @@ -88,6 +88,10 @@ public: const QString &rightInput, QList *leftOutput, QList *rightOutput); + static void ignoreWhitespaceBetweenEqualities(const QList &leftInput, + const QList &rightInput, + QList *leftOutput, + QList *rightOutput); static void diffBetweenEqualities(const QList &leftInput, const QList &rightInput, QList *leftOutput, diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp index ed15a283d3..072d43a18d 100644 --- a/src/plugins/diffeditor/diffutils.cpp +++ b/src/plugins/diffeditor/diffutils.cpp @@ -33,7 +33,6 @@ #include "texteditor/fontsettings.h" namespace DiffEditor { -namespace Internal { static QList assemblyRows(const QList &lines, const QMap &lineSpans) @@ -102,11 +101,9 @@ static void handleDifference(const QString &text, * while rightDiffList can contain only insertions and equalities. * The number of equalities on both lists must be the same. */ -ChunkData calculateOriginalData(const QList &leftDiffList, +ChunkData DiffUtils::calculateOriginalData(const QList &leftDiffList, const QList &rightDiffList) { - ChunkData chunkData; - int i = 0; int j = 0; @@ -152,61 +149,71 @@ ChunkData calculateOriginalData(const QList &leftDiffList, int line = 0; - while (line < qMax(newLeftLines.count(), newRightLines.count())) { - handleLine(newLeftLines, line, &leftLines, &leftLineNumber); - handleLine(newRightLines, line, &rightLines, &rightLineNumber); - - const int commonLineCount = qMin(newLeftLines.count(), newRightLines.count()); - if (line < commonLineCount) { - // try to align - const int leftDifference = leftLineNumber - leftLineAligned; - const int rightDifference = rightLineNumber - rightLineAligned; - - if (leftDifference && rightDifference) { - bool doAlign = true; - if (line == 0 // omit alignment when first lines of equalities are empty and last generated lines are not equal - && (newLeftLines.at(0).isEmpty() || newRightLines.at(0).isEmpty()) - && !lastLineEqual) { - doAlign = false; - } - - if (line == commonLineCount - 1) { - // omit alignment when last lines of equalities are empty - if (leftLines.last().text.isEmpty() || rightLines.last().text.isEmpty()) + if (i < leftDiffList.count() || j < rightDiffList.count() || (leftLines.count() && rightLines.count())) { + while (line < qMax(newLeftLines.count(), newRightLines.count())) { + handleLine(newLeftLines, line, &leftLines, &leftLineNumber); + handleLine(newRightLines, line, &rightLines, &rightLineNumber); + + const int commonLineCount = qMin(newLeftLines.count(), + newRightLines.count()); + if (line < commonLineCount) { + // try to align + const int leftDifference = leftLineNumber - leftLineAligned; + const int rightDifference = rightLineNumber - rightLineAligned; + + if (leftDifference && rightDifference) { + bool doAlign = true; + if (line == 0 + && (newLeftLines.at(0).isEmpty() + || newRightLines.at(0).isEmpty()) + && !lastLineEqual) { + // omit alignment when first lines of equalities + // are empty and last generated lines are not equal doAlign = false; - - // unless it's the last dummy line (don't omit in that case) - if (i == leftDiffList.count() && j == rightDiffList.count()) - doAlign = true; - } - - if (doAlign) { - // align here - leftLineAligned = leftLineNumber; - rightLineAligned = rightLineNumber; - - // insert separators if needed - if (rightDifference > leftDifference) - leftSpans.insert(leftLineNumber, rightDifference - leftDifference); - else if (leftDifference > rightDifference) - rightSpans.insert(rightLineNumber, leftDifference - rightDifference); + } + + if (line == commonLineCount - 1) { + // omit alignment when last lines of equalities are empty + if (leftLines.last().text.isEmpty() + || rightLines.last().text.isEmpty()) + doAlign = false; + + // unless it's the last dummy line (don't omit in that case) + if (i == leftDiffList.count() + && j == rightDiffList.count()) + doAlign = true; + } + + if (doAlign) { + // align here + leftLineAligned = leftLineNumber; + rightLineAligned = rightLineNumber; + + // insert separators if needed + if (rightDifference > leftDifference) + leftSpans.insert(leftLineNumber, + rightDifference - leftDifference); + else if (leftDifference > rightDifference) + rightSpans.insert(rightLineNumber, + leftDifference - rightDifference); + } } } - } - // check if lines are equal - if ((line < commonLineCount - 1) // before the last common line in equality - || (line == commonLineCount - 1 // or the last common line in equality - && i == leftDiffList.count() // and it's the last iteration - && j == rightDiffList.count())) { - if (line > 0 || lastLineEqual) - equalLines.insert(leftLineNumber, rightLineNumber); - } + // check if lines are equal + if ((line < commonLineCount - 1) // before the last common line in equality + || (line == commonLineCount - 1 // or the last common line in equality + && i == leftDiffList.count() // and it's the last iteration + && j == rightDiffList.count())) { + if (line > 0 || lastLineEqual) + equalLines.insert(leftLineNumber, rightLineNumber); + } - if (line > 0) - lastLineEqual = true; + if (line > 0) + lastLineEqual = true; - line++; + line++; + } } i++; j++; @@ -227,6 +234,8 @@ ChunkData calculateOriginalData(const QList &leftDiffList, const int visualLineCount = leftData.count(); int leftLine = -1; int rightLine = -1; + ChunkData chunkData; + for (int i = 0; i < visualLineCount; i++) { const TextLineData &leftTextLine = leftData.at(i); const TextLineData &rightTextLine = rightData.at(i); @@ -244,14 +253,16 @@ ChunkData calculateOriginalData(const QList &leftDiffList, return chunkData; } -FileData calculateContextData(const ChunkData &originalData, int contextLinesNumber) +FileData DiffUtils::calculateContextData(const ChunkData &originalData, + int contextLinesNumber, + int joinChunkThreshold) { if (contextLinesNumber < 0) return FileData(originalData); - const int joinChunkThreshold = 1; - FileData fileData; + fileData.contextChunksIncluded = true; + QMap hiddenRows; int i = 0; while (i < originalData.rows.count()) { @@ -269,8 +280,10 @@ FileData calculateContextData(const ChunkData &originalData, int contextLinesNum const bool first = equalRowStart == 0; // includes first line? const bool last = i == originalData.rows.count(); // includes last line? - const int firstLine = first ? 0 : equalRowStart + contextLinesNumber; - const int lastLine = last ? originalData.rows.count() : i - contextLinesNumber; + const int firstLine = first + ? 0 : equalRowStart + contextLinesNumber; + const int lastLine = last + ? originalData.rows.count() : i - contextLinesNumber; if (firstLine < lastLine - joinChunkThreshold) { for (int j = firstLine; j < lastLine; j++) { @@ -283,15 +296,23 @@ FileData calculateContextData(const ChunkData &originalData, int contextLinesNum } } i = 0; + int leftLineNumber = 0; + int rightLineNumber = 0; while (i < originalData.rows.count()) { const bool contextChunk = hiddenRows.contains(i); ChunkData chunkData; chunkData.contextChunk = contextChunk; + chunkData.leftStartingLineNumber = leftLineNumber; + chunkData.rightStartingLineNumber = rightLineNumber; while (i < originalData.rows.count()) { if (contextChunk != hiddenRows.contains(i)) break; RowData rowData = originalData.rows.at(i); chunkData.rows.append(rowData); + if (rowData.leftLine.textLineType == TextLineData::TextLine) + ++leftLineNumber; + if (rowData.rightLine.textLineType == TextLineData::TextLine) + ++rightLineNumber; ++i; } fileData.chunks.append(chunkData); @@ -300,54 +321,630 @@ FileData calculateContextData(const ChunkData &originalData, int contextLinesNum return fileData; } -void addChangedPositions(int positionOffset, const QMap &originalChangedPositions, QMap *changedPositions) +QString DiffUtils::makePatchLine(const QChar &startLineCharacter, + const QString &textLine, + bool lastChunk, + bool lastLine) +{ + QString line; + + const bool addNoNewline = lastChunk // it's the last chunk in file + && lastLine // it's the last row in chunk + && !textLine.isEmpty(); // the row is not empty + + const bool addLine = !lastChunk // not the last chunk in file + || !lastLine // not the last row in chunk + || addNoNewline; // no addNoNewline case + + if (addLine) { + line = startLineCharacter + textLine + QLatin1Char('\n'); + if (addNoNewline) + line += QLatin1String("\\ No newline at end of file\n"); + } + + return line; +} + +QString DiffUtils::makePatch(const ChunkData &chunkData, + const QString &leftFileName, + const QString &rightFileName, + bool lastChunk) +{ + QString diffText; + int leftLineCount = 0; + int rightLineCount = 0; + QList leftBuffer, rightBuffer; + + for (int i = 0; i <= chunkData.rows.count(); i++) { + const RowData &rowData = i < chunkData.rows.count() + ? chunkData.rows.at(i) + : RowData(TextLineData(TextLineData::Separator)); // dummy, + // ensure we process buffers to the end. + // rowData will be equal + if (rowData.equal) { + if (leftBuffer.count()) { + for (int j = 0; j < leftBuffer.count(); j++) { + const QString line = makePatchLine(QLatin1Char('-'), + leftBuffer.at(j).text, + lastChunk, + i == chunkData.rows.count() + && j == leftBuffer.count() - 1); + + if (!line.isEmpty()) + ++leftLineCount; + + diffText += line; + } + leftBuffer.clear(); + } + if (rightBuffer.count()) { + for (int j = 0; j < rightBuffer.count(); j++) { + const QString line = makePatchLine(QLatin1Char('+'), + rightBuffer.at(j).text, + lastChunk, + i == chunkData.rows.count() + && j == rightBuffer.count() - 1); + + if (!line.isEmpty()) + ++rightLineCount; + + diffText += line; + } + rightBuffer.clear(); + } + if (i < chunkData.rows.count()) { + const QString line = makePatchLine(QLatin1Char(' '), + rowData.rightLine.text, + lastChunk, + i == chunkData.rows.count() - 1); + + if (!line.isEmpty()) { + ++leftLineCount; + ++rightLineCount; + } + + diffText += line; + } + } else { + if (rowData.leftLine.textLineType == TextLineData::TextLine) + leftBuffer.append(rowData.leftLine); + if (rowData.rightLine.textLineType == TextLineData::TextLine) + rightBuffer.append(rowData.rightLine); + } + } + + const QString chunkLine = QLatin1String("@@ -") + + QString::number(chunkData.leftStartingLineNumber + 1) + + QLatin1Char(',') + + QString::number(leftLineCount) + + QLatin1String(" +") + + QString::number(chunkData.rightStartingLineNumber + 1) + + QLatin1Char(',') + + QString::number(rightLineCount) + + QLatin1String(" @@\n"); + + diffText.prepend(chunkLine); + + const QString rightFileInfo = QLatin1String("+++ ") + rightFileName + QLatin1Char('\n'); + const QString leftFileInfo = QLatin1String("--- ") + leftFileName + QLatin1Char('\n'); + + diffText.prepend(rightFileInfo); + diffText.prepend(leftFileInfo); + + return diffText; +} + +static QList readLines(const QString &patch, + bool ignoreWhitespace, + bool lastChunk, + bool *lastChunkAtTheEndOfFile, + bool *ok) +{ +// const QRegExp lineRegExp(QLatin1String("(?:\\n)" // beginning of the line +// "([ \\-\\+\\\\])([^\\n]*)" // -, +, \\ or space, followed by any no-newline character +// "(?:\\n|$)")); // end of line or file + QList diffList; + + const QChar newLine = QLatin1Char('\n'); + + int lastEqual = -1; + int lastDelete = -1; + int lastInsert = -1; + + int noNewLineInEqual = -1; + int noNewLineInDelete = -1; + int noNewLineInInsert = -1; + + const QStringList lines = patch.split(newLine); + int i; + for (i = 0; i < lines.count(); i++) { + const QString line = lines.at(i); + if (line.isEmpty()) + break; // need to have at least one character (1 column) + QChar firstCharacter = line.at(0); + if (firstCharacter == QLatin1Char('\\')) { // no new line marker + if (!lastChunk) // can only appear in last chunk of the file + break; + if (!diffList.isEmpty()) { + Diff &last = diffList.last(); + if (last.text.isEmpty()) + break; + if (last.text.at(0) == newLine) // there is a new line + break; + + if (last.command == Diff::Equal) { + if (noNewLineInEqual >= 0) + break; + noNewLineInEqual = diffList.count() - 1; + } else if (last.command == Diff::Delete) { + if (noNewLineInDelete >= 0) + break; + noNewLineInDelete = diffList.count() - 1; + } else if (last.command == Diff::Insert) { + if (noNewLineInInsert >= 0) + break; + noNewLineInInsert = diffList.count() - 1; + } + } + } else { + Diff::Command command = Diff::Equal; + if (firstCharacter == QLatin1Char(' ')) // common line + command = Diff::Equal; + else if (firstCharacter == QLatin1Char('-')) // deleted line + command = Diff::Delete; + else if (firstCharacter == QLatin1Char('+')) // inserted line + command = Diff::Insert; + else + break; // no other character may exist as the first character + + Diff diffToBeAdded(command, line.mid(1) + newLine); + + if (!diffList.isEmpty() && diffList.last().command == command) + diffList.last().text.append(diffToBeAdded.text); + else + diffList.append(diffToBeAdded); + + if (command == Diff::Equal) // common line + lastEqual = diffList.count() - 1; + else if (command == Diff::Delete) // deleted line + lastDelete = diffList.count() - 1; + else if (command == Diff::Insert) // inserted line + lastInsert = diffList.count() - 1; + } + } + + if (i < lines.count() + || (noNewLineInEqual >= 0 && (noNewLineInDelete >= 0 || noNewLineInInsert >= 0)) + || (noNewLineInEqual >= 0 && noNewLineInEqual != lastEqual) + || (noNewLineInDelete >= 0 && noNewLineInDelete != lastDelete) + || (noNewLineInInsert >= 0 && noNewLineInInsert != lastInsert)) { + if (ok) + *ok = false; + return QList(); + } + + if (ok) + *ok = true; + + bool removeNewLineFromLastEqual = false; + bool removeNewLineFromLastDelete = false; + bool removeNewLineFromLastInsert = false; + bool prependNewLineAfterLastEqual = false; + + if (noNewLineInDelete >= 0 || noNewLineInInsert >= 0) { + if (noNewLineInDelete >= 0) + removeNewLineFromLastDelete = true; + if (noNewLineInInsert >= 0) + removeNewLineFromLastInsert = true; + } else if (lastEqual > lastDelete && lastEqual > lastInsert) { + removeNewLineFromLastEqual = true; + } else if (lastDelete > lastEqual && lastDelete > lastInsert) { + if (lastInsert > lastEqual) { + removeNewLineFromLastDelete = true; + removeNewLineFromLastInsert = true; + } else if (lastEqual > lastInsert) { + removeNewLineFromLastEqual = true; + prependNewLineAfterLastEqual = true; + } + } else if (lastInsert > lastEqual && lastInsert > lastDelete) { + if (lastDelete > lastEqual) { + removeNewLineFromLastDelete = true; + removeNewLineFromLastInsert = true; + } else if (lastEqual > lastDelete) { + removeNewLineFromLastEqual = true; + prependNewLineAfterLastEqual = true; + } + } + + if (removeNewLineFromLastEqual) { + Diff &diff = diffList[lastEqual]; + diff.text = diff.text.left(diff.text.count() - 1); + } + if (removeNewLineFromLastDelete) { + Diff &diff = diffList[lastDelete]; + diff.text = diff.text.left(diff.text.count() - 1); + } + if (removeNewLineFromLastInsert) { + Diff &diff = diffList[lastInsert]; + diff.text = diff.text.left(diff.text.count() - 1); + } + if (prependNewLineAfterLastEqual) { + Diff &diff = diffList[lastEqual + 1]; + diff.text = newLine + diff.text; + } + + if (lastChunkAtTheEndOfFile) { + *lastChunkAtTheEndOfFile = noNewLineInEqual >= 0 + || noNewLineInDelete >= 0|| noNewLineInInsert >= 0; + } + +// diffList = Differ::merge(diffList); + QList leftDiffList; + QList rightDiffList; + Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList); + QList outputLeftDiffList; + QList outputRightDiffList; + + if (ignoreWhitespace) { + const QList leftIntermediate = + Differ::moveWhitespaceIntoEqualities(leftDiffList); + const QList rightIntermediate = + Differ::moveWhitespaceIntoEqualities(rightDiffList); + Differ::ignoreWhitespaceBetweenEqualities(leftIntermediate, + rightIntermediate, + &outputLeftDiffList, + &outputRightDiffList); + } else { + Differ::diffBetweenEqualities(leftDiffList, + rightDiffList, + &outputLeftDiffList, + &outputRightDiffList); + } + + return DiffUtils::calculateOriginalData(outputLeftDiffList, + outputRightDiffList).rows; +} + +static QList readChunks(const QString &patch, + bool ignoreWhitespace, + bool *lastChunkAtTheEndOfFile, + bool *ok) +{ + const QRegExp chunkRegExp(QLatin1String("((?:\\n|^)" // beginning of the line + "@@ \\-([\\d]+)\\,[\\d]+ \\+([\\d]+)\\,[\\d]+ @@" // @@ -leftPos,leftCount +rightPos,rightCount @@ + "(?:\\ +[^\\n]*)?" // optional hint (e.g. function name) + "(?:\\n))")); // end of line (need to be followed by text line) + + bool readOk = false; + + QList chunkDataList; + + int pos = chunkRegExp.indexIn(patch, 0); + if (pos == 0) { + int endOfLastChunk = 0; + do { + const QStringList capturedTexts = chunkRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + const int leftStartingPos = capturedTexts.at(2).toInt(); + const int rightStartingPos = capturedTexts.at(3).toInt(); + if (endOfLastChunk > 0) { + const QString lines = patch.mid(endOfLastChunk, + pos - endOfLastChunk); + chunkDataList.last().rows = readLines(lines, + ignoreWhitespace, + false, + lastChunkAtTheEndOfFile, + &readOk); + if (!readOk) + break; + } + pos += captured.count(); + endOfLastChunk = pos; + ChunkData chunkData; + chunkData.leftStartingLineNumber = leftStartingPos - 1; + chunkData.rightStartingLineNumber = rightStartingPos - 1; + chunkDataList.append(chunkData); + } while ((pos = chunkRegExp.indexIn(patch, pos)) != -1); + + if (endOfLastChunk > 0) { + const QString lines = patch.mid(endOfLastChunk); + chunkDataList.last().rows = readLines(lines, + ignoreWhitespace, + true, + lastChunkAtTheEndOfFile, + &readOk); + } + } + + if (ok) + *ok = readOk; + + return chunkDataList; +} + +static FileData readDiffHeaderAndChunks(const QString &headerAndChunks, + bool ignoreWhitespace, + bool *ok) +{ + QString patch = headerAndChunks; + FileData fileData; + bool readOk = false; + + const QRegExp leftFileRegExp(QLatin1String("((?:\\n|^)\\-{3} ") // "--- " + + QLatin1String("([^\\t\\n]+)") // "fileName1" + + QLatin1String("(?:\\t[^\\n]*)*\\n)")); // optionally followed by: \t anything \t anything ...) + const QRegExp rightFileRegExp(QLatin1String("(^\\+{3} ") // "+++ " + + QLatin1String("([^\\t\\n]+)") // "fileName2" + + QLatin1String("(?:\\t[^\\n]*)*\\n)")); // optionally followed by: \t anything \t anything ...) + const QRegExp binaryRegExp(QLatin1String("(^Binary files ") + + QLatin1String("([^\\t\\n]+)") + + QLatin1String(" and ") + + QLatin1String("([^\\t\\n]+)") + + QLatin1String(" differ$)")); + + // followed either by leftFileRegExp or by binaryRegExp + if (leftFileRegExp.indexIn(patch, 0) == 0) { + const QStringList leftCaptured = leftFileRegExp.capturedTexts(); + patch = patch.mid(leftCaptured.at(1).count()); + fileData.leftFileInfo.fileName = leftCaptured.at(2); + + // followed by rightFileRegExp + if (rightFileRegExp.indexIn(patch, 0) == 0) { + const QStringList rightCaptured = rightFileRegExp.capturedTexts(); + patch = patch.mid(rightCaptured.at(1).count()); + fileData.rightFileInfo.fileName = rightCaptured.at(2); + + fileData.chunks = readChunks(patch, + ignoreWhitespace, + &fileData.lastChunkAtTheEndOfFile, + &readOk); + } + } else if (binaryRegExp.indexIn(patch, 0) == 0) { + const QStringList binaryCaptured = binaryRegExp.capturedTexts(); + fileData.leftFileInfo.fileName = binaryCaptured.at(2); + fileData.rightFileInfo.fileName = binaryCaptured.at(3); + fileData.binaryFiles = true; + readOk = true; + } + + if (ok) + *ok = readOk; + + if (!readOk) + return FileData(); + + return fileData; + +} + +static QList readDiffPatch(const QString &patch, + bool ignoreWhitespace, + bool *ok) { - QMapIterator it(originalChangedPositions); - while (it.hasNext()) { - it.next(); - const int startPos = it.key(); - const int endPos = it.value(); - const int newStartPos = startPos < 0 ? -1 : startPos + positionOffset; - const int newEndPos = endPos < 0 ? -1 : endPos + positionOffset; - if (startPos < 0 && !changedPositions->isEmpty()) { - QMap::iterator last = changedPositions->end(); - --last; - last.value() = newEndPos; - } else - changedPositions->insert(newStartPos, newEndPos); + const QRegExp diffRegExp(QLatin1String("(" // capture all + "(?:\\n|^)" // new line of the beginning of a patch + "(" // either + "\\-{3} " // --- + "[^\\t\\n]+" // filename1 + "(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ... + "\\+{3} " // +++ + "[^\\t\\n]+" // filename2 + "(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ... + "|" // or + "Binary files " + "[^\\t\\n]+" // filename1 + " and " + "[^\\t\\n]+" // filename2 + " differ" + ")" // end of or + ")")); // end of capture all + + bool readOk = false; + + QList fileDataList; + + int pos = diffRegExp.indexIn(patch, 0); + if (pos == 0) { // git style patch + readOk = true; + int lastPos = -1; + do { + const QStringList capturedTexts = diffRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + if (lastPos >= 0) { + const QString headerAndChunks = patch.mid(lastPos, + pos - lastPos); + + const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, + ignoreWhitespace, + &readOk); + + if (!readOk) + break; + + fileDataList.append(fileData); + } + lastPos = pos; + pos += captured.count(); + } while ((pos = diffRegExp.indexIn(patch, pos)) != -1); + + if (lastPos >= 0 && readOk) { + const QString headerAndChunks = patch.mid(lastPos, + patch.count() - lastPos - 1); + + const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, + ignoreWhitespace, + &readOk); + + if (readOk) + fileDataList.append(fileData); + } } + + if (ok) + *ok = readOk; + + if (!readOk) + return QList(); + + return fileDataList; } -QList colorPositions( - const QTextCharFormat &format, - QTextCursor &cursor, - const QMap &positions) +static FileData readGitHeaderAndChunks(const QString &headerAndChunks, + const QString &fileName, + bool ignoreWhitespace, + bool *ok) { - QList lineSelections; + FileData fileData; + fileData.leftFileInfo.fileName = fileName; + fileData.rightFileInfo.fileName = fileName; + + QString patch = headerAndChunks; + bool readOk = false; + + const QString devNull(QLatin1String("/dev/null")); + + // will be followed by: index 0000000..shasha, file "a" replaced by "/dev/null", @@ -0,0 +m,n @@ + const QRegExp newFileMode(QLatin1String("(^new file mode [\\d]+\\n)")); // new file mode octal + + // will be followed by: index shasha..0000000, file "b" replaced by "/dev/null", @@ -m,n +0,0 @@ + const QRegExp deletedFileMode(QLatin1String("(^deleted file mode [\\d]+\\n)")); // deleted file mode octal + + const QRegExp indexRegExp(QLatin1String("(^index ([\\w]+)\\.{2}([\\w]+)(?: [\\d]+)?\\n)")); // index cap2..cap3(optionally: octal) + + QString leftFileName = QLatin1String("a/") + fileName; + QString rightFileName = QLatin1String("b/") + fileName; + + if (newFileMode.indexIn(patch, 0) == 0) { + fileData.leftFileInfo.devNull = true; + leftFileName = devNull; + patch = patch.mid(newFileMode.capturedTexts().at(1).count()); + } else if (deletedFileMode.indexIn(patch, 0) == 0) { + fileData.rightFileInfo.devNull = true; + rightFileName = devNull; + patch = patch.mid(deletedFileMode.capturedTexts().at(1).count()); + } + + if (indexRegExp.indexIn(patch, 0) == 0) { + const QStringList capturedTexts = indexRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + fileData.leftFileInfo.typeInfo = capturedTexts.at(2); + fileData.rightFileInfo.typeInfo = capturedTexts.at(3); + + patch = patch.mid(captured.count()); + + const QRegExp leftFileRegExp(QLatin1String("(^\\-{3} ") // "--- " + + leftFileName // "a/fileName" or "/dev/null" + + QLatin1String("(?:\\t[^\\n]*)*\\n)")); // optionally followed by: \t anything \t anything ...) + const QRegExp rightFileRegExp(QLatin1String("(^\\+{3} ") // "+++ " + + rightFileName // "b/fileName" or "/dev/null" + + QLatin1String("(?:\\t[^\\n]*)*\\n)")); // optionally followed by: \t anything \t anything ...) + const QRegExp binaryRegExp(QLatin1String("(^Binary files ") + + leftFileName + + QLatin1String(" and ") + + rightFileName + + QLatin1String(" differ$)")); + + // followed either by leftFileRegExp or by binaryRegExp + if (leftFileRegExp.indexIn(patch, 0) == 0) { + patch = patch.mid(leftFileRegExp.capturedTexts().at(1).count()); + + // followed by rightFileRegExp + if (rightFileRegExp.indexIn(patch, 0) == 0) { + patch = patch.mid(rightFileRegExp.capturedTexts().at(1).count()); + + fileData.chunks = readChunks(patch, + ignoreWhitespace, + &fileData.lastChunkAtTheEndOfFile, + &readOk); + } + } else if (binaryRegExp.indexIn(patch, 0) == 0) { + readOk = true; + fileData.binaryFiles = true; + } + } + + if (ok) + *ok = readOk; - cursor.setPosition(0); - QMapIterator itPositions(positions); - while (itPositions.hasNext()) { - itPositions.next(); + if (!readOk) + return FileData(); + + return fileData; +} - cursor.setPosition(itPositions.key()); - cursor.setPosition(itPositions.value(), QTextCursor::KeepAnchor); +static QList readGitPatch(const QString &patch, bool ignoreWhitespace, bool *ok) +{ + const QRegExp gitRegExp(QLatin1String("((?:\\n|^)diff --git a/([^\\n]+) b/\\2\\n)")); // diff --git a/cap2 b/cap2 + + bool readOk = false; + + QList fileDataList; + + int pos = gitRegExp.indexIn(patch, 0); + if (pos == 0) { // git style patch + readOk = true; + int endOfLastHeader = 0; + QString lastFileName; + do { + const QStringList capturedTexts = gitRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + const QString fileName = capturedTexts.at(2); + if (endOfLastHeader > 0) { + const QString headerAndChunks = patch.mid(endOfLastHeader, + pos - endOfLastHeader); + + const FileData fileData = readGitHeaderAndChunks(headerAndChunks, + lastFileName, + ignoreWhitespace, + &readOk); + + if (!readOk) + break; - QTextEdit::ExtraSelection selection; - selection.cursor = cursor; - selection.format = format; - lineSelections.append(selection); + fileDataList.append(fileData); + } + pos += captured.count(); + endOfLastHeader = pos; + lastFileName = fileName; + } while ((pos = gitRegExp.indexIn(patch, pos)) != -1); + + if (endOfLastHeader > 0 && readOk) { + const QString headerAndChunks = patch.mid(endOfLastHeader, + patch.count() - endOfLastHeader - 1); + + const FileData fileData = readGitHeaderAndChunks(headerAndChunks, + lastFileName, + ignoreWhitespace, + &readOk); + + if (readOk) + fileDataList.append(fileData); + } } - return lineSelections; + + if (ok) + *ok = readOk; + + if (!readOk) + return QList(); + + return fileDataList; } -QTextCharFormat fullWidthFormatForTextStyle(const TextEditor::FontSettings &fontSettings, - TextEditor::TextStyle textStyle) +QList DiffUtils::readPatch(const QString &patch, bool ignoreWhitespace, bool *ok) { - QTextCharFormat format = fontSettings.toTextCharFormat(textStyle); - format.setProperty(QTextFormat::FullWidthSelection, true); - return format; + bool readOk = false; + + QList fileDataList; + + fileDataList = readGitPatch(patch, ignoreWhitespace, &readOk); + if (!readOk) + fileDataList = readDiffPatch(patch, ignoreWhitespace, &readOk); + + if (ok) + *ok = readOk; + + return fileDataList; } -} // namespace Internal } // namespace DiffEditor diff --git a/src/plugins/diffeditor/diffutils.h b/src/plugins/diffeditor/diffutils.h index 75102c7406..9043969929 100644 --- a/src/plugins/diffeditor/diffutils.h +++ b/src/plugins/diffeditor/diffutils.h @@ -30,11 +30,12 @@ #ifndef DIFFUTILS_H #define DIFFUTILS_H +#include "diffeditor_global.h" + #include #include #include -#include "diffeditorcontroller.h" #include "texteditor/texteditorconstants.h" namespace TextEditor { class FontSettings; } @@ -43,9 +44,18 @@ namespace DiffEditor { class Diff; -namespace Internal { +class DIFFEDITOR_EXPORT DiffFileInfo { +public: + DiffFileInfo() : devNull(false) {} + DiffFileInfo(const QString &file) : fileName(file), devNull(false) {} + DiffFileInfo(const QString &file, const QString &type) + : fileName(file), typeInfo(type), devNull(false) {} + QString fileName; + QString typeInfo; + bool devNull; +}; -class TextLineData { +class DIFFEDITOR_EXPORT TextLineData { public: enum TextLineType { TextLine, @@ -66,7 +76,7 @@ public: QMap changedPositions; // counting from the beginning of the line }; -class RowData { +class DIFFEDITOR_EXPORT RowData { public: RowData() : equal(false) {} RowData(const TextLineData &l) @@ -78,35 +88,55 @@ public: bool equal; }; -class ChunkData { +class DIFFEDITOR_EXPORT ChunkData { public: - ChunkData() : contextChunk(false) {} + ChunkData() : contextChunk(false), + leftStartingLineNumber(0), rightStartingLineNumber(0) {} QList rows; bool contextChunk; + int leftStartingLineNumber; + int rightStartingLineNumber; }; -class FileData { +class DIFFEDITOR_EXPORT FileData { public: - FileData() {} - FileData(const ChunkData &chunkData) { chunks.append(chunkData); } + FileData() + : binaryFiles(false), + lastChunkAtTheEndOfFile(false), + contextChunksIncluded(false) {} + FileData(const ChunkData &chunkData) + : binaryFiles(false), + lastChunkAtTheEndOfFile(false), + contextChunksIncluded(false) { chunks.append(chunkData); } QList chunks; - DiffEditorController::DiffFileInfo leftFileInfo; - DiffEditorController::DiffFileInfo rightFileInfo; + DiffFileInfo leftFileInfo; + DiffFileInfo rightFileInfo; + bool binaryFiles; + bool lastChunkAtTheEndOfFile; + bool contextChunksIncluded; +}; + +class DIFFEDITOR_EXPORT DiffUtils { +public: + + static ChunkData calculateOriginalData(const QList &leftDiffList, + const QList &rightDiffList); + static FileData calculateContextData(const ChunkData &originalData, + int contextLinesNumber, + int joinChunkThreshold = 1); + static QString makePatchLine(const QChar &startLineCharacter, + const QString &textLine, + bool lastChunk, + bool lastLine); + static QString makePatch(const ChunkData &chunkData, + const QString &leftFileName, + const QString &rightFileName, + bool lastChunk = false); + static QList readPatch(const QString &patch, + bool ignoreWhitespace, + bool *ok = 0); }; -ChunkData calculateOriginalData(const QList &leftDiffList, - const QList &rightDiffList); -FileData calculateContextData(const ChunkData &originalData, - int contextLinesNumber); -void addChangedPositions(int positionOffset, - const QMap &originalChangedPositions, - QMap *changedPositions); -QList colorPositions(const QTextCharFormat &format, - QTextCursor &cursor, - const QMap &positions); -QTextCharFormat fullWidthFormatForTextStyle(const TextEditor::FontSettings &fontSettings, - TextEditor::TextStyle textStyle); -} // namespace Internal } // namespace DiffEditor #endif // DIFFUTILS_H diff --git a/src/plugins/diffeditor/images/reload.png b/src/plugins/diffeditor/images/reload.png new file mode 100644 index 0000000000..bd07fb34b0 Binary files /dev/null and b/src/plugins/diffeditor/images/reload.png differ diff --git a/src/plugins/diffeditor/images/sidebysidediff.png b/src/plugins/diffeditor/images/sidebysidediff.png new file mode 100644 index 0000000000..55c6f2802d Binary files /dev/null and b/src/plugins/diffeditor/images/sidebysidediff.png differ diff --git a/src/plugins/diffeditor/images/topbar.png b/src/plugins/diffeditor/images/topbar.png new file mode 100644 index 0000000000..40d0fd842e Binary files /dev/null and b/src/plugins/diffeditor/images/topbar.png differ diff --git a/src/plugins/diffeditor/images/unifieddiff.png b/src/plugins/diffeditor/images/unifieddiff.png new file mode 100644 index 0000000000..254b187744 Binary files /dev/null and b/src/plugins/diffeditor/images/unifieddiff.png differ diff --git a/src/plugins/diffeditor/selectabletexteditorwidget.cpp b/src/plugins/diffeditor/selectabletexteditorwidget.cpp new file mode 100644 index 0000000000..91e31b0e8c --- /dev/null +++ b/src/plugins/diffeditor/selectabletexteditorwidget.cpp @@ -0,0 +1,125 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "selectabletexteditorwidget.h" + +#include +#include + +namespace DiffEditor { + +SelectableTextEditorWidget::SelectableTextEditorWidget(QWidget *parent) + : BaseTextEditorWidget(parent) +{ + setFrameStyle(QFrame::NoFrame); +} + +SelectableTextEditorWidget::~SelectableTextEditorWidget() +{ + +} + +void SelectableTextEditorWidget::paintEvent(QPaintEvent *e) +{ + paintSelections(e); + BaseTextEditorWidget::paintEvent(e); +} + +void SelectableTextEditorWidget::paintSelections(QPaintEvent *e) +{ + QPainter painter(viewport()); + + QPointF offset = contentOffset(); + QTextBlock firstBlock = firstVisibleBlock(); + QTextBlock currentBlock = firstBlock; + + while (currentBlock.isValid()) { + if (currentBlock.isVisible()) { + qreal top = blockBoundingGeometry(currentBlock).translated(offset).top(); + qreal bottom = top + blockBoundingRect(currentBlock).height(); + + if (top > e->rect().bottom()) + break; + + if (bottom >= e->rect().top()) { + const int blockNumber = currentBlock.blockNumber(); + + paintSelections(painter, m_selections.value(blockNumber), + currentBlock, top); + } + } + currentBlock = currentBlock.next(); + } +} + +void SelectableTextEditorWidget::paintSelections(QPainter &painter, + const QList &selections, + const QTextBlock &block, + int top) +{ + QPointF offset = contentOffset(); + painter.save(); + + QTextLayout *layout = block.layout(); + QTextLine textLine = layout->lineAt(0); + QRectF lineRect = textLine.naturalTextRect().translated(offset.x(), top); + QRect clipRect = contentsRect(); + painter.setClipRect(clipRect); + for (int i = 0; i < selections.count(); i++) { + const DiffSelection &selection = selections.at(i); + + if (!selection.format) + continue; + if (selection.start == -1 && selection.end == 0) + continue; + if (selection.start == selection.end && selection.start >= 0) + continue; + + painter.save(); + const QBrush &brush = selection.format->background(); + painter.setPen(brush.color()); + painter.setBrush(brush); + + const int x1 = selection.start <= 0 + ? -1 + : textLine.cursorToX(selection.start) + offset.x(); + const int x2 = selection.end < 0 + ? clipRect.right() + : textLine.cursorToX(selection.end) + offset.x(); + painter.drawRect(QRectF(QPointF(x1, lineRect.top()), + QPointF(x2, lineRect.bottom()))); + + painter.restore(); + } + painter.restore(); +} + + +} // namespace DiffEditor + diff --git a/src/plugins/diffeditor/selectabletexteditorwidget.h b/src/plugins/diffeditor/selectabletexteditorwidget.h new file mode 100644 index 0000000000..0c0f2d9af9 --- /dev/null +++ b/src/plugins/diffeditor/selectabletexteditorwidget.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SELECTABLETEXTEDITORWIDGET_H +#define SELECTABLETEXTEDITORWIDGET_H + +#include "diffeditor_global.h" +#include + +namespace DiffEditor { + +class DIFFEDITOR_EXPORT DiffSelection +{ +public: + DiffSelection() : start(-1), end(-1), format(0) {} + DiffSelection(QTextCharFormat *f) : start(-1), end(-1), format(f) {} + DiffSelection(int s, int e, QTextCharFormat *f) : start(s), end(e), format(f) {} + + int start; + int end; + QTextCharFormat *format; +}; + +class DIFFEDITOR_EXPORT SelectableTextEditorWidget + : public TextEditor::BaseTextEditorWidget +{ + Q_OBJECT +public: + SelectableTextEditorWidget(QWidget *parent = 0); + ~SelectableTextEditorWidget(); + void setSelections(const QMap > &selections) { + m_selections = selections; + } + +protected: + virtual void paintEvent(QPaintEvent *e); + +private: + void paintSelections(QPaintEvent *e); + void paintSelections(QPainter &painter, + const QList &selections, + const QTextBlock &block, + int top); + + // block number, list of ranges + // DiffSelection.start - can be -1 (continues from the previous line) + // DiffSelection.end - can be -1 (spans to the end of line, even after the last character in line) + QMap > m_selections; +}; + +} // namespace DiffEditor + +#endif // SELECTABLETEXTEDITORWIDGET_H diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp index 9f0c7e8604..32d8ce73eb 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp @@ -28,8 +28,10 @@ ****************************************************************************/ #include "sidebysidediffeditorwidget.h" +#include "selectabletexteditorwidget.h" #include "diffeditorguicontroller.h" #include "diffutils.h" +#include "diffeditorconstants.h" #include #include @@ -39,6 +41,8 @@ #include #include #include +#include +#include #include #include @@ -53,6 +57,7 @@ #include #include #include +#include #include @@ -67,8 +72,6 @@ using namespace TextEditor; namespace DiffEditor { -using namespace Internal; - ////////////////////// class SideDiffEditor : public BaseTextEditor @@ -84,12 +87,14 @@ public: } private slots: - void slotTooltipRequested(TextEditor::ITextEditor *editor, const QPoint &globalPoint, int position); + void slotTooltipRequested(TextEditor::ITextEditor *editor, + const QPoint &globalPoint, + int position); }; //////////////////////// - +/* class MultiHighlighter : public SyntaxHighlighter { Q_OBJECT @@ -98,7 +103,7 @@ public: ~MultiHighlighter(); virtual void setFontSettings(const TextEditor::FontSettings &fontSettings); - void setDocuments(const QList > &documents); + void setDocuments(const QList > &documents); protected: virtual void highlightBlock(const QString &text); @@ -109,36 +114,43 @@ private: QList m_highlighters; QList m_documents; }; - +*/ //////////////////////// -class SideDiffEditorWidget : public BaseTextEditorWidget +class SideDiffEditorWidget : public SelectableTextEditorWidget { Q_OBJECT public: - class ExtendedFileInfo { - public: - DiffEditorController::DiffFileInfo fileInfo; - TextEditor::SyntaxHighlighter *highlighter; - }; - SideDiffEditorWidget(QWidget *parent = 0); // block number, file info - QMap fileInfo() const { return m_fileInfo; } + QMap fileInfo() const { return m_fileInfo; } void setLineNumber(int blockNumber, int lineNumber); - void setFileInfo(int blockNumber, const DiffEditorController::DiffFileInfo &fileInfo); - void setSkippedLines(int blockNumber, int skippedLines) { m_skippedLines[blockNumber] = skippedLines; setSeparator(blockNumber, true); } - void setSeparator(int blockNumber, bool separator) { m_separators[blockNumber] = separator; } - bool isFileLine(int blockNumber) const { return m_fileInfo.contains(blockNumber); } + void setFileInfo(int blockNumber, const DiffFileInfo &fileInfo); + void setSkippedLines(int blockNumber, int skippedLines) { + m_skippedLines[blockNumber] = skippedLines; + setSeparator(blockNumber, true); + } + void setChunkIndex(int startBlockNumber, int blockCount, int chunkIndex); + void setSeparator(int blockNumber, bool separator) { + m_separators[blockNumber] = separator; + } + bool isFileLine(int blockNumber) const { + return m_fileInfo.contains(blockNumber); + } int blockNumberForFileIndex(int fileIndex) const; int fileIndexForBlockNumber(int blockNumber) const; - bool isChunkLine(int blockNumber) const { return m_skippedLines.contains(blockNumber); } + int chunkIndexForBlockNumber(int blockNumber) const; + bool isChunkLine(int blockNumber) const { + return m_skippedLines.contains(blockNumber); + } void clearAll(const QString &message); void clearAllData(); - QTextBlock firstVisibleBlock() const { return BaseTextEditorWidget::firstVisibleBlock(); } - void setDocuments(const QList > &documents); + QTextBlock firstVisibleBlock() const { + return BaseTextEditorWidget::firstVisibleBlock(); + } +// void setDocuments(const QList > &documents); public slots: void setDisplaySettings(const DisplaySettings &ds); @@ -147,9 +159,14 @@ signals: void jumpToOriginalFileRequested(int diffFileIndex, int lineNumber, int columnNumber); + void contextMenuRequested(QMenu *menu, + int diffFileIndex, + int chunkIndex); protected: - virtual int extraAreaWidth(int *markWidthPtr = 0) const { return BaseTextEditorWidget::extraAreaWidth(markWidthPtr); } + virtual int extraAreaWidth(int *markWidthPtr = 0) const { + return SelectableTextEditorWidget::extraAreaWidth(markWidthPtr); + } void applyFontSettings(); BaseTextEditor *createEditor() { return new SideDiffEditor(this); } virtual QString lineNumber(int blockNumber) const; @@ -158,16 +175,17 @@ protected: virtual bool replacementVisible(int blockNumber) const; QColor replacementPenColor(int blockNumber) const; virtual QString plainTextFromSelection(const QTextCursor &cursor) const; - virtual void drawCollapsedBlockPopup(QPainter &painter, - const QTextBlock &block, - QPointF offset, - const QRect &clip); +// virtual void drawCollapsedBlockPopup(QPainter &painter, +// const QTextBlock &block, +// QPointF offset, +// const QRect &clip); void mouseDoubleClickEvent(QMouseEvent *e); + void contextMenuEvent(QContextMenuEvent *e); virtual void paintEvent(QPaintEvent *e); virtual void scrollContentsBy(int dx, int dy); private: - void paintCollapsedBlockPopup(QPainter &painter, const QRect &clipRect); +// void paintCollapsedBlockPopup(QPainter &painter, const QRect &clipRect); void paintSeparator(QPainter &painter, QColor &color, const QString &text, const QTextBlock &block, int top); void jumpToOriginalFile(const QTextCursor &cursor); @@ -176,28 +194,32 @@ private: QMap m_lineNumbers; int m_lineNumberDigits; // block number, fileInfo. Set for file lines only. - QMap m_fileInfo; + QMap m_fileInfo; // block number, skipped lines. Set for chunk lines only. QMap m_skippedLines; + // start block number, block count of a chunk, chunk index inside a file. + QMap > m_chunkInfo; // block number, separator. Set for file, chunk or span line. QMap m_separators; bool m_inPaintEvent; QColor m_fileLineForeground; QColor m_chunkLineForeground; QColor m_textForeground; - MultiHighlighter *m_highlighter; +// MultiHighlighter *m_highlighter; }; //////////////////////// -void SideDiffEditor::slotTooltipRequested(TextEditor::ITextEditor *editor, const QPoint &globalPoint, int position) +void SideDiffEditor::slotTooltipRequested(TextEditor::ITextEditor *editor, + const QPoint &globalPoint, + int position) { SideDiffEditorWidget *ew = qobject_cast(editorWidget()); if (!ew) return; - QMap fi = ew->fileInfo(); - QMap::const_iterator it + QMap fi = ew->fileInfo(); + QMap::const_iterator it = fi.constFind(ew->document()->findBlock(position).blockNumber()); if (it != fi.constEnd()) { Utils::ToolTip::show(globalPoint, Utils::TextContent(it.value().fileName), @@ -208,7 +230,7 @@ void SideDiffEditor::slotTooltipRequested(TextEditor::ITextEditor *editor, const } //////////////////////// - +/* MultiHighlighter::MultiHighlighter(SideDiffEditorWidget *editor, QTextDocument *document) : SyntaxHighlighter(document), m_editor(editor) @@ -224,7 +246,7 @@ MultiHighlighter::MultiHighlighter(SideDiffEditorWidget *editor, QTextDocument * MultiHighlighter::~MultiHighlighter() { - setDocuments(QList >()); + setDocuments(QList >()); } void MultiHighlighter::setFontSettings(const TextEditor::FontSettings &fontSettings) @@ -237,7 +259,7 @@ void MultiHighlighter::setFontSettings(const TextEditor::FontSettings &fontSetti } } -void MultiHighlighter::setDocuments(const QList > &documents) +void MultiHighlighter::setDocuments(const QList > &documents) { // clear old documents qDeleteAll(m_documents); @@ -247,7 +269,7 @@ void MultiHighlighter::setDocuments(const QList formats = documentBlock.layout()->additionalFormats(); setExtraAdditionalFormats(block, formats); } - +*/ //////////////////////// SideDiffEditorWidget::SideDiffEditorWidget(QWidget *parent) - : BaseTextEditorWidget(parent), m_lineNumberDigits(1), m_inPaintEvent(false) + : SelectableTextEditorWidget(parent), + m_lineNumberDigits(1), + m_inPaintEvent(false) { DisplaySettings settings = displaySettings(); settings.m_textWrapping = false; @@ -306,25 +330,24 @@ SideDiffEditorWidget::SideDiffEditorWidget(QWidget *parent) settings.m_displayFoldingMarkers = true; settings.m_markTextChanges = false; settings.m_highlightBlocks = false; - BaseTextEditorWidget::setDisplaySettings(settings); + SelectableTextEditorWidget::setDisplaySettings(settings); - setCodeFoldingSupported(true); - setFrameStyle(QFrame::NoFrame); +// setCodeFoldingSupported(true); - m_highlighter = new MultiHighlighter(this, baseTextDocument()->document()); - baseTextDocument()->setSyntaxHighlighter(m_highlighter); +// m_highlighter = new MultiHighlighter(this, baseTextDocument()->document()); +// baseTextDocument()->setSyntaxHighlighter(m_highlighter); } void SideDiffEditorWidget::setDisplaySettings(const DisplaySettings &ds) { DisplaySettings settings = displaySettings(); settings.m_visualizeWhitespace = ds.m_visualizeWhitespace; - BaseTextEditorWidget::setDisplaySettings(settings); + SelectableTextEditorWidget::setDisplaySettings(settings); } void SideDiffEditorWidget::applyFontSettings() { - BaseTextEditorWidget::applyFontSettings(); + SelectableTextEditorWidget::applyFontSettings(); const TextEditor::FontSettings &fs = baseTextDocument()->fontSettings(); m_fileLineForeground = fs.formatFor(C_DIFF_FILE_LINE).foreground(); m_chunkLineForeground = fs.formatFor(C_DIFF_CONTEXT_LINE).foreground(); @@ -352,7 +375,8 @@ bool SideDiffEditorWidget::selectionVisible(int blockNumber) const bool SideDiffEditorWidget::replacementVisible(int blockNumber) const { return isChunkLine(blockNumber) || (isFileLine(blockNumber) - && TextEditor::BaseTextDocumentLayout::isFolded(document()->findBlockByNumber(blockNumber))); + && TextEditor::BaseTextDocumentLayout::isFolded( + document()->findBlockByNumber(blockNumber))); } QColor SideDiffEditorWidget::replacementPenColor(int blockNumber) const @@ -403,18 +427,23 @@ void SideDiffEditorWidget::setLineNumber(int blockNumber, int lineNumber) m_lineNumberDigits = qMax(m_lineNumberDigits, lineNumberString.count()); } -void SideDiffEditorWidget::setFileInfo(int blockNumber, const DiffEditorController::DiffFileInfo &fileInfo) +void SideDiffEditorWidget::setFileInfo(int blockNumber, const DiffFileInfo &fileInfo) { m_fileInfo[blockNumber] = fileInfo; setSeparator(blockNumber, true); } +void SideDiffEditorWidget::setChunkIndex(int startBlockNumber, int blockCount, int chunkIndex) +{ + m_chunkInfo.insert(startBlockNumber, qMakePair(blockCount, chunkIndex)); +} + int SideDiffEditorWidget::blockNumberForFileIndex(int fileIndex) const { if (fileIndex < 0 || fileIndex >= m_fileInfo.count()) return -1; - QMap::const_iterator it + QMap::const_iterator it = m_fileInfo.constBegin(); for (int i = 0; i < fileIndex; i++) ++it; @@ -424,9 +453,9 @@ int SideDiffEditorWidget::blockNumberForFileIndex(int fileIndex) const int SideDiffEditorWidget::fileIndexForBlockNumber(int blockNumber) const { - QMap::const_iterator it + QMap::const_iterator it = m_fileInfo.constBegin(); - QMap::const_iterator itEnd + QMap::const_iterator itEnd = m_fileInfo.constEnd(); int i = -1; @@ -439,13 +468,33 @@ int SideDiffEditorWidget::fileIndexForBlockNumber(int blockNumber) const return i; } +int SideDiffEditorWidget::chunkIndexForBlockNumber(int blockNumber) const +{ + if (m_chunkInfo.isEmpty()) + return -1; + + QMap >::const_iterator it + = m_chunkInfo.upperBound(blockNumber); + if (it == m_chunkInfo.constBegin()) + return -1; + + --it; + + if (blockNumber < it.key() + it.value().first) + return it.value().second; + + return -1; +} + void SideDiffEditorWidget::clearAll(const QString &message) { setBlockSelection(false); clear(); clearAllData(); + setExtraSelections(BaseTextEditorWidget::OtherSelection, + QList()); setPlainText(message); - m_highlighter->setDocuments(QList >()); +// m_highlighter->setDocuments(QList >()); } void SideDiffEditorWidget::clearAllData() @@ -454,17 +503,19 @@ void SideDiffEditorWidget::clearAllData() m_lineNumbers.clear(); m_fileInfo.clear(); m_skippedLines.clear(); + m_chunkInfo.clear(); m_separators.clear(); + setSelections(QMap >()); } - -void SideDiffEditorWidget::setDocuments(const QList > &documents) +/* +void SideDiffEditorWidget::setDocuments(const QList > &documents) { m_highlighter->setDocuments(documents); } - +*/ void SideDiffEditorWidget::scrollContentsBy(int dx, int dy) { - BaseTextEditorWidget::scrollContentsBy(dx, dy); + SelectableTextEditorWidget::scrollContentsBy(dx, dy); // TODO: update only chunk lines viewport()->update(); } @@ -491,8 +542,10 @@ void SideDiffEditorWidget::paintSeparator(QPainter &painter, + QLatin1String("}; "); const int replacementTextWidth = fontMetrics().width(replacementText) + 24; int x = replacementTextWidth + offset.x(); - if (x < document()->documentMargin() || !TextEditor::BaseTextDocumentLayout::isFolded(block)) + if (x < document()->documentMargin() + || !TextEditor::BaseTextDocumentLayout::isFolded(block)) { x = document()->documentMargin(); + } const QString elidedText = fontMetrics().elidedText(text, Qt::ElideRight, viewport()->width() - x); @@ -515,7 +568,22 @@ void SideDiffEditorWidget::mouseDoubleClickEvent(QMouseEvent *e) e->accept(); return; } - BaseTextEditorWidget::mouseDoubleClickEvent(e); + SelectableTextEditorWidget::mouseDoubleClickEvent(e); +} + +void SideDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e) +{ + QPointer menu = createStandardContextMenu(); + + QTextCursor cursor = cursorForPosition(e->pos()); + const int blockNumber = cursor.blockNumber(); + + emit contextMenuRequested(menu, fileIndexForBlockNumber(blockNumber), + chunkIndexForBlockNumber(blockNumber)); + + connect(this, SIGNAL(destroyed()), menu, SLOT(deleteLater())); + menu->exec(e->globalPos()); + delete menu; } void SideDiffEditorWidget::jumpToOriginalFile(const QTextCursor &cursor) @@ -530,16 +598,26 @@ void SideDiffEditorWidget::jumpToOriginalFile(const QTextCursor &cursor) const int lineNumber = m_lineNumbers.value(blockNumber); - emit jumpToOriginalFileRequested(fileIndexForBlockNumber(blockNumber), lineNumber, columnNumber); + emit jumpToOriginalFileRequested(fileIndexForBlockNumber(blockNumber), + lineNumber, columnNumber); +} + +static QString skippedText(int skippedNumber) +{ + if (skippedNumber > 0) + return SideBySideDiffEditorWidget::tr("Skipped %n lines...", 0, skippedNumber); + if (skippedNumber == -2) + return SideBySideDiffEditorWidget::tr("Binary files differ"); + return SideBySideDiffEditorWidget::tr("Skipped unknown number of lines..."); } void SideDiffEditorWidget::paintEvent(QPaintEvent *e) { m_inPaintEvent = true; - BaseTextEditorWidget::paintEvent(e); + SelectableTextEditorWidget::paintEvent(e); m_inPaintEvent = false; - QPainter painter(viewport()); + QPainter painter(viewport()); QPointF offset = contentOffset(); QTextBlock firstBlock = firstVisibleBlock(); QTextBlock currentBlock = firstBlock; @@ -557,16 +635,17 @@ void SideDiffEditorWidget::paintEvent(QPaintEvent *e) const int skippedBefore = m_skippedLines.value(blockNumber); if (skippedBefore) { - const QString skippedRowsText = tr("Skipped %n lines...", 0, skippedBefore); + const QString skippedRowsText = skippedText(skippedBefore); paintSeparator(painter, m_chunkLineForeground, skippedRowsText, currentBlock, top); } - const DiffEditorController::DiffFileInfo fileInfo = m_fileInfo.value(blockNumber); + const DiffFileInfo fileInfo = m_fileInfo.value(blockNumber); if (!fileInfo.fileName.isEmpty()) { const QString fileNameText = fileInfo.typeInfo.isEmpty() ? fileInfo.fileName - : tr("[%1] %2").arg(fileInfo.typeInfo).arg(fileInfo.fileName); + : tr("[%1] %2").arg(fileInfo.typeInfo) + .arg(fileInfo.fileName); paintSeparator(painter, m_fileLineForeground, fileNameText, currentBlock, top); } @@ -574,9 +653,9 @@ void SideDiffEditorWidget::paintEvent(QPaintEvent *e) } currentBlock = currentBlock.next(); } - paintCollapsedBlockPopup(painter, e->rect()); +// paintCollapsedBlockPopup(painter, e->rect()); } - +/* void SideDiffEditorWidget::paintCollapsedBlockPopup(QPainter &painter, const QRect &clipRect) { QPointF offset(contentOffset()); @@ -620,7 +699,7 @@ void SideDiffEditorWidget::drawCollapsedBlockPopup(QPainter &painter, QPointF offset, const QRect &clip) { - // We ignore the call coming from the BaseTextEditorWidget::paintEvent() + // We ignore the call coming from the SelectableTextEditorWidget::paintEvent() // since we will draw it later, after custom drawings of this paintEvent. // We need to draw it after our custom drawings, otherwise custom // drawings will appear in front of block popup. @@ -676,7 +755,7 @@ void SideDiffEditorWidget::drawCollapsedBlockPopup(QPainter &painter, b = b.next(); } } - +*/ ////////////////// @@ -684,7 +763,10 @@ SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent) : QWidget(parent) , m_guiController(0) , m_controller(0) + , m_ignoreCurrentIndexChange(false) , m_foldingBlocker(false) + , m_contextMenuFileIndex(-1) + , m_contextMenuChunkIndex(-1) { m_leftEditor = new SideDiffEditorWidget(this); m_leftEditor->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -696,6 +778,8 @@ SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent) m_leftEditor->setCodeStyle(TextEditorSettings::codeStyle()); connect(m_leftEditor, SIGNAL(jumpToOriginalFileRequested(int,int,int)), this, SLOT(slotLeftJumpToOriginalFileRequested(int,int,int))); + connect(m_leftEditor, SIGNAL(contextMenuRequested(QMenu*,int,int)), + this, SLOT(slotLeftContextMenuRequested(QMenu*,int,int))); m_rightEditor = new SideDiffEditorWidget(this); m_rightEditor->setReadOnly(true); @@ -706,6 +790,8 @@ SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent) m_rightEditor->setCodeStyle(TextEditorSettings::codeStyle()); connect(m_rightEditor, SIGNAL(jumpToOriginalFileRequested(int,int,int)), this, SLOT(slotRightJumpToOriginalFileRequested(int,int,int))); + connect(m_rightEditor, SIGNAL(contextMenuRequested(QMenu*,int,int)), + this, SLOT(slotRightContextMenuRequested(QMenu*,int,int))); connect(TextEditorSettings::instance(), SIGNAL(fontSettingsChanged(TextEditor::FontSettings)), @@ -724,8 +810,8 @@ SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent) connect(m_leftEditor, SIGNAL(cursorPositionChanged()), this, SLOT(leftCursorPositionChanged())); - connect(m_leftEditor->document()->documentLayout(), SIGNAL(documentSizeChanged(QSizeF)), - this, SLOT(leftDocumentSizeChanged())); +// connect(m_leftEditor->document()->documentLayout(), SIGNAL(documentSizeChanged(QSizeF)), +// this, SLOT(leftDocumentSizeChanged())); connect(m_rightEditor->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(rightVSliderChanged())); @@ -739,8 +825,8 @@ SideBySideDiffEditorWidget::SideBySideDiffEditorWidget(QWidget *parent) connect(m_rightEditor, SIGNAL(cursorPositionChanged()), this, SLOT(rightCursorPositionChanged())); - connect(m_rightEditor->document()->documentLayout(), SIGNAL(documentSizeChanged(QSizeF)), - this, SLOT(rightDocumentSizeChanged())); +// connect(m_rightEditor->document()->documentLayout(), SIGNAL(documentSizeChanged(QSizeF)), +// this, SLOT(rightDocumentSizeChanged())); m_splitter = new Core::MiniSplitter(this); m_splitter->addWidget(m_leftEditor); @@ -757,42 +843,38 @@ SideBySideDiffEditorWidget::~SideBySideDiffEditorWidget() } -void SideBySideDiffEditorWidget::setDiffEditorGuiController(DiffEditorGuiController *controller) +void SideBySideDiffEditorWidget::setDiffEditorGuiController( + DiffEditorGuiController *controller) { if (m_guiController == controller) return; if (m_guiController) { - disconnect(m_controller, SIGNAL(cleared(QString)), this, SLOT(clear(QString))); - disconnect(m_controller, SIGNAL(diffContentsChanged(QList,QString)), - this, SLOT(setDiff(QList,QString))); - - disconnect(m_guiController, SIGNAL(contextLinesNumberChanged(int)), - this, SLOT(setContextLinesNumber(int))); - disconnect(m_guiController, SIGNAL(ignoreWhitespacesChanged(bool)), - this, SLOT(setIgnoreWhitespaces(bool))); + disconnect(m_controller, SIGNAL(cleared(QString)), + this, SLOT(clearAll(QString))); + disconnect(m_controller, SIGNAL(diffFilesChanged(QList,QString)), + this, SLOT(setDiff(QList,QString))); + disconnect(m_guiController, SIGNAL(currentDiffFileIndexChanged(int)), - this, SLOT(setCurrentDiffFileIndex(int))); + this, SLOT(setCurrentDiffFileIndex(int))); - clear(tr("No controller")); + clearAll(tr("No controller")); } m_guiController = controller; m_controller = 0; if (m_guiController) { m_controller = m_guiController->controller(); - connect(m_controller, SIGNAL(cleared(QString)), this, SLOT(clear(QString))); - connect(m_controller, SIGNAL(diffContentsChanged(QList,QString)), - this, SLOT(setDiff(QList,QString))); + connect(m_controller, SIGNAL(cleared(QString)), + this, SLOT(clearAll(QString))); + connect(m_controller, SIGNAL(diffFilesChanged(QList,QString)), + this, SLOT(setDiff(QList,QString))); - connect(m_guiController, SIGNAL(contextLinesNumberChanged(int)), - this, SLOT(setContextLinesNumber(int))); - connect(m_guiController, SIGNAL(ignoreWhitespacesChanged(bool)), - this, SLOT(setIgnoreWhitespaces(bool))); connect(m_guiController, SIGNAL(currentDiffFileIndexChanged(int)), this, SLOT(setCurrentDiffFileIndex(int))); - setDiff(m_controller->diffContents(), m_controller->workingDirectory()); + setDiff(m_controller->diffFiles(), m_controller->workingDirectory()); + setCurrentDiffFileIndex(m_guiController->currentDiffFileIndex()); } } @@ -804,91 +886,37 @@ DiffEditorGuiController *SideBySideDiffEditorWidget::diffEditorGuiController() c void SideBySideDiffEditorWidget::clear(const QString &message) { + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; m_leftEditor->clearAll(message); m_rightEditor->clearAll(message); + m_ignoreCurrentIndexChange = oldIgnore; } -void SideBySideDiffEditorWidget::setDiff(const QList &diffFileList, const QString &workingDirectory) +void SideBySideDiffEditorWidget::clearAll(const QString &message) { - Q_UNUSED(workingDirectory) - - Differ differ; - QList diffList; - for (int i = 0; i < diffFileList.count(); i++) { - DiffEditorController::DiffFilesContents dfc = diffFileList.at(i); - DiffList dl; - dl.leftFileInfo = dfc.leftFileInfo; - dl.rightFileInfo = dfc.rightFileInfo; - dl.diffList = differ.cleanupSemantics(differ.diff(dfc.leftText, dfc.rightText)); - diffList.append(dl); - } - setDiff(diffList); -} - -void SideBySideDiffEditorWidget::setDiff(const QList &diffList) -{ - m_diffList = diffList; - m_originalChunkData.clear(); - m_contextFileData.clear(); - const int contextLinesNumber = m_guiController ? m_guiController->contextLinesNumber() : 3; - - for (int i = 0; i < m_diffList.count(); i++) { - const DiffList &dl = m_diffList.at(i); - QList leftDiffs; - QList rightDiffs; - handleWhitespaces(dl.diffList, &leftDiffs, &rightDiffs); - ChunkData chunkData = calculateOriginalData(leftDiffs, rightDiffs); - m_originalChunkData.append(chunkData); - FileData fileData = calculateContextData(chunkData, contextLinesNumber); - fileData.leftFileInfo = dl.leftFileInfo; - fileData.rightFileInfo = dl.rightFileInfo; - m_contextFileData.append(fileData); - } - showDiff(); -} - -void SideBySideDiffEditorWidget::handleWhitespaces(const QList &input, - QList *leftOutput, - QList *rightOutput) const -{ - if (!leftOutput || !rightOutput) - return; - - Differ::splitDiffList(input, leftOutput, rightOutput); - if (m_guiController && m_guiController->isIgnoreWhitespaces()) { - const QList leftDiffList = Differ::moveWhitespaceIntoEqualities(*leftOutput); - const QList rightDiffList = Differ::moveWhitespaceIntoEqualities(*rightOutput); - Differ::diffBetweenEqualities(leftDiffList, rightDiffList, leftOutput, rightOutput); - } + setDiff(QList(), QString()); + clear(message); } -void SideBySideDiffEditorWidget::setContextLinesNumber(int lines) +void SideBySideDiffEditorWidget::setDiff(const QList &diffFileList, + const QString &workingDirectory) { - Q_UNUSED(lines) - const int contextLinesNumber = m_guiController ? m_guiController->contextLinesNumber() : 3; - - for (int i = 0; i < m_contextFileData.count(); i++) { - const FileData oldFileData = m_contextFileData.at(i); - FileData newFileData = calculateContextData(m_originalChunkData.at(i), contextLinesNumber); - newFileData.leftFileInfo = oldFileData.leftFileInfo; - newFileData.rightFileInfo = oldFileData.rightFileInfo; - m_contextFileData[i] = newFileData; - } + Q_UNUSED(workingDirectory) + m_contextFileData = diffFileList; showDiff(); } -void SideBySideDiffEditorWidget::setIgnoreWhitespaces(bool ignore) -{ - Q_UNUSED(ignore) - - setDiff(m_diffList); -} - void SideBySideDiffEditorWidget::setCurrentDiffFileIndex(int diffFileIndex) { + if (m_ignoreCurrentIndexChange) + return; + const int blockNumber = m_leftEditor->blockNumberForFileIndex(diffFileIndex); + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; QTextBlock leftBlock = m_leftEditor->document()->findBlockByNumber(blockNumber); QTextCursor leftCursor = m_leftEditor->textCursor(); leftCursor.setPosition(leftBlock.position()); @@ -901,6 +929,7 @@ void SideBySideDiffEditorWidget::setCurrentDiffFileIndex(int diffFileIndex) m_leftEditor->centerCursor(); m_rightEditor->centerCursor(); + m_ignoreCurrentIndexChange = oldIgnore; } void SideBySideDiffEditorWidget::showDiff() @@ -912,7 +941,10 @@ void SideBySideDiffEditorWidget::showDiff() clear(tr("No difference")); - QList > leftDocs, rightDocs; + QMap > leftFormats; + QMap > rightFormats; + +// QList > leftDocs, rightDocs; QString leftTexts, rightTexts; int blockNumber = 0; QChar separator = QLatin1Char('\n'); @@ -920,68 +952,151 @@ void SideBySideDiffEditorWidget::showDiff() QString leftText, rightText; const FileData &contextFileData = m_contextFileData.at(i); - int leftLineNumber = 0; - int rightLineNumber = 0; + leftFormats[blockNumber].append(DiffSelection(&m_fileLineFormat)); + rightFormats[blockNumber].append(DiffSelection(&m_fileLineFormat)); m_leftEditor->setFileInfo(blockNumber, contextFileData.leftFileInfo); m_rightEditor->setFileInfo(blockNumber, contextFileData.rightFileInfo); leftText = separator; rightText = separator; blockNumber++; - for (int j = 0; j < contextFileData.chunks.count(); j++) { - ChunkData chunkData = contextFileData.chunks.at(j); - if (chunkData.contextChunk) { - const int skippedLines = chunkData.rows.count(); - m_leftEditor->setSkippedLines(blockNumber, skippedLines); - m_rightEditor->setSkippedLines(blockNumber, skippedLines); - leftText += separator; - rightText += separator; - blockNumber++; - } + int lastLeftLineNumber = -1; - for (int k = 0; k < chunkData.rows.count(); k++) { - RowData rowData = chunkData.rows.at(k); - TextLineData leftLineData = rowData.leftLine; - TextLineData rightLineData = rowData.rightLine; - if (leftLineData.textLineType == TextLineData::TextLine) { - leftText += leftLineData.text; - leftLineNumber++; - m_leftEditor->setLineNumber(blockNumber, leftLineNumber); - } else if (leftLineData.textLineType == TextLineData::Separator) { - m_leftEditor->setSeparator(blockNumber, true); + if (contextFileData.binaryFiles) { + leftFormats[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + rightFormats[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + m_leftEditor->setSkippedLines(blockNumber, -2); + m_rightEditor->setSkippedLines(blockNumber, -2); + leftText += separator; + rightText += separator; + blockNumber++; + } else { + for (int j = 0; j < contextFileData.chunks.count(); j++) { + ChunkData chunkData = contextFileData.chunks.at(j); + + int leftLineNumber = chunkData.leftStartingLineNumber; + int rightLineNumber = chunkData.rightStartingLineNumber; + + if (!chunkData.contextChunk) { + const int skippedLines = leftLineNumber - lastLeftLineNumber - 1; + if (skippedLines > 0) { + leftFormats[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + rightFormats[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + m_leftEditor->setSkippedLines(blockNumber, skippedLines); + m_rightEditor->setSkippedLines(blockNumber, skippedLines); + leftText += separator; + rightText += separator; + blockNumber++; + } + + m_leftEditor->setChunkIndex(blockNumber, chunkData.rows.count(), j); + m_rightEditor->setChunkIndex(blockNumber, chunkData.rows.count(), j); + + for (int k = 0; k < chunkData.rows.count(); k++) { + RowData rowData = chunkData.rows.at(k); + TextLineData leftLineData = rowData.leftLine; + TextLineData rightLineData = rowData.rightLine; + if (leftLineData.textLineType == TextLineData::TextLine) { + leftText += leftLineData.text; + lastLeftLineNumber = leftLineNumber; + leftLineNumber++; + m_leftEditor->setLineNumber(blockNumber, leftLineNumber); + } else if (leftLineData.textLineType == TextLineData::Separator) { + m_leftEditor->setSeparator(blockNumber, true); + } + + if (rightLineData.textLineType == TextLineData::TextLine) { + rightText += rightLineData.text; + rightLineNumber++; + m_rightEditor->setLineNumber(blockNumber, rightLineNumber); + } else if (rightLineData.textLineType == TextLineData::Separator) { + m_rightEditor->setSeparator(blockNumber, true); + } + + if (!rowData.equal) { + if (rowData.leftLine.textLineType == TextLineData::TextLine) + leftFormats[blockNumber].append(DiffSelection(&m_leftLineFormat)); + else + leftFormats[blockNumber].append(DiffSelection(&m_spanLineFormat)); + if (rowData.rightLine.textLineType == TextLineData::TextLine) + rightFormats[blockNumber].append(DiffSelection(&m_rightLineFormat)); + else + rightFormats[blockNumber].append(DiffSelection(&m_spanLineFormat)); + } + + QMapIterator itLeft(leftLineData.changedPositions); + while (itLeft.hasNext()) { + itLeft.next(); + leftFormats[blockNumber].append( + DiffSelection(itLeft.key(), itLeft.value(), + &m_leftCharFormat)); + } + + QMapIterator itRight(rightLineData.changedPositions); + while (itRight.hasNext()) { + itRight.next(); + rightFormats[blockNumber].append( + DiffSelection(itRight.key(), itRight.value(), + &m_rightCharFormat)); + } + + leftText += separator; + rightText += separator; + blockNumber++; + } } - if (rightLineData.textLineType == TextLineData::TextLine) { - rightText += rightLineData.text; - rightLineNumber++; - m_rightEditor->setLineNumber(blockNumber, rightLineNumber); - } else if (rightLineData.textLineType == TextLineData::Separator) { - m_rightEditor->setSeparator(blockNumber, true); + if (j == contextFileData.chunks.count() - 1) { // the last chunk + int skippedLines = -2; + if (chunkData.contextChunk) { + // if it's context chunk + skippedLines = chunkData.rows.count(); + } else if (!contextFileData.lastChunkAtTheEndOfFile + && !contextFileData.contextChunksIncluded) { + // if not a context chunk and not a chunk at the end of file + // and context lines not included + skippedLines = -1; // unknown count skipped by the end of file + } + + if (skippedLines >= -1) { + leftFormats[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + rightFormats[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + m_leftEditor->setSkippedLines(blockNumber, skippedLines); + m_rightEditor->setSkippedLines(blockNumber, skippedLines); + leftText += separator; + rightText += separator; + blockNumber++; + } // otherwise nothing skipped } - leftText += separator; - rightText += separator; - blockNumber++; } } leftText.replace(QLatin1Char('\r'), QLatin1Char(' ')); rightText.replace(QLatin1Char('\r'), QLatin1Char(' ')); leftTexts += leftText; rightTexts += rightText; - leftDocs.append(qMakePair(contextFileData.leftFileInfo, leftText)); - rightDocs.append(qMakePair(contextFileData.rightFileInfo, rightText)); +// leftDocs.append(qMakePair(contextFileData.leftFileInfo, leftText)); +// rightDocs.append(qMakePair(contextFileData.rightFileInfo, rightText)); } if (leftTexts.isEmpty() && rightTexts.isEmpty()) return; - m_leftEditor->setDocuments(leftDocs); - m_rightEditor->setDocuments(rightDocs); +// m_leftEditor->setDocuments(leftDocs); +// m_rightEditor->setDocuments(rightDocs); + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + m_leftEditor->clear(); m_leftEditor->setPlainText(leftTexts); + m_rightEditor->clear(); m_rightEditor->setPlainText(rightTexts); + m_ignoreCurrentIndexChange = oldIgnore; - colorDiff(m_contextFileData); + m_leftEditor->setSelections(leftFormats); + m_rightEditor->setSelections(rightFormats); + +/* QTextBlock leftBlock = m_leftEditor->document()->firstBlock(); QTextBlock rightBlock = m_rightEditor->document()->firstBlock(); for (int i = 0; i < m_contextFileData.count(); i++) { @@ -1033,134 +1148,38 @@ void SideBySideDiffEditorWidget::showDiff() rightLayout->emitDocumentSizeChanged(); } m_foldingBlocker = false; - +*/ m_leftEditor->verticalScrollBar()->setValue(verticalValue); m_rightEditor->verticalScrollBar()->setValue(verticalValue); m_leftEditor->horizontalScrollBar()->setValue(leftHorizontalValue); m_rightEditor->horizontalScrollBar()->setValue(rightHorizontalValue); - m_leftEditor->updateFoldingHighlight(QPoint(-1, -1)); - m_rightEditor->updateFoldingHighlight(QPoint(-1, -1)); -} - -void SideBySideDiffEditorWidget::colorDiff(const QList &fileDataList) -{ - QPalette pal = m_leftEditor->extraArea()->palette(); - pal.setCurrentColorGroup(QPalette::Active); - QTextCharFormat spanLineFormat; - spanLineFormat.setBackground(pal.color(QPalette::Background)); - spanLineFormat.setProperty(QTextFormat::FullWidthSelection, true); - - int leftPos = 0; - int rightPos = 0; - // - QMap leftLinePos; - QMap rightLinePos; - QMap leftCharPos; - QMap rightCharPos; - QMap leftSkippedPos; - QMap rightSkippedPos; - QMap leftChunkPos; - QMap rightChunkPos; - QMap leftFilePos; - QMap rightFilePos; - int leftLastDiffBlockStartPos = 0; - int rightLastDiffBlockStartPos = 0; - int leftLastSkippedBlockStartPos = 0; - int rightLastSkippedBlockStartPos = 0; - - for (int i = 0; i < fileDataList.count(); i++) { - const FileData &fileData = fileDataList.at(i); - leftFilePos[leftPos] = leftPos + 1; - rightFilePos[rightPos] = rightPos + 1; - leftPos++; // for file line - rightPos++; // for file line - - for (int j = 0; j < fileData.chunks.count(); j++) { - const ChunkData &chunkData = fileData.chunks.at(j); - if (chunkData.contextChunk) { - leftChunkPos[leftPos] = leftPos + 1; - rightChunkPos[rightPos] = rightPos + 1; - leftPos++; // for chunk line - rightPos++; // for chunk line - } - leftLastDiffBlockStartPos = leftPos; - rightLastDiffBlockStartPos = rightPos; - leftLastSkippedBlockStartPos = leftPos; - rightLastSkippedBlockStartPos = rightPos; - - for (int k = 0; k < chunkData.rows.count(); k++) { - const RowData &rowData = chunkData.rows.at(k); - - addChangedPositions(leftPos, rowData.leftLine.changedPositions, &leftCharPos); - addChangedPositions(rightPos, rowData.rightLine.changedPositions, &rightCharPos); - - leftPos += rowData.leftLine.text.count() + 1; // +1 for '\n' - rightPos += rowData.rightLine.text.count() + 1; // +1 for '\n' - - if (!rowData.equal) { - if (rowData.leftLine.textLineType == TextLineData::TextLine) { - leftLinePos[leftLastDiffBlockStartPos] = leftPos; - leftLastSkippedBlockStartPos = leftPos; - } else { - leftSkippedPos[leftLastSkippedBlockStartPos] = leftPos; - leftLastDiffBlockStartPos = leftPos; - } - if (rowData.rightLine.textLineType == TextLineData::TextLine) { - rightLinePos[rightLastDiffBlockStartPos] = rightPos; - rightLastSkippedBlockStartPos = rightPos; - } else { - rightSkippedPos[rightLastSkippedBlockStartPos] = rightPos; - rightLastDiffBlockStartPos = rightPos; - } - } else { - leftLastDiffBlockStartPos = leftPos; - leftLastSkippedBlockStartPos = leftPos; - rightLastDiffBlockStartPos = rightPos; - rightLastSkippedBlockStartPos = rightPos; - } - } - } - } - - QTextCursor leftCursor = m_leftEditor->textCursor(); - QTextCursor rightCursor = m_rightEditor->textCursor(); - QList leftSelections; - leftSelections += colorPositions(m_leftLineFormat, leftCursor, leftLinePos); - leftSelections += colorPositions(m_leftCharFormat, leftCursor, leftCharPos); - leftSelections += colorPositions(spanLineFormat, leftCursor, leftSkippedPos); - leftSelections += colorPositions(m_chunkLineFormat, leftCursor, leftChunkPos); - leftSelections += colorPositions(m_fileLineFormat, leftCursor, leftFilePos); - - QList rightSelections; - rightSelections += colorPositions(m_rightLineFormat, rightCursor, rightLinePos); - rightSelections += colorPositions(m_rightCharFormat, rightCursor, rightCharPos); - rightSelections += colorPositions(spanLineFormat, rightCursor, rightSkippedPos); - rightSelections += colorPositions(m_chunkLineFormat, rightCursor, rightChunkPos); - rightSelections += colorPositions(m_fileLineFormat, rightCursor, rightFilePos); - - m_leftEditor->setExtraSelections(BaseTextEditorWidget::OtherSelection, leftSelections); - m_rightEditor->setExtraSelections(BaseTextEditorWidget::OtherSelection, rightSelections); +// m_leftEditor->updateFoldingHighlight(QPoint(-1, -1)); +// m_rightEditor->updateFoldingHighlight(QPoint(-1, -1)); } -void SideBySideDiffEditorWidget::setFontSettings(const TextEditor::FontSettings &fontSettings) +void SideBySideDiffEditorWidget::setFontSettings( + const TextEditor::FontSettings &fontSettings) { m_leftEditor->baseTextDocument()->setFontSettings(fontSettings); m_rightEditor->baseTextDocument()->setFontSettings(fontSettings); - m_fileLineFormat = fullWidthFormatForTextStyle(fontSettings, C_DIFF_FILE_LINE); - m_chunkLineFormat = fullWidthFormatForTextStyle(fontSettings, C_DIFF_CONTEXT_LINE); - m_leftLineFormat = fullWidthFormatForTextStyle(fontSettings, C_DIFF_SOURCE_LINE); - m_leftCharFormat = fullWidthFormatForTextStyle(fontSettings, C_DIFF_SOURCE_CHAR); - m_rightLineFormat = fullWidthFormatForTextStyle(fontSettings, C_DIFF_DEST_LINE); - m_rightCharFormat = fullWidthFormatForTextStyle(fontSettings, C_DIFF_DEST_CHAR); + m_spanLineFormat = fontSettings.toTextCharFormat(C_LINE_NUMBER); + 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); - colorDiff(m_contextFileData); + m_leftEditor->update(); + m_rightEditor->update(); } -void SideBySideDiffEditorWidget::slotLeftJumpToOriginalFileRequested(int diffFileIndex, - int lineNumber, - int columnNumber) +void SideBySideDiffEditorWidget::slotLeftJumpToOriginalFileRequested( + int diffFileIndex, + int lineNumber, + int columnNumber) { if (diffFileIndex < 0 || diffFileIndex >= m_contextFileData.count()) return; @@ -1196,8 +1215,10 @@ void SideBySideDiffEditorWidget::slotLeftJumpToOriginalFileRequested(int diffFil } } -void SideBySideDiffEditorWidget::slotRightJumpToOriginalFileRequested(int diffFileIndex, - int lineNumber, int columnNumber) +void SideBySideDiffEditorWidget::slotRightJumpToOriginalFileRequested( + int diffFileIndex, + int lineNumber, + int columnNumber) { if (diffFileIndex < 0 || diffFileIndex >= m_contextFileData.count()) return; @@ -1218,26 +1239,188 @@ void SideBySideDiffEditorWidget::jumpToOriginalFile(const QString &fileName, Core::EditorManager::openEditorAt(absoluteFileName, lineNumber, columnNumber); } +void SideBySideDiffEditorWidget::slotLeftContextMenuRequested(QMenu *menu, + int diffFileIndex, + int chunkIndex) +{ + menu->addSeparator(); + QAction *sendChunkToCodePasterAction = + menu->addAction(tr("Send Chunk to CodePaster...")); + connect(sendChunkToCodePasterAction, SIGNAL(triggered()), + this, SLOT(slotSendChunkToCodePaster())); + menu->addSeparator(); + QAction *applyAction = menu->addAction(tr("Apply Chunk...")); + connect(applyAction, SIGNAL(triggered()), this, SLOT(slotApplyChunk())); + applyAction->setEnabled(false); + + m_contextMenuFileIndex = diffFileIndex; + m_contextMenuChunkIndex = chunkIndex; + + if (m_contextMenuFileIndex < 0 || m_contextMenuChunkIndex < 0) + return; + + if (m_contextMenuFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(m_contextMenuFileIndex); + if (m_contextMenuChunkIndex >= fileData.chunks.count()) + return; + + emit m_controller->chunkActionsRequested(menu, diffFileIndex, chunkIndex); + + if (fileData.leftFileInfo.fileName == fileData.rightFileInfo.fileName) + return; + + applyAction->setEnabled(true); +} + +void SideBySideDiffEditorWidget::slotRightContextMenuRequested(QMenu *menu, + int diffFileIndex, + int chunkIndex) +{ + menu->addSeparator(); + QAction *sendChunkToCodePasterAction = + menu->addAction(tr("Send Chunk to CodePaster...")); + connect(sendChunkToCodePasterAction, SIGNAL(triggered()), + this, SLOT(slotSendChunkToCodePaster())); + menu->addSeparator(); + QAction *revertAction = menu->addAction(tr("Revert Chunk...")); + connect(revertAction, SIGNAL(triggered()), this, SLOT(slotRevertChunk())); + revertAction->setEnabled(false); + + m_contextMenuFileIndex = diffFileIndex; + m_contextMenuChunkIndex = chunkIndex; + + if (m_contextMenuFileIndex < 0 || m_contextMenuChunkIndex < 0) + return; + + if (m_contextMenuFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(m_contextMenuFileIndex); + if (m_contextMenuChunkIndex >= fileData.chunks.count()) + return; + + emit m_controller->chunkActionsRequested(menu, diffFileIndex, chunkIndex); + + revertAction->setEnabled(true); +} + +void SideBySideDiffEditorWidget::slotSendChunkToCodePaster() +{ + if (!m_controller) + return; + + if (m_contextMenuFileIndex < 0 || m_contextMenuChunkIndex < 0) + return; + + if (m_contextMenuFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(m_contextMenuFileIndex); + if (m_contextMenuChunkIndex >= fileData.chunks.count()) + return; + + const QString patch = m_controller->makePatch(m_contextMenuFileIndex, + m_contextMenuChunkIndex, + false); + if (patch.isEmpty()) + return; + + // Retrieve service by soft dependency. + QObject *pasteService = + ExtensionSystem::PluginManager::getObjectByClassName( + QLatin1String("CodePaster::CodePasterService")); + if (pasteService) { + QMetaObject::invokeMethod(pasteService, "postText", + Q_ARG(QString, patch), + Q_ARG(QString, QLatin1String(DiffEditor::Constants::DIFF_EDITOR_MIMETYPE))); + } else { + QMessageBox::information(this, tr("Unable to Paste"), + tr("Code pasting services are not available.")); + } +} + +void SideBySideDiffEditorWidget::slotApplyChunk() +{ + patch(m_contextMenuFileIndex, m_contextMenuChunkIndex, false); +} + +void SideBySideDiffEditorWidget::slotRevertChunk() +{ + patch(m_contextMenuFileIndex, m_contextMenuChunkIndex, true); +} + +void SideBySideDiffEditorWidget::patch(int diffFileIndex, int chunkIndex, bool revert) +{ + if (!m_controller) + return; + + if (diffFileIndex < 0 || chunkIndex < 0) + return; + + if (diffFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(diffFileIndex); + if (chunkIndex >= fileData.chunks.count()) + 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(this, title, + question, + QMessageBox::Yes + | QMessageBox::No)) { + return; + } + + const int strip = m_controller->workingDirectory().isEmpty() ? -1 : 0; + + const QString fileName = revert + ? fileData.rightFileInfo.fileName + : fileData.leftFileInfo.fileName; + + const QString workingDirectory = m_controller->workingDirectory().isEmpty() + ? QFileInfo(fileName).absolutePath() + : m_controller->workingDirectory(); + + const QString patch = m_controller->makePatch(diffFileIndex, chunkIndex, revert); + + if (patch.isEmpty()) + return; + + if (PatchTool::runPatch(Core::EditorManager::defaultTextCodec()->fromUnicode(patch), + workingDirectory, strip, revert)) + m_controller->requestReload(); +} + void SideBySideDiffEditorWidget::leftVSliderChanged() { - m_rightEditor->verticalScrollBar()->setValue(m_leftEditor->verticalScrollBar()->value()); + m_rightEditor->verticalScrollBar()->setValue( + m_leftEditor->verticalScrollBar()->value()); } void SideBySideDiffEditorWidget::rightVSliderChanged() { - m_leftEditor->verticalScrollBar()->setValue(m_rightEditor->verticalScrollBar()->value()); + m_leftEditor->verticalScrollBar()->setValue( + m_rightEditor->verticalScrollBar()->value()); } void SideBySideDiffEditorWidget::leftHSliderChanged() { if (!m_guiController || m_guiController->horizontalScrollBarSynchronization()) - m_rightEditor->horizontalScrollBar()->setValue(m_leftEditor->horizontalScrollBar()->value()); + m_rightEditor->horizontalScrollBar()->setValue( + m_leftEditor->horizontalScrollBar()->value()); } void SideBySideDiffEditorWidget::rightHSliderChanged() { if (!m_guiController || m_guiController->horizontalScrollBarSynchronization()) - m_leftEditor->horizontalScrollBar()->setValue(m_rightEditor->horizontalScrollBar()->value()); + m_leftEditor->horizontalScrollBar()->setValue( + m_rightEditor->horizontalScrollBar()->value()); } void SideBySideDiffEditorWidget::leftCursorPositionChanged() @@ -1248,7 +1431,15 @@ void SideBySideDiffEditorWidget::leftCursorPositionChanged() if (!m_guiController) return; - m_guiController->setCurrentDiffFileIndex(m_leftEditor->fileIndexForBlockNumber(m_leftEditor->textCursor().blockNumber())); + if (m_ignoreCurrentIndexChange) + return; + + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + m_guiController->setCurrentDiffFileIndex( + m_leftEditor->fileIndexForBlockNumber( + m_leftEditor->textCursor().blockNumber())); + m_ignoreCurrentIndexChange = oldIgnore; } void SideBySideDiffEditorWidget::rightCursorPositionChanged() @@ -1259,9 +1450,18 @@ void SideBySideDiffEditorWidget::rightCursorPositionChanged() if (!m_guiController) return; - m_guiController->setCurrentDiffFileIndex(m_rightEditor->fileIndexForBlockNumber(m_rightEditor->textCursor().blockNumber())); + if (m_ignoreCurrentIndexChange) + return; + + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + m_guiController->setCurrentDiffFileIndex( + m_rightEditor->fileIndexForBlockNumber( + m_rightEditor->textCursor().blockNumber())); + m_ignoreCurrentIndexChange = oldIgnore; } +#if 0 void SideBySideDiffEditorWidget::leftDocumentSizeChanged() { synchronizeFoldings(m_leftEditor, m_rightEditor); @@ -1400,6 +1600,7 @@ void SideBySideDiffEditorWidget::synchronizeFoldings(SideDiffEditorWidget *sourc } m_foldingBlocker = false; } +#endif } // namespace DiffEditor diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.h b/src/plugins/diffeditor/sidebysidediffeditorwidget.h index a25809e475..7367f489f9 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.h +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.h @@ -40,17 +40,15 @@ namespace TextEditor { class FontSettings; } QT_BEGIN_NAMESPACE class QSplitter; +class QMenu; QT_END_NAMESPACE namespace DiffEditor { class DiffEditorGuiController; class SideDiffEditorWidget; - -namespace Internal { class ChunkData; class FileData; -} class DIFFEDITOR_EXPORT SideBySideDiffEditorWidget : public QWidget { @@ -64,40 +62,39 @@ public: private slots: void clear(const QString &message = QString()); - void setDiff(const QList &diffFileList, const QString &workingDirectory); + void clearAll(const QString &message = QString()); + void setDiff(const QList &diffFileList, + const QString &workingDirectory); - void setContextLinesNumber(int lines); - void setIgnoreWhitespaces(bool ignore); void setCurrentDiffFileIndex(int diffFileIndex); void setFontSettings(const TextEditor::FontSettings &fontSettings); - void slotLeftJumpToOriginalFileRequested(int diffFileIndex, int lineNumber, int columnNumber); - void slotRightJumpToOriginalFileRequested(int diffFileIndex, int lineNumber, int columnNumber); + void slotLeftJumpToOriginalFileRequested(int diffFileIndex, + int lineNumber, int columnNumber); + void slotRightJumpToOriginalFileRequested(int diffFileIndex, + int lineNumber, int columnNumber); + void slotLeftContextMenuRequested(QMenu *menu, int diffFileIndex, + int chunkIndex); + void slotRightContextMenuRequested(QMenu *menu, int diffFileIndex, + int chunkIndex); + void slotSendChunkToCodePaster(); + void slotApplyChunk(); + void slotRevertChunk(); void leftVSliderChanged(); void rightVSliderChanged(); void leftHSliderChanged(); void rightHSliderChanged(); void leftCursorPositionChanged(); void rightCursorPositionChanged(); - void leftDocumentSizeChanged(); - void rightDocumentSizeChanged(); +// void leftDocumentSizeChanged(); +// void rightDocumentSizeChanged(); private: - class DiffList { - public: - DiffEditorController::DiffFileInfo leftFileInfo; - DiffEditorController::DiffFileInfo rightFileInfo; - QList diffList; - }; - - void setDiff(const QList &diffList); - void handleWhitespaces(const QList &input, - QList *leftOutput, - QList *rightOutput) const; - void colorDiff(const QList &fileDataList); void showDiff(); - void synchronizeFoldings(SideDiffEditorWidget *source, SideDiffEditorWidget *destination); - void jumpToOriginalFile(const QString &fileName, int lineNumber, int columnNumber); +// void synchronizeFoldings(SideDiffEditorWidget *source, SideDiffEditorWidget *destination); + void jumpToOriginalFile(const QString &fileName, + int lineNumber, int columnNumber); + void patch(int diffFileIndex, int chunkIndex, bool revert); DiffEditorGuiController *m_guiController; DiffEditorController *m_controller; @@ -105,12 +102,14 @@ private: SideDiffEditorWidget *m_rightEditor; QSplitter *m_splitter; - QList m_diffList; // list of original outputs from differ - QList m_originalChunkData; // one big chunk for every file, ignoreWhitespace taken into account - QList m_contextFileData; // ultimate data to be shown, contextLinesNumber taken into account + QList m_contextFileData; // ultimate data to be shown, contextLinesNumber taken into account + bool m_ignoreCurrentIndexChange; bool m_foldingBlocker; + int m_contextMenuFileIndex; + int m_contextMenuChunkIndex; + QTextCharFormat m_spanLineFormat; QTextCharFormat m_fileLineFormat; QTextCharFormat m_chunkLineFormat; QTextCharFormat m_leftLineFormat; diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp new file mode 100644 index 0000000000..9c58d47257 --- /dev/null +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp @@ -0,0 +1,783 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "unifieddiffeditorwidget.h" +#include "diffeditorguicontroller.h" +#include "diffutils.h" +#include "diffeditorconstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +static const int FILE_LEVEL = 1; +static const int CHUNK_LEVEL = 2; + +using namespace Core; +using namespace TextEditor; + +namespace DiffEditor { + +class UnifiedDiffEditor : public BaseTextEditor +{ + Q_OBJECT +public: + UnifiedDiffEditor(BaseTextEditorWidget *editorWidget) + : BaseTextEditor(editorWidget) { + document()->setId("DiffEditor.UnifiedDiffEditor"); + } +}; + +UnifiedDiffEditorWidget::UnifiedDiffEditorWidget(QWidget *parent) + : SelectableTextEditorWidget(parent) + , m_guiController(0) + , m_controller(0) + , m_ignoreCurrentIndexChange(false) + , m_contextMenuFileIndex(-1) + , m_contextMenuChunkIndex(-1) + , m_leftLineNumberDigits(1) + , m_rightLineNumberDigits(1) +{ + DisplaySettings settings = displaySettings(); + settings.m_textWrapping = false; + settings.m_displayLineNumbers = true; + settings.m_highlightCurrentLine = false; + settings.m_displayFoldingMarkers = true; + settings.m_markTextChanges = false; + settings.m_highlightBlocks = false; + SelectableTextEditorWidget::setDisplaySettings(settings); + + setReadOnly(true); + connect(TextEditorSettings::instance(), + SIGNAL(displaySettingsChanged(TextEditor::DisplaySettings)), + this, SLOT(setDisplaySettings(TextEditor::DisplaySettings))); + setDisplaySettings(TextEditorSettings::displaySettings()); + setCodeStyle(TextEditorSettings::codeStyle()); + + connect(TextEditorSettings::instance(), + SIGNAL(fontSettingsChanged(TextEditor::FontSettings)), + this, SLOT(setFontSettings(TextEditor::FontSettings))); + setFontSettings(TextEditorSettings::fontSettings()); + + clear(tr("No controller")); + + connect(this, SIGNAL(cursorPositionChanged()), + this, SLOT(slotCursorPositionChangedInEditor())); +} + +UnifiedDiffEditorWidget::~UnifiedDiffEditorWidget() +{ + +} + +void UnifiedDiffEditorWidget::setDiffEditorGuiController( + DiffEditorGuiController *controller) +{ + if (m_guiController == controller) + return; + + if (m_guiController) { + disconnect(m_controller, SIGNAL(cleared(QString)), + this, SLOT(clearAll(QString))); + disconnect(m_controller, SIGNAL(diffFilesChanged(QList,QString)), + this, SLOT(setDiff(QList,QString))); + + disconnect(m_guiController, SIGNAL(currentDiffFileIndexChanged(int)), + this, SLOT(setCurrentDiffFileIndex(int))); + + clear(tr("No controller")); + } + m_guiController = controller; + m_controller = 0; + if (m_guiController) { + m_controller = m_guiController->controller(); + + connect(m_controller, SIGNAL(cleared(QString)), + this, SLOT(clearAll(QString))); + connect(m_controller, SIGNAL(diffFilesChanged(QList,QString)), + this, SLOT(setDiff(QList,QString))); + + connect(m_guiController, SIGNAL(currentDiffFileIndexChanged(int)), + this, SLOT(setCurrentDiffFileIndex(int))); + + setDiff(m_controller->diffFiles(), m_controller->workingDirectory()); + setCurrentDiffFileIndex(m_guiController->currentDiffFileIndex()); + } +} + +DiffEditorGuiController *UnifiedDiffEditorWidget::diffEditorGuiController() const +{ + return m_guiController; +} + +void UnifiedDiffEditorWidget::setDisplaySettings(const DisplaySettings &ds) +{ + DisplaySettings settings = displaySettings(); + settings.m_visualizeWhitespace = ds.m_visualizeWhitespace; + SelectableTextEditorWidget::setDisplaySettings(settings); +} + +void UnifiedDiffEditorWidget::setFontSettings(const FontSettings &fontSettings) +{ + baseTextDocument()->setFontSettings(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); + + update(); +} + +void UnifiedDiffEditorWidget::slotCursorPositionChangedInEditor() +{ + if (!m_guiController) + return; + + if (m_ignoreCurrentIndexChange) + return; + + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + m_guiController->setCurrentDiffFileIndex(fileIndexForBlockNumber( + textCursor().blockNumber())); + m_ignoreCurrentIndexChange = oldIgnore; +} + +void UnifiedDiffEditorWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() == Qt::LeftButton && !(e->modifiers() & Qt::ShiftModifier)) { + QTextCursor cursor = cursorForPosition(e->pos()); + jumpToOriginalFile(cursor); + e->accept(); + return; + } + SelectableTextEditorWidget::mouseDoubleClickEvent(e); +} + +void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e) +{ + QPointer menu = createStandardContextMenu(); + + QTextCursor cursor = cursorForPosition(e->pos()); + const int blockNumber = cursor.blockNumber(); + + addContextMenuActions(menu, fileIndexForBlockNumber(blockNumber), + chunkIndexForBlockNumber(blockNumber)); + + connect(this, SIGNAL(destroyed()), menu, SLOT(deleteLater())); + menu->exec(e->globalPos()); + delete menu; +} + +void UnifiedDiffEditorWidget::addContextMenuActions(QMenu *menu, + int diffFileIndex, + int chunkIndex) +{ + if (!m_controller) + return; + + menu->addSeparator(); + menu->addSeparator(); + QAction *sendChunkToCodePasterAction = + menu->addAction(tr("Send Chunk to CodePaster...")); + connect(sendChunkToCodePasterAction, SIGNAL(triggered()), + this, SLOT(slotSendChunkToCodePaster())); + QAction *applyAction = menu->addAction(tr("Apply Chunk...")); + connect(applyAction, SIGNAL(triggered()), this, SLOT(slotApplyChunk())); + QAction *revertAction = menu->addAction(tr("Revert Chunk...")); + connect(revertAction, SIGNAL(triggered()), this, SLOT(slotRevertChunk())); + + m_contextMenuFileIndex = diffFileIndex; + m_contextMenuChunkIndex = chunkIndex; + + applyAction->setEnabled(false); + revertAction->setEnabled(false); + + if (m_contextMenuFileIndex < 0 || m_contextMenuChunkIndex < 0) + return; + + if (m_contextMenuFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(m_contextMenuFileIndex); + if (m_contextMenuChunkIndex >= fileData.chunks.count()) + return; + + emit m_controller->chunkActionsRequested(menu, diffFileIndex, chunkIndex); + + revertAction->setEnabled(true); + + if (fileData.leftFileInfo.fileName == fileData.rightFileInfo.fileName) + return; + + applyAction->setEnabled(true); +} + +void UnifiedDiffEditorWidget::slotSendChunkToCodePaster() +{ + if (!m_controller) + return; + + if (m_contextMenuFileIndex < 0 || m_contextMenuChunkIndex < 0) + return; + + if (m_contextMenuFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(m_contextMenuFileIndex); + if (m_contextMenuChunkIndex >= fileData.chunks.count()) + return; + + const QString patch = m_controller->makePatch(m_contextMenuFileIndex, + m_contextMenuChunkIndex, + false); + + if (patch.isEmpty()) + return; + + // Retrieve service by soft dependency. + QObject *pasteService = + ExtensionSystem::PluginManager::getObjectByClassName( + QLatin1String("CodePaster::CodePasterService")); + if (pasteService) { + QMetaObject::invokeMethod(pasteService, "postText", + Q_ARG(QString, patch), + Q_ARG(QString, QLatin1String(DiffEditor::Constants::DIFF_EDITOR_MIMETYPE))); + } else { + QMessageBox::information(this, tr("Unable to Paste"), + tr("Code pasting services are not available.")); + } +} + +void UnifiedDiffEditorWidget::slotApplyChunk() +{ + patch(m_contextMenuFileIndex, m_contextMenuChunkIndex, false); +} + +void UnifiedDiffEditorWidget::slotRevertChunk() +{ + patch(m_contextMenuFileIndex, m_contextMenuChunkIndex, true); +} + +void UnifiedDiffEditorWidget::patch(int diffFileIndex, int chunkIndex, bool revert) +{ + if (!m_controller) + return; + + if (diffFileIndex < 0 || chunkIndex < 0) + return; + + if (diffFileIndex >= m_contextFileData.count()) + return; + + const FileData fileData = m_contextFileData.at(diffFileIndex); + if (chunkIndex >= fileData.chunks.count()) + 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(this, title, question, + QMessageBox::Yes + | QMessageBox::No)) { + return; + } + + const int strip = m_controller->workingDirectory().isEmpty() ? -1 : 0; + + const QString fileName = revert + ? fileData.rightFileInfo.fileName + : fileData.leftFileInfo.fileName; + + const QString workingDirectory = m_controller->workingDirectory().isEmpty() + ? QFileInfo(fileName).absolutePath() + : m_controller->workingDirectory(); + + const QString patch = m_controller->makePatch(diffFileIndex, + chunkIndex, + revert); + if (patch.isEmpty()) + return; + + if (PatchTool::runPatch( + Core::EditorManager::defaultTextCodec()->fromUnicode(patch), + workingDirectory, strip, revert)) + m_controller->requestReload(); +} + +TextEditor::BaseTextEditor *UnifiedDiffEditorWidget::createEditor() +{ + return new UnifiedDiffEditor(this); +} + +void UnifiedDiffEditorWidget::clear(const QString &message) +{ + m_leftLineNumberDigits = 1; + m_rightLineNumberDigits = 1; + m_leftLineNumbers.clear(); + m_rightLineNumbers.clear(); + m_fileInfo.clear(); + m_chunkInfo.clear(); + setSelections(QMap >()); + + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + SelectableTextEditorWidget::clear(); + setPlainText(message); + m_ignoreCurrentIndexChange = oldIgnore; +} + +void UnifiedDiffEditorWidget::clearAll(const QString &message) +{ + setDiff(QList(), QString()); + clear(message); +} + +QString UnifiedDiffEditorWidget::lineNumber(int blockNumber) const +{ + QString lineNumberString; + + const bool leftLineExists = m_leftLineNumbers.contains(blockNumber); + const bool rightLineExists = m_rightLineNumbers.contains(blockNumber); + + if (leftLineExists || rightLineExists) { + const QString leftLine = leftLineExists + ? QString::number(m_leftLineNumbers.value(blockNumber)) + : QString(); + lineNumberString += QString(m_leftLineNumberDigits - leftLine.count(), + QLatin1Char(' ')) + leftLine; + + lineNumberString += QLatin1Char('|'); + + const QString rightLine = rightLineExists + ? QString::number(m_rightLineNumbers.value(blockNumber)) + : QString(); + lineNumberString += QString(m_rightLineNumberDigits - rightLine.count(), + QLatin1Char(' ')) + rightLine; + } + return lineNumberString; +} + +int UnifiedDiffEditorWidget::lineNumberDigits() const +{ + return m_leftLineNumberDigits + m_rightLineNumberDigits + 1; +} + +void UnifiedDiffEditorWidget::setLeftLineNumber(int blockNumber, int lineNumber) +{ + const QString lineNumberString = QString::number(lineNumber); + m_leftLineNumbers.insert(blockNumber, lineNumber); + m_leftLineNumberDigits = qMax(m_leftLineNumberDigits, + lineNumberString.count()); +} + +void UnifiedDiffEditorWidget::setRightLineNumber(int blockNumber, int lineNumber) +{ + const QString lineNumberString = QString::number(lineNumber); + m_rightLineNumbers.insert(blockNumber, lineNumber); + m_rightLineNumberDigits = qMax(m_rightLineNumberDigits, + lineNumberString.count()); +} + +void UnifiedDiffEditorWidget::setFileInfo(int blockNumber, + const DiffFileInfo &leftFileInfo, + const DiffFileInfo &rightFileInfo) +{ + m_fileInfo[blockNumber] = qMakePair(leftFileInfo, rightFileInfo); +} + +void UnifiedDiffEditorWidget::setChunkIndex(int startBlockNumber, + int blockCount, + int chunkIndex) +{ + m_chunkInfo.insert(startBlockNumber, qMakePair(blockCount, chunkIndex)); +} + +void UnifiedDiffEditorWidget::setDiff(const QList &diffFileList, + const QString &workingDirectory) +{ + Q_UNUSED(workingDirectory) + + m_contextFileData = diffFileList; + + showDiff(); +} + +QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, + bool lastChunk, + int *blockNumber, + int *charNumber, + QMap > *selections) +{ + if (chunkData.contextChunk) + return QString(); + + QString diffText; + int leftLineCount = 0; + int rightLineCount = 0; + int blockCount = 0; + int charCount = 0; + QList leftBuffer, rightBuffer; + + (*selections)[*blockNumber].append(DiffSelection(&m_chunkLineFormat)); + + for (int i = 0; i <= chunkData.rows.count(); i++) { + const RowData &rowData = i < chunkData.rows.count() + ? chunkData.rows.at(i) + : RowData(TextLineData(TextLineData::Separator)); // dummy, + // ensure we process buffers to the end. + // rowData will be equal + if (rowData.equal) { + if (leftBuffer.count()) { + for (int j = 0; j < leftBuffer.count(); j++) { + const TextLineData &lineData = leftBuffer.at(j); + const QString line = DiffUtils::makePatchLine( + QLatin1Char('-'), + lineData.text, + lastChunk, + i == chunkData.rows.count() + && j == leftBuffer.count() - 1); + + const int blockDelta = line.count(QLatin1Char('\n')); // no new line + // could have been added + for (int k = 0; k < blockDelta; k++) + (*selections)[*blockNumber + blockCount + 1 + k].append(&m_leftLineFormat); + QMapIterator itPos(lineData.changedPositions); + while (itPos.hasNext()) { + itPos.next(); + const int startPos = itPos.key() < 0 + ? 1 : itPos.key() + 1; + const int endPos = itPos.value() < 0 + ? itPos.value() : itPos.value() + 1; + (*selections)[*blockNumber + blockCount + 1].append( + DiffSelection(startPos, endPos, &m_leftCharFormat)); + } + + if (!line.isEmpty()) { + setLeftLineNumber(*blockNumber + blockCount + 1, + chunkData.leftStartingLineNumber + + leftLineCount + 1); + blockCount += blockDelta; + ++leftLineCount; + } + + diffText += line; + + charCount += line.count(); + } + leftBuffer.clear(); + } + if (rightBuffer.count()) { + for (int j = 0; j < rightBuffer.count(); j++) { + const TextLineData &lineData = rightBuffer.at(j); + const QString line = DiffUtils::makePatchLine( + QLatin1Char('+'), + lineData.text, + lastChunk, + i == chunkData.rows.count() + && j == rightBuffer.count() - 1); + + const int blockDelta = line.count(QLatin1Char('\n')); // no new line + // could have been added + + for (int k = 0; k < blockDelta; k++) + (*selections)[*blockNumber + blockCount + 1 + k].append(&m_rightLineFormat); + QMapIterator itPos(lineData.changedPositions); + while (itPos.hasNext()) { + itPos.next(); + const int startPos = itPos.key() < 0 + ? 1 : itPos.key() + 1; + const int endPos = itPos.value() < 0 + ? itPos.value() : itPos.value() + 1; + (*selections)[*blockNumber + blockCount + 1].append + (DiffSelection(startPos, endPos, &m_rightCharFormat)); + } + + if (!line.isEmpty()) { + setRightLineNumber(*blockNumber + blockCount + 1, + chunkData.rightStartingLineNumber + + rightLineCount + 1); + blockCount += blockDelta; + ++rightLineCount; + } + + diffText += line; + + charCount += line.count(); + } + rightBuffer.clear(); + } + if (i < chunkData.rows.count()) { + const QString line = DiffUtils::makePatchLine(QLatin1Char(' '), + rowData.rightLine.text, + lastChunk, + i == chunkData.rows.count() - 1); + + if (!line.isEmpty()) { + setLeftLineNumber(*blockNumber + blockCount + 1, + chunkData.leftStartingLineNumber + + leftLineCount + 1); + setRightLineNumber(*blockNumber + blockCount + 1, + chunkData.rightStartingLineNumber + + rightLineCount + 1); + blockCount += line.count(QLatin1Char('\n')); + ++leftLineCount; + ++rightLineCount; + } + + diffText += line; + + charCount += line.count(); + } + } else { + if (rowData.leftLine.textLineType == TextLineData::TextLine) + leftBuffer.append(rowData.leftLine); + if (rowData.rightLine.textLineType == TextLineData::TextLine) + rightBuffer.append(rowData.rightLine); + } + } + + const QString chunkLine = QLatin1String("@@ -") + + QString::number(chunkData.leftStartingLineNumber + 1) + + QLatin1Char(',') + + QString::number(leftLineCount) + + QLatin1String(" +") + + QString::number(chunkData.rightStartingLineNumber+ 1) + + QLatin1Char(',') + + QString::number(rightLineCount) + + QLatin1String(" @@\n"); + + diffText.prepend(chunkLine); + + *blockNumber += blockCount + 1; // +1 for chunk line + *charNumber += charCount + chunkLine.count(); + return diffText; +} + +void UnifiedDiffEditorWidget::showDiff() +{ + clear(tr("No difference")); + + QString diffText; + + int blockNumber = 0; + int charNumber = 0; + + QMap > selections; + + for (int i = 0; i < m_contextFileData.count(); i++) { + const FileData &fileData = m_contextFileData.at(i); + const QString leftFileInfo = QLatin1String("--- ") + + fileData.leftFileInfo.fileName + QLatin1Char('\n'); + const QString rightFileInfo = QLatin1String("+++ ") + + fileData.rightFileInfo.fileName + QLatin1Char('\n'); + setFileInfo(blockNumber, fileData.leftFileInfo, fileData.rightFileInfo); + selections[blockNumber].append(DiffSelection(&m_fileLineFormat)); + blockNumber++; + selections[blockNumber].append(DiffSelection(&m_fileLineFormat)); + blockNumber++; + + diffText += leftFileInfo; + diffText += rightFileInfo; + charNumber += leftFileInfo.count() + rightFileInfo.count(); + + if (fileData.binaryFiles) { + selections[blockNumber].append(DiffSelection(&m_chunkLineFormat)); + blockNumber++; + const QString binaryLine = QLatin1String("Binary files ") + + fileData.leftFileInfo.fileName + + QLatin1String(" and ") + + fileData.rightFileInfo.fileName + + QLatin1String(" differ\n"); + diffText += binaryLine; + charNumber += binaryLine.count(); + } else { + for (int j = 0; j < fileData.chunks.count(); j++) { + const int oldBlockNumber = blockNumber; + diffText += showChunk(fileData.chunks.at(j), + (j == fileData.chunks.count() - 1) + && fileData.lastChunkAtTheEndOfFile, + &blockNumber, + &charNumber, + &selections); + if (!fileData.chunks.at(j).contextChunk) + setChunkIndex(oldBlockNumber, blockNumber - oldBlockNumber, j); + } + } + + } + + if (diffText.isEmpty()) + return; + + diffText.replace(QLatin1Char('\r'), QLatin1Char(' ')); + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + setPlainText(diffText); + m_ignoreCurrentIndexChange = oldIgnore; + + setSelections(selections); +} + +int UnifiedDiffEditorWidget::blockNumberForFileIndex(int fileIndex) const +{ + if (fileIndex < 0 || fileIndex >= m_fileInfo.count()) + return -1; + + QMap >::const_iterator it + = m_fileInfo.constBegin(); + for (int i = 0; i < fileIndex; i++) + ++it; + + return it.key(); +} + +int UnifiedDiffEditorWidget::fileIndexForBlockNumber(int blockNumber) const +{ + QMap >::const_iterator it + = m_fileInfo.constBegin(); + QMap >::const_iterator itEnd + = m_fileInfo.constEnd(); + + int i = -1; + while (it != itEnd) { + if (it.key() > blockNumber) + break; + ++it; + ++i; + } + return i; +} + +int UnifiedDiffEditorWidget::chunkIndexForBlockNumber(int blockNumber) const +{ + if (m_chunkInfo.isEmpty()) + return -1; + + QMap >::const_iterator it + = m_chunkInfo.upperBound(blockNumber); + if (it == m_chunkInfo.constBegin()) + return -1; + + --it; + + if (blockNumber < it.key() + it.value().first) + return it.value().second; + + return -1; +} + +void UnifiedDiffEditorWidget::jumpToOriginalFile(const QTextCursor &cursor) +{ + if (m_fileInfo.isEmpty()) + return; + + const int blockNumber = cursor.blockNumber(); + const int columnNumber = cursor.positionInBlock() - 1; // -1 for the first character in line + + const int rightLineNumber = m_rightLineNumbers.value(blockNumber, -1); + if (rightLineNumber >= 0) { + jumpToOriginalFile(m_contextFileData.at( + fileIndexForBlockNumber(blockNumber)).rightFileInfo.fileName, + rightLineNumber, columnNumber); + return; + } + + const int leftLineNumber = m_leftLineNumbers.value(blockNumber, -1); + if (leftLineNumber >= 0) { + jumpToOriginalFile(m_contextFileData.at( + fileIndexForBlockNumber(blockNumber)).leftFileInfo.fileName, + leftLineNumber, columnNumber); + return; + } +} + +void UnifiedDiffEditorWidget::jumpToOriginalFile(const QString &fileName, + int lineNumber, + int columnNumber) +{ + if (!m_controller) + return; + + const QDir dir(m_controller->workingDirectory()); + const QString absoluteFileName = dir.absoluteFilePath(fileName); + Core::EditorManager::openEditorAt(absoluteFileName, lineNumber, columnNumber); +} + +void UnifiedDiffEditorWidget::setCurrentDiffFileIndex(int diffFileIndex) +{ + if (m_ignoreCurrentIndexChange) + return; + + const bool oldIgnore = m_ignoreCurrentIndexChange; + m_ignoreCurrentIndexChange = true; + const int blockNumber = blockNumberForFileIndex(diffFileIndex); + + QTextBlock block = document()->findBlockByNumber(blockNumber); + QTextCursor cursor = textCursor(); + cursor.setPosition(block.position()); + setTextCursor(cursor); + centerCursor(); + m_ignoreCurrentIndexChange = oldIgnore; +} + +} // namespace DiffEditor + +#include "unifieddiffeditorwidget.moc" diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.h b/src/plugins/diffeditor/unifieddiffeditorwidget.h new file mode 100644 index 0000000000..dc94d22660 --- /dev/null +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.h @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef UNIFIEDDIFFEDITORWIDGET_H +#define UNIFIEDDIFFEDITORWIDGET_H + +#include "diffeditor_global.h" +#include "differ.h" +#include "diffeditorcontroller.h" +#include "selectabletexteditorwidget.h" + +namespace TextEditor { +class DisplaySettings; +class FontSettings; +} + +QT_BEGIN_NAMESPACE +class QSplitter; +class QTextCharFormat; +QT_END_NAMESPACE + +namespace DiffEditor { + +class DiffEditorGuiController; +class ChunkData; +class FileData; + +class DIFFEDITOR_EXPORT UnifiedDiffEditorWidget + : public SelectableTextEditorWidget +{ + Q_OBJECT +public: + UnifiedDiffEditorWidget(QWidget *parent = 0); + ~UnifiedDiffEditorWidget(); + + void setDiffEditorGuiController(DiffEditorGuiController *controller); + DiffEditorGuiController *diffEditorGuiController() const; + +public slots: + void setDisplaySettings(const TextEditor::DisplaySettings &ds); + +protected: + void mouseDoubleClickEvent(QMouseEvent *e); + void contextMenuEvent(QContextMenuEvent *e); + TextEditor::BaseTextEditor *createEditor(); + QString lineNumber(int blockNumber) const; + int lineNumberDigits() const; + +private slots: + void clear(const QString &message = QString()); + void clearAll(const QString &message = QString()); + void setDiff(const QList &diffFileList, + const QString &workingDirectory); + + void setCurrentDiffFileIndex(int diffFileIndex); + + void setFontSettings(const TextEditor::FontSettings &fontSettings); + + void slotCursorPositionChangedInEditor(); + + void slotSendChunkToCodePaster(); + void slotApplyChunk(); + void slotRevertChunk(); + +private: + void setLeftLineNumber(int blockNumber, int lineNumber); + void setRightLineNumber(int blockNumber, int lineNumber); + void setFileInfo(int blockNumber, + const DiffFileInfo &leftFileInfo, + const DiffFileInfo &rightFileInfo); + void setChunkIndex(int startBlockNumber, int blockCount, int chunkIndex); + void showDiff(); + QString showChunk(const ChunkData &chunkData, + bool lastChunk, + int *blockNumber, + int *charNumber, + QMap > *selections); + int blockNumberForFileIndex(int fileIndex) const; + int fileIndexForBlockNumber(int blockNumber) const; + int chunkIndexForBlockNumber(int blockNumber) const; + void jumpToOriginalFile(const QTextCursor &cursor); + void jumpToOriginalFile(const QString &fileName, + int lineNumber, + int columnNumber); + void addContextMenuActions(QMenu *menu, + int diffFileIndex, + int chunkIndex); + void patch(int diffFileIndex, int chunkIndex, bool revert); + + DiffEditorGuiController *m_guiController; + DiffEditorController *m_controller; + + // block number, visual line number. + QMap m_leftLineNumbers; + QMap m_rightLineNumbers; + bool m_ignoreCurrentIndexChange; + int m_contextMenuFileIndex; + int m_contextMenuChunkIndex; + + int m_leftLineNumberDigits; + int m_rightLineNumberDigits; + // block number, visual line number. + QMap > m_fileInfo; + // start block number, block count of a chunk, chunk index inside a file. + QMap > m_chunkInfo; + + QList m_contextFileData; // ultimate data to be shown + // contextLinesNumber taken into account + + QTextCharFormat m_fileLineFormat; + QTextCharFormat m_chunkLineFormat; + QTextCharFormat m_leftLineFormat; + QTextCharFormat m_rightLineFormat; + QTextCharFormat m_leftCharFormat; + QTextCharFormat m_rightCharFormat; +}; + +} // namespace DiffEditor + +#endif // UNIFIEDDIFFEDITORWIDGET_H diff --git a/src/plugins/git/branchdialog.cpp b/src/plugins/git/branchdialog.cpp index 6badb5e29f..2531867dcb 100644 --- a/src/plugins/git/branchdialog.cpp +++ b/src/plugins/git/branchdialog.cpp @@ -310,7 +310,7 @@ void BranchDialog::diff() if (fullName.isEmpty()) return; // Do not pass working dir by reference since it might change - GitPlugin::instance()->gitClient()->diffBranch(QString(m_repository), QStringList(), fullName); + GitPlugin::instance()->gitClient()->diffBranch(QString(m_repository), fullName); } void BranchDialog::log() diff --git a/src/plugins/git/gerrit/gerritplugin.cpp b/src/plugins/git/gerrit/gerritplugin.cpp index 9c0af1c0db..11fccfa3d9 100644 --- a/src/plugins/git/gerrit/gerritplugin.cpp +++ b/src/plugins/git/gerrit/gerritplugin.cpp @@ -246,7 +246,7 @@ void FetchContext::show() { const QString title = QString::number(m_change->number) + QLatin1Char('/') + QString::number(m_change->currentPatchSet.patchSetNumber); - gitClient()->show(m_repository, QLatin1String("FETCH_HEAD"), QStringList(), title); + gitClient()->show(m_repository, QLatin1String("FETCH_HEAD"), title); } void FetchContext::cherryPick() diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 57bad5f1ea..9f1edfb229 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -63,6 +63,8 @@ #include #include #include +#include +#include #include #include @@ -70,7 +72,7 @@ #include #include #include -#include +#include #include #include @@ -94,195 +96,20 @@ static inline unsigned diffExecutionFlags() using VcsBase::VcsBasePlugin; -class GitDiffSwitcher : public QObject -{ - Q_OBJECT - -public: - enum DiffType { - DiffRepository, - DiffFile, - DiffFileList, - DiffProjectList, - DiffBranch, - DiffShow - }; - - GitDiffSwitcher(Core::IDocument *parentDocument, GitClient *gitClient); - - void setWorkingDirectory(const QString &workingDir) { m_workingDirectory = workingDir; } - void setDiffType(DiffType type) { m_diffType = type; } - void setFileName(const QString &fileName) { m_fileName = fileName; } - void setFileList(const QStringList &stagedFiles, const QStringList &unstagedFiles) - { - m_stagedFiles = stagedFiles; - m_unstagedFiles = unstagedFiles; - } - void setProjectList(const QStringList &projectFiles) { m_projectFiles = projectFiles; } - void setBranchName(const QString &branchName) { m_branchName = branchName; } - void setId(const QString &id) { m_id = id; } - void setDisplayName(const QString &displayName) { m_displayName = displayName; } - void setBaseArguments(const QStringList &args) { m_baseArguments = args; } - -private slots: - void slotEditorOpened(Core::IEditor *editor); - void slotEditorClosed(Core::IEditor *editor); - void execute(QObject *editor); - -private: - void attachAction(Core::IEditor *editor); - QString actionText(); - - Core::IDocument *m_document; - GitClient *m_gitClient; - QString m_workingDirectory; - DiffType m_diffType; - bool m_usingDiffEditor; - QString m_fileName; - QStringList m_stagedFiles; - QStringList m_unstagedFiles; - QStringList m_projectFiles; - QString m_branchName; - QString m_id; - QString m_displayName; - QStringList m_baseArguments; - - QSignalMapper *m_signalMapper; - QMap m_editorToUsingSideBySideDiffEditor; -}; - -GitDiffSwitcher::GitDiffSwitcher(Core::IDocument *parentDocument, GitClient *gitClient) - : QObject(parentDocument), - m_document(parentDocument), - m_gitClient(gitClient), - m_signalMapper(new QSignalMapper(this)) -{ - QList editors = Core::DocumentModel::editorsForDocument(m_document); - for (int i = 0; i < editors.count(); i++) - attachAction(editors.at(i)); - - // must be queued connection because execute() removes the editor & tool bar that the action was added to - connect(m_signalMapper, SIGNAL(mapped(QObject*)), this, SLOT(execute(QObject*)), Qt::QueuedConnection); - connect(Core::EditorManager::instance(), SIGNAL(editorOpened(Core::IEditor*)), - this, SLOT(slotEditorOpened(Core::IEditor*))); - connect(Core::EditorManager::instance(), SIGNAL(editorAboutToClose(Core::IEditor*)), - this, SLOT(slotEditorClosed(Core::IEditor*))); -} - -void GitDiffSwitcher::attachAction(Core::IEditor *editor) -{ - if (m_editorToUsingSideBySideDiffEditor.contains(editor)) - return; - - const bool usingSideBySideDiffEditor = m_gitClient->settings()->boolValue(GitSettings::useDiffEditorKey); - QIcon actionIcon = usingSideBySideDiffEditor - ? QIcon(QLatin1String(Core::Constants::ICON_TEXT_DIFF)) - : QIcon(QLatin1String(Core::Constants::ICON_SIDE_BY_SIDE_DIFF)); - - const QString actionToolTip = usingSideBySideDiffEditor - ? tr("Switch to Text Diff Editor") - : tr("Switch to Side By Side Diff Editor"); - - QAction *switchAction = new QAction(actionIcon, actionToolTip, editor); - editor->toolBar()->addAction(switchAction); - connect(switchAction, SIGNAL(triggered()), - m_signalMapper, SLOT(map())); - m_signalMapper->setMapping(switchAction, editor); - - m_editorToUsingSideBySideDiffEditor.insert(editor, usingSideBySideDiffEditor); -} - -void GitDiffSwitcher::slotEditorOpened(Core::IEditor *editor) -{ - Core::IDocument *document = editor->document(); - if (document != m_document) - return; - - attachAction(editor); -} - -void GitDiffSwitcher::slotEditorClosed(Core::IEditor *editor) -{ - Core::IDocument *document = editor->document(); - if (document != m_document) - return; - - m_editorToUsingSideBySideDiffEditor.remove(editor); -} - -void GitDiffSwitcher::execute(QObject *editor) -{ - bool usingSideBySideEditor = !m_editorToUsingSideBySideDiffEditor.value(editor); - m_editorToUsingSideBySideDiffEditor[editor] = usingSideBySideEditor; - m_gitClient->settings()->setValue(GitSettings::useDiffEditorKey, usingSideBySideEditor); - - Core::IEditor *ieditor = qobject_cast(editor); - Core::EditorManager::activateEditor(ieditor); - - switch (m_diffType) { - case DiffRepository: - m_gitClient->diff(m_workingDirectory, QStringList(), QStringList()); - break; - case DiffFile: - m_gitClient->diff(m_workingDirectory, m_fileName); - break; - case DiffFileList: - m_gitClient->diff(m_workingDirectory, m_unstagedFiles, m_stagedFiles); - break; - case DiffProjectList: - m_gitClient->diff(m_workingDirectory, m_projectFiles, QStringList()); - break; - case DiffBranch: - m_gitClient->diffBranch(m_workingDirectory, m_baseArguments, m_branchName); - break; - case DiffShow: - m_gitClient->show(m_fileName, m_id, m_baseArguments, m_displayName); - break; - default: - break; - } - - Core::EditorManager::closeEditor(ieditor, false); -} - class GitDiffHandler : public QObject { Q_OBJECT public: - enum RevisionType { - WorkingTree, - Index, - Other - }; - - struct Revision { - Revision() : type(WorkingTree) { } - Revision(RevisionType t) : type(t) { } - Revision(RevisionType t, const QString &i) : type(t), id(i) { } - RevisionType type; - QString id; // can be sha or HEAD - QString infoText() const - { - switch (type) { - case WorkingTree: return tr("Working tree"); - case Index: return tr("Index"); - default: return id; - } - } - }; - GitDiffHandler(DiffEditor::DiffEditorController *editorController, - const QString &gitPath, - const QString &workingDirectory, - const QProcessEnvironment &environment, - int timeout); + const QString &workingDirectory); // index -> working tree void diffFile(const QString &fileName); // stagedFileNames: HEAD -> index // unstagedFileNames: index -> working tree - void diffFiles(const QStringList &stagedFileNames, const QStringList &unstagedFileNames); + void diffFiles(const QStringList &stagedFileNames, + const QStringList &unstagedFileNames); // index -> working tree void diffProjects(const QStringList &projectPaths); // index -> working tree @@ -294,127 +121,82 @@ public: private slots: void slotShowDescriptionReceived(const QString &data); - void slotFileListReceived(const QString &fileList); - void slotFileContentsReceived(const QString &contents); + void slotDiffOutputReceived(const QString &contents); private: - void collectShowDescription(const QString &id); - void collectFilesList(const QStringList &additionalArguments); - void prepareForCollection(); - void collectFilesContents(); - void feedEditor(); - QString workingTreeContents(const QString &fileName) const; + void postCollectShowDescription(const QString &id); + void postCollectDiffOutput(const QStringList &arguments); + void postCollectDiffOutput(const QList &argumentsList); + void addJob(VcsBase::Command *command, const QStringList &arguments); + int timeout() const; + QProcessEnvironment processEnvironment() const; + QString gitPath() const; QPointer m_editorController; - const QString m_gitPath; const QString m_workingDirectory; - const QProcessEnvironment m_processEnvironment; - const int m_timeout; + GitClient *m_gitClient; const QString m_waitMessage; - struct RevisionRange { - RevisionRange() { } - RevisionRange(const Revision &b, const Revision &e) : begin(b), end(e) { } - Revision begin; - Revision end; - }; - - // filename, revision range - QMap > m_requestedRevisionRanges; - // filename, revision, dummy - QMap > m_pendingRevisions; - // filename, revision, contents - QMap > m_collectedRevisions; - - RevisionRange m_requestedRevisionRange; + QString m_id; }; -inline bool operator<(const GitDiffHandler::Revision &rev1, const GitDiffHandler::Revision &rev2) -{ - if (rev1.type != rev2.type) - return rev1.type < rev2.type; - return rev1.id < rev2.id; -} - GitDiffHandler::GitDiffHandler(DiffEditor::DiffEditorController *editorController, - const QString &gitPath, - const QString &workingDirectory, - const QProcessEnvironment &environment, - int timeout) + const QString &workingDirectory) : m_editorController(editorController), - m_gitPath(gitPath), m_workingDirectory(workingDirectory), - m_processEnvironment(environment), - m_timeout(timeout), + m_gitClient(GitPlugin::instance()->gitClient()), m_waitMessage(tr("Waiting for data...")) { } void GitDiffHandler::diffFile(const QString &fileName) { - m_requestedRevisionRange = RevisionRange( - Revision(Index), - Revision(WorkingTree)); - - collectFilesList(QStringList() << QLatin1String("--") << fileName); + postCollectDiffOutput(QStringList() << QLatin1String("--") << fileName); } -void GitDiffHandler::diffFiles(const QStringList &stagedFileNames, const QStringList &unstagedFileNames) +void GitDiffHandler::diffFiles(const QStringList &stagedFileNames, + const QStringList &unstagedFileNames) { - RevisionRange stagedRange = RevisionRange( - Revision(Other, QLatin1String(HEAD)), - Revision(Index)); - RevisionRange unstagedRange = RevisionRange( - Revision(Index), - Revision(WorkingTree)); + QList arguments; - for (int i = 0; i < stagedFileNames.count(); i++) - m_requestedRevisionRanges[stagedFileNames.at(i)].append(stagedRange); + QStringList stagedArguments; + stagedArguments << QLatin1String("--cached"); + stagedArguments << QLatin1String("--"); + stagedArguments << stagedFileNames; + arguments << stagedArguments; - for (int i = 0; i < unstagedFileNames.count(); i++) - m_requestedRevisionRanges[unstagedFileNames.at(i)].append(unstagedRange); + if (!unstagedFileNames.isEmpty()) { + QStringList unstagedArguments; + unstagedArguments << QLatin1String("--"); + unstagedArguments << unstagedFileNames; + arguments << unstagedArguments; + } - prepareForCollection(); - collectFilesContents(); + postCollectDiffOutput(arguments); } void GitDiffHandler::diffProjects(const QStringList &projectPaths) { - m_requestedRevisionRange = RevisionRange( - Revision(Index), - Revision(WorkingTree)); - - collectFilesList(QStringList() << QLatin1String("--") << projectPaths); + postCollectDiffOutput(QStringList() << QLatin1String("--") << projectPaths); } void GitDiffHandler::diffRepository() { - m_requestedRevisionRange = RevisionRange( - Revision(Index), - Revision(WorkingTree)); - - collectFilesList(QStringList()); + postCollectDiffOutput(QStringList()); } void GitDiffHandler::diffBranch(const QString &branchName) { - m_requestedRevisionRange = RevisionRange( - Revision(Other, branchName), - Revision(WorkingTree)); - - collectFilesList(QStringList() << branchName); + postCollectDiffOutput(QStringList() << branchName); } void GitDiffHandler::show(const QString &id) { - Revision begin(Other, id + QLatin1Char('^')); - Revision end(Other, id); - m_requestedRevisionRange = RevisionRange(begin, end); - - collectShowDescription(id); + m_id = id; + postCollectShowDescription(id); } -void GitDiffHandler::collectShowDescription(const QString &id) +void GitDiffHandler::postCollectShowDescription(const QString &id) { if (m_editorController.isNull()) { deleteLater(); @@ -422,14 +204,20 @@ void GitDiffHandler::collectShowDescription(const QString &id) } m_editorController->clear(m_waitMessage); - VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment); - command->setCodec(GitPlugin::instance()->gitClient()->encoding(m_workingDirectory, - "i18n.commitEncoding")); - connect(command, SIGNAL(output(QString)), this, SLOT(slotShowDescriptionReceived(QString))); + VcsBase::Command *command = new VcsBase::Command(gitPath(), + m_workingDirectory, + processEnvironment()); + command->setCodec(m_gitClient->encoding(m_workingDirectory, + "i18n.commitEncoding")); + connect(command, SIGNAL(output(QString)), + this, SLOT(slotShowDescriptionReceived(QString))); QStringList arguments; - arguments << QLatin1String("show") << QLatin1String("-s") - << QLatin1String(noColorOption) << QLatin1String(decorateOption) << id; - command->addJob(arguments, m_timeout); + arguments << QLatin1String("show") + << QLatin1String("-s") + << QLatin1String(noColorOption) + << QLatin1String(decorateOption) + << id; + command->addJob(arguments, timeout()); command->execute(); } @@ -440,15 +228,32 @@ void GitDiffHandler::slotShowDescriptionReceived(const QString &description) return; } - m_editorController->setDescription(GitPlugin::instance()->gitClient()-> - extendedShowDescription(m_workingDirectory, description)); + postCollectDiffOutput(QStringList() << m_id + QLatin1Char('^') << m_id); + + // need to be called after postCollectDiffOutput(), since it clears the description + m_editorController->setDescription( + m_gitClient->extendedShowDescription(m_workingDirectory, + description)); +} + +void GitDiffHandler::addJob(VcsBase::Command *command, const QStringList &arguments) +{ + QStringList args; + args << QLatin1String("diff"); + if (m_editorController->isIgnoreWhitespace()) + args << QLatin1String("--ignore-space-change"); + args << QLatin1String("--unified=") + QString::number( + m_editorController->contextLinesNumber()); + args << arguments; + command->addJob(args, timeout()); +} - collectFilesList(QStringList() - << m_requestedRevisionRange.begin.id - << m_requestedRevisionRange.end.id); +void GitDiffHandler::postCollectDiffOutput(const QStringList &arguments) +{ + postCollectDiffOutput(QList() << arguments); } -void GitDiffHandler::collectFilesList(const QStringList &additionalArguments) +void GitDiffHandler::postCollectDiffOutput(const QList &argumentsList) { if (m_editorController.isNull()) { deleteLater(); @@ -456,175 +261,145 @@ void GitDiffHandler::collectFilesList(const QStringList &additionalArguments) } m_editorController->clear(m_waitMessage); - VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment); + VcsBase::Command *command = new VcsBase::Command(gitPath(), + m_workingDirectory, + processEnvironment()); command->setCodec(Core::EditorManager::defaultTextCodec()); - connect(command, SIGNAL(output(QString)), this, SLOT(slotFileListReceived(QString))); - QStringList arguments; - arguments << QLatin1String("diff") << QLatin1String("--name-only") << additionalArguments; - command->addJob(arguments, m_timeout); + connect(command, SIGNAL(output(QString)), + this, SLOT(slotDiffOutputReceived(QString))); command->addFlags(diffExecutionFlags()); + + for (int i = 0; i < argumentsList.count(); i++) + addJob(command, argumentsList.at(i)); + command->execute(); } -void GitDiffHandler::slotFileListReceived(const QString &fileList) +void GitDiffHandler::slotDiffOutputReceived(const QString &contents) { if (m_editorController.isNull()) { deleteLater(); return; } - QStringList fileNames = fileList.split(QLatin1Char('\n'), QString::SkipEmptyParts); - fileNames.removeDuplicates(); - - for (int i = 0; i < fileNames.count(); i++) - m_requestedRevisionRanges[fileNames.at(i)].append(m_requestedRevisionRange); - - prepareForCollection(); - collectFilesContents(); + bool ok; + QList fileDataList + = DiffEditor::DiffUtils::readPatch( + contents, m_editorController->isIgnoreWhitespace(), &ok); + m_editorController->setDiffFiles(fileDataList, m_workingDirectory); + deleteLater(); } -void GitDiffHandler::prepareForCollection() +int GitDiffHandler::timeout() const { - QMap >::const_iterator it - = m_requestedRevisionRanges.constBegin(); - QMap >::const_iterator itEnd - = m_requestedRevisionRanges.constEnd(); - while (it != itEnd) { - const QString fileName = it.key(); - const QList &ranges = it.value(); - for (int i = 0; i < ranges.count(); i++) { - const RevisionRange &range = ranges.at(i); - m_pendingRevisions[fileName][range.begin] = false; - m_pendingRevisions[fileName][range.end] = false; - } - - ++it; - } + return m_gitClient->settings()->intValue(GitSettings::timeoutKey); } -void GitDiffHandler::collectFilesContents() +QProcessEnvironment GitDiffHandler::processEnvironment() const { - QMap >::iterator itFile - = m_pendingRevisions.begin(); - QMap >::iterator itFileEnd - = m_pendingRevisions.end(); - while (itFile != itFileEnd) { - const QString fileName = itFile.key(); - QMap &revisions = itFile.value(); - QMap::iterator itRev - = revisions.begin(); - QMap::iterator itRevEnd - = revisions.end(); - while (itRev != itRevEnd) { - const Revision revision = itRev.key(); - if (revision.type == WorkingTree) { - // collect file here - - m_collectedRevisions[fileName][revision] = workingTreeContents(fileName); - - itRev = revisions.erase(itRev); // iterate to the next revision - } else { - // prepare job here + return m_gitClient->processEnvironment(); +} - VcsBase::Command *command = new VcsBase::Command(m_gitPath, m_workingDirectory, m_processEnvironment); - command->setCodec(Core::EditorManager::defaultTextCodec()); - connect(command, SIGNAL(output(QString)), this, SLOT(slotFileContentsReceived(QString))); +QString GitDiffHandler::gitPath() const +{ + return m_gitClient->gitBinaryPath(); +} - QString revisionArgument = (revision.type == Other) - ? revision.id : QString(); - revisionArgument += QLatin1Char(':'); - QStringList arguments; - arguments << QLatin1String("show") << revisionArgument + fileName; - command->addJob(arguments, m_timeout); - command->execute(); +///////////////////////////////////// - return; - } - } +class GitDiffEditorReloader : public DiffEditor::DiffEditorReloader +{ + Q_OBJECT +public: + enum DiffType { + DiffRepository, + DiffFile, + DiffFileList, + DiffProjectList, + DiffBranch, + DiffShow + }; - itFile = m_pendingRevisions.erase(itFile); // iterate to the next file + GitDiffEditorReloader(QObject *parent); + void setWorkingDirectory(const QString &workingDir) { + m_workingDirectory = workingDir; } - - feedEditor(); -} - -void GitDiffHandler::slotFileContentsReceived(const QString &contents) -{ - if (m_editorController.isNull()) { - deleteLater(); - return; + void setDiffType(DiffType type) { m_diffType = type; } + void setFileName(const QString &fileName) { m_fileName = fileName; } + void setFileList(const QStringList &stagedFiles, + const QStringList &unstagedFiles) { + m_stagedFiles = stagedFiles; + m_unstagedFiles = unstagedFiles; } - - QMap >::iterator itFile - = m_pendingRevisions.begin(); - QMap >::iterator itFileEnd - = m_pendingRevisions.end(); - if (itFile != itFileEnd) { - const QString fileName = itFile.key(); - QMap &revisions = itFile.value(); - QMap::iterator itRev - = revisions.begin(); - QMap::iterator itRevEnd - = revisions.end(); - if (itRev != itRevEnd) { - m_collectedRevisions[fileName][itRev.key()] = contents; - - itRev = revisions.erase(itRev); - if (revisions.isEmpty()) - m_pendingRevisions.erase(itFile); - } + void setProjectList(const QStringList &projectFiles) { + m_projectFiles = projectFiles; + } + void setBranchName(const QString &branchName) { + m_branchName = branchName; + } + void setId(const QString &id) { m_id = id; } + void setDisplayName(const QString &displayName) { + m_displayName = displayName; } - collectFilesContents(); -} -void GitDiffHandler::feedEditor() -{ - if (m_editorController.isNull()) { - deleteLater(); - return; - } +protected: + void reload(); - QList list; - - QMap >::const_iterator itFile - = m_requestedRevisionRanges.constBegin(); - QMap >::const_iterator itFileEnd - = m_requestedRevisionRanges.constEnd(); - while (itFile != itFileEnd) { - const QString fileName = itFile.key(); - const QList &ranges = itFile.value(); - for (int i = 0; i < ranges.count(); i++) { - const Revision leftRevision = ranges.at(i).begin; - const Revision rightRevision = ranges.at(i).end; - - DiffEditor::DiffEditorController::DiffFilesContents dfc; - dfc.leftFileInfo = DiffEditor::DiffEditorController::DiffFileInfo(fileName, leftRevision.infoText()); - dfc.leftText = m_collectedRevisions[fileName][leftRevision]; - dfc.rightFileInfo = DiffEditor::DiffEditorController::DiffFileInfo(fileName, rightRevision.infoText()); - dfc.rightText = m_collectedRevisions[fileName][rightRevision]; - list.append(dfc); - } +private: + GitClient *m_gitClient; - ++itFile; - } + QString m_workingDirectory; + DiffType m_diffType; + QString m_fileName; + QStringList m_stagedFiles; + QStringList m_unstagedFiles; + QStringList m_projectFiles; + QString m_branchName; + QString m_id; + QString m_displayName; +}; - m_editorController->setDiffContents(list, m_workingDirectory); - deleteLater(); +GitDiffEditorReloader::GitDiffEditorReloader(QObject *parent) + : DiffEditorReloader(parent), + m_gitClient(GitPlugin::instance()->gitClient()) +{ } -QString GitDiffHandler::workingTreeContents(const QString &fileName) const +void GitDiffEditorReloader::reload() { - QDir workingDir(m_workingDirectory); - QString absoluteFileName = workingDir.absoluteFilePath(fileName); + const QString workingDirectory = m_diffType == DiffShow + ? m_gitClient->findRepositoryForDirectory(m_workingDirectory) + : m_workingDirectory; + GitDiffHandler *handler = new GitDiffHandler(diffEditorController(), + workingDirectory); + connect(handler, SIGNAL(destroyed()), this, SLOT(reloadFinished())); - QFile file(absoluteFileName); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) - return Core::EditorManager::defaultTextCodec()->toUnicode(file.readAll()); - return QString(); + switch (m_diffType) { + case DiffRepository: + handler->diffRepository(); + break; + case DiffFile: + handler->diffFile(m_fileName); + break; + case DiffFileList: + handler->diffFiles(m_stagedFiles, m_unstagedFiles); + break; + case DiffProjectList: + handler->diffProjects(m_projectFiles); + break; + case DiffBranch: + handler->diffBranch(m_branchName); + break; + case DiffShow: + handler->show(m_id); + break; + default: + break; + } } -/////////////////////////////////////////////////////////// +/////////////////////////////// class BaseGitDiffArgumentsWidget : public VcsBase::VcsBaseEditorParameterWidget { @@ -639,12 +414,17 @@ public: QTC_ASSERT(!directory.isEmpty(), return); QTC_ASSERT(m_client, return); - m_patienceButton = addToggleButton(QLatin1String("--patience"), tr("Patience"), - tr("Use the patience algorithm for calculating the differences.")); - mapSetting(m_patienceButton, client->settings()->boolPointer(GitSettings::diffPatienceKey)); - m_ignoreWSButton = addToggleButton(QLatin1String("--ignore-space-change"), tr("Ignore Whitespace"), - tr("Ignore whitespace only changes.")); - mapSetting(m_ignoreWSButton, m_client->settings()->boolPointer(GitSettings::ignoreSpaceChangesInDiffKey)); + m_patienceButton = addToggleButton( + QLatin1String("--patience"), + tr("Patience"), + tr("Use the patience algorithm for calculating the differences.")); + mapSetting(m_patienceButton, client->settings()->boolPointer( + GitSettings::diffPatienceKey)); + m_ignoreWSButton = addToggleButton( + QLatin1String("--ignore-space-change"), tr("Ignore Whitespace"), + tr("Ignore whitespace only changes.")); + mapSetting(m_ignoreWSButton, + m_client->settings()->boolPointer(GitSettings::ignoreSpaceChangesInDiffKey)); setBaseArguments(args); } @@ -656,109 +436,6 @@ protected: QToolButton *m_ignoreWSButton; }; -class GitCommitDiffArgumentsWidget : public BaseGitDiffArgumentsWidget -{ - Q_OBJECT - -public: - GitCommitDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory, - const QStringList &unstaged, const QStringList &staged) : - BaseGitDiffArgumentsWidget(client, directory, QStringList()) - { - setFileNames(unstaged, staged); - } - - void setFileNames(const QStringList &unstaged, const QStringList &staged) - { - m_unstagedFileNames = unstaged; - m_stagedFileNames = staged; - } - - void executeCommand() - { - m_client->diff(m_workingDirectory, m_unstagedFileNames, m_stagedFileNames); - } - -private: - QStringList m_unstagedFileNames; - QStringList m_stagedFileNames; -}; - -class GitFileDiffArgumentsWidget : public BaseGitDiffArgumentsWidget -{ - Q_OBJECT -public: - GitFileDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory, - const QString &file) : - BaseGitDiffArgumentsWidget(client, directory, QStringList()), - m_fileName(file) - { } - - void executeCommand() - { - m_client->diff(m_workingDirectory, m_fileName); - } - -private: - const QString m_fileName; -}; - -class GitBranchDiffArgumentsWidget : public BaseGitDiffArgumentsWidget -{ - Q_OBJECT -public: - GitBranchDiffArgumentsWidget(Git::Internal::GitClient *client, const QString &directory, - const QStringList &args, const QString &branch) : - BaseGitDiffArgumentsWidget(client, directory, args), - m_branchName(branch) - { } - - void executeCommand() - { - m_client->diffBranch(m_workingDirectory, baseArguments(), m_branchName); - } - -private: - const QString m_branchName; -}; - -class GitShowArgumentsWidget : public BaseGitDiffArgumentsWidget -{ - Q_OBJECT - -public: - GitShowArgumentsWidget(Git::Internal::GitClient *client, - const QString &directory, - const QStringList &args, - const QString &id) : - BaseGitDiffArgumentsWidget(client, directory, args), - m_client(client), - m_workingDirectory(directory), - m_id(id) - { - QList prettyChoices; - prettyChoices << ComboBoxItem(tr("oneline"), QLatin1String("oneline")) - << ComboBoxItem(tr("short"), QLatin1String("short")) - << ComboBoxItem(tr("medium"), QLatin1String("medium")) - << ComboBoxItem(tr("full"), QLatin1String("full")) - << ComboBoxItem(tr("fuller"), QLatin1String("fuller")) - << ComboBoxItem(tr("email"), QLatin1String("email")) - << ComboBoxItem(tr("raw"), QLatin1String("raw")); - mapSetting(addComboBox(QStringList(QLatin1String("--pretty=%1")), prettyChoices), - m_client->settings()->intPointer(GitSettings::showPrettyFormatKey)); - } - - void executeCommand() - { - m_client->show(m_workingDirectory, m_id, baseArguments()); - } - -private: - GitClient *m_client; - QString m_workingDirectory; - QString m_id; -}; - class GitBlameArgumentsWidget : public VcsBase::VcsBaseEditorParameterWidget { Q_OBJECT @@ -1021,7 +698,9 @@ GitClient::GitClient(GitSettings *settings) : m_cachedGitVersion(0), m_msgWait(tr("Waiting for data...")), m_settings(settings), - m_disableEditor(false) + m_disableEditor(false), + m_contextDiffFileIndex(-1), + m_contextChunkIndex(-1) { QTC_CHECK(settings); connect(Core::ICore::instance(), SIGNAL(saveSettingsRequested()), this, SLOT(saveSettings())); @@ -1102,9 +781,122 @@ DiffEditor::DiffEditorDocument *GitClient::createDiffEditor(const QString &docum DiffEditor::DiffEditorDocument *diffEditorDocument = DiffEditor::DiffEditorManager::findOrCreate(documentId, title); QTC_ASSERT(diffEditorDocument, return 0); VcsBasePlugin::setSource(diffEditorDocument, source); + + connect(diffEditorDocument->controller(), SIGNAL(chunkActionsRequested(QMenu*,int,int)), + this, SLOT(slotChunkActionsRequested(QMenu*,int,int))); + return diffEditorDocument; } +void GitClient::slotChunkActionsRequested(QMenu *menu, int diffFileIndex, int chunkIndex) +{ + menu->addSeparator(); + QAction *stageChunkAction = menu->addAction(tr("Stage Chunk")); + connect(stageChunkAction, SIGNAL(triggered()), this, SLOT(slotStageChunk())); + QAction *unstageChunkAction = menu->addAction(tr("Unstage Chunk")); + connect(unstageChunkAction, SIGNAL(triggered()), this, SLOT(slotUnstageChunk())); + + m_contextDiffFileIndex = diffFileIndex; + m_contextChunkIndex = chunkIndex; + m_contextDocument = qobject_cast(sender()); + + if (m_contextDiffFileIndex < 0 || m_contextChunkIndex < 0 || !m_contextDocument) { + stageChunkAction->setEnabled(false); + unstageChunkAction->setEnabled(false); + } +} + +QString GitClient::makePatch(int diffFileIndex, int chunkIndex, bool revert) const +{ + if (m_contextDocument.isNull()) + return QString(); + + if (diffFileIndex < 0 || chunkIndex < 0) + return QString(); + + QList fileDataList = m_contextDocument->diffFiles(); + + if (diffFileIndex >= fileDataList.count()) + return QString(); + + const DiffEditor::FileData fileData = fileDataList.at(diffFileIndex); + if (chunkIndex >= fileData.chunks.count()) + return QString(); + + const DiffEditor::ChunkData chunkData = fileData.chunks.at(chunkIndex); + const bool lastChunk = (chunkIndex == fileData.chunks.count() - 1); + + const QString fileName = revert + ? fileData.rightFileInfo.fileName + : fileData.leftFileInfo.fileName; + + return DiffEditor::DiffUtils::makePatch(chunkData, + QLatin1String("a/") + fileName, + QLatin1String("b/") + fileName, + lastChunk && fileData.lastChunkAtTheEndOfFile); +} + +void GitClient::slotStageChunk() +{ + if (m_contextDocument.isNull()) + return; + + const QString patch = makePatch(m_contextDiffFileIndex, + m_contextChunkIndex, false); + if (patch.isEmpty()) + return; + + stage(patch, false); +} + +void GitClient::slotUnstageChunk() +{ + if (m_contextDocument.isNull()) + return; + + const QString patch = makePatch(m_contextDiffFileIndex, + m_contextChunkIndex, true); + if (patch.isEmpty()) + return; + + stage(patch, true); +} + +void GitClient::stage(const QString &patch, bool revert) +{ + VcsBase::VcsBaseOutputWindow *outwin = + VcsBase::VcsBaseOutputWindow::instance(); + QTemporaryFile patchFile; + if (!patchFile.open()) + return; + + const QString baseDir = m_contextDocument->workingDirectory(); + QTextCodec *codec = Core::EditorManager::defaultTextCodec(); + const QByteArray patchData = codec + ? codec->fromUnicode(patch) : patch.toLocal8Bit(); + patchFile.write(patchData); + patchFile.close(); + + QStringList args = QStringList() << QLatin1String("--cached"); + if (revert) + args << QLatin1String("--reverse"); + QString errorMessage; + if (synchronousApplyPatch(baseDir, patchFile.fileName(), + &errorMessage, args)) { + if (errorMessage.isEmpty()) { + if (revert) + outwin->append(tr("Chunk successfully unstaged")); + else + outwin->append(tr("Chunk successfully staged")); + } else { + outwin->append(errorMessage); + } + m_contextDocument->requestReload(); + } else { + outwin->appendError(errorMessage); + } +} + /* Create an editor associated to VCS output of a source file/directory * (using the file's codec). Makes use of a dynamic property to find an * existing instance and to reuse it (in case, say, 'git diff foo' is @@ -1149,235 +941,98 @@ void GitClient::diff(const QString &workingDirectory, const QStringList &unstagedFileNames, const QStringList &stagedFileNames) { - const QString title = tr("Git Diff"); - const int timeout = settings()->intValue(GitSettings::timeoutKey); - Core::IDocument *newDocument = 0; - if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - const QString documentId = QLatin1String("sideBySideOriginalFileName") + workingDirectory; - DiffEditor::DiffEditorDocument *diffEditorDocument = DiffEditor::DiffEditorManager::find(documentId); - if (!diffEditorDocument) - newDocument = diffEditorDocument = createDiffEditor(documentId, workingDirectory, title); - - Core::EditorManager::activateEditorForDocument(diffEditorDocument); - - GitDiffHandler *handler = new GitDiffHandler(diffEditorDocument->controller(), - gitBinaryPath(), - workingDirectory, - processEnvironment(), - timeout); - - if (unstagedFileNames.empty() && stagedFileNames.empty()) { - // local repository diff - handler->diffRepository(); - } else if (!stagedFileNames.empty()) { - // diff of selected files only with --cached option, used in commit editor - handler->diffFiles(stagedFileNames, unstagedFileNames); - } else { - // current project diff - handler->diffProjects(unstagedFileNames); - } - } else { - const QString binary = settings()->stringValue(GitSettings::binaryPathKey); - const char *propertyName = "originalFileName"; - VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, workingDirectory); - if (!vcsEditor) { - GitCommitDiffArgumentsWidget *argWidget = - new GitCommitDiffArgumentsWidget(this, - workingDirectory, - unstagedFileNames, - stagedFileNames); - vcsEditor = createVcsEditor(Git::Constants::GIT_DIFF_EDITOR_ID, - title, - workingDirectory, - CodecSource, - propertyName, - workingDirectory, - argWidget); - newDocument = vcsEditor->editor()->document(); - connect(vcsEditor, SIGNAL(diffChunkApplied(VcsBase::DiffChunk)), - argWidget, SLOT(executeCommand())); - connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), - argWidget, SLOT(executeCommand())); - } + GitDiffEditorReloader::DiffType diffType = GitDiffEditorReloader::DiffProjectList; - GitCommitDiffArgumentsWidget *argWidget = qobject_cast( - vcsEditor->configurationWidget()); - argWidget->setFileNames(unstagedFileNames, stagedFileNames); - QStringList userDiffArgs = argWidget->arguments(); - vcsEditor->setWorkingDirectory(workingDirectory); - - // Create a batch of 2 commands to be run after each other in case - // we have a mixture of staged/unstaged files as is the case - // when using the submit dialog. - VcsBase::Command *command = createCommand(workingDirectory, vcsEditor); - // Directory diff? - - QStringList cmdArgs; - cmdArgs << QLatin1String("diff") - << QLatin1String(noColorOption); - - if (unstagedFileNames.empty() && stagedFileNames.empty()) { - QStringList arguments(cmdArgs); - arguments << userDiffArgs; - outputWindow()->appendCommand(workingDirectory, binary, arguments); - command->addJob(arguments, timeout); - } else { - // Files diff. - if (!unstagedFileNames.empty()) { - QStringList arguments(cmdArgs); - arguments << userDiffArgs - << QLatin1String("--") - << unstagedFileNames; - outputWindow()->appendCommand(workingDirectory, binary, arguments); - command->addJob(arguments, timeout); - } - if (!stagedFileNames.empty()) { - QStringList arguments(cmdArgs); - arguments << userDiffArgs - << QLatin1String("--cached") - << QLatin1String("--") - << stagedFileNames; - outputWindow()->appendCommand(workingDirectory, binary, arguments); - command->addJob(arguments, timeout); - } - } - command->addFlags(diffExecutionFlags()); - command->execute(); - } - if (newDocument) { - GitDiffSwitcher *switcher = new GitDiffSwitcher(newDocument, this); - switcher->setWorkingDirectory(workingDirectory); - if (unstagedFileNames.empty() && stagedFileNames.empty()) { - // local repository diff - switcher->setDiffType(GitDiffSwitcher::DiffRepository); - } else if (!stagedFileNames.empty()) { - // diff of selected files only with --cached option, used in commit editor - switcher->setDiffType(GitDiffSwitcher::DiffFileList); - switcher->setFileList(stagedFileNames, unstagedFileNames); - } else { - // current project diff - switcher->setDiffType(GitDiffSwitcher::DiffProjectList); - switcher->setProjectList(unstagedFileNames); - } + if (unstagedFileNames.empty() && stagedFileNames.empty()) + diffType = GitDiffEditorReloader::DiffRepository; + else if (!stagedFileNames.empty()) + diffType = GitDiffEditorReloader::DiffFileList; + + QString title = tr("Git Diff Projects"); + QString documentTypeId = QLatin1String("Projects:"); + if (diffType == GitDiffEditorReloader::DiffRepository) { + title = tr("Git Diff Repository"); + documentTypeId = QLatin1String("Repository:"); + } else if (diffType == GitDiffEditorReloader::DiffFileList) { + title = tr("Git Diff Files"); + documentTypeId = QLatin1String("Files:"); + } + + const QString documentId = documentTypeId + workingDirectory; + + DiffEditor::DiffEditorDocument *diffEditorDocument = + DiffEditor::DiffEditorManager::find(documentId); + if (!diffEditorDocument) { + diffEditorDocument = createDiffEditor(documentId, workingDirectory, title); + + GitDiffEditorReloader *reloader = + new GitDiffEditorReloader(diffEditorDocument->controller()); + reloader->setDiffEditorController(diffEditorDocument->controller()); + + reloader->setWorkingDirectory(workingDirectory); + reloader->setDiffType(diffType); + if (diffType == GitDiffEditorReloader::DiffFileList) + reloader->setFileList(stagedFileNames, unstagedFileNames); + else if (diffType == GitDiffEditorReloader::DiffProjectList) + reloader->setProjectList(unstagedFileNames); } + + diffEditorDocument->controller()->requestReload(); + + Core::EditorManager::activateEditorForDocument(diffEditorDocument); } void GitClient::diff(const QString &workingDirectory, const QString &fileName) { const QString title = tr("Git Diff \"%1\"").arg(fileName); - const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, fileName); - Core::IDocument *newDocument = 0; - if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - const QString documentId = QLatin1String("sideBySideOriginalFileName") + sourceFile; - DiffEditor::DiffEditorDocument *diffEditorDocument = DiffEditor::DiffEditorManager::find(documentId); - if (!diffEditorDocument) - newDocument = diffEditorDocument = createDiffEditor(documentId, sourceFile, title); - - Core::EditorManager::activateEditorForDocument(diffEditorDocument); - - GitDiffHandler *handler = new GitDiffHandler(diffEditorDocument->controller(), - gitBinaryPath(), - workingDirectory, - processEnvironment(), - settings()->intValue(GitSettings::timeoutKey)); - handler->diffFile(fileName); - } else { - const char *propertyName = "originalFileName"; - VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, sourceFile); - if (!vcsEditor) { - GitFileDiffArgumentsWidget *argWidget = - new GitFileDiffArgumentsWidget(this, workingDirectory, fileName); - - vcsEditor = createVcsEditor(Git::Constants::GIT_DIFF_EDITOR_ID, - title, - sourceFile, - CodecSource, - propertyName, - sourceFile, - argWidget); - newDocument = vcsEditor->editor()->document(); - connect(vcsEditor, SIGNAL(diffChunkApplied(VcsBase::DiffChunk)), - argWidget, SLOT(executeCommand())); - connect(vcsEditor, SIGNAL(diffChunkReverted(VcsBase::DiffChunk)), - argWidget, SLOT(executeCommand())); - } - vcsEditor->setWorkingDirectory(workingDirectory); + const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource( + workingDirectory, fileName); + const QString documentId = QLatin1String("File:") + sourceFile; + DiffEditor::DiffEditorDocument *diffEditorDocument = + DiffEditor::DiffEditorManager::find(documentId); + if (!diffEditorDocument) { + diffEditorDocument = createDiffEditor(documentId, sourceFile, title); - QStringList cmdArgs; - cmdArgs << QLatin1String("diff") - << QLatin1String(noColorOption) - << vcsEditor->configurationWidget()->arguments(); + GitDiffEditorReloader *reloader = + new GitDiffEditorReloader(diffEditorDocument->controller()); + reloader->setDiffEditorController(diffEditorDocument->controller()); - if (!fileName.isEmpty()) - cmdArgs << QLatin1String("--") << fileName; - executeGit(workingDirectory, cmdArgs, vcsEditor, false, diffExecutionFlags()); - } - if (newDocument) { - GitDiffSwitcher *switcher = new GitDiffSwitcher(newDocument, this); - switcher->setWorkingDirectory(workingDirectory); - switcher->setDiffType(GitDiffSwitcher::DiffFile); - switcher->setFileName(fileName); + reloader->setWorkingDirectory(workingDirectory); + reloader->setDiffType(GitDiffEditorReloader::DiffFile); + reloader->setFileName(fileName); } + + diffEditorDocument->controller()->requestReload(); + + Core::EditorManager::activateEditorForDocument(diffEditorDocument); } void GitClient::diffBranch(const QString &workingDirectory, - const QStringList &diffArgs, const QString &branchName) { const QString title = tr("Git Diff Branch \"%1\"").arg(branchName); - Core::IDocument *newDocument = 0; - if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - const QString documentId = QLatin1String("sideBySideBranchName") + branchName; - DiffEditor::DiffEditorDocument *diffEditorDocument = DiffEditor::DiffEditorManager::find(documentId); - if (!diffEditorDocument) - newDocument = diffEditorDocument = createDiffEditor(documentId, workingDirectory, title); - - Core::EditorManager::activateEditorForDocument(diffEditorDocument); - - GitDiffHandler *handler = new GitDiffHandler(diffEditorDocument->controller(), - gitBinaryPath(), - workingDirectory, - processEnvironment(), - settings()->intValue(GitSettings::timeoutKey)); - handler->diffBranch(branchName); - } else { - const char *propertyName = "BranchName"; - const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, QStringList()); - - VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, branchName); - if (!vcsEditor) { - vcsEditor = createVcsEditor(Git::Constants::GIT_DIFF_EDITOR_ID, - title, - sourceFile, - CodecSource, - propertyName, - branchName, - new GitBranchDiffArgumentsWidget(this, - workingDirectory, - diffArgs, - branchName)); - newDocument = vcsEditor->editor()->document(); - } - vcsEditor->setWorkingDirectory(workingDirectory); + const QString documentId = QLatin1String("Branch:") + branchName; + DiffEditor::DiffEditorDocument *diffEditorDocument = + DiffEditor::DiffEditorManager::find(documentId); + if (!diffEditorDocument) { + diffEditorDocument = createDiffEditor(documentId, workingDirectory, title); - QStringList cmdArgs; - cmdArgs << QLatin1String("diff") - << QLatin1String(noColorOption) - << vcsEditor->configurationWidget()->arguments() - << branchName; + GitDiffEditorReloader *reloader = + new GitDiffEditorReloader(diffEditorDocument->controller()); + reloader->setDiffEditorController(diffEditorDocument->controller()); - executeGit(workingDirectory, cmdArgs, vcsEditor, false, diffExecutionFlags()); - } - if (newDocument) { - GitDiffSwitcher *switcher = new GitDiffSwitcher(newDocument, this); - switcher->setWorkingDirectory(workingDirectory); - switcher->setDiffType(GitDiffSwitcher::DiffBranch); - switcher->setBaseArguments(diffArgs); - switcher->setBranchName(branchName); + reloader->setWorkingDirectory(workingDirectory); + reloader->setDiffType(GitDiffEditorReloader::DiffBranch); + reloader->setBranchName(branchName); } + + diffEditorDocument->controller()->requestReload(); + + Core::EditorManager::activateEditorForDocument(diffEditorDocument); } -void GitClient::merge(const QString &workingDirectory, const QStringList &unmergedFileNames) +void GitClient::merge(const QString &workingDirectory, + const QStringList &unmergedFileNames) { MergeTool *mergeTool = new MergeTool(this); if (!mergeTool->start(workingDirectory, unmergedFileNames)) @@ -1469,8 +1124,7 @@ static inline QString msgCannotShow(const QString &sha) return GitClient::tr("Cannot describe \"%1\".").arg(sha); } -void GitClient::show(const QString &source, const QString &id, - const QStringList &args, const QString &name) +void GitClient::show(const QString &source, const QString &id, const QString &name) { if (!canShow(id)) { outputWindow()->appendError(msgCannotShow(id)); @@ -1479,59 +1133,28 @@ void GitClient::show(const QString &source, const QString &id, const QString title = tr("Git Show \"%1\"").arg(name.isEmpty() ? id : name); const QFileInfo sourceFi(source); - const QString workingDirectory = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath(); - Core::IDocument *newDocument = 0; - if (settings()->boolValue(GitSettings::useDiffEditorKey)) { - const QString documentId = QLatin1String("sideBySideShow") + id; - DiffEditor::DiffEditorDocument *diffEditorDocument = DiffEditor::DiffEditorManager::find(documentId); - if (!diffEditorDocument) - newDocument = diffEditorDocument = createDiffEditor(documentId, source, title); - + const QString workingDirectory = sourceFi.isDir() + ? sourceFi.absoluteFilePath() : sourceFi.absolutePath(); + const QString documentId = QLatin1String("Show:") + id; + DiffEditor::DiffEditorDocument *diffEditorDocument = + DiffEditor::DiffEditorManager::find(documentId); + if (!diffEditorDocument) { + diffEditorDocument = createDiffEditor(documentId, source, title); diffEditorDocument->controller()->setDescriptionEnabled(true); - Core::EditorManager::activateEditorForDocument(diffEditorDocument); - - GitDiffHandler *handler = new GitDiffHandler(diffEditorDocument->controller(), - gitBinaryPath(), - findRepositoryForDirectory(workingDirectory), - processEnvironment(), - settings()->intValue(GitSettings::timeoutKey)); - handler->show(id); - } else { - const char *propertyName = "show"; - const Core::Id editorId = Git::Constants::GIT_DIFF_EDITOR_ID; - VcsBase::VcsBaseEditorWidget *vcsEditor = findExistingVCSEditor(propertyName, id); - if (!vcsEditor) { - vcsEditor = createVcsEditor(editorId, - title, - source, - CodecSource, - propertyName, - id, - new GitShowArgumentsWidget(this, - source, - args, - id)); - newDocument = vcsEditor->editor()->document(); - } + GitDiffEditorReloader *reloader = + new GitDiffEditorReloader(diffEditorDocument->controller()); + reloader->setDiffEditorController(diffEditorDocument->controller()); - QStringList arguments; - arguments << QLatin1String("show") - << QLatin1String(noColorOption) - << QLatin1String(decorateOption) - << vcsEditor->configurationWidget()->arguments() - << id; - - vcsEditor->setWorkingDirectory(workingDirectory); - executeGit(workingDirectory, arguments, vcsEditor); - } - if (newDocument) { - GitDiffSwitcher *switcher = new GitDiffSwitcher(newDocument, this); - switcher->setDiffType(GitDiffSwitcher::DiffShow); - switcher->setFileName(source); - switcher->setBaseArguments(args); - switcher->setId(id); + reloader->setWorkingDirectory(workingDirectory); + reloader->setDiffType(GitDiffEditorReloader::DiffShow); + reloader->setFileName(source); + reloader->setId(id); } + + diffEditorDocument->controller()->requestReload(); + + Core::EditorManager::activateEditorForDocument(diffEditorDocument); } void GitClient::saveSettings() diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h index 85d50c796c..810019355a 100644 --- a/src/plugins/git/gitclient.h +++ b/src/plugins/git/gitclient.h @@ -45,6 +45,7 @@ class QCheckBox; class QSignalMapper; class QDebug; class QProcessEnvironment; +class QMenu; QT_END_NAMESPACE namespace Core { class ICore; } @@ -58,7 +59,10 @@ namespace VcsBase { namespace Utils { struct SynchronousProcessResponse; } -namespace DiffEditor { class DiffEditorDocument; } +namespace DiffEditor { +class DiffEditorDocument; +class DiffEditorController; +} namespace Git { namespace Internal { @@ -141,7 +145,6 @@ public: const QStringList &unstagedFileNames, const QStringList &stagedFileNames = QStringList()); void diffBranch(const QString &workingDirectory, - const QStringList &diffArgs, const QString &branchName); void merge(const QString &workingDirectory, const QStringList &unmergedFileNames = QStringList()); @@ -332,7 +335,6 @@ public: public slots: void show(const QString &source, const QString &id, - const QStringList &args = QStringList(), const QString &name = QString()); void saveSettings(); @@ -341,8 +343,13 @@ private slots: QString change, int lineNumber); void finishSubmoduleUpdate(); void fetchFinished(const QVariant &cookie); + void slotChunkActionsRequested(QMenu *menu, int diffFileIndex, int chunkIndex); + void slotStageChunk(); + void slotUnstageChunk(); private: + QString makePatch(int diffFileIndex, int chunkIndex, bool revert) const; + void stage(const QString &patch, bool revert); QByteArray readConfigBytes(const QString &workingDirectory, const QString &configVar) const; QTextCodec *getSourceCodec(const QString &file) const; VcsBase::VcsBaseEditorWidget *findExistingVCSEditor(const char *registerDynamicProperty, @@ -421,6 +428,9 @@ private: QMap m_stashInfo; QStringList m_updatedSubmodules; bool m_disableEditor; + int m_contextDiffFileIndex; + int m_contextChunkIndex; + QPointer m_contextDocument; }; } // namespace Internal diff --git a/src/plugins/git/gitsettings.cpp b/src/plugins/git/gitsettings.cpp index 70a1522970..177c49884a 100644 --- a/src/plugins/git/gitsettings.cpp +++ b/src/plugins/git/gitsettings.cpp @@ -35,7 +35,6 @@ namespace Git { namespace Internal { -const QLatin1String GitSettings::useDiffEditorKey("UseDiffEditor"); const QLatin1String GitSettings::pullRebaseKey("PullRebase"); const QLatin1String GitSettings::showTagsKey("ShowTags"); const QLatin1String GitSettings::omitAnnotationDateKey("OmitAnnotationDate"); @@ -56,7 +55,6 @@ GitSettings::GitSettings() declareKey(binaryPathKey, QLatin1String("git")); declareKey(timeoutKey, Utils::HostOsInfo::isWindowsHost() ? 60 : 30); - declareKey(useDiffEditorKey, true); declareKey(pullRebaseKey, false); declareKey(showTagsKey, false); declareKey(omitAnnotationDateKey, false); diff --git a/src/plugins/git/gitsettings.h b/src/plugins/git/gitsettings.h index 701af49229..0c813caee3 100644 --- a/src/plugins/git/gitsettings.h +++ b/src/plugins/git/gitsettings.h @@ -48,7 +48,6 @@ class GitSettings : public VcsBase::VcsBaseClientSettings public: GitSettings(); - static const QLatin1String useDiffEditorKey; static const QLatin1String pullRebaseKey; static const QLatin1String showTagsKey; static const QLatin1String omitAnnotationDateKey; diff --git a/src/plugins/vcsbase/basevcseditorfactory.cpp b/src/plugins/vcsbase/basevcseditorfactory.cpp index 9045388836..baca25d3cc 100644 --- a/src/plugins/vcsbase/basevcseditorfactory.cpp +++ b/src/plugins/vcsbase/basevcseditorfactory.cpp @@ -33,6 +33,8 @@ #include #include +#include + #include #include @@ -67,7 +69,8 @@ BaseVcsEditorFactory::BaseVcsEditorFactory(const VcsBaseEditorParameters *t, d->m_describeSlot = describeSlot; setId(t->id); setDisplayName(QCoreApplication::translate("VCS", t->displayName)); - addMimeType(t->mimeType); + if (QLatin1String(t->mimeType) != QLatin1String(DiffEditor::Constants::DIFF_EDITOR_MIMETYPE)) + addMimeType(t->mimeType); new TextEditor::TextEditorActionHandler(this, t->context); } @@ -84,7 +87,8 @@ Core::IEditor *BaseVcsEditorFactory::createEditor() if (d->m_describeReceiver) connect(vcsEditor, SIGNAL(describeRequested(QString,QString)), d->m_describeReceiver, d->m_describeSlot); - vcsEditor->baseTextDocument()->setMimeType(mimeTypes().front()); + if (!mimeTypes().isEmpty()) + vcsEditor->baseTextDocument()->setMimeType(mimeTypes().front()); TextEditor::TextEditorSettings::initializeEditor(vcsEditor); return vcsEditor->editor(); -- cgit v1.2.1