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

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