Experimental integration of DiffEditor

After enabling the plugin go to Tools|Diff...

Change-Id: I793b6faedb93f58039df0a62e82fe04a017978ee
Reviewed-by: hjk <hjk121@nokiamail.com>
This commit is contained in:
jkobus
2013-02-15 12:49:50 +01:00
committed by hjk
parent aa69b190f7
commit 0e91d10878
15 changed files with 2453 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<plugin name=\"DiffEditor\" version=\"$$QTCREATOR_VERSION\" compatVersion=\"$$QTCREATOR_VERSION\" experimental=\"true\">
<vendor>Digia Plc</vendor>
<copyright>(C) 2013 Digia Plc</copyright>
<license>
Commercial Usage
Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Digia.
GNU Lesser General Public License Usage
Alternatively, this plugin may be used under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation. 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.
</license>
<category>Qt Creator</category>
<description>Diff editor component.</description>
<url>http://www.qt-project.org</url>
<dependencyList>
<dependency name=\"Core\" version=\"$$QTCREATOR_VERSION\"/>
<dependency name=\"TextEditor\" version=\"$$QTCREATOR_VERSION\"/>
</dependencyList>
</plugin>

View File

@@ -0,0 +1,3 @@
include(diffeditor_dependencies.pri)
LIBS *= -l$$qtLibraryName(DiffEditor)

View File

@@ -0,0 +1,17 @@
TEMPLATE = lib
TARGET = DiffEditor
DEFINES += DIFFEDITOR_LIBRARY
include(../../qtcreatorplugin.pri)
include(diffeditor_dependencies.pri)
HEADERS += diffeditorplugin.h \
diffeditorwidget.h \
diffeditorconstants.h \
diffeditor_global.h \
differ.h
SOURCES += diffeditorplugin.cpp \
diffeditorwidget.cpp \
differ.cpp
RESOURCES +=

View File

@@ -0,0 +1,26 @@
import qbs.base 1.0
import "../QtcPlugin.qbs" as QtcPlugin
QtcPlugin {
name: "DiffEditor"
Depends { name: "Qt.widgets" }
Depends { name: "Core" }
Depends { name: "TextEditor" }
Depends { name: "Find" }
Depends { name: "cpp" }
files: [
"diffeditorplugin.cpp",
"diffeditorplugin.h",
"differ.cpp",
"differ.h",
"diffeditorwidget.cpp",
"diffeditorwidget.h",
"diffeditorconstants.h",
"diffeditor_global.h",
]
}

View File

@@ -0,0 +1,3 @@
include(../../libs/utils/utils.pri)
include(../../plugins/texteditor/texteditor.pri)
include(../../plugins/coreplugin/coreplugin.pri)

View File

@@ -0,0 +1,41 @@
/****************************************************************************
**
** Copyright (C) 2013 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 DIFFEDITOR_GLOBAL_H
#define DIFFEDITOR_GLOBAL_H
#include <QtGlobal>
#if defined(DIFFEDITOR_LIBRARY)
# define DIFFEDITOR_EXPORT Q_DECL_EXPORT
#else
# define DIFFEDITOR_EXPORT Q_DECL_IMPORT
#endif
#endif // DIFFEDITOR_GLOBAL_H

View File

@@ -0,0 +1,44 @@
/****************************************************************************
**
** Copyright (C) 2013 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 DIFFEDITORCONSTANTS_H
#define DIFFEDITORCONSTANTS_H
namespace DIFFEditor {
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";
} // namespace Constants
} // namespace DIFFEditor
#endif // DIFFEDITORCONSTANTS_H

View File

@@ -0,0 +1,352 @@
/****************************************************************************
**
** Copyright (C) 2013 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 "diffeditorplugin.h"
#include "diffeditorwidget.h"
#include "diffeditorconstants.h"
#include <coreplugin/icore.h>
#include <QCoreApplication>
#include <QToolButton>
#include <QSpinBox>
#include <QStyle>
#include <QLabel>
#include <QFileDialog>
#include <QTextCodec>
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/coreconstants.h>
using namespace DIFFEditor;
using namespace DIFFEditor::Internal;
///////////////////////////////// DiffEditor //////////////////////////////////
DiffEditor::DiffEditor(DiffEditorWidget *editorWidget)
:
IEditor(0),
m_file(new DiffFile(QLatin1String(Constants::DIFF_EDITOR_MIMETYPE), this)),
m_editorWidget(editorWidget),
m_toolWidget(0)
{
setWidget(editorWidget);
}
DiffEditor::~DiffEditor()
{
delete m_toolWidget;
if (m_widget)
delete m_widget;
}
bool DiffEditor::createNew(const QString &contents)
{
Q_UNUSED(contents)
// setFileContents(contents);
return true;
}
bool DiffEditor::open(QString *errorString, const QString &fileName, const QString &realFileName)
{
Q_UNUSED(errorString)
Q_UNUSED(fileName)
Q_UNUSED(realFileName)
const QString text = QLatin1String("Open");
if (!createNew(text))
return false;
return true;
}
Core::IDocument *DiffEditor::document()
{
return m_file;
}
QString DiffEditor::displayName() const
{
if (m_displayName.isEmpty())
m_displayName = QCoreApplication::translate("DiffEditor", Constants::DIFF_EDITOR_DISPLAY_NAME);
return m_displayName;
}
void DiffEditor::setDisplayName(const QString &title)
{
m_displayName = title;
emit changed();
}
bool DiffEditor::duplicateSupported() const
{
return false;
}
Core::IEditor *DiffEditor::duplicate(QWidget * /*parent*/)
{
return 0;
}
Core::Id DiffEditor::id() const
{
return Constants::DIFF_EDITOR_ID;
}
static QToolBar *createToolBar(const QWidget *someWidget)
{
// Create
QToolBar *toolBar = new QToolBar;
toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
const int size = someWidget->style()->pixelMetric(QStyle::PM_SmallIconSize);
toolBar->setIconSize(QSize(size, size));
toolBar->addSeparator();
return toolBar;
}
QWidget *DiffEditor::toolBar()
{
if (m_toolWidget)
return m_toolWidget;
// Create
m_toolWidget = createToolBar(m_editorWidget);
QToolButton *whitespaceButton = new QToolButton(m_toolWidget);
whitespaceButton->setText(tr("Ignore Whitespaces"));
whitespaceButton->setCheckable(true);
whitespaceButton->setChecked(true);
connect(whitespaceButton, SIGNAL(clicked(bool)),
m_editorWidget, SLOT(setIgnoreWhitespaces(bool)));
m_toolWidget->addWidget(whitespaceButton);
QLabel *contextLabel = new QLabel(tr("Context Lines:"), m_toolWidget);
m_toolWidget->addWidget(contextLabel);
QSpinBox *contextSpinBox = new QSpinBox(m_toolWidget);
contextSpinBox->setRange(-1, 100);
contextSpinBox->setValue(1);
connect(contextSpinBox, SIGNAL(valueChanged(int)),
m_editorWidget, SLOT(setContextLinesNumber(int)));
m_toolWidget->addWidget(contextSpinBox);
return m_toolWidget;
}
QByteArray DiffEditor::saveState() const
{
return QByteArray();
}
bool DiffEditor::restoreState(const QByteArray &/*state*/)
{
return true;
}
///////////////////////////////// DiffFile //////////////////////////////////
DiffFile::DiffFile(const QString &mimeType, QObject *parent) :
Core::IDocument(parent),
m_mimeType(mimeType),
m_modified(false)
{
}
void DiffFile::rename(const QString &newName)
{
Q_UNUSED(newName);
return;
}
void DiffFile::setFileName(const QString &name)
{
if (m_fileName == name)
return;
m_fileName = name;
emit changed();
}
void DiffFile::setModified(bool modified)
{
if (m_modified == modified)
return;
m_modified = modified;
emit changed();
}
bool DiffFile::save(QString *errorString, const QString &fileName, bool autoSave)
{
emit saveMe(errorString, fileName, autoSave);
if (!errorString->isEmpty())
return false;
emit changed();
return true;
}
QString DiffFile::mimeType() const
{
return m_mimeType;
}
Core::IDocument::ReloadBehavior DiffFile::reloadBehavior(ChangeTrigger state, ChangeType type) const
{
Q_UNUSED(state)
Q_UNUSED(type)
return BehaviorSilent;
}
bool DiffFile::reload(QString *errorString, ReloadFlag flag, ChangeType type)
{
Q_UNUSED(errorString)
Q_UNUSED(flag)
Q_UNUSED(type)
return true;
}
///////////////////////////////// DiffEditorFactory //////////////////////////////////
DiffEditorFactory::DiffEditorFactory(DiffEditorPlugin *owner) :
m_mimeTypes(QLatin1String(Constants::DIFF_EDITOR_MIMETYPE)),
m_owner(owner)
{
}
Core::Id DiffEditorFactory::id() const
{
return Constants::DIFF_EDITOR_ID;
}
QString DiffEditorFactory::displayName() const
{
return qApp->translate("DiffEditorFactory", Constants::DIFF_EDITOR_DISPLAY_NAME);
}
Core::IEditor *DiffEditorFactory::createEditor(QWidget *parent)
{
DiffEditorWidget *editorWidget = new DiffEditorWidget(parent);
DiffEditor *editor = new DiffEditor(editorWidget);
return editor;
}
QStringList DiffEditorFactory::mimeTypes() const
{
return m_mimeTypes;
}
///////////////////////////////// DiffEditorPlugin //////////////////////////////////
DiffEditorPlugin::DiffEditorPlugin()
{
}
DiffEditorPlugin::~DiffEditorPlugin()
{
}
void DiffEditorPlugin::initializeEditor(DiffEditorWidget *editor)
{
Q_UNUSED(editor)
}
bool DiffEditorPlugin::initialize(const QStringList &arguments, QString *errorMessage)
{
Q_UNUSED(arguments)
Q_UNUSED(errorMessage)
//register actions
Core::ActionContainer *toolsContainer =
Core::ActionManager::actionContainer(Core::Constants::M_TOOLS);
toolsContainer->insertGroup(Core::Constants::G_TOOLS_OPTIONS, Constants::G_TOOLS_DIFF);
Core::Context globalcontext(Core::Constants::C_GLOBAL);
QAction *diffAction = new QAction(tr("Diff..."), this);
Core::Command *diffCommand = Core::ActionManager::registerAction(diffAction,
"DiffEditor.Diff", globalcontext);
connect(diffAction, SIGNAL(triggered()), this, SLOT(diff()));
toolsContainer->addAction(diffCommand, Constants::G_TOOLS_DIFF);
addAutoReleasedObject(new DiffEditorFactory(this));
return true;
}
void DiffEditorPlugin::extensionsInitialized()
{
}
void DiffEditorPlugin::diff()
{
QString fileName1 = QFileDialog::getOpenFileName(0,
tr("Select the first file for diff"),
QString());
if (fileName1.isNull())
return;
QString fileName2 = QFileDialog::getOpenFileName(0,
tr("Select the second file for diff"),
QString());
if (fileName2.isNull())
return;
const Core::Id editorId = Constants::DIFF_EDITOR_ID;
QString title = tr("Diff \"%1\", \"%2\"").arg(fileName1).arg(fileName2);
Core::IEditor *outputEditor = Core::EditorManager::openEditorWithContents(editorId, &title, QString());
Core::EditorManager::activateEditor(outputEditor, Core::EditorManager::ModeSwitch);
DiffEditorWidget *editorWidget = getDiffEditorWidget(outputEditor);
if (editorWidget) {
const QString text1 = getFileContents(fileName1, editorWidget->codec());
const QString text2 = getFileContents(fileName2, editorWidget->codec());
editorWidget->setDiff(text1, text2);
}
}
DiffEditorWidget *DiffEditorPlugin::getDiffEditorWidget(const Core::IEditor *editor) const
{
if (const DiffEditor *de = qobject_cast<const DiffEditor *>(editor))
return de->editorWidget();
return 0;
}
QString DiffEditorPlugin::getFileContents(const QString &fileName, QTextCodec *codec) const
{
QFile file(fileName);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return codec->toUnicode(file.readAll());
}
return QString();
}
Q_EXPORT_PLUGIN(DiffEditorPlugin)

View File

@@ -0,0 +1,158 @@
/****************************************************************************
**
** Copyright (C) 2013 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 DIFFEDITORPLUGIN_H
#define DIFFEDITORPLUGIN_H
#include <extensionsystem/iplugin.h>
#include <coreplugin/editormanager/ieditorfactory.h>
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icontext.h>
#include <coreplugin/idocument.h>
#include <QtPlugin>
#include <QPointer>
#include <QStringList>
#include <QAction>
#include <QToolBar>
namespace DIFFEditor {
class DiffEditorWidget;
namespace Internal {
class DiffFile;
class DiffEditor : public Core::IEditor
{
Q_OBJECT
public:
explicit DiffEditor(DiffEditorWidget *editorWidget);
virtual ~DiffEditor();
public:
// Core::IEditor
bool createNew(const QString &contents);
bool open(QString *errorString, const QString &fileName, const QString &realFileName);
Core::IDocument *document();
QString displayName() const;
void setDisplayName(const QString &title);
bool duplicateSupported() const;
Core::IEditor *duplicate(QWidget *parent);
Core::Id id() const;
bool isTemporary() const { return true; }
DiffEditorWidget *editorWidget() const { return m_editorWidget; }
QWidget *toolBar();
QByteArray saveState() const;
bool restoreState(const QByteArray &state);
private:
DiffFile *m_file;
DiffEditorWidget *m_editorWidget;
QToolBar *m_toolWidget;
mutable QString m_displayName;
};
class DiffFile : public Core::IDocument
{
Q_OBJECT
public:
explicit DiffFile(const QString &mimeType,
QObject *parent = 0);
QString fileName() const { return m_fileName; }
QString defaultPath() const { return QString(); }
QString suggestedFileName() const { return QString(); }
bool isModified() const { return m_modified; }
QString mimeType() const;
bool isSaveAsAllowed() const { return false; }
bool save(QString *errorString, const QString &fileName, bool autoSave);
ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const;
bool reload(QString *errorString, ReloadFlag flag, ChangeType type);
void rename(const QString &newName);
void setFileName(const QString &name);
void setModified(bool modified = true);
signals:
void saveMe(QString *errorString, const QString &fileName, bool autoSave);
private:
const QString m_mimeType;
bool m_modified;
QString m_fileName;
};
class DiffEditorPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "DiffEditor.json")
public:
DiffEditorPlugin();
~DiffEditorPlugin();
bool initialize(const QStringList &arguments, QString *errorMessage = 0);
void extensionsInitialized();
// Connect editor to settings changed signals.
void initializeEditor(DiffEditorWidget *editor);
private slots:
void diff();
private:
DiffEditorWidget *getDiffEditorWidget(const Core::IEditor *editor) const;
QString getFileContents(const QString &fileName, QTextCodec *codec) const;
};
class DiffEditorFactory : public Core::IEditorFactory
{
Q_OBJECT
public:
explicit DiffEditorFactory(DiffEditorPlugin *owner);
QStringList mimeTypes() const;
Core::IEditor *createEditor(QWidget *parent);
Core::Id id() const;
QString displayName() const;
private:
const QStringList m_mimeTypes;
DiffEditorPlugin *m_owner;
};
} // namespace Internal
} // namespace DIFFEditor
#endif // DIFFEDITORPLUGIN_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
/****************************************************************************
**
** Copyright (C) 2013 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 DIFFEDITORWIDGET_H
#define DIFFEDITORWIDGET_H
#include "diffeditor_global.h"
#include "differ.h"
#include <QTextEdit>
namespace TextEditor {
class BaseTextEditorWidget;
class SnippetEditorWidget;
}
QT_BEGIN_NAMESPACE
class QPlainTextEdit;
class QSplitter;
class QSyntaxHighlighter;
class QTextCharFormat;
QT_END_NAMESPACE
namespace DIFFEditor {
class DiffViewEditorWidget;
struct TextLineData {
enum TextLineType {
TextLine,
Separator,
Invalid
};
TextLineData() : textLineType(Invalid) {}
TextLineData(const QString &txt) : textLineType(TextLine), text(txt) {}
TextLineData(TextLineType t) : textLineType(t) {}
TextLineType textLineType;
QString text;
};
struct RowData {
RowData() : equal(true) {}
RowData(const TextLineData &l)
: leftLine(l), rightLine(l), equal(true) {}
RowData(const TextLineData &l, const TextLineData &r, bool e = false)
: leftLine(l), rightLine(r), equal(e) {}
RowData(const QString &txt)
: text(txt), equal(true) {}
TextLineData leftLine;
TextLineData rightLine;
QString text; // file of context description
bool equal; // true if left and right lines are equal, taking whitespaces into account (or both invalid)
};
struct ChunkData {
ChunkData() : skippedLinesBefore(0), leftOffset(0), rightOffset(0) {}
QList<RowData> rows;
int skippedLinesBefore; // info for text
int leftOffset;
int rightOffset;
// <absolute position in the file, absolute position in the file>
QMap<int, int> changedLeftPositions; // counting from the beginning of the chunk
QMap<int, int> changedRightPositions; // counting from the beginning of the chunk
QString text;
};
struct FileData {
FileData() {}
FileData(const ChunkData &chunkData) { chunks.append(chunkData); }
QList<ChunkData> chunks;
QList<int> chunkOffset;
QString text;
};
struct DiffData {
QList<FileData> files;
QString text;
};
class DIFFEDITOR_EXPORT DiffEditorWidget : public QWidget
{
Q_OBJECT
public:
DiffEditorWidget(QWidget *parent = 0);
~DiffEditorWidget();
void setDiff(const QString &leftText, const QString &rightText);
void setDiff(const QList<Diff> &diffList);
QTextCodec *codec() const;
public slots:
void setContextLinesNumber(int lines);
void setIgnoreWhitespaces(bool ignore);
protected:
TextEditor::SnippetEditorWidget *leftEditor() const;
TextEditor::SnippetEditorWidget *rightEditor() const;
private slots:
void leftSliderChanged();
void rightSliderChanged();
private:
bool isWhitespace(const QChar &c) const;
bool isWhitespace(const Diff &diff) const;
bool isEqual(const QList<Diff> &diffList, int diffNumber) const;
QList<QTextEdit::ExtraSelection> colorPositions(const QTextCharFormat &format,
QTextCursor &cursor,
const QMap<int, int> &positions) const;
void colorDiff(const FileData &fileData);
ChunkData calculateOriginalData(const QList<Diff> &diffList) const;
FileData calculateContextData(const ChunkData &originalData) const;
void showDiff();
DiffViewEditorWidget *m_leftEditor;
DiffViewEditorWidget *m_rightEditor;
QSplitter *m_splitter;
QList<Diff> m_diffList;
int m_contextLinesNumber;
bool m_ignoreWhitespaces;
ChunkData m_originalChunkData;
FileData m_contextFileData;
int m_leftSafePosHack;
int m_rightSafePosHack;
};
} // namespace DIFFEditor
#endif // DIFFEDITORWIDGET_H

View File

@@ -0,0 +1,499 @@
/****************************************************************************
**
** Copyright (C) 2013 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 "differ.h"
#include <QList>
#include <QStringList>
#include <QMap>
namespace DIFFEditor {
Diff::Diff() :
command(Diff::Equal)
{
}
Diff::Diff(Command com, const QString &txt) :
command(com),
text(txt)
{
}
Differ::Differ()
: m_diffMode(Differ::LineMode),
m_currentDiffMode(Differ::LineMode)
{
}
QList<Diff> Differ::diff(const QString &text1, const QString &text2)
{
m_currentDiffMode = m_diffMode;
return merge(preprocess1AndDiff(text1, text2));
}
void Differ::setDiffMode(Differ::DiffMode mode)
{
m_diffMode = mode;
}
bool Differ::diffMode() const
{
return m_diffMode;
}
QList<Diff> Differ::preprocess1AndDiff(const QString &text1, const QString &text2)
{
if (text1.isNull() && text2.isNull())
return QList<Diff>();
if (text1 == text2) {
QList<Diff> diffList;
if (!text1.isEmpty())
diffList.append(Diff(Diff::Equal, text1));
return diffList;
}
QString newText1 = text1;
QString newText2 = text2;
QString prefix;
QString suffix;
const int prefixCount = commonPrefix(text1, text2);
if (prefixCount) {
prefix = text1.left(prefixCount);
newText1 = text1.mid(prefixCount);
newText2 = text2.mid(prefixCount);
}
const int suffixCount = commonSuffix(newText1, newText2);
if (suffixCount) {
suffix = newText1.right(suffixCount);
newText1 = newText1.left(newText1.count() - suffixCount);
newText2 = newText2.left(newText2.count() - suffixCount);
}
QList<Diff> diffList = preprocess2AndDiff(newText1, newText2);
if (prefixCount)
diffList.prepend(Diff(Diff::Equal, prefix));
if (suffixCount)
diffList.append(Diff(Diff::Equal, suffix));
return diffList;
}
QList<Diff> Differ::preprocess2AndDiff(const QString &text1, const QString &text2)
{
QList<Diff> diffList;
if (text1.isEmpty()) {
diffList.append(Diff(Diff::Insert, text2));
return diffList;
}
if (text2.isEmpty()) {
diffList.append(Diff(Diff::Delete, text1));
return diffList;
}
if (text1.count() != text2.count())
{
const QString longtext = text1.count() > text2.count() ? text1 : text2;
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;
diffList.append(Diff(command, longtext.left(i)));
diffList.append(Diff(Diff::Equal, shorttext));
diffList.append(Diff(command, longtext.mid(i + shorttext.count())));
return diffList;
}
if (shorttext.count() == 1) {
diffList.append(Diff(Diff::Delete, text1));
diffList.append(Diff(Diff::Insert, text2));
return diffList;
}
}
if (m_currentDiffMode != Differ::CharMode && text1.count() > 80 && text2.count() > 80)
return diffNonCharMode(text1, text2);
return diffMyers(text1, text2);
}
QList<Diff> Differ::diffMyers(const QString &text1, const QString &text2)
{
const int n = text1.count();
const int m = text2.count();
const bool odd = (n + m) % 2;
const int D = odd ? (n + m) / 2 + 1 : (n + m) / 2;
const int delta = n - m;
const int vShift = D;
int *forwardV = new int[2 * D + 1]; // free me
int *reverseV = new int[2 * D + 1]; // free me
for (int i = 0; i <= 2 * D; i++) {
forwardV[i] = -1;
reverseV[i] = -1;
}
forwardV[vShift + 1] = 0;
reverseV[vShift + 1] = 0;
int kMinForward = -D;
int kMaxForward = D;
int kMinReverse = -D;
int kMaxReverse = D;
for (int d = 0; d <= D; d++) {
// going forward
for (int k = qMax(-d, kMinForward + qAbs(d + kMinForward) % 2);
k <= qMin(d, kMaxForward - qAbs(d + kMaxForward) % 2);
k = k + 2) {
int x;
if (k == -d || (k < d && forwardV[k + vShift - 1] < forwardV[k + vShift + 1])) {
x = forwardV[k + vShift + 1]; // copy vertically from diagonal k + 1, y increases, y may exceed the graph
} else {
x = forwardV[k + vShift - 1] + 1; // copy horizontally from diagonal k - 1, x increases, x may exceed the graph
}
int y = x - k;
if (x > n) {
kMaxForward = k - 1; // we are beyond the graph (right border), don't check diagonals >= current k anymore
} else if (y > m) {
kMinForward = k + 1; // we are beyond the graph (bottom border), don't check diagonals <= current k anymore
} else {
// find snake
while (x < n && y < m) {
if (text1.at(x) != text2.at(y))
break;
x++;
y++;
}
forwardV[k + vShift] = x;
if (odd) { // check if overlap
if (k >= delta - (d - 1) && k <= delta + (d - 1)) {
if (n - reverseV[delta - k + vShift] <= x) {
delete [] forwardV;
delete [] reverseV;
return diffMyersSplit(text1, x, text2, y);
}
}
}
}
}
// in reverse direction
for (int k = qMax(-d, kMinReverse + qAbs(d + kMinReverse) % 2);
k <= qMin(d, kMaxReverse - qAbs(d + kMaxReverse) % 2);
k = k + 2) {
int x;
if (k == -d || (k < d && reverseV[k + vShift - 1] < reverseV[k + vShift + 1])) {
x = reverseV[k + vShift + 1];
} else {
x = reverseV[k + vShift - 1] + 1;
}
int y = x - k;
if (x > n) {
kMaxReverse = k - 1; // we are beyond the graph (right border), don't check diagonals >= current k anymore
} else if (y > m) {
kMinReverse = k + 1; // we are beyond the graph (bottom border), don't check diagonals <= current k anymore
} else {
// find snake
while (x < n && y < m) {
if (text1.at(n - x - 1) != text2.at(m - y - 1))
break;
x++;
y++;
}
reverseV[k + vShift] = x;
if (!odd) { // check if overlap
if (k >= delta - d && k <= delta + d) {
if (n - forwardV[delta - k + vShift] <= x) {
delete [] forwardV;
delete [] reverseV;
return diffMyersSplit(text1, n - x, text2, m - x + k);
}
}
}
}
}
}
delete [] forwardV;
delete [] reverseV;
// Completely different
QList<Diff> diffList;
diffList.append(Diff(Diff::Delete, text1));
diffList.append(Diff(Diff::Insert, text2));
return diffList;
}
QList<Diff> Differ::diffMyersSplit(
const QString &text1, int x,
const QString &text2, int y)
{
const QString text11 = text1.left(x);
const QString text12 = text1.mid(x);
const QString text21 = text2.left(y);
const QString text22 = text2.mid(y);
QList<Diff> diffList1 = preprocess1AndDiff(text11, text21);
QList<Diff> diffList2 = preprocess1AndDiff(text12, text22);
return diffList1 + diffList2;
}
QList<Diff> Differ::diffNonCharMode(const QString text1, const QString text2)
{
QString encodedText1;
QString encodedText2;
QStringList subtexts = encode(text1, text2, &encodedText1, &encodedText2);
DiffMode diffMode = m_currentDiffMode;
m_currentDiffMode = CharMode;
// Each different subtext is a separate symbol
// process these symbols as text with bigger alphabet
QList<Diff> diffList = preprocess1AndDiff(encodedText1, encodedText2);
diffList = decode(diffList, subtexts);
QString lastDelete;
QString lastInsert;
QList<Diff> newDiffList;
for (int i = 0; i <= diffList.count(); i++) {
const Diff diffItem = i < diffList.count()
? diffList.at(i)
: Diff(Diff::Equal, QLatin1String("")); // 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) {
lastInsert += diffItem.text;
} else { // Diff::Equal
if (lastDelete.count() || lastInsert.count()) {
// Rediff here on char basis
newDiffList += preprocess1AndDiff(lastDelete, lastInsert);
lastDelete = QString();
lastInsert = QString();
}
newDiffList.append(diffItem);
}
}
m_currentDiffMode = diffMode;
return newDiffList;
}
QStringList Differ::encode(const QString &text1,
const QString &text2,
QString *encodedText1,
QString *encodedText2)
{
QStringList lines;
lines.append(QLatin1String("")); // don't use code: 0
QMap<QString, int> lineToCode;
*encodedText1 = encode(text1, &lines, &lineToCode);
*encodedText2 = encode(text2, &lines, &lineToCode);
return lines;
}
int Differ::findSubtextEnd(const QString &text,
int subtextStart)
{
if (m_currentDiffMode == Differ::LineMode) {
int subtextEnd = text.indexOf(QLatin1Char('\n'), subtextStart);
if (subtextEnd == -1) {
subtextEnd = text.count() - 1;
}
return ++subtextEnd;
} else if (m_currentDiffMode == Differ::WordMode) {
if (!text.at(subtextStart).isLetter())
return subtextStart + 1;
int i = subtextStart + 1;
const int count = text.count();
while (i < count && text.at(i).isLetter())
i++;
return i;
}
return subtextStart + 1; // CharMode
}
QString Differ::encode(const QString &text,
QStringList *lines,
QMap<QString, int> *lineToCode)
{
int subtextStart = 0;
int subtextEnd = -1;
QString codes;
while (subtextEnd < text.count()) {
subtextEnd = findSubtextEnd(text, subtextStart);
const QString line = text.mid(subtextStart, subtextEnd - subtextStart);
subtextStart = subtextEnd;
if (lineToCode->contains(line)) {
int code = lineToCode->value(line);
codes += QChar(static_cast<ushort>(code));
} else {
lines->append(line);
lineToCode->insert(line, lines->count() - 1);
codes += QChar(static_cast<ushort>(lines->count() - 1));
}
}
return codes;
}
QList<Diff> Differ::decode(const QList<Diff> &diffList,
const QStringList &lines)
{
QList<Diff> newDiffList;
for (int i = 0; i < diffList.count(); i++) {
Diff diff = diffList.at(i);
QString text;
for (int j = 0; j < diff.text.count(); j++) {
const int idx = static_cast<ushort>(diff.text.at(j).unicode());
text += lines.value(idx);
}
diff.text = text;
newDiffList.append(diff);
}
return newDiffList;
}
QList<Diff> Differ::merge(const QList<Diff> &diffList)
{
QString lastDelete;
QString lastInsert;
QList<Diff> newDiffList;
for (int i = 0; i <= diffList.count(); i++) {
const Diff diff = i < diffList.count()
? diffList.at(i)
: Diff(Diff::Equal, QString()); // 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) {
lastInsert += diff.text;
} else { // Diff::Equal
if (lastDelete.count() || lastInsert.count()) {
// common prefix and suffix?
if (lastDelete.count())
newDiffList.append(Diff(Diff::Delete, lastDelete));
if (lastInsert.count())
newDiffList.append(Diff(Diff::Insert, lastInsert));
if (diff.text.count())
newDiffList.append(diff);
lastDelete = QString();
lastInsert = QString();
} else { // join with last equal diff
if (newDiffList.count()
&& newDiffList.last().command == Diff::Equal) {
newDiffList.last().text += diff.text;
} else {
if (diff.text.count())
newDiffList.append(diff);
}
}
}
}
QList<Diff> squashedDiffList = squashEqualities(newDiffList);
if (squashedDiffList.count() != newDiffList.count())
return merge(squashedDiffList);
return squashedDiffList;
}
QList<Diff> Differ::squashEqualities(const QList<Diff> &diffList)
{
if (diffList.count() <= 3) // we need at least 3 items
return diffList;
QList<Diff> squashedDiffList;
Diff prevDiff = diffList.at(0);
Diff thisDiff = diffList.at(1);
Diff nextDiff = diffList.at(2);
int i = 2;
while (i < diffList.count()) {
if (prevDiff.command == Diff::Equal
&& nextDiff.command == Diff::Equal) {
if (thisDiff.text.endsWith(prevDiff.text)) {
thisDiff.text = prevDiff.text
+ thisDiff.text.left(thisDiff.text.count()
- prevDiff.text.count());
nextDiff.text = prevDiff.text + nextDiff.text;
} else if (thisDiff.text.startsWith(nextDiff.text)) {
prevDiff.text += nextDiff.text;
thisDiff.text = thisDiff.text.mid(nextDiff.text.count())
+ nextDiff.text;
i++;
if (i < diffList.count())
nextDiff = diffList.at(i);
squashedDiffList.append(prevDiff);
} else {
squashedDiffList.append(prevDiff);
}
} else {
squashedDiffList.append(prevDiff);
}
prevDiff = thisDiff;
thisDiff = nextDiff;
i++;
if (i < diffList.count())
nextDiff = diffList.at(i);
}
squashedDiffList.append(prevDiff);
if (i == diffList.count())
squashedDiffList.append(thisDiff);
return squashedDiffList;
}
int Differ::commonPrefix(const QString &text1, const QString &text2) const
{
int i = 0;
const int minCount = qMin(text1.count(), text2.count());
while (i < minCount) {
if (text1.at(i) != text2.at(i))
break;
i++;
}
return i;
}
int Differ::commonSuffix(const QString &text1, const QString &text2) const
{
int i = 0;
const int text1Count = text1.count();
const int text2Count = text2.count();
const int minCount = qMin(text1.count(), text2.count());
while (i < minCount) {
if (text1.at(text1Count - i - 1) != text2.at(text2Count - i - 1))
break;
i++;
}
return i;
}
} // namespace DIFFEditor

View File

@@ -0,0 +1,98 @@
/****************************************************************************
**
** Copyright (C) 2013 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 DIFFER_H
#define DIFFER_H
#include "diffeditor_global.h"
#include <QString>
QT_BEGIN_NAMESPACE
template <class K, class T>
class QMap;
QT_END_NAMESPACE
namespace DIFFEditor {
class DIFFEDITOR_EXPORT Diff
{
public:
enum Command {
Delete,
Insert,
Equal
};
Command command;
QString text;
Diff(Command com, const QString &txt);
Diff();
};
class DIFFEDITOR_EXPORT Differ
{
public:
enum DiffMode
{
CharMode,
WordMode,
LineMode
};
Differ();
QList<Diff> diff(const QString &text1, const QString &text2);
void setDiffMode(DiffMode mode);
bool diffMode() const;
private:
QList<Diff> preprocess1AndDiff(const QString &text1, const QString &text2);
QList<Diff> preprocess2AndDiff(const QString &text1, const QString &text2);
QList<Diff> diffMyers(const QString &text1, const QString &text2);
QList<Diff> diffMyersSplit(const QString &text1, int x,
const QString &text2, int y);
QList<Diff> merge(const QList<Diff> &diffList);
QList<Diff> squashEqualities(const QList<Diff> &diffList);
QList<Diff> diffNonCharMode(const QString text1, const QString text2);
QStringList encode(const QString &text1,
const QString &text2,
QString *encodedText1,
QString *encodedText2);
QString encode(const QString &text,
QStringList *lines,
QMap<QString, int> *lineToCode);
QList<Diff> decode(const QList<Diff> &diffList,
const QStringList &lines);
int findSubtextEnd(const QString &text,
int subTextStart);
int commonPrefix(const QString &text1, const QString &text2) const;
int commonSuffix(const QString &text1, const QString &text2) const;
DiffMode m_diffMode;
DiffMode m_currentDiffMode;
};
} // namespace DIFFEditor
#endif // DIFF_H