summaryrefslogtreecommitdiff
path: root/src/plugins/diffeditor/diffeditorwidget.cpp
diff options
context:
space:
mode:
authorjkobus <jaroslaw.kobus@digia.com>2013-02-15 12:49:50 +0100
committerhjk <hjk121@nokiamail.com>2013-02-18 18:15:51 +0100
commit0e91d10878e18412609e9d9e991ce12f0c2192c5 (patch)
treea5bb59988f615938d2388196eeb16deb48ec12db /src/plugins/diffeditor/diffeditorwidget.cpp
parentaa69b190f705702b24227d2e9c4665b225474698 (diff)
downloadqt-creator-0e91d10878e18412609e9d9e991ce12f0c2192c5.tar.gz
Experimental integration of DiffEditor
After enabling the plugin go to Tools|Diff... Change-Id: I793b6faedb93f58039df0a62e82fe04a017978ee Reviewed-by: hjk <hjk121@nokiamail.com>
Diffstat (limited to 'src/plugins/diffeditor/diffeditorwidget.cpp')
-rw-r--r--src/plugins/diffeditor/diffeditorwidget.cpp1027
1 files changed, 1027 insertions, 0 deletions
diff --git a/src/plugins/diffeditor/diffeditorwidget.cpp b/src/plugins/diffeditor/diffeditorwidget.cpp
new file mode 100644
index 0000000000..ed7998a020
--- /dev/null
+++ b/src/plugins/diffeditor/diffeditorwidget.cpp
@@ -0,0 +1,1027 @@
+/****************************************************************************
+**
+** 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 "diffeditorwidget.h"
+#include <QPlainTextEdit>
+#include <QSplitter>
+#include <QVBoxLayout>
+#include <QPlainTextDocumentLayout>
+#include <QTextBlock>
+#include <QScrollBar>
+#include <QPainter>
+#include <QTime>
+
+#include <QDebug>
+
+
+#include <texteditor/basetexteditor.h>
+#include <texteditor/snippets/snippeteditor.h>
+#include <texteditor/basetextdocumentlayout.h>
+#include <texteditor/syntaxhighlighter.h>
+#include <texteditor/basetextdocument.h>
+#include <texteditor/texteditorsettings.h>
+
+using namespace TextEditor;
+
+namespace DIFFEditor {
+
+//////////////////////
+
+class DiffViewEditor : public BaseTextEditor
+{
+Q_OBJECT
+public:
+ DiffViewEditor(BaseTextEditorWidget *editorWidget) : BaseTextEditor(editorWidget) {}
+ virtual Core::Id id() const { return "DiffViewEditor"; }
+ virtual bool duplicateSupported() const { return false; }
+ virtual IEditor *duplicate(QWidget *parent) { Q_UNUSED(parent) return 0; }
+ virtual bool isTemporary() const { return false; }
+
+};
+
+////////////////////////
+
+class DiffViewEditorWidget : public SnippetEditorWidget
+{
+ Q_OBJECT
+public:
+ DiffViewEditorWidget(QWidget *parent = 0);
+
+ void setSyntaxHighlighter(SyntaxHighlighter *sh) {
+ baseTextDocument()->setSyntaxHighlighter(sh);
+ }
+ QTextCodec *codec() const {
+ return const_cast<QTextCodec *>(baseTextDocument()->codec());
+ }
+
+ QMap<int, int> skippedLines() const { return m_skippedLines; }
+
+ void setLineNumber(int blockNumber, const QString &lineNumber);
+ void setSkippedLines(int visualLineNumber, int skippedLines) { m_skippedLines.insert(visualLineNumber, skippedLines); }
+ void clearLineNumbers();
+ void clearSkippedLines() { m_skippedLines.clear(); }
+
+protected:
+ virtual int extraAreaWidth(int *markWidthPtr = 0) const { return BaseTextEditorWidget::extraAreaWidth(markWidthPtr); }
+ BaseTextEditor *createEditor() { return new DiffViewEditor(this); }
+ virtual QString lineNumber(int blockNumber) const;
+ int lineNumberTopPositionOffset(int blockNumber) const;
+ virtual int lineNumberDigits() const;
+ virtual void paintEvent(QPaintEvent *e);
+ virtual void scrollContentsBy(int dx, int dy);
+
+private:
+ QMap<int, QString> m_lineNumbers;
+ int m_lineNumberDigits;
+ QMap<int, int> m_skippedLines;
+};
+
+DiffViewEditorWidget::DiffViewEditorWidget(QWidget *parent)
+ : SnippetEditorWidget(parent), m_lineNumberDigits(1)
+{
+ setLineNumbersVisible(true);
+ setFrameStyle(QFrame::NoFrame);
+}
+
+QString DiffViewEditorWidget::lineNumber(int blockNumber) const
+{
+ return m_lineNumbers.value(blockNumber);
+}
+
+int DiffViewEditorWidget::lineNumberTopPositionOffset(int blockNumber) const
+{
+ int offset = 0;
+ const QFontMetrics fm(extraArea()->font());
+ int i = 0;
+ const QString text = document()->findBlockByNumber(blockNumber).text();
+ while (i < text.count()) {
+ if (text.at(i) != QChar::LineSeparator)
+ break;
+ offset += fm.height();
+ i++;
+ }
+ return offset;
+}
+
+
+int DiffViewEditorWidget::lineNumberDigits() const
+{
+ return m_lineNumberDigits;
+}
+
+void DiffViewEditorWidget::setLineNumber(int blockNumber, const QString &lineNumber)
+{
+ m_lineNumbers.insert(blockNumber, lineNumber);
+ m_lineNumberDigits = qMax(m_lineNumberDigits, lineNumber.count());
+}
+
+void DiffViewEditorWidget::clearLineNumbers()
+{
+ m_lineNumbers.clear();
+ m_lineNumberDigits = 1;
+}
+
+void DiffViewEditorWidget::scrollContentsBy(int dx, int dy)
+{
+ SnippetEditorWidget::scrollContentsBy(dx, dy);
+ // TODO: update only chunk lines
+ viewport()->update();
+}
+
+void DiffViewEditorWidget::paintEvent(QPaintEvent *e)
+{
+ SnippetEditorWidget::paintEvent(e);
+
+ QPainter painter(viewport());
+
+ QPointF offset = contentOffset();
+ QTextBlock firstBlock = firstVisibleBlock();
+
+ QMap<int, int> skipped = skippedLines();
+ QMapIterator<int, int> itChunk(skipped);
+ while (itChunk.hasNext()) {
+ itChunk.next();
+ const int line = itChunk.key();
+ const int skipped = itChunk.value();
+
+ QTextCursor cursor = textCursor();
+ cursor.movePosition(QTextCursor::Start);
+ cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor, line);
+ QTextBlock block = cursor.block();
+ qreal top = blockBoundingGeometry(block).translated(offset).top();
+ qreal bottom = top + blockBoundingRect(block).height();
+
+ if (block.blockNumber() < firstBlock.blockNumber()
+ || top > e->rect().bottom())
+ continue;
+
+ if (block.isVisible() && bottom >= e->rect().top()) {
+ QTextLayout *layout = block.layout();
+ const int blockLine = line - block.firstLineNumber();
+ const int blockLineCount = layout->lineCount();
+ if (blockLine < blockLineCount) {
+ painter.save();
+ painter.setPen(palette().foreground().color());
+ QTextLine textLine = layout->lineAt(blockLine);
+// QRectF lineRect = textLine.naturalTextRect().translated(offset.x(), top);
+ QRectF lineRect = textLine.naturalTextRect().translated(0, top);
+ QString skippedRowsText = tr("Skipped %n lines...", 0, skipped);
+ QFontMetrics fm(font());
+ const int textWidth = fm.width(skippedRowsText);
+ painter.drawText(QPointF(lineRect.right()
+ + (viewport()->width() - textWidth) / 2.0,
+ lineRect.top() + textLine.ascent()),
+ skippedRowsText);
+ painter.restore();
+ }
+ }
+ }
+}
+
+//////////////////
+
+DiffEditorWidget::DiffEditorWidget(QWidget *parent)
+ : QWidget(parent),
+ m_contextLinesNumber(1),
+ m_ignoreWhitespaces(true),
+ m_leftSafePosHack(-1),
+ m_rightSafePosHack(-1)
+{
+ TextEditor::TextEditorSettings *settings = TextEditorSettings::instance();
+
+ m_leftEditor = new DiffViewEditorWidget(this);
+ m_leftEditor->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_leftEditor->setReadOnly(true);
+ m_leftEditor->setHighlightCurrentLine(false);
+ m_leftEditor->setWordWrapMode(QTextOption::NoWrap);
+ m_leftEditor->setFontSettings(settings->fontSettings());
+
+ m_rightEditor = new DiffViewEditorWidget(this);
+ m_rightEditor->setReadOnly(true);
+ m_rightEditor->setHighlightCurrentLine(false);
+ m_rightEditor->setWordWrapMode(QTextOption::NoWrap);
+ m_rightEditor->setFontSettings(settings->fontSettings());
+
+ connect(m_leftEditor->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(leftSliderChanged()));
+ connect(m_leftEditor->verticalScrollBar(), SIGNAL(actionTriggered(int)),
+ this, SLOT(leftSliderChanged()));
+ connect(m_leftEditor, SIGNAL(cursorPositionChanged()),
+ this, SLOT(leftSliderChanged()));
+ connect(m_rightEditor->verticalScrollBar(), SIGNAL(valueChanged(int)),
+ this, SLOT(rightSliderChanged()));
+ connect(m_rightEditor->verticalScrollBar(), SIGNAL(actionTriggered(int)),
+ this, SLOT(rightSliderChanged()));
+ connect(m_rightEditor, SIGNAL(cursorPositionChanged()),
+ this, SLOT(rightSliderChanged()));
+
+ m_splitter = new QSplitter(this);
+ m_splitter->addWidget(m_leftEditor);
+ m_splitter->addWidget(m_rightEditor);
+ QVBoxLayout *l = new QVBoxLayout(this);
+ l->addWidget(m_splitter);
+}
+
+DiffEditorWidget::~DiffEditorWidget()
+{
+
+}
+
+void DiffEditorWidget::setDiff(const QString &leftText, const QString &rightText)
+{
+// QTime time;
+// time.start();
+ Differ diffGenerator;
+ QList<Diff> list = diffGenerator.diff(leftText, rightText);
+// int ela = time.elapsed();
+// qDebug() << "Time spend in diff:" << ela;
+ setDiff(list);
+}
+
+void DiffEditorWidget::setDiff(const QList<Diff> &diffList)
+{
+ m_diffList = diffList;
+
+ QList<Diff> transformedDiffList = m_diffList;
+
+ m_originalChunkData = calculateOriginalData(transformedDiffList);
+ m_contextFileData = calculateContextData(m_originalChunkData);
+ showDiff();
+}
+
+void DiffEditorWidget::setContextLinesNumber(int lines)
+{
+ if (m_contextLinesNumber == lines)
+ return;
+
+ m_contextLinesNumber = lines;
+ m_contextFileData = calculateContextData(m_originalChunkData);
+ showDiff();
+}
+
+void DiffEditorWidget::setIgnoreWhitespaces(bool ignore)
+{
+ if (m_ignoreWhitespaces == ignore)
+ return;
+
+ m_ignoreWhitespaces = ignore;
+ setDiff(m_diffList);
+}
+
+QTextCodec *DiffEditorWidget::codec() const
+{
+ return const_cast<QTextCodec *>(m_leftEditor->codec());
+}
+
+SnippetEditorWidget *DiffEditorWidget::leftEditor() const
+{
+ return m_leftEditor;
+}
+
+SnippetEditorWidget *DiffEditorWidget::rightEditor() const
+{
+ return m_rightEditor;
+}
+
+bool DiffEditorWidget::isWhitespace(const QChar &c) const
+{
+ if (c == QLatin1Char(' ') || c == QLatin1Char('\t'))
+ return true;
+ return false;
+}
+
+bool DiffEditorWidget::isWhitespace(const Diff &diff) const
+{
+ for (int i = 0; i < diff.text.count(); i++) {
+ if (!isWhitespace(diff.text.at(i)))
+ return false;
+ }
+ return true;
+}
+
+bool DiffEditorWidget::isEqual(const QList<Diff> &diffList, int diffNumber) const
+{
+ const Diff &diff = diffList.at(diffNumber);
+ if (diff.command == Diff::Equal)
+ return true;
+
+ if (diff.text.count() == 0)
+ return true;
+
+ if (!m_ignoreWhitespaces)
+ return false;
+
+ if (isWhitespace(diff) == false)
+ return false;
+
+ if (diffNumber == 0 || diffNumber == diffList.count() - 1)
+ return false; // it's a Diff start or end
+
+ // Examine previous diff
+ if (diffNumber > 0) {
+ const Diff &previousDiff = diffList.at(diffNumber - 1);
+ if (previousDiff.command == Diff::Equal) {
+ const int previousDiffCount = previousDiff.text.count();
+ if (previousDiffCount && isWhitespace(previousDiff.text.at(previousDiffCount - 1)))
+ return true;
+ } else if (diff.command != previousDiff.command
+ && isWhitespace(previousDiff)) {
+ return true;
+ }
+ }
+
+ // Examine next diff
+ if (diffNumber < diffList.count() - 1) {
+ const Diff &nextDiff = diffList.at(diffNumber + 1);
+ if (nextDiff.command == Diff::Equal) {
+ const int nextDiffCount = nextDiff.text.count();
+ if (nextDiffCount && isWhitespace(nextDiff.text.at(0)))
+ return true;
+ } else if (diff.command != nextDiff.command
+ && isWhitespace(nextDiff)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+ChunkData DiffEditorWidget::calculateOriginalData(const QList<Diff> &diffList) const
+{
+ ChunkData chunkData;
+
+ QStringList leftLines;
+ QStringList rightLines;
+ leftLines.append(QString());
+ rightLines.append(QString());
+ QMap<int, int> leftLineSpans;
+ QMap<int, int> rightLineSpans;
+ QMap<int, int> leftChangedPositions;
+ QMap<int, int> rightChangedPositions;
+ QList<int> leftEqualLines;
+ QList<int> rightEqualLines;
+
+ int currentLeftLine = 0;
+ int currentLeftPos = 0;
+ int currentLeftLineOffset = 0;
+ int currentRightLine = 0;
+ int currentRightPos = 0;
+ int currentRightLineOffset = 0;
+ int lastAlignedLeftLine = -1;
+ int lastAlignedRightLine = -1;
+ bool lastLeftLineEqual = true;
+ bool lastRightLineEqual = true;
+
+ for (int i = 0; i < diffList.count(); i++) {
+ Diff diff = diffList.at(i);
+
+ const QStringList lines = diff.text.split(QLatin1Char('\n'));
+
+ const bool equal = isEqual(diffList, i);
+ if (diff.command == Diff::Insert) {
+ lastRightLineEqual = lastRightLineEqual ? equal : false;
+ } else if (diff.command == Diff::Delete) {
+ lastLeftLineEqual = lastLeftLineEqual ? equal : false;
+ }
+
+ const int lastLeftPos = currentLeftPos;
+ const int lastRightPos = currentRightPos;
+
+ for (int j = 0; j < lines.count(); j++) {
+ const QString line = lines.at(j);
+
+ if (j > 0) {
+ if (diff.command == Diff::Equal) {
+ if (lastLeftLineEqual && lastRightLineEqual) {
+ leftEqualLines.append(currentLeftLine);
+ rightEqualLines.append(currentRightLine);
+ }
+ }
+
+ if (diff.command != Diff::Insert) {
+ currentLeftLine++;
+ currentLeftLineOffset++;
+ leftLines.append(QString());
+ currentLeftPos++;
+ lastLeftLineEqual = line.count() ? equal : true;
+ }
+ if (diff.command != Diff::Delete) {
+ currentRightLine++;
+ currentRightLineOffset++;
+ rightLines.append(QString());
+ currentRightPos++;
+ lastRightLineEqual = line.count() ? equal : true;
+ }
+ }
+
+ if (diff.command == Diff::Delete) {
+ leftLines.last() += line;
+ currentLeftPos += line.count();
+ }
+ else if (diff.command == Diff::Insert) {
+ rightLines.last() += line;
+ currentRightPos += line.count();
+ }
+ else if (diff.command == Diff::Equal) {
+ if ((line.count() || (j && j < lines.count() - 1)) && // don't treat empty ending line as a line to be aligned unless a line is a one char '/n' only.
+ currentLeftLine != lastAlignedLeftLine &&
+ currentRightLine != lastAlignedRightLine) {
+ // apply line spans before the current lines
+ if (currentLeftLineOffset < currentRightLineOffset) {
+ const int spans = currentRightLineOffset - currentLeftLineOffset;
+ leftLineSpans[currentLeftLine] = spans;
+// currentLeftPos += spans;
+ } else if (currentRightLineOffset < currentLeftLineOffset) {
+ const int spans = currentLeftLineOffset - currentRightLineOffset;
+ rightLineSpans[currentRightLine] = spans;
+// currentRightPos += spans;
+ }
+ currentLeftLineOffset = 0;
+ currentRightLineOffset = 0;
+ lastAlignedLeftLine = currentLeftLine;
+ lastAlignedRightLine = currentRightLine;
+ }
+
+ leftLines.last() += line;
+ rightLines.last() += line;
+ currentLeftPos += line.count();
+ currentRightPos += line.count();
+ }
+ }
+
+ if (!equal) {
+ if (diff.command == Diff::Delete && lastLeftPos != currentLeftPos)
+ leftChangedPositions.insert(lastLeftPos, currentLeftPos);
+ else if (diff.command == Diff::Insert && lastRightPos != currentRightPos)
+ rightChangedPositions.insert(lastRightPos, currentRightPos);
+ }
+ }
+
+ if (diffList.count() && diffList.last().command == Diff::Equal) {
+ if (lastLeftLineEqual && lastRightLineEqual) {
+ leftEqualLines.append(currentLeftLine);
+ rightEqualLines.append(currentRightLine);
+ }
+ }
+
+ QList<TextLineData> leftData;
+ int spanOffset = 0;
+ int pos = 0;
+ QMap<int, int>::ConstIterator leftChangedIt = leftChangedPositions.constBegin();
+ for (int i = 0; i < leftLines.count(); i++) {
+ for (int j = 0; j < leftLineSpans.value(i); j++) {
+ leftData.append(TextLineData(TextLineData::Separator));
+ spanOffset++;
+ }
+ const int textLength = leftLines.at(i).count() + 1;
+ pos += textLength;
+ leftData.append(leftLines.at(i));
+ while (leftChangedIt != leftChangedPositions.constEnd()) {
+ if (leftChangedIt.key() >= pos)
+ break;
+
+ const int startPos = leftChangedIt.key() + spanOffset;
+ const int endPos = leftChangedIt.value() + spanOffset;
+ chunkData.changedLeftPositions.insert(startPos, endPos);
+ leftChangedIt++;
+ }
+ }
+ while (leftChangedIt != leftChangedPositions.constEnd()) {
+ if (leftChangedIt.key() >= pos)
+ break;
+
+ const int startPos = leftChangedIt.key() + spanOffset;
+ const int endPos = leftChangedIt.value() + spanOffset;
+ chunkData.changedLeftPositions.insert(startPos, endPos);
+ leftChangedIt++;
+ }
+
+ QList<TextLineData> rightData;
+ spanOffset = 0;
+ pos = 0;
+ QMap<int, int>::ConstIterator rightChangedIt = rightChangedPositions.constBegin();
+ for (int i = 0; i < rightLines.count(); i++) {
+ for (int j = 0; j < rightLineSpans.value(i); j++) {
+ rightData.append(TextLineData(TextLineData::Separator));
+ spanOffset++;
+ }
+ const int textLength = rightLines.at(i).count() + 1;
+ pos += textLength;
+ rightData.append(rightLines.at(i));
+ while (rightChangedIt != rightChangedPositions.constEnd()) {
+ if (rightChangedIt.key() >= pos)
+ break;
+
+ const int startPos = rightChangedIt.key() + spanOffset;
+ const int endPos = rightChangedIt.value() + spanOffset;
+ chunkData.changedRightPositions.insert(startPos, endPos);
+ rightChangedIt++;
+ }
+ }
+ while (rightChangedIt != rightChangedPositions.constEnd()) {
+ if (rightChangedIt.key() >= pos)
+ break;
+
+ const int startPos = rightChangedIt.key() + spanOffset;
+ const int endPos = rightChangedIt.value() + spanOffset;
+ chunkData.changedRightPositions.insert(startPos, endPos);
+ rightChangedIt++;
+ }
+
+ // fill ending separators
+ for (int i = leftData.count(); i < rightData.count(); i++)
+ leftData.append(TextLineData(TextLineData::Separator));
+ for (int i = rightData.count(); i < leftData.count(); i++)
+ rightData.append(TextLineData(TextLineData::Separator));
+
+ const int visualLineCount = leftData.count();
+ int l = 0;
+ int r = 0;
+ for (int i = 0; i < visualLineCount; i++) {
+ RowData row(leftData.at(i), rightData.at(i));
+ if (row.leftLine.textLineType == TextLineData::Separator
+ && row.rightLine.textLineType == TextLineData::Separator)
+ row.equal = true;
+ if (row.leftLine.textLineType == TextLineData::TextLine
+ && row.rightLine.textLineType == TextLineData::TextLine
+ && leftEqualLines.contains(l)
+ && rightEqualLines.contains(r))
+ row.equal = true;
+ chunkData.rows.append(row);
+ if (leftData.at(i).textLineType == TextLineData::TextLine)
+ l++;
+ if (rightData.at(i).textLineType == TextLineData::TextLine)
+ r++;
+ }
+ return chunkData;
+}
+
+FileData DiffEditorWidget::calculateContextData(const ChunkData &originalData) const
+{
+ if (m_contextLinesNumber < 0)
+ return FileData(originalData);
+
+ const int joinChunkThreshold = 0;
+
+ FileData fileData;
+ QMap<int, bool> hiddenRows;
+ int i = 0;
+ while (i < originalData.rows.count()) {
+ const RowData &row = originalData.rows[i];
+ if (row.equal) {
+ // count how many equal
+ int equalRowStart = i;
+ i++;
+ while (i < originalData.rows.count()) {
+ if (!originalData.rows.at(i).equal)
+ break;
+ i++;
+ }
+ const bool first = equalRowStart == 0; // includes first line?
+ const bool last = i == originalData.rows.count(); // includes last line?
+
+ const int firstLine = first ? 0 : equalRowStart + m_contextLinesNumber;
+ const int lastLine = last ? originalData.rows.count() : i - m_contextLinesNumber;
+
+ if (firstLine < lastLine - joinChunkThreshold) {
+ for (int j = firstLine; j < lastLine; j++) {
+ hiddenRows.insert(j, true);
+ }
+ }
+ } else {
+ // iterate to the next row
+ i++;
+ }
+ }
+ i = 0;
+ int skippedLines = 0;
+ int leftCharCounter = 0;
+ int rightCharCounter = 0;
+ int leftCharSkipped = 0;
+ int rightCharSkipped = 0;
+ int chunkOffset = 0;
+ QMap<int, int>::ConstIterator leftChangedIt = originalData.changedLeftPositions.constBegin();
+ QMap<int, int>::ConstIterator rightChangedIt = originalData.changedRightPositions.constBegin();
+ while (i < originalData.rows.count()) {
+ if (!hiddenRows.contains(i)) {
+ ChunkData chunkData;
+ if (skippedLines)
+ chunkOffset++; // for chunk line
+ chunkData.leftOffset = leftCharCounter;
+ chunkData.rightOffset = rightCharCounter;
+ chunkData.skippedLinesBefore = skippedLines;
+ while (i < originalData.rows.count()) {
+ if (hiddenRows.contains(i))
+ break;
+ RowData rowData = originalData.rows.at(i);
+ chunkData.rows.append(rowData);
+
+ leftCharCounter += rowData.leftLine.text.count() + 1; // +1 for separator or for '\n', each line has one of it
+ rightCharCounter += rowData.rightLine.text.count() + 1; // +1 for separator or for '\n', each line has one of it
+ i++;
+ }
+ while (leftChangedIt != originalData.changedLeftPositions.constEnd()) {
+ if (leftChangedIt.key() < chunkData.leftOffset
+ || leftChangedIt.key() > leftCharCounter)
+ break;
+
+ const int startPos = leftChangedIt.key() - leftCharSkipped + chunkOffset;
+ const int endPos = leftChangedIt.value() - leftCharSkipped + chunkOffset;
+ chunkData.changedLeftPositions.insert(startPos, endPos);
+ leftChangedIt++;
+ }
+ while (rightChangedIt != originalData.changedRightPositions.constEnd()) {
+ if (rightChangedIt.key() < chunkData.rightOffset
+ || rightChangedIt.key() > rightCharCounter)
+ break;
+
+ const int startPos = rightChangedIt.key() - rightCharSkipped + chunkOffset;
+ const int endPos = rightChangedIt.value() - rightCharSkipped + chunkOffset;
+ chunkData.changedRightPositions.insert(startPos, endPos);
+ rightChangedIt++;
+ }
+ fileData.chunks.append(chunkData);
+ skippedLines = 0;
+ } else {
+ const int leftChars = originalData.rows.at(i).leftLine.text.count() + 1;
+ const int rightChars = originalData.rows.at(i).rightLine.text.count() + 1;
+ leftCharCounter += leftChars;
+ rightCharCounter += rightChars;
+ leftCharSkipped += leftChars;
+ rightCharSkipped += rightChars;
+ i++;
+ skippedLines++;
+ }
+ }
+
+ if (skippedLines) {
+ ChunkData chunkData;
+ chunkData.leftOffset = leftCharCounter;
+ chunkData.rightOffset = rightCharCounter;
+ chunkData.skippedLinesBefore = skippedLines;
+ fileData.chunks.append(chunkData);
+ }
+
+ return fileData;
+}
+
+void DiffEditorWidget::showDiff()
+{
+ QTime time;
+ time.start();
+ const int verticalValue = m_leftEditor->verticalScrollBar()->value();
+ const int leftHorizontalValue = m_leftEditor->horizontalScrollBar()->value();
+ const int rightHorizontalValue = m_rightEditor->horizontalScrollBar()->value();
+
+ // Ugly hack starts here
+ // When the cursor stays on the line with QChar::LineSeparator
+ // the new inserted text will have the background taken from that line.
+ if (m_leftSafePosHack >= 0) {
+ QTextCursor cursor = m_leftEditor->textCursor();
+ cursor.setPosition(m_leftSafePosHack);
+ m_leftEditor->setTextCursor(cursor);
+ m_leftSafePosHack = -1;
+ }
+ if (m_rightSafePosHack >= 0) {
+ QTextCursor cursor = m_rightEditor->textCursor();
+ cursor.setPosition(m_rightSafePosHack);
+ m_rightEditor->setTextCursor(cursor);
+ m_rightSafePosHack = -1;
+ }
+ // Ugly hack ends here
+
+ m_leftEditor->clear();
+ m_rightEditor->clear();
+ m_leftEditor->clearLineNumbers();
+ m_rightEditor->clearLineNumbers();
+ m_leftEditor->clearSkippedLines();
+ m_rightEditor->clearSkippedLines();
+ int ela1 = time.elapsed();
+
+ QString leftText, rightText;
+ bool leftStartWithNewLine = false;
+ bool rightStartWithNewLine = false;
+ int leftLineNumber = 0;
+ int leftBlockNumber = 0;
+ int rightLineNumber = 0;
+ int rightBlockNumber = 0;
+ int line = 0;
+ int leftPos = 0;
+ int rightPos = 0;
+ for (int i = 0; i < m_contextFileData.chunks.count(); i++) {
+ ChunkData chunkData = m_contextFileData.chunks.at(i);
+ int leftSeparatorCount = 0;
+ int rightSeparatorCount = 0;
+ leftLineNumber += chunkData.skippedLinesBefore;
+ rightLineNumber += chunkData.skippedLinesBefore;
+
+ if (chunkData.skippedLinesBefore) {
+ if (leftStartWithNewLine) {
+ leftText += QLatin1Char('\n');
+ leftStartWithNewLine = false;
+ leftPos++;
+ }
+ leftText += QChar::LineSeparator; // for chunk description
+ leftPos++;
+ m_leftEditor->setSkippedLines(line, chunkData.skippedLinesBefore);
+ if (rightStartWithNewLine) {
+ rightText += QLatin1Char('\n');
+ rightStartWithNewLine = false;
+ rightPos++;
+ }
+ rightText += QChar::LineSeparator; // for chunk description
+ rightPos++;
+ m_rightEditor->setSkippedLines(line, chunkData.skippedLinesBefore);
+
+ line++;
+ }
+
+ for (int j = 0; j < chunkData.rows.count(); j++) {
+ line++;
+ RowData rowData = chunkData.rows.at(j);
+ TextLineData leftLineData = rowData.leftLine;
+ TextLineData rightLineData = rowData.rightLine;
+
+ if (leftLineData.textLineType == TextLineData::TextLine) {
+ if (leftStartWithNewLine) {
+ leftText += QLatin1Char('\n');
+ leftPos++;
+ }
+ if (leftSeparatorCount) {
+ QString separators;
+ separators.fill(QChar::LineSeparator, leftSeparatorCount);
+ leftText += separators;
+ leftSeparatorCount = 0;
+ leftPos += leftSeparatorCount;
+ }
+ if (m_leftSafePosHack < 0)
+ m_leftSafePosHack = leftPos;
+ leftPos += leftLineData.text.count();
+ leftText += leftLineData.text;
+ leftStartWithNewLine = true;
+ leftLineNumber++;
+ m_leftEditor->setLineNumber(leftBlockNumber, QString::number(leftLineNumber));
+ leftBlockNumber++;
+ } else if (leftLineData.textLineType == TextLineData::Separator) {
+ leftSeparatorCount++;
+ }
+
+ if (j == chunkData.rows.count() - 1 && leftSeparatorCount) {
+ if (leftStartWithNewLine) {
+ leftText += QLatin1Char('\n');
+ leftPos++;
+ }
+ QString separators;
+ separators.fill(QChar::LineSeparator, leftSeparatorCount);
+ leftText += separators;
+ leftStartWithNewLine = false;
+ leftPos += leftSeparatorCount;
+ }
+
+ if (rightLineData.textLineType == TextLineData::TextLine) {
+ if (rightStartWithNewLine) {
+ rightText += QLatin1Char('\n');
+ rightPos++;
+ }
+ if (rightSeparatorCount) {
+ QString separators;
+ separators.fill(QChar::LineSeparator, rightSeparatorCount);
+ rightText += separators;
+ rightSeparatorCount = 0;
+ rightPos += rightSeparatorCount;
+ }
+ if (m_rightSafePosHack < 0)
+ m_rightSafePosHack = rightPos;
+ rightPos += leftLineData.text.count();
+ rightText += rightLineData.text;
+ rightStartWithNewLine = true;
+ rightLineNumber++;
+ m_rightEditor->setLineNumber(rightBlockNumber, QString::number(rightLineNumber));
+ rightBlockNumber++;
+ } else if (rightLineData.textLineType == TextLineData::Separator) {
+ rightSeparatorCount++;
+ }
+
+ if (j == chunkData.rows.count() - 1 && rightSeparatorCount) {
+ if (rightStartWithNewLine) {
+ rightText += QLatin1Char('\n');
+ rightPos++;
+ }
+ QString separators;
+ separators.fill(QChar::LineSeparator, rightSeparatorCount);
+ rightText += separators;
+ rightStartWithNewLine = false;
+ rightPos += rightSeparatorCount;
+ }
+ }
+ }
+ int ela2 = time.elapsed();
+
+ m_leftEditor->setPlainText(leftText + QLatin1String("\n"));
+ m_rightEditor->setPlainText(rightText + QLatin1String("\n"));
+ int ela3 = time.elapsed();
+
+ QTextBlock bl;
+ bl = m_leftEditor->document()->lastBlock();
+ bl.setVisible(false);
+ bl.setLineCount(0);
+ bl = m_rightEditor->document()->lastBlock();
+ bl.setVisible(false);
+ bl.setLineCount(0);
+
+ int ela4 = time.elapsed();
+
+ colorDiff(m_contextFileData);
+ int ela5 = time.elapsed();
+
+ m_leftEditor->verticalScrollBar()->setValue(verticalValue);
+ m_rightEditor->verticalScrollBar()->setValue(verticalValue);
+ m_leftEditor->horizontalScrollBar()->setValue(leftHorizontalValue);
+ m_rightEditor->horizontalScrollBar()->setValue(rightHorizontalValue);
+ int ela6 = time.elapsed();
+ qDebug() << ela1 << ela2 << ela3 << ela4 << ela5 << ela6;
+}
+
+QList<QTextEdit::ExtraSelection> DiffEditorWidget::colorPositions(
+ const QTextCharFormat &format,
+ QTextCursor &cursor,
+ const QMap<int, int> &positions) const
+{
+ QList<QTextEdit::ExtraSelection> lineSelections;
+
+ cursor.setPosition(0);
+ QMapIterator<int, int> itPositions(positions);
+ while (itPositions.hasNext()) {
+ itPositions.next();
+
+ cursor.setPosition(itPositions.key());
+ cursor.setPosition(itPositions.value(), QTextCursor::KeepAnchor);
+
+ QTextEdit::ExtraSelection selection;
+ selection.cursor = cursor;
+ selection.format = format;
+ lineSelections.append(selection);
+ }
+ return lineSelections;
+}
+
+void DiffEditorWidget::colorDiff(const FileData &fileData)
+{
+ QTextCharFormat leftLineFormat;
+ leftLineFormat.setBackground(QColor(255, 223, 223));
+ leftLineFormat.setProperty(QTextFormat::FullWidthSelection, true);
+
+ QTextCharFormat leftCharFormat;
+ leftCharFormat.setBackground(QColor(255, 175, 175));
+ leftCharFormat.setProperty(QTextFormat::FullWidthSelection, true);
+
+ QTextCharFormat rightLineFormat;
+ rightLineFormat.setBackground(QColor(223, 255, 223));
+ rightLineFormat.setProperty(QTextFormat::FullWidthSelection, true);
+
+ QTextCharFormat rightCharFormat;
+ rightCharFormat.setBackground(QColor(175, 255, 175));
+ rightCharFormat.setProperty(QTextFormat::FullWidthSelection, true);
+
+ QPalette pal = m_leftEditor->extraArea()->palette();
+ pal.setCurrentColorGroup(QPalette::Active);
+ QTextCharFormat spanLineFormat;
+ spanLineFormat.setBackground(pal.color(QPalette::Background));
+ spanLineFormat.setProperty(QTextFormat::FullWidthSelection, true);
+
+ QTextCharFormat chunkLineFormat;
+ chunkLineFormat.setBackground(QColor(175, 215, 231));
+ chunkLineFormat.setProperty(QTextFormat::FullWidthSelection, true);
+
+ int leftPos = 0;
+ int rightPos = 0;
+ // startPos, endPos
+ QMap<int, int> leftChangedPos;
+ QMap<int, int> rightChangedPos;
+ QMap<int, int> leftSkippedPos;
+ QMap<int, int> rightSkippedPos;
+ QMap<int, int> leftChunkPos;
+ QMap<int, int> rightChunkPos;
+ int leftLastDiffBlockStartPos = 0;
+ int rightLastDiffBlockStartPos = 0;
+ int leftLastSkippedBlockStartPos = 0;
+ int rightLastSkippedBlockStartPos = 0;
+
+ for (int i = 0; i < fileData.chunks.count(); i++) {
+ ChunkData chunkData = fileData.chunks.at(i);
+ if (chunkData.skippedLinesBefore) {
+ leftChunkPos[leftPos] = leftPos + 1;
+ rightChunkPos[rightPos] = rightPos + 1;
+ leftPos++; // for chunk line
+ rightPos++; // for chunk line
+ }
+ leftLastDiffBlockStartPos = leftPos;
+ rightLastDiffBlockStartPos = rightPos;
+ leftLastSkippedBlockStartPos = leftPos;
+ rightLastSkippedBlockStartPos = rightPos;
+
+ for (int j = 0; j < chunkData.rows.count(); j++) {
+ RowData rowData = chunkData.rows.at(j);
+
+ leftPos += rowData.leftLine.text.count() + 1; // +1 for separator or for '\n', each line has one of it
+ rightPos += rowData.rightLine.text.count() + 1; // +1 for separator or for '\n', each line has one of it
+
+ if (!rowData.equal) {
+ if (rowData.leftLine.textLineType == TextLineData::TextLine) {
+ leftChangedPos[leftLastDiffBlockStartPos] = leftPos;
+ leftLastSkippedBlockStartPos = leftPos;
+ } else {
+ leftSkippedPos[leftLastSkippedBlockStartPos] = leftPos;
+ leftLastDiffBlockStartPos = leftPos;
+ }
+ if (rowData.rightLine.textLineType == TextLineData::TextLine) {
+ rightChangedPos[rightLastDiffBlockStartPos] = rightPos;
+ rightLastSkippedBlockStartPos = rightPos;
+ } else {
+ rightSkippedPos[rightLastSkippedBlockStartPos] = rightPos;
+ rightLastDiffBlockStartPos = rightPos;
+ }
+ } else {
+ leftLastDiffBlockStartPos = leftPos;
+ rightLastDiffBlockStartPos = rightPos;
+ leftLastSkippedBlockStartPos = leftPos;
+ rightLastSkippedBlockStartPos = rightPos;
+ }
+ }
+ }
+
+ QTextCursor leftCursor = m_leftEditor->textCursor();
+ QTextCursor rightCursor = m_rightEditor->textCursor();
+
+ QList<QTextEdit::ExtraSelection> leftSelections
+ = colorPositions(leftLineFormat, leftCursor, leftChangedPos);
+ leftSelections
+ += colorPositions(spanLineFormat, leftCursor, leftSkippedPos);
+ leftSelections
+ += colorPositions(chunkLineFormat, leftCursor, leftChunkPos);
+
+ QList<QTextEdit::ExtraSelection> rightSelections
+ = colorPositions(rightLineFormat, rightCursor, rightChangedPos);
+ rightSelections
+ += colorPositions(spanLineFormat, rightCursor, rightSkippedPos);
+ rightSelections
+ += colorPositions(chunkLineFormat, rightCursor, rightChunkPos);
+
+ for (int i = 0; i < fileData.chunks.count(); i++) {
+ ChunkData chunkData = fileData.chunks.at(i);
+ leftSelections
+ += colorPositions(leftCharFormat, leftCursor, chunkData.changedLeftPositions);
+ rightSelections
+ += colorPositions(rightCharFormat, rightCursor, chunkData.changedRightPositions);
+ }
+
+ m_leftEditor->setExtraSelections(BaseTextEditorWidget::OtherSelection,
+ m_leftEditor->extraSelections(BaseTextEditorWidget::OtherSelection)
+ + leftSelections);
+ m_rightEditor->setExtraSelections(BaseTextEditorWidget::OtherSelection,
+ m_rightEditor->extraSelections(BaseTextEditorWidget::OtherSelection)
+ + rightSelections);
+}
+
+void DiffEditorWidget::leftSliderChanged()
+{
+ m_rightEditor->verticalScrollBar()->setValue(m_leftEditor->verticalScrollBar()->value());
+}
+
+void DiffEditorWidget::rightSliderChanged()
+{
+ m_leftEditor->verticalScrollBar()->setValue(m_rightEditor->verticalScrollBar()->value());
+}
+
+
+
+} // namespace DIFFEditor
+
+#include "diffeditorwidget.moc"