Implement unified diff editor

Change-Id: I93e0bfd71a8a650afbe2ca9e0f1f3dbfc9d57db0
Reviewed-by: Jarek Kobus <jaroslaw.kobus@digia.com>
This commit is contained in:
jkobus
2014-02-13 16:43:28 +01:00
committed by Jarek Kobus
parent 8cb25f9e3e
commit 8cad94534f
39 changed files with 4075 additions and 1441 deletions

View File

@@ -32,6 +32,7 @@
#include "diffeditordocument.h"
#include "diffeditorguicontroller.h"
#include "sidebysidediffeditorwidget.h"
#include "unifieddiffeditorwidget.h"
#include <coreplugin/icore.h>
#include <coreplugin/coreconstants.h>
@@ -41,6 +42,7 @@
#include <texteditor/texteditorsettings.h>
#include <texteditor/displaysettings.h>
#include <QStackedWidget>
#include <QToolButton>
#include <QSpinBox>
#include <QStyle>
@@ -49,6 +51,15 @@
#include <QToolBar>
#include <QComboBox>
#include <QFileInfo>
#include <QTextCodec>
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<DiffEditorController::DiffFilesContents>,QString)),
this, SLOT(slotDiffContentsChanged(QList<DiffEditorController::DiffFilesContents>,QString)));
connect(m_controller, SIGNAL(diffFilesChanged(QList<FileData>,QString)),
this, SLOT(slotDiffFilesChanged(QList<FileData>,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<FileData> 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<DiffEditorController::DiffFilesContents> &diffFileList,
const QString &workingDirectory)
void DiffEditor::slotDiffFilesChanged(const QList<FileData> &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 QList<DiffEditorController::DiffF
itemToolTip = leftEntry.fileName;
} else {
itemToolTip = tr("[%1] vs. [%2] %3")
.arg(leftEntry.typeInfo, rightEntry.typeInfo, leftEntry.fileName);
.arg(leftEntry.typeInfo,
rightEntry.typeInfo,
leftEntry.fileName);
}
} else {
if (leftShortFileName == rightShortFileName) {
itemText = leftShortFileName;
} else {
itemText = tr("%1 vs. %2")
.arg(leftShortFileName, rightShortFileName);
.arg(leftShortFileName,
rightShortFileName);
}
if (leftEntry.typeInfo.isEmpty() && rightEntry.typeInfo.isEmpty()) {
itemToolTip = tr("%1 vs. %2")
.arg(leftEntry.fileName, rightEntry.fileName);
.arg(leftEntry.fileName,
rightEntry.fileName);
} else {
itemToolTip = tr("[%1] %2 vs. [%3] %4")
.arg(leftEntry.typeInfo, leftEntry.fileName, rightEntry.typeInfo, rightEntry.fileName);
.arg(leftEntry.typeInfo,
leftEntry.fileName,
rightEntry.typeInfo,
rightEntry.fileName);
}
}
m_entriesComboBox->addItem(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

View File

@@ -37,9 +37,10 @@
#include <coreplugin/idocument.h>
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<DiffEditorController::DiffFilesContents> &diffFileList,
const QString &workingDirectory);
void slotDiffFilesChanged(const QList<FileData> &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<DiffEditorDocument> 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

View File

@@ -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

View File

@@ -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",
]
}

View File

@@ -0,0 +1,8 @@
<RCC>
<qresource prefix="/diffeditor">
<file>images/reload.png</file>
<file>images/sidebysidediff.png</file>
<file>images/unifieddiff.png</file>
<file>images/topbar.png</file>
</qresource>
</RCC>

View File

@@ -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

View File

@@ -29,12 +29,28 @@
#include "diffeditorcontroller.h"
#include <coreplugin/icore.h>
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::DiffFilesContents> DiffEditorController::diffContents() const
QList<FileData> 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<FileData>());
m_clearMessage = message;
emit cleared(message);
}
void DiffEditorController::setDiffContents(const QList<DiffFilesContents> &diffFileList,
const QString &workingDirectory)
void DiffEditorController::setDiffFiles(const QList<FileData> &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

View File

@@ -31,6 +31,7 @@
#define DIFFEDITORCONTROLLER_H
#include "diffeditor_global.h"
#include "diffutils.h"
#include <QObject>
@@ -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<DiffFilesContents> diffContents() const;
QList<FileData> 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<DiffEditorController::DiffFilesContents> &diffFileList,
const QString &workingDirectory = QString());
void setDiffFiles(const QList<FileData> &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<DiffEditorController::DiffFilesContents> &diffFileList, const QString &workingDirectory);
void diffFilesChanged(const QList<FileData> &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<DiffFilesContents> m_diffFileList;
QList<FileData> m_diffFiles;
QString m_workingDirectory;
QString m_description;
bool m_descriptionEnabled;
int m_contextLinesNumber;
bool m_ignoreWhitespace;
};
} // namespace DiffEditor

View File

@@ -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()

View File

@@ -30,19 +30,35 @@
#include "diffeditorguicontroller.h"
#include "diffeditorcontroller.h"
#include <coreplugin/icore.h>
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<DiffEditorController::DiffFilesContents>,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<FileData>,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,43 +99,38 @@ void DiffEditorGuiController::setDescriptionVisible(bool on)
return;
m_descriptionVisible = on;
QSettings *s = Core::ICore::settings();
s->beginGroup(QLatin1String(settingsGroupC));
s->setValue(QLatin1String(descriptionVisibleKeyC), m_descriptionVisible);
s->endGroup();
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;
m_ignoreWhitespaces = ignore;
emit ignoreWhitespacesChanged(ignore);
}
void DiffEditorGuiController::setHorizontalScrollBarSynchronization(bool on)
{
if (m_syncScrollBars == 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;

View File

@@ -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;
};

View File

@@ -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 <QFileDialog>
#include <QTextCodec>
@@ -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<Diff> diffList = differ.cleanupSemantics(
differ.diff(leftText, rightText));
QList<Diff> leftDiffList;
QList<Diff> rightDiffList;
Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
QList<Diff> outputLeftDiffList;
QList<Diff> outputRightDiffList;
if (diffEditorController()->isIgnoreWhitespace()) {
const QList<Diff> leftIntermediate =
Differ::moveWhitespaceIntoEqualities(leftDiffList);
const QList<Diff> 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<FileData> 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<DiffEditor *>
(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<DiffEditorController::DiffFilesContents> list;
list.append(dfc);
Core::EditorManager::activateEditorForDocument(document);
editor->controller()->setDiffContents(list);
}
QString DiffEditorPlugin::getFileContents(const QString &fileName) const
{
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
return Core::EditorManager::defaultTextCodec()->toUnicode(file.readAll());
return QString();
document->controller()->requestReload();
}
} // namespace Internal
} // namespace DiffEditor
#ifdef WITH_TESTS
#include <QTest>
#include "diffutils.h"
Q_DECLARE_METATYPE(DiffEditor::ChunkData)
Q_DECLARE_METATYPE(QList<DiffEditor::FileData>)
void DiffEditor::Internal::DiffEditorPlugin::testMakePatch_data()
{
QTest::addColumn<ChunkData>("sourceChunk");
QTest::addColumn<QString>("leftFileName");
QTest::addColumn<QString>("rightFileName");
QTest::addColumn<bool>("lastChunk");
QTest::addColumn<QString>("patchText");
const QString fileName = QLatin1String("a.txt");
const QString header = QLatin1String("--- ") + fileName
+ QLatin1String("\n+++ ") + fileName + QLatin1String("\n");
QList<RowData> 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;
}
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<QString>("sourcePatch");
QTest::addColumn<QList<FileData> >("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<DiffEditorController::DiffFilesContents>,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<RowData> 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<DiffEditorController::DiffFilesContents>,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<RowData> 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<FileData> fileDataList;
fileDataList.append(fileData1);
fileDataList.append(fileData2);
QTest::newRow("Git patch") << patch
<< fileDataList;
}
void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
{
QFETCH(QString, sourcePatch);
QFETCH(QList<FileData>, fileDataList);
bool ok;
QList<FileData> 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"

View File

@@ -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

View File

@@ -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

View File

@@ -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 <QObject>
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

View File

@@ -297,14 +297,16 @@ QList<Diff> Differ::moveWhitespaceIntoEqualities(const QList<Diff> &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<Diff> Differ::moveWhitespaceIntoEqualities(const QList<Diff> &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<Diff> decodeExpandedWhitespace(const QList<Diff> &input,
return QList<Diff>(); // 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<Diff> &leftInput,
QMapIterator<int, QPair<int, QString> > itLeft(leftCodeMap);
while (itLeft.hasNext()) {
itLeft.next();
commonLeftCodeMap.insert(leftText.count() + itLeft.key(), itLeft.value());
commonLeftCodeMap.insert(leftText.count() + itLeft.key(),
itLeft.value());
}
QMapIterator<int, QPair<int, QString> > 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<Diff> &leftInput,
}
Differ differ;
QList<Diff> diffList = differ.cleanupSemantics(differ.diff(leftText, rightText));
QList<Diff> diffList = differ.cleanupSemantics(
differ.diff(leftText, rightText));
QList<Diff> leftDiffList;
QList<Diff> rightDiffList;
@@ -716,10 +727,12 @@ static bool diffWithWhitespaceExpandedInEqualities(const QList<Diff> &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<Diff> &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<Diff> &leftInput,
void Differ::ignoreWhitespaceBetweenEqualities(const QList<Diff> &leftInput,
const QList<Diff> &rightInput,
QList<Diff> *leftOutput,
QList<Diff> *rightOutput)
@@ -848,6 +863,88 @@ void Differ::diffBetweenEqualities(const QList<Diff> &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<Diff> &leftInput,
const QList<Diff> &rightInput,
QList<Diff> *leftOutput,
QList<Diff> *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<Diff> commonOutput = differ.cleanupSemantics(
differ.diff(previousLeftDiff.text, previousRightDiff.text));
QList<Diff> outputLeftDiffList;
QList<Diff> 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<Diff> leftEquality;
QList<Diff> 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<Diff> 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<Diff> 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<Diff> Differ::merge(const QList<Diff> &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<Diff> Differ::cleanupSemantics(const QList<Diff> &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();

View File

@@ -88,6 +88,10 @@ public:
const QString &rightInput,
QList<Diff> *leftOutput,
QList<Diff> *rightOutput);
static void ignoreWhitespaceBetweenEqualities(const QList<Diff> &leftInput,
const QList<Diff> &rightInput,
QList<Diff> *leftOutput,
QList<Diff> *rightOutput);
static void diffBetweenEqualities(const QList<Diff> &leftInput,
const QList<Diff> &rightInput,
QList<Diff> *leftOutput,

View File

@@ -33,7 +33,6 @@
#include "texteditor/fontsettings.h"
namespace DiffEditor {
namespace Internal {
static QList<TextLineData> assemblyRows(const QList<TextLineData> &lines,
const QMap<int, int> &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<Diff> &leftDiffList,
ChunkData DiffUtils::calculateOriginalData(const QList<Diff> &leftDiffList,
const QList<Diff> &rightDiffList)
{
ChunkData chunkData;
int i = 0;
int j = 0;
@@ -152,61 +149,71 @@ ChunkData calculateOriginalData(const QList<Diff> &leftDiffList,
int line = 0;
while (line < qMax(newLeftLines.count(), newRightLines.count())) {
handleLine(newLeftLines, line, &leftLines, &leftLineNumber);
handleLine(newRightLines, line, &rightLines, &rightLineNumber);
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;
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 (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 (line == commonLineCount - 1) {
// omit alignment when last lines of equalities are empty
if (leftLines.last().text.isEmpty()
|| rightLines.last().text.isEmpty())
doAlign = false;
if (doAlign) {
// align here
leftLineAligned = leftLineNumber;
rightLineAligned = rightLineNumber;
// unless it's the last dummy line (don't omit in that case)
if (i == leftDiffList.count()
&& j == rightDiffList.count())
doAlign = true;
}
// insert separators if needed
if (rightDifference > leftDifference)
leftSpans.insert(leftLineNumber, rightDifference - leftDifference);
else if (leftDifference > rightDifference)
rightSpans.insert(rightLineNumber, leftDifference - rightDifference);
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);
}
if (line > 0)
lastLineEqual = true;
line++;
}
// 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;
line++;
}
i++;
j++;
@@ -227,6 +234,8 @@ ChunkData calculateOriginalData(const QList<Diff> &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<Diff> &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<int, bool> 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<int, int> &originalChangedPositions, QMap<int, int> *changedPositions)
QString DiffUtils::makePatchLine(const QChar &startLineCharacter,
const QString &textLine,
bool lastChunk,
bool lastLine)
{
QMapIterator<int, int> 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<int, int>::iterator last = changedPositions->end();
--last;
last.value() = newEndPos;
} else
changedPositions->insert(newStartPos, newEndPos);
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;
}
QList<QTextEdit::ExtraSelection> colorPositions(
const QTextCharFormat &format,
QTextCursor &cursor,
const QMap<int, int> &positions)
QString DiffUtils::makePatch(const ChunkData &chunkData,
const QString &leftFileName,
const QString &rightFileName,
bool lastChunk)
{
QList<QTextEdit::ExtraSelection> lineSelections;
QString diffText;
int leftLineCount = 0;
int rightLineCount = 0;
QList<TextLineData> leftBuffer, rightBuffer;
cursor.setPosition(0);
QMapIterator<int, int> itPositions(positions);
while (itPositions.hasNext()) {
itPositions.next();
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);
cursor.setPosition(itPositions.key());
cursor.setPosition(itPositions.value(), QTextCursor::KeepAnchor);
if (!line.isEmpty())
++leftLineCount;
QTextEdit::ExtraSelection selection;
selection.cursor = cursor;
selection.format = format;
lineSelections.append(selection);
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);
}
}
return lineSelections;
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;
}
QTextCharFormat fullWidthFormatForTextStyle(const TextEditor::FontSettings &fontSettings,
TextEditor::TextStyle textStyle)
static QList<RowData> readLines(const QString &patch,
bool ignoreWhitespace,
bool lastChunk,
bool *lastChunkAtTheEndOfFile,
bool *ok)
{
QTextCharFormat format = fontSettings.toTextCharFormat(textStyle);
format.setProperty(QTextFormat::FullWidthSelection, true);
return format;
// 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<Diff> 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<RowData>();
}
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<Diff> leftDiffList;
QList<Diff> rightDiffList;
Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
QList<Diff> outputLeftDiffList;
QList<Diff> outputRightDiffList;
if (ignoreWhitespace) {
const QList<Diff> leftIntermediate =
Differ::moveWhitespaceIntoEqualities(leftDiffList);
const QList<Diff> 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<ChunkData> 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<ChunkData> 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<FileData> readDiffPatch(const QString &patch,
bool ignoreWhitespace,
bool *ok)
{
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<FileData> 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<FileData>();
return fileDataList;
}
static FileData readGitHeaderAndChunks(const QString &headerAndChunks,
const QString &fileName,
bool ignoreWhitespace,
bool *ok)
{
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;
if (!readOk)
return FileData();
return fileData;
}
static QList<FileData> 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<FileData> 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;
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);
}
}
if (ok)
*ok = readOk;
if (!readOk)
return QList<FileData>();
return fileDataList;
}
QList<FileData> DiffUtils::readPatch(const QString &patch, bool ignoreWhitespace, bool *ok)
{
bool readOk = false;
QList<FileData> fileDataList;
fileDataList = readGitPatch(patch, ignoreWhitespace, &readOk);
if (!readOk)
fileDataList = readDiffPatch(patch, ignoreWhitespace, &readOk);
if (ok)
*ok = readOk;
return fileDataList;
}
} // namespace Internal
} // namespace DiffEditor

View File

@@ -30,11 +30,12 @@
#ifndef DIFFUTILS_H
#define DIFFUTILS_H
#include "diffeditor_global.h"
#include <QString>
#include <QMap>
#include <QTextEdit>
#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<int, int> 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<RowData> 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<ChunkData> 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<Diff> &leftDiffList,
const QList<Diff> &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<FileData> readPatch(const QString &patch,
bool ignoreWhitespace,
bool *ok = 0);
};
ChunkData calculateOriginalData(const QList<Diff> &leftDiffList,
const QList<Diff> &rightDiffList);
FileData calculateContextData(const ChunkData &originalData,
int contextLinesNumber);
void addChangedPositions(int positionOffset,
const QMap<int, int> &originalChangedPositions,
QMap<int, int> *changedPositions);
QList<QTextEdit::ExtraSelection> colorPositions(const QTextCharFormat &format,
QTextCursor &cursor,
const QMap<int, int> &positions);
QTextCharFormat fullWidthFormatForTextStyle(const TextEditor::FontSettings &fontSettings,
TextEditor::TextStyle textStyle);
} // namespace Internal
} // namespace DiffEditor
#endif // DIFFUTILS_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 B

View File

@@ -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 <QPainter>
#include <QTextBlock>
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<DiffSelection> &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

View File

@@ -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 <texteditor/basetexteditor.h>
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<int,
QList<DiffSelection> > &selections) {
m_selections = selections;
}
protected:
virtual void paintEvent(QPaintEvent *e);
private:
void paintSelections(QPaintEvent *e);
void paintSelections(QPainter &painter,
const QList<DiffSelection> &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<int, QList<DiffSelection> > m_selections;
};
} // namespace DiffEditor
#endif // SELECTABLETEXTEDITORWIDGET_H

File diff suppressed because it is too large Load Diff

View File

@@ -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<DiffEditorController::DiffFilesContents> &diffFileList, const QString &workingDirectory);
void clearAll(const QString &message = QString());
void setDiff(const QList<FileData> &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<Diff> diffList;
};
void setDiff(const QList<DiffList> &diffList);
void handleWhitespaces(const QList<Diff> &input,
QList<Diff> *leftOutput,
QList<Diff> *rightOutput) const;
void colorDiff(const QList<Internal::FileData> &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<DiffList> m_diffList; // list of original outputs from differ
QList<Internal::ChunkData> m_originalChunkData; // one big chunk for every file, ignoreWhitespace taken into account
QList<Internal::FileData> m_contextFileData; // ultimate data to be shown, contextLinesNumber taken into account
QList<FileData> 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;

View File

@@ -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 <QPlainTextEdit>
#include <QVBoxLayout>
#include <QPlainTextDocumentLayout>
#include <QTextBlock>
#include <QScrollBar>
#include <QPainter>
#include <QDir>
#include <QToolButton>
#include <QTextCodec>
#include <QMessageBox>
#include <texteditor/basetexteditor.h>
#include <texteditor/basetextdocumentlayout.h>
#include <texteditor/ihighlighterfactory.h>
#include <texteditor/syntaxhighlighter.h>
#include <texteditor/basetextdocument.h>
#include <texteditor/texteditorsettings.h>
#include <texteditor/fontsettings.h>
#include <texteditor/displaysettings.h>
#include <texteditor/highlighterutils.h>
#include <coreplugin/icore.h>
#include <coreplugin/minisplitter.h>
#include <coreplugin/mimedatabase.h>
#include <coreplugin/patchtool.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/tooltip/tipcontents.h>
#include <utils/tooltip/tooltip.h>
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<FileData>,QString)),
this, SLOT(setDiff(QList<FileData>,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<FileData>,QString)),
this, SLOT(setDiff(QList<FileData>,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<QMenu> 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<int, QList<DiffSelection> >());
const bool oldIgnore = m_ignoreCurrentIndexChange;
m_ignoreCurrentIndexChange = true;
SelectableTextEditorWidget::clear();
setPlainText(message);
m_ignoreCurrentIndexChange = oldIgnore;
}
void UnifiedDiffEditorWidget::clearAll(const QString &message)
{
setDiff(QList<FileData>(), 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<FileData> &diffFileList,
const QString &workingDirectory)
{
Q_UNUSED(workingDirectory)
m_contextFileData = diffFileList;
showDiff();
}
QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
bool lastChunk,
int *blockNumber,
int *charNumber,
QMap<int, QList<DiffSelection> > *selections)
{
if (chunkData.contextChunk)
return QString();
QString diffText;
int leftLineCount = 0;
int rightLineCount = 0;
int blockCount = 0;
int charCount = 0;
QList<TextLineData> 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<int, int> 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<int, int> 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<int, QList<DiffSelection> > 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<int, QPair<DiffFileInfo, DiffFileInfo> >::const_iterator it
= m_fileInfo.constBegin();
for (int i = 0; i < fileIndex; i++)
++it;
return it.key();
}
int UnifiedDiffEditorWidget::fileIndexForBlockNumber(int blockNumber) const
{
QMap<int, QPair<DiffFileInfo, DiffFileInfo> >::const_iterator it
= m_fileInfo.constBegin();
QMap<int, QPair<DiffFileInfo, DiffFileInfo> >::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<int, QPair<int, int> >::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"

View File

@@ -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<FileData> &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<int, QList<DiffSelection> > *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<int, int> m_leftLineNumbers;
QMap<int, int> m_rightLineNumbers;
bool m_ignoreCurrentIndexChange;
int m_contextMenuFileIndex;
int m_contextMenuChunkIndex;
int m_leftLineNumberDigits;
int m_rightLineNumberDigits;
// block number, visual line number.
QMap<int, QPair<DiffFileInfo, DiffFileInfo> > m_fileInfo;
// start block number, block count of a chunk, chunk index inside a file.
QMap<int, QPair<int, int> > m_chunkInfo;
QList<FileData> 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