forked from qt-creator/qt-creator
Move some functions to Differ, add unified diff
Change-Id: Id8178fb84f25a18fda42e02954f28359d0ef4623 Reviewed-by: Jarek Kobus <jaroslaw.kobus@digia.com>
This commit is contained in:
@@ -236,6 +236,617 @@ static int cleanupSemanticsScore(const QString &text1, const QString &text2)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool isWhitespace(const QChar &c)
|
||||
{
|
||||
if (c == QLatin1Char(' ') || c == QLatin1Char('\t'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isNewLine(const QChar &c)
|
||||
{
|
||||
if (c == QLatin1Char('\n'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Splits the diffList into left and right diff lists.
|
||||
* The left diff list contains the original (insertions removed),
|
||||
* while the right diff list contains the destination (deletions removed).
|
||||
*/
|
||||
void Differ::splitDiffList(const QList<Diff> &diffList,
|
||||
QList<Diff> *leftDiffList,
|
||||
QList<Diff> *rightDiffList)
|
||||
{
|
||||
if (!leftDiffList || !rightDiffList)
|
||||
return;
|
||||
|
||||
leftDiffList->clear();
|
||||
rightDiffList->clear();
|
||||
|
||||
for (int i = 0; i < diffList.count(); i++) {
|
||||
const Diff diff = diffList.at(i);
|
||||
|
||||
if (diff.command != Diff::Delete)
|
||||
rightDiffList->append(diff);
|
||||
if (diff.command != Diff::Insert)
|
||||
leftDiffList->append(diff);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* input should be only the left or right list of diffs, not a mix of both.
|
||||
*
|
||||
* Moves whitespace characters from Diff::Delete or Diff::Insert into
|
||||
* surrounding Diff::Equal, if possible.
|
||||
* It may happen, that some Diff::Delete of Diff::Insert will disappear.
|
||||
*/
|
||||
QList<Diff> Differ::moveWhitespaceIntoEqualities(const QList<Diff> &input)
|
||||
{
|
||||
QList<Diff> output = input;
|
||||
|
||||
for (int i = 0; i < output.count(); i++) {
|
||||
Diff diff = output[i];
|
||||
|
||||
if (diff.command != Diff::Equal) {
|
||||
if (i > 0) { // check previous equality
|
||||
Diff &previousDiff = output[i - 1];
|
||||
const int previousDiffCount = previousDiff.text.count();
|
||||
if (previousDiff.command == Diff::Equal
|
||||
&& previousDiffCount
|
||||
&& 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
|
||||
previousDiff.text.append(diff.text.left(j));
|
||||
diff.text = diff.text.mid(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i < output.count() - 1) { // check next equality
|
||||
const int diffCount = diff.text.count();
|
||||
Diff &nextDiff = output[i + 1];
|
||||
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
|
||||
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
|
||||
nextDiff.text.prepend(diff.text.mid(diffCount - j));
|
||||
diff.text = diff.text.left(diffCount - j);
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove diff if empty
|
||||
if (diff.text.isEmpty()) {
|
||||
output.removeAt(i);
|
||||
--i;
|
||||
} else {
|
||||
output[i] = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encodes any sentence of whitespaces with one simple space.
|
||||
*
|
||||
* The mapping is returned by codeMap argument, which contains
|
||||
* the position in output string of encoded whitespace character
|
||||
* and it's corresponding original sequence of whitespace characters.
|
||||
*
|
||||
* The returned string contains encoded version of the input string.
|
||||
*/
|
||||
static QString encodeReducedWhitespace(const QString &input,
|
||||
QMap<int, QString> *codeMap)
|
||||
{
|
||||
QString output;
|
||||
if (!codeMap)
|
||||
return output;
|
||||
|
||||
int inputIndex = 0;
|
||||
int outputIndex = 0;
|
||||
while (inputIndex < input.count()) {
|
||||
QChar c = input.at(inputIndex);
|
||||
|
||||
if (isWhitespace(c)) {
|
||||
output.append(QLatin1Char(' '));
|
||||
codeMap->insert(outputIndex, QString(c));
|
||||
++inputIndex;
|
||||
|
||||
while (inputIndex < input.count()) {
|
||||
QChar reducedChar = input.at(inputIndex);
|
||||
|
||||
if (!isWhitespace(reducedChar))
|
||||
break;
|
||||
|
||||
(*codeMap)[outputIndex].append(reducedChar);
|
||||
++inputIndex;
|
||||
}
|
||||
} else {
|
||||
output.append(c);
|
||||
++inputIndex;
|
||||
}
|
||||
++outputIndex;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is corresponding function to encodeReducedWhitespace().
|
||||
*
|
||||
* The input argument contains version encoded with codeMap,
|
||||
* the returned value contains decoded diff list.
|
||||
*/
|
||||
static QList<Diff> decodeReducedWhitespace(const QList<Diff> &input,
|
||||
const QMap<int, QString> &codeMap)
|
||||
{
|
||||
QList<Diff> output;
|
||||
|
||||
int counter = 0;
|
||||
QMap<int, QString>::const_iterator it = codeMap.constBegin();
|
||||
const QMap<int, QString>::const_iterator itEnd = codeMap.constEnd();
|
||||
for (int i = 0; i < input.count(); i++) {
|
||||
Diff diff = input.at(i);
|
||||
const int diffCount = diff.text.count();
|
||||
while ((it != itEnd) && (it.key() < counter + diffCount)) {
|
||||
const int reversePosition = diffCount + counter - it.key();
|
||||
const int updatedDiffCount = diff.text.count();
|
||||
diff.text.replace(updatedDiffCount - reversePosition, 1, it.value());
|
||||
++it;
|
||||
}
|
||||
output.append(diff);
|
||||
counter += diffCount;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftDiff is expected to be Diff::Delete and rightDiff is expected to be Diff::Insert.
|
||||
*
|
||||
* Encode any sentence of whitespaces with simple space (inside leftDiff and rightDiff),
|
||||
* diff without cleanup,
|
||||
* split diffs,
|
||||
* decode.
|
||||
*/
|
||||
void Differ::diffWithWhitespaceReduced(const QString &leftInput,
|
||||
const QString &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (!leftOutput || !rightOutput)
|
||||
return;
|
||||
|
||||
leftOutput->clear();
|
||||
rightOutput->clear();
|
||||
|
||||
QMap<int, QString> leftCodeMap;
|
||||
QMap<int, QString> rightCodeMap;
|
||||
const QString leftString = encodeReducedWhitespace(leftInput, &leftCodeMap);
|
||||
const QString rightString = encodeReducedWhitespace(rightInput, &rightCodeMap);
|
||||
|
||||
Differ differ;
|
||||
QList<Diff> diffList = differ.diff(leftString, rightString);
|
||||
|
||||
QList<Diff> leftDiffList;
|
||||
QList<Diff> rightDiffList;
|
||||
Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
|
||||
|
||||
*leftOutput = decodeReducedWhitespace(leftDiffList, leftCodeMap);
|
||||
*rightOutput = decodeReducedWhitespace(rightDiffList, rightCodeMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftDiff is expected to be Diff::Delete and rightDiff is expected to be Diff::Insert.
|
||||
*
|
||||
* Encode any sentence of whitespaces with simple space (inside leftDiff and rightDiff),
|
||||
* diff without cleanup,
|
||||
* split diffs,
|
||||
* decode.
|
||||
*/
|
||||
void Differ::unifiedDiffWithWhitespaceReduced(const QString &leftInput,
|
||||
const QString &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (!leftOutput || !rightOutput)
|
||||
return;
|
||||
|
||||
leftOutput->clear();
|
||||
rightOutput->clear();
|
||||
|
||||
QMap<int, QString> leftCodeMap;
|
||||
QMap<int, QString> rightCodeMap;
|
||||
const QString leftString = encodeReducedWhitespace(leftInput, &leftCodeMap);
|
||||
const QString rightString = encodeReducedWhitespace(rightInput, &rightCodeMap);
|
||||
|
||||
Differ differ;
|
||||
QList<Diff> diffList = differ.unifiedDiff(leftString, rightString);
|
||||
|
||||
QList<Diff> leftDiffList;
|
||||
QList<Diff> rightDiffList;
|
||||
Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
|
||||
|
||||
*leftOutput = decodeReducedWhitespace(leftDiffList, leftCodeMap);
|
||||
*rightOutput = decodeReducedWhitespace(rightDiffList, rightCodeMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftEquality and rightEquality needs to be equal. They may differ only with
|
||||
* whitespaces character (count and kind).
|
||||
*
|
||||
* Replaces any corresponding sentence of whitespaces inside left and right equality
|
||||
* with space characters. The number of space characters inside
|
||||
* replaced sequence depends on the longest sequence of whitespace characters
|
||||
* either in left or right equlity.
|
||||
*
|
||||
* E.g., when:
|
||||
* leftEquality: "a b c" (3 whitespace characters, 5 whitespace characters)
|
||||
* rightEquality: "a /tb /t c" (2 whitespace characters, 7 whitespace characters)
|
||||
* then:
|
||||
* returned value: "a b c" (3 space characters, 7 space characters)
|
||||
*
|
||||
* The returned code maps contains the info about the encoding done.
|
||||
* The key on the map is the position of encoding inside the output string,
|
||||
* and the value, which is a pair of int and string,
|
||||
* describes how many characters were encoded in the output string
|
||||
* and what was the original whitespace sequence in the original
|
||||
* For the above example it would be:
|
||||
*
|
||||
* leftCodeMap: <1, <3, " "> >
|
||||
* <5, <7, " "> >
|
||||
* rightCodeMap: <1, <3, " /t"> >
|
||||
* <5, <7, " /t "> >
|
||||
*
|
||||
*/
|
||||
static QString encodeExpandedWhitespace(const QString &leftEquality,
|
||||
const QString &rightEquality,
|
||||
QMap<int, QPair<int, QString> > *leftCodeMap,
|
||||
QMap<int, QPair<int, QString> > *rightCodeMap,
|
||||
bool *ok)
|
||||
{
|
||||
if (ok)
|
||||
*ok = false;
|
||||
|
||||
if (!leftCodeMap || !rightCodeMap)
|
||||
return QString();
|
||||
|
||||
leftCodeMap->clear();
|
||||
rightCodeMap->clear();
|
||||
QString output;
|
||||
|
||||
const int leftCount = leftEquality.count();
|
||||
const int rightCount = rightEquality.count();
|
||||
int leftIndex = 0;
|
||||
int rightIndex = 0;
|
||||
while (leftIndex < leftCount && rightIndex < rightCount) {
|
||||
QString leftWhitespaces;
|
||||
QString rightWhitespaces;
|
||||
while (leftIndex < leftCount && isWhitespace(leftEquality.at(leftIndex))) {
|
||||
leftWhitespaces.append(leftEquality.at(leftIndex));
|
||||
++leftIndex;
|
||||
}
|
||||
while (rightIndex < rightCount && isWhitespace(rightEquality.at(rightIndex))) {
|
||||
rightWhitespaces.append(rightEquality.at(rightIndex));
|
||||
++rightIndex;
|
||||
}
|
||||
|
||||
if (leftIndex < leftCount && rightIndex < rightCount) {
|
||||
if (leftEquality.at(leftIndex) != rightEquality.at(rightIndex))
|
||||
return QString(); // equalities broken
|
||||
|
||||
} else if (leftIndex == leftCount && rightIndex == rightCount) {
|
||||
; // do nothing, the last iteration
|
||||
} else {
|
||||
return QString(); // equalities broken
|
||||
}
|
||||
|
||||
if ((leftWhitespaces.count() && !rightWhitespaces.count())
|
||||
|| (!leftWhitespaces.count() && rightWhitespaces.count())) {
|
||||
return QString(); // there must be at least 1 corresponding whitespace, equalities broken
|
||||
}
|
||||
|
||||
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));
|
||||
output.append(replacement);
|
||||
}
|
||||
|
||||
if (leftIndex < leftCount)
|
||||
output.append(leftEquality.at(leftIndex)); // add common character
|
||||
|
||||
++leftIndex;
|
||||
++rightIndex;
|
||||
}
|
||||
|
||||
if (ok)
|
||||
*ok = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is corresponding function to encodeExpandedWhitespace().
|
||||
*
|
||||
* The input argument contains version encoded with codeMap,
|
||||
* the returned value contains decoded diff list.
|
||||
*/
|
||||
static QList<Diff> decodeExpandedWhitespace(const QList<Diff> input,
|
||||
const QMap<int, QPair<int, QString> > &codeMap,
|
||||
bool *ok)
|
||||
{
|
||||
if (ok)
|
||||
*ok = false;
|
||||
|
||||
QList<Diff> output;
|
||||
|
||||
int counter = 0;
|
||||
QMap<int, QPair<int, QString> >::const_iterator it = codeMap.constBegin();
|
||||
const QMap<int, QPair<int, QString> >::const_iterator itEnd = codeMap.constEnd();
|
||||
for (int i = 0; i < input.count(); i++) {
|
||||
Diff diff = input.at(i);
|
||||
const int diffCount = diff.text.count();
|
||||
while ((it != itEnd) && (it.key() < counter + diffCount)) {
|
||||
const int replacementSize = it.value().first;
|
||||
const int reversePosition = diffCount + counter - it.key();
|
||||
if (reversePosition < replacementSize)
|
||||
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);
|
||||
++it;
|
||||
}
|
||||
output.append(diff);
|
||||
counter += diffCount;
|
||||
}
|
||||
|
||||
if (ok)
|
||||
*ok = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftInput and rightInput should contain the same number of equalities,
|
||||
* equalities should differ only in whitespaces.
|
||||
*
|
||||
* Encodes any sentence of whitespace characters in equalities only
|
||||
* with the maximal number of corresponding whitespace characters
|
||||
* (inside leftInput and rightInput), so that the leftInput and rightInput
|
||||
* can be merged together again,
|
||||
* diff merged sequence with cleanup,
|
||||
* decode.
|
||||
*/
|
||||
static bool diffWithWhitespaceExpandedInEqualities(const QList<Diff> &leftInput,
|
||||
const QList<Diff> &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (!leftOutput || !rightOutput)
|
||||
return false;
|
||||
|
||||
leftOutput->clear();
|
||||
rightOutput->clear();
|
||||
|
||||
const int leftCount = leftInput.count();
|
||||
const int rightCount = rightInput.count();
|
||||
int l = 0;
|
||||
int r = 0;
|
||||
|
||||
QString leftText;
|
||||
QString rightText;
|
||||
|
||||
QMap<int, QPair<int, QString> > commonLeftCodeMap;
|
||||
QMap<int, QPair<int, QString> > commonRightCodeMap;
|
||||
|
||||
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) {
|
||||
QMap<int, QPair<int, QString> > leftCodeMap;
|
||||
QMap<int, QPair<int, QString> > rightCodeMap;
|
||||
|
||||
bool ok = false;
|
||||
QString commonEquality = encodeExpandedWhitespace(leftDiff.text,
|
||||
rightDiff.text,
|
||||
&leftCodeMap,
|
||||
&rightCodeMap,
|
||||
&ok);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
// join code map positions with common maps
|
||||
QMapIterator<int, QPair<int, QString> > itLeft(leftCodeMap);
|
||||
while (itLeft.hasNext()) {
|
||||
itLeft.next();
|
||||
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());
|
||||
}
|
||||
|
||||
leftText.append(commonEquality);
|
||||
rightText.append(commonEquality);
|
||||
|
||||
++l;
|
||||
++r;
|
||||
}
|
||||
|
||||
if (leftDiff.command != Diff::Equal) {
|
||||
leftText.append(leftDiff.text);
|
||||
++l;
|
||||
}
|
||||
if (rightDiff.command != Diff::Equal) {
|
||||
rightText.append(rightDiff.text);
|
||||
++r;
|
||||
}
|
||||
}
|
||||
|
||||
Differ differ;
|
||||
QList<Diff> diffList = differ.cleanupSemantics(differ.diff(leftText, rightText));
|
||||
|
||||
QList<Diff> leftDiffList;
|
||||
QList<Diff> rightDiffList;
|
||||
Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
|
||||
|
||||
leftDiffList = Differ::moveWhitespaceIntoEqualities(leftDiffList);
|
||||
rightDiffList = Differ::moveWhitespaceIntoEqualities(rightDiffList);
|
||||
|
||||
bool ok = false;
|
||||
*leftOutput = decodeExpandedWhitespace(leftDiffList, commonLeftCodeMap, &ok);
|
||||
if (!ok)
|
||||
return false;
|
||||
*rightOutput = decodeExpandedWhitespace(rightDiffList, commonRightCodeMap, &ok);
|
||||
if (!ok)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void appendWithEqualitiesSquashed(const QList<Diff> &leftInput,
|
||||
const QList<Diff> &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (leftInput.count()
|
||||
&& rightInput.count()
|
||||
&& leftOutput->count()
|
||||
&& rightOutput->count()
|
||||
&& leftInput.first().command == Diff::Equal
|
||||
&& rightInput.first().command == Diff::Equal
|
||||
&& leftOutput->last().command == Diff::Equal
|
||||
&& rightOutput->last().command == Diff::Equal) {
|
||||
leftOutput->last().text += leftInput.first().text;
|
||||
rightOutput->last().text += rightInput.first().text;
|
||||
leftOutput->append(leftInput.mid(1));
|
||||
rightOutput->append(rightInput.mid(1));
|
||||
return;
|
||||
}
|
||||
leftOutput->append(leftInput);
|
||||
rightOutput->append(rightInput);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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:
|
||||
* - 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
|
||||
*/
|
||||
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) {
|
||||
QList<Diff> outputLeftDiffList;
|
||||
QList<Diff> outputRightDiffList;
|
||||
|
||||
QList<Diff> reducedLeftDiffList;
|
||||
QList<Diff> reducedRightDiffList;
|
||||
diffWithWhitespaceReduced(previousLeftDiff.text,
|
||||
previousRightDiff.text,
|
||||
&reducedLeftDiffList,
|
||||
&reducedRightDiffList);
|
||||
|
||||
reducedLeftDiffList = moveWhitespaceIntoEqualities(reducedLeftDiffList);
|
||||
reducedRightDiffList = moveWhitespaceIntoEqualities(reducedRightDiffList);
|
||||
|
||||
QList<Diff> cleanedLeftDiffList;
|
||||
QList<Diff> cleanedRightDiffList;
|
||||
if (diffWithWhitespaceExpandedInEqualities(reducedLeftDiffList,
|
||||
reducedRightDiffList,
|
||||
&cleanedLeftDiffList,
|
||||
&cleanedRightDiffList)) {
|
||||
outputLeftDiffList = cleanedLeftDiffList;
|
||||
outputRightDiffList = cleanedRightDiffList;
|
||||
} else {
|
||||
outputLeftDiffList = reducedLeftDiffList;
|
||||
outputRightDiffList = reducedRightDiffList;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////
|
||||
|
||||
|
||||
@@ -293,6 +904,24 @@ QList<Diff> Differ::diff(const QString &text1, const QString &text2)
|
||||
return merge(preprocess1AndDiff(text1, text2));
|
||||
}
|
||||
|
||||
QList<Diff> Differ::unifiedDiff(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 = merge(preprocess1AndDiff(encodedText1, encodedText2));
|
||||
|
||||
diffList = decode(diffList, subtexts);
|
||||
m_currentDiffMode = diffMode;
|
||||
return diffList;
|
||||
}
|
||||
|
||||
void Differ::setDiffMode(Differ::DiffMode mode)
|
||||
{
|
||||
m_diffMode = mode;
|
||||
|
@@ -69,12 +69,30 @@ public:
|
||||
};
|
||||
Differ();
|
||||
QList<Diff> diff(const QString &text1, const QString &text2);
|
||||
QList<Diff> unifiedDiff(const QString &text1, const QString &text2);
|
||||
void setDiffMode(DiffMode mode);
|
||||
DiffMode diffMode() const;
|
||||
static QList<Diff> merge(const QList<Diff> &diffList);
|
||||
static QList<Diff> cleanupSemantics(const QList<Diff> &diffList);
|
||||
static QList<Diff> cleanupSemanticsLossless(const QList<Diff> &diffList);
|
||||
|
||||
static void splitDiffList(const QList<Diff> &diffList,
|
||||
QList<Diff> *leftDiffList,
|
||||
QList<Diff> *rightDiffList);
|
||||
static QList<Diff> moveWhitespaceIntoEqualities(const QList<Diff> &input);
|
||||
static void diffWithWhitespaceReduced(const QString &leftInput,
|
||||
const QString &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput);
|
||||
static void unifiedDiffWithWhitespaceReduced(const QString &leftInput,
|
||||
const QString &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput);
|
||||
static void diffBetweenEqualities(const QList<Diff> &leftInput,
|
||||
const QList<Diff> &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput);
|
||||
|
||||
private:
|
||||
QList<Diff> preprocess1AndDiff(const QString &text1, const QString &text2);
|
||||
QList<Diff> preprocess2AndDiff(const QString &text1, const QString &text2);
|
||||
|
@@ -109,20 +109,6 @@ public:
|
||||
|
||||
//////////////////////
|
||||
|
||||
static bool isWhitespace(const QChar &c)
|
||||
{
|
||||
if (c == QLatin1Char(' ') || c == QLatin1Char('\t'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isNewLine(const QChar &c)
|
||||
{
|
||||
if (c == QLatin1Char('\n'))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static QList<TextLineData> assemblyRows(const QStringList &lines,
|
||||
const QMap<int, int> &lineSpans,
|
||||
const QMap<int, bool> &equalLines,
|
||||
@@ -174,573 +160,6 @@ static QList<TextLineData> assemblyRows(const QStringList &lines,
|
||||
return data;
|
||||
}
|
||||
|
||||
/*
|
||||
* Splits the diffList into left and right diff lists.
|
||||
* The left diff list contains the original (insertions removed),
|
||||
* while the right diff list contains the destination (deletions removed).
|
||||
*/
|
||||
static void splitDiffList(const QList<Diff> &diffList,
|
||||
QList<Diff> *leftDiffList,
|
||||
QList<Diff> *rightDiffList)
|
||||
{
|
||||
if (!leftDiffList || !rightDiffList)
|
||||
return;
|
||||
|
||||
leftDiffList->clear();
|
||||
rightDiffList->clear();
|
||||
|
||||
for (int i = 0; i < diffList.count(); i++) {
|
||||
const Diff diff = diffList.at(i);
|
||||
|
||||
if (diff.command != Diff::Delete)
|
||||
rightDiffList->append(diff);
|
||||
if (diff.command != Diff::Insert)
|
||||
leftDiffList->append(diff);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* input should be only the left or right list of diffs, not a mix of both.
|
||||
*
|
||||
* Moves whitespace characters from Diff::Delete or Diff::Insert into
|
||||
* surrounding Diff::Equal, if possible.
|
||||
* It may happen, that some Diff::Delete of Diff::Insert will disappear.
|
||||
*/
|
||||
static QList<Diff> moveWhitespaceIntoEqualities(const QList<Diff> &input)
|
||||
{
|
||||
QList<Diff> output = input;
|
||||
|
||||
for (int i = 0; i < output.count(); i++) {
|
||||
Diff diff = output[i];
|
||||
|
||||
if (diff.command != Diff::Equal) {
|
||||
if (i > 0) { // check previous equality
|
||||
Diff &previousDiff = output[i - 1];
|
||||
const int previousDiffCount = previousDiff.text.count();
|
||||
if (previousDiff.command == Diff::Equal
|
||||
&& previousDiffCount
|
||||
&& 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
|
||||
previousDiff.text.append(diff.text.left(j));
|
||||
diff.text = diff.text.mid(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i < output.count() - 1) { // check next equality
|
||||
const int diffCount = diff.text.count();
|
||||
Diff &nextDiff = output[i + 1];
|
||||
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
|
||||
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
|
||||
nextDiff.text.prepend(diff.text.mid(diffCount - j));
|
||||
diff.text = diff.text.left(diffCount - j);
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove diff if empty
|
||||
if (diff.text.isEmpty()) {
|
||||
output.removeAt(i);
|
||||
--i;
|
||||
} else {
|
||||
output[i] = diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encodes any sentence of whitespaces with one simple space.
|
||||
*
|
||||
* The mapping is returned by codeMap argument, which contains
|
||||
* the position in output string of encoded whitespace character
|
||||
* and it's corresponding original sequence of whitespace characters.
|
||||
*
|
||||
* The returned string contains encoded version of the input string.
|
||||
*/
|
||||
static QString encodeReducedWhitespace(const QString &input,
|
||||
QMap<int, QString> *codeMap)
|
||||
{
|
||||
QString output;
|
||||
if (!codeMap)
|
||||
return output;
|
||||
|
||||
int inputIndex = 0;
|
||||
int outputIndex = 0;
|
||||
while (inputIndex < input.count()) {
|
||||
QChar c = input.at(inputIndex);
|
||||
|
||||
if (isWhitespace(c)) {
|
||||
output.append(QLatin1Char(' '));
|
||||
codeMap->insert(outputIndex, QString(c));
|
||||
++inputIndex;
|
||||
|
||||
while (inputIndex < input.count()) {
|
||||
QChar reducedChar = input.at(inputIndex);
|
||||
|
||||
if (!isWhitespace(reducedChar))
|
||||
break;
|
||||
|
||||
(*codeMap)[outputIndex].append(reducedChar);
|
||||
++inputIndex;
|
||||
}
|
||||
} else {
|
||||
output.append(c);
|
||||
++inputIndex;
|
||||
}
|
||||
++outputIndex;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is corresponding function to encodeReducedWhitespace().
|
||||
*
|
||||
* The input argument contains version encoded with codeMap,
|
||||
* the returned value contains decoded diff list.
|
||||
*/
|
||||
static QList<Diff> decodeReducedWhitespace(const QList<Diff> &input,
|
||||
const QMap<int, QString> &codeMap)
|
||||
{
|
||||
QList<Diff> output;
|
||||
|
||||
int counter = 0;
|
||||
QMap<int, QString>::const_iterator it = codeMap.constBegin();
|
||||
const QMap<int, QString>::const_iterator itEnd = codeMap.constEnd();
|
||||
for (int i = 0; i < input.count(); i++) {
|
||||
Diff diff = input.at(i);
|
||||
const int diffCount = diff.text.count();
|
||||
while ((it != itEnd) && (it.key() < counter + diffCount)) {
|
||||
const int reversePosition = diffCount + counter - it.key();
|
||||
const int updatedDiffCount = diff.text.count();
|
||||
diff.text.replace(updatedDiffCount - reversePosition, 1, it.value());
|
||||
++it;
|
||||
}
|
||||
output.append(diff);
|
||||
counter += diffCount;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftDiff is expected to be Diff::Delete and rightDiff is expected to be Diff::Insert.
|
||||
*
|
||||
* Encode any sentence of whitespaces with simple space (inside leftDiff and rightDiff),
|
||||
* diff without cleanup,
|
||||
* split diffs,
|
||||
* decode.
|
||||
*/
|
||||
static void diffWithWhitespaceReduced(const Diff &leftDiff,
|
||||
const Diff &rightDiff,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (!leftOutput || !rightOutput)
|
||||
return;
|
||||
|
||||
leftOutput->clear();
|
||||
rightOutput->clear();
|
||||
|
||||
if (leftDiff.command != Diff::Delete)
|
||||
return;
|
||||
|
||||
if (rightDiff.command != Diff::Insert)
|
||||
return;
|
||||
|
||||
QMap<int, QString> leftCodeMap;
|
||||
QMap<int, QString> rightCodeMap;
|
||||
const QString leftString = encodeReducedWhitespace(leftDiff.text, &leftCodeMap);
|
||||
const QString rightString = encodeReducedWhitespace(rightDiff.text, &rightCodeMap);
|
||||
|
||||
Differ differ;
|
||||
QList<Diff> diffList = differ.diff(leftString, rightString);
|
||||
|
||||
QList<Diff> leftDiffList;
|
||||
QList<Diff> rightDiffList;
|
||||
splitDiffList(diffList, &leftDiffList, &rightDiffList);
|
||||
|
||||
*leftOutput = decodeReducedWhitespace(leftDiffList, leftCodeMap);
|
||||
*rightOutput = decodeReducedWhitespace(rightDiffList, rightCodeMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftEquality and rightEquality needs to be equal. They may differ only with
|
||||
* whitespaces character (count and kind).
|
||||
*
|
||||
* Replaces any corresponding sentence of whitespaces inside left and right equality
|
||||
* with space characters. The number of space characters inside
|
||||
* replaced sequence depends on the longest sequence of whitespace characters
|
||||
* either in left or right equlity.
|
||||
*
|
||||
* E.g., when:
|
||||
* leftEquality: "a b c" (3 whitespace characters, 5 whitespace characters)
|
||||
* rightEquality: "a /tb /t c" (2 whitespace characters, 7 whitespace characters)
|
||||
* then:
|
||||
* returned value: "a b c" (3 space characters, 7 space characters)
|
||||
*
|
||||
* The returned code maps contains the info about the encoding done.
|
||||
* The key on the map is the position of encoding inside the output string,
|
||||
* and the value, which is a pair of int and string,
|
||||
* describes how many characters were encoded in the output string
|
||||
* and what was the original whitespace sequence in the original
|
||||
* For the above example it would be:
|
||||
*
|
||||
* leftCodeMap: <1, <3, " "> >
|
||||
* <5, <7, " "> >
|
||||
* rightCodeMap: <1, <3, " /t"> >
|
||||
* <5, <7, " /t "> >
|
||||
*
|
||||
*/
|
||||
static QString encodeExpandedWhitespace(const QString &leftEquality,
|
||||
const QString &rightEquality,
|
||||
QMap<int, QPair<int, QString> > *leftCodeMap,
|
||||
QMap<int, QPair<int, QString> > *rightCodeMap,
|
||||
bool *ok)
|
||||
{
|
||||
if (ok)
|
||||
*ok = false;
|
||||
|
||||
if (!leftCodeMap || !rightCodeMap)
|
||||
return QString();
|
||||
|
||||
leftCodeMap->clear();
|
||||
rightCodeMap->clear();
|
||||
QString output;
|
||||
|
||||
const int leftCount = leftEquality.count();
|
||||
const int rightCount = rightEquality.count();
|
||||
int leftIndex = 0;
|
||||
int rightIndex = 0;
|
||||
while (leftIndex < leftCount && rightIndex < rightCount) {
|
||||
QString leftWhitespaces;
|
||||
QString rightWhitespaces;
|
||||
while (leftIndex < leftCount && isWhitespace(leftEquality.at(leftIndex))) {
|
||||
leftWhitespaces.append(leftEquality.at(leftIndex));
|
||||
++leftIndex;
|
||||
}
|
||||
while (rightIndex < rightCount && isWhitespace(rightEquality.at(rightIndex))) {
|
||||
rightWhitespaces.append(rightEquality.at(rightIndex));
|
||||
++rightIndex;
|
||||
}
|
||||
|
||||
if (leftIndex < leftCount && rightIndex < rightCount) {
|
||||
if (leftEquality.at(leftIndex) != rightEquality.at(rightIndex))
|
||||
return QString(); // equalities broken
|
||||
|
||||
} else if (leftIndex == leftCount && rightIndex == rightCount) {
|
||||
; // do nothing, the last iteration
|
||||
} else {
|
||||
return QString(); // equalities broken
|
||||
}
|
||||
|
||||
if ((leftWhitespaces.count() && !rightWhitespaces.count())
|
||||
|| (!leftWhitespaces.count() && rightWhitespaces.count())) {
|
||||
return QString(); // there must be at least 1 corresponding whitespace, equalities broken
|
||||
}
|
||||
|
||||
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));
|
||||
output.append(replacement);
|
||||
}
|
||||
|
||||
if (leftIndex < leftCount)
|
||||
output.append(leftEquality.at(leftIndex)); // add common character
|
||||
|
||||
++leftIndex;
|
||||
++rightIndex;
|
||||
}
|
||||
|
||||
if (ok)
|
||||
*ok = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* This is corresponding function to encodeExpandedWhitespace().
|
||||
*
|
||||
* The input argument contains version encoded with codeMap,
|
||||
* the returned value contains decoded diff list.
|
||||
*/
|
||||
static QList<Diff> decodeExpandedWhitespace(const QList<Diff> input,
|
||||
const QMap<int, QPair<int, QString> > &codeMap,
|
||||
bool *ok)
|
||||
{
|
||||
if (ok)
|
||||
*ok = false;
|
||||
|
||||
QList<Diff> output;
|
||||
|
||||
int counter = 0;
|
||||
QMap<int, QPair<int, QString> >::const_iterator it = codeMap.constBegin();
|
||||
const QMap<int, QPair<int, QString> >::const_iterator itEnd = codeMap.constEnd();
|
||||
for (int i = 0; i < input.count(); i++) {
|
||||
Diff diff = input.at(i);
|
||||
const int diffCount = diff.text.count();
|
||||
while ((it != itEnd) && (it.key() < counter + diffCount)) {
|
||||
const int replacementSize = it.value().first;
|
||||
const int reversePosition = diffCount + counter - it.key();
|
||||
if (reversePosition < replacementSize)
|
||||
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);
|
||||
++it;
|
||||
}
|
||||
output.append(diff);
|
||||
counter += diffCount;
|
||||
}
|
||||
|
||||
if (ok)
|
||||
*ok = true;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prerequisites:
|
||||
* leftInput and rightInput should contain the same number of equalities,
|
||||
* equalities should differ only in whitespaces.
|
||||
*
|
||||
* Encodes any sentence of whitespace characters in equalities only
|
||||
* with the maximal number of corresponding whitespace characters
|
||||
* (inside leftInput and rightInput), so that the leftInput and rightInput
|
||||
* can be merged together again,
|
||||
* diff merged sequence with cleanup,
|
||||
* decode.
|
||||
*/
|
||||
static bool diffWithWhitespaceExpandedInEqualities(const QList<Diff> &leftInput,
|
||||
const QList<Diff> &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (!leftOutput || !rightOutput)
|
||||
return false;
|
||||
|
||||
leftOutput->clear();
|
||||
rightOutput->clear();
|
||||
|
||||
const int leftCount = leftInput.count();
|
||||
const int rightCount = rightInput.count();
|
||||
int l = 0;
|
||||
int r = 0;
|
||||
|
||||
QString leftText;
|
||||
QString rightText;
|
||||
|
||||
QMap<int, QPair<int, QString> > commonLeftCodeMap;
|
||||
QMap<int, QPair<int, QString> > commonRightCodeMap;
|
||||
|
||||
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) {
|
||||
QMap<int, QPair<int, QString> > leftCodeMap;
|
||||
QMap<int, QPair<int, QString> > rightCodeMap;
|
||||
|
||||
bool ok = false;
|
||||
QString commonEquality = encodeExpandedWhitespace(leftDiff.text,
|
||||
rightDiff.text,
|
||||
&leftCodeMap,
|
||||
&rightCodeMap,
|
||||
&ok);
|
||||
if (!ok)
|
||||
return false;
|
||||
|
||||
// join code map positions with common maps
|
||||
QMapIterator<int, QPair<int, QString> > itLeft(leftCodeMap);
|
||||
while (itLeft.hasNext()) {
|
||||
itLeft.next();
|
||||
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());
|
||||
}
|
||||
|
||||
leftText.append(commonEquality);
|
||||
rightText.append(commonEquality);
|
||||
|
||||
++l;
|
||||
++r;
|
||||
}
|
||||
|
||||
if (leftDiff.command != Diff::Equal) {
|
||||
leftText.append(leftDiff.text);
|
||||
++l;
|
||||
}
|
||||
if (rightDiff.command != Diff::Equal) {
|
||||
rightText.append(rightDiff.text);
|
||||
++r;
|
||||
}
|
||||
}
|
||||
|
||||
Differ differ;
|
||||
QList<Diff> diffList = differ.cleanupSemantics(differ.diff(leftText, rightText));
|
||||
|
||||
QList<Diff> leftDiffList;
|
||||
QList<Diff> rightDiffList;
|
||||
splitDiffList(diffList, &leftDiffList, &rightDiffList);
|
||||
|
||||
leftDiffList = moveWhitespaceIntoEqualities(leftDiffList);
|
||||
rightDiffList = moveWhitespaceIntoEqualities(rightDiffList);
|
||||
|
||||
bool ok = false;
|
||||
*leftOutput = decodeExpandedWhitespace(leftDiffList, commonLeftCodeMap, &ok);
|
||||
if (!ok)
|
||||
return false;
|
||||
*rightOutput = decodeExpandedWhitespace(rightDiffList, commonRightCodeMap, &ok);
|
||||
if (!ok)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void appendWithEqualitiesSquashed(const QList<Diff> &leftInput,
|
||||
const QList<Diff> &rightInput,
|
||||
QList<Diff> *leftOutput,
|
||||
QList<Diff> *rightOutput)
|
||||
{
|
||||
if (leftInput.count()
|
||||
&& rightInput.count()
|
||||
&& leftOutput->count()
|
||||
&& rightOutput->count()
|
||||
&& leftInput.first().command == Diff::Equal
|
||||
&& rightInput.first().command == Diff::Equal
|
||||
&& leftOutput->last().command == Diff::Equal
|
||||
&& rightOutput->last().command == Diff::Equal) {
|
||||
leftOutput->last().text += leftInput.first().text;
|
||||
rightOutput->last().text += rightInput.first().text;
|
||||
leftOutput->append(leftInput.mid(1));
|
||||
rightOutput->append(rightInput.mid(1));
|
||||
return;
|
||||
}
|
||||
leftOutput->append(leftInput);
|
||||
rightOutput->append(rightInput);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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:
|
||||
* - 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
|
||||
*/
|
||||
static void 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) {
|
||||
QList<Diff> outputLeftDiffList;
|
||||
QList<Diff> outputRightDiffList;
|
||||
|
||||
QList<Diff> reducedLeftDiffList;
|
||||
QList<Diff> reducedRightDiffList;
|
||||
diffWithWhitespaceReduced(previousLeftDiff,
|
||||
previousRightDiff,
|
||||
&reducedLeftDiffList,
|
||||
&reducedRightDiffList);
|
||||
|
||||
reducedLeftDiffList = moveWhitespaceIntoEqualities(reducedLeftDiffList);
|
||||
reducedRightDiffList = moveWhitespaceIntoEqualities(reducedRightDiffList);
|
||||
|
||||
QList<Diff> cleanedLeftDiffList;
|
||||
QList<Diff> cleanedRightDiffList;
|
||||
if (diffWithWhitespaceExpandedInEqualities(reducedLeftDiffList,
|
||||
reducedRightDiffList,
|
||||
&cleanedLeftDiffList,
|
||||
&cleanedRightDiffList)) {
|
||||
outputLeftDiffList = cleanedLeftDiffList;
|
||||
outputRightDiffList = cleanedRightDiffList;
|
||||
} else {
|
||||
outputLeftDiffList = reducedLeftDiffList;
|
||||
outputRightDiffList = reducedRightDiffList;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static void handleLine(const QStringList &newLines,
|
||||
int line,
|
||||
QStringList *lines,
|
||||
@@ -1707,12 +1126,12 @@ void SideBySideDiffEditorWidget::handleWhitespaces(const QList<Diff> &input,
|
||||
if (!leftOutput || !rightOutput)
|
||||
return;
|
||||
|
||||
splitDiffList(input, leftOutput, rightOutput);
|
||||
Differ::splitDiffList(input, leftOutput, rightOutput);
|
||||
if (m_controller && m_controller->isIgnoreWhitespaces()) {
|
||||
QList<Diff> leftDiffList = moveWhitespaceIntoEqualities(*leftOutput);
|
||||
QList<Diff> rightDiffList = moveWhitespaceIntoEqualities(*rightOutput);
|
||||
QList<Diff> leftDiffList = Differ::moveWhitespaceIntoEqualities(*leftOutput);
|
||||
QList<Diff> rightDiffList = Differ::moveWhitespaceIntoEqualities(*rightOutput);
|
||||
|
||||
diffBetweenEqualities(leftDiffList, rightDiffList, leftOutput, rightOutput);
|
||||
Differ::diffBetweenEqualities(leftDiffList, rightDiffList, leftOutput, rightOutput);
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user