diff --git a/src/plugins/diffeditor/differ.cpp b/src/plugins/diffeditor/differ.cpp index 8ba71282166..58b3487c39a 100644 --- a/src/plugins/diffeditor/differ.cpp +++ b/src/plugins/diffeditor/differ.cpp @@ -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 &diffList, + QList *leftDiffList, + QList *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 Differ::moveWhitespaceIntoEqualities(const QList &input) +{ + QList 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 *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 decodeReducedWhitespace(const QList &input, + const QMap &codeMap) +{ + QList output; + + int counter = 0; + QMap::const_iterator it = codeMap.constBegin(); + const QMap::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 *leftOutput, + QList *rightOutput) +{ + if (!leftOutput || !rightOutput) + return; + + leftOutput->clear(); + rightOutput->clear(); + + QMap leftCodeMap; + QMap rightCodeMap; + const QString leftString = encodeReducedWhitespace(leftInput, &leftCodeMap); + const QString rightString = encodeReducedWhitespace(rightInput, &rightCodeMap); + + Differ differ; + QList diffList = differ.diff(leftString, rightString); + + QList leftDiffList; + QList 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 *leftOutput, + QList *rightOutput) +{ + if (!leftOutput || !rightOutput) + return; + + leftOutput->clear(); + rightOutput->clear(); + + QMap leftCodeMap; + QMap rightCodeMap; + const QString leftString = encodeReducedWhitespace(leftInput, &leftCodeMap); + const QString rightString = encodeReducedWhitespace(rightInput, &rightCodeMap); + + Differ differ; + QList diffList = differ.unifiedDiff(leftString, rightString); + + QList leftDiffList; + QList 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 > *leftCodeMap, + QMap > *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 decodeExpandedWhitespace(const QList input, + const QMap > &codeMap, + bool *ok) +{ + if (ok) + *ok = false; + + QList output; + + int counter = 0; + QMap >::const_iterator it = codeMap.constBegin(); + const QMap >::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(); // 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 &leftInput, + const QList &rightInput, + QList *leftOutput, + QList *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 > commonLeftCodeMap; + QMap > 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 > leftCodeMap; + QMap > 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 > itLeft(leftCodeMap); + while (itLeft.hasNext()) { + itLeft.next(); + commonLeftCodeMap.insert(leftText.count() + itLeft.key(), itLeft.value()); + } + QMapIterator > 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 diffList = differ.cleanupSemantics(differ.diff(leftText, rightText)); + + QList leftDiffList; + QList 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 &leftInput, + const QList &rightInput, + QList *leftOutput, + QList *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 &leftInput, + const QList &rightInput, + QList *leftOutput, + QList *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 outputLeftDiffList; + QList outputRightDiffList; + + QList reducedLeftDiffList; + QList reducedRightDiffList; + diffWithWhitespaceReduced(previousLeftDiff.text, + previousRightDiff.text, + &reducedLeftDiffList, + &reducedRightDiffList); + + reducedLeftDiffList = moveWhitespaceIntoEqualities(reducedLeftDiffList); + reducedRightDiffList = moveWhitespaceIntoEqualities(reducedRightDiffList); + + QList cleanedLeftDiffList; + QList 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 leftEquality; + QList 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 Differ::diff(const QString &text1, const QString &text2) return merge(preprocess1AndDiff(text1, text2)); } +QList 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 diffList = merge(preprocess1AndDiff(encodedText1, encodedText2)); + + diffList = decode(diffList, subtexts); + m_currentDiffMode = diffMode; + return diffList; +} + void Differ::setDiffMode(Differ::DiffMode mode) { m_diffMode = mode; diff --git a/src/plugins/diffeditor/differ.h b/src/plugins/diffeditor/differ.h index e0df7050625..48d9b35fd1d 100644 --- a/src/plugins/diffeditor/differ.h +++ b/src/plugins/diffeditor/differ.h @@ -69,12 +69,30 @@ public: }; Differ(); QList diff(const QString &text1, const QString &text2); + QList unifiedDiff(const QString &text1, const QString &text2); void setDiffMode(DiffMode mode); DiffMode diffMode() const; static QList merge(const QList &diffList); static QList cleanupSemantics(const QList &diffList); static QList cleanupSemanticsLossless(const QList &diffList); + static void splitDiffList(const QList &diffList, + QList *leftDiffList, + QList *rightDiffList); + static QList moveWhitespaceIntoEqualities(const QList &input); + static void diffWithWhitespaceReduced(const QString &leftInput, + const QString &rightInput, + QList *leftOutput, + QList *rightOutput); + static void unifiedDiffWithWhitespaceReduced(const QString &leftInput, + const QString &rightInput, + QList *leftOutput, + QList *rightOutput); + static void diffBetweenEqualities(const QList &leftInput, + const QList &rightInput, + QList *leftOutput, + QList *rightOutput); + private: QList preprocess1AndDiff(const QString &text1, const QString &text2); QList preprocess2AndDiff(const QString &text1, const QString &text2); diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp index 5ea9782ac7e..b13f07d9de9 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp @@ -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 assemblyRows(const QStringList &lines, const QMap &lineSpans, const QMap &equalLines, @@ -174,573 +160,6 @@ static QList 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 &diffList, - QList *leftDiffList, - QList *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 moveWhitespaceIntoEqualities(const QList &input) -{ - QList 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 *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 decodeReducedWhitespace(const QList &input, - const QMap &codeMap) -{ - QList output; - - int counter = 0; - QMap::const_iterator it = codeMap.constBegin(); - const QMap::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 *leftOutput, - QList *rightOutput) -{ - if (!leftOutput || !rightOutput) - return; - - leftOutput->clear(); - rightOutput->clear(); - - if (leftDiff.command != Diff::Delete) - return; - - if (rightDiff.command != Diff::Insert) - return; - - QMap leftCodeMap; - QMap rightCodeMap; - const QString leftString = encodeReducedWhitespace(leftDiff.text, &leftCodeMap); - const QString rightString = encodeReducedWhitespace(rightDiff.text, &rightCodeMap); - - Differ differ; - QList diffList = differ.diff(leftString, rightString); - - QList leftDiffList; - QList 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 > *leftCodeMap, - QMap > *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 decodeExpandedWhitespace(const QList input, - const QMap > &codeMap, - bool *ok) -{ - if (ok) - *ok = false; - - QList output; - - int counter = 0; - QMap >::const_iterator it = codeMap.constBegin(); - const QMap >::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(); // 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 &leftInput, - const QList &rightInput, - QList *leftOutput, - QList *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 > commonLeftCodeMap; - QMap > 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 > leftCodeMap; - QMap > 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 > itLeft(leftCodeMap); - while (itLeft.hasNext()) { - itLeft.next(); - commonLeftCodeMap.insert(leftText.count() + itLeft.key(), itLeft.value()); - } - QMapIterator > 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 diffList = differ.cleanupSemantics(differ.diff(leftText, rightText)); - - QList leftDiffList; - QList 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 &leftInput, - const QList &rightInput, - QList *leftOutput, - QList *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 &leftInput, - const QList &rightInput, - QList *leftOutput, - QList *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 outputLeftDiffList; - QList outputRightDiffList; - - QList reducedLeftDiffList; - QList reducedRightDiffList; - diffWithWhitespaceReduced(previousLeftDiff, - previousRightDiff, - &reducedLeftDiffList, - &reducedRightDiffList); - - reducedLeftDiffList = moveWhitespaceIntoEqualities(reducedLeftDiffList); - reducedRightDiffList = moveWhitespaceIntoEqualities(reducedRightDiffList); - - QList cleanedLeftDiffList; - QList 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 leftEquality; - QList 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 &input, if (!leftOutput || !rightOutput) return; - splitDiffList(input, leftOutput, rightOutput); + Differ::splitDiffList(input, leftOutput, rightOutput); if (m_controller && m_controller->isIgnoreWhitespaces()) { - QList leftDiffList = moveWhitespaceIntoEqualities(*leftOutput); - QList rightDiffList = moveWhitespaceIntoEqualities(*rightOutput); + QList leftDiffList = Differ::moveWhitespaceIntoEqualities(*leftOutput); + QList rightDiffList = Differ::moveWhitespaceIntoEqualities(*rightOutput); - diffBetweenEqualities(leftDiffList, rightDiffList, leftOutput, rightOutput); + Differ::diffBetweenEqualities(leftDiffList, rightDiffList, leftOutput, rightOutput); } }