diff --git a/lib/src/third_party/diff_match_patch/README.md b/lib/src/third_party/diff_match_patch/README.md index a145b2e43..b34c95e9e 100644 --- a/lib/src/third_party/diff_match_patch/README.md +++ b/lib/src/third_party/diff_match_patch/README.md @@ -1,12 +1,14 @@ # Diff-Match-Patch -This is code was forked from google's [diff-match-patch](https://github.com/google/diff-match-patch) library. +This code was forked from Google's [diff-match-patch](https://github.com/google/diff-match-patch) library. ## Modifications made in code +- Code was updated for modern Dart naming standards and language features. - `diff` - Code related to diff functionality was retained and ported to [null-safety](https://dart.dev/null-safety) along with linting. - `Levenshtein distance` - Levenshtein distance which was calculated initially based on the number of characters modified was altered to calculate based on the number of words modified. ## Source + Repository - [link](https://github.com/google/diff-match-patch) Git revision - [a6367d7](https://github.com/google/diff-match-patch/commit/a6367d7866833ac037fbdefcdbcbee4def86e326) diff --git a/lib/src/third_party/diff_match_patch/diff.dart b/lib/src/third_party/diff_match_patch/diff.dart index d8c1cb7dc..989997c94 100644 --- a/lib/src/third_party/diff_match_patch/diff.dart +++ b/lib/src/third_party/diff_match_patch/diff.dart @@ -70,7 +70,7 @@ double diffTimeout = 1.0; /// off the texts before diffing. /// [text1] is the old string to be diffed. /// [text2] is the new string to be diffed. -/// [checklines] is an optional speedup flag. If present and false, then don't +/// [checkLines] is an optional speedup flag. If present and false, then don't /// run a line-level diff first to identify the changed areas. /// Defaults to true, which does a faster, slightly less optimal diff. /// [deadline] is an optional time when the diff should be complete by. Used @@ -79,7 +79,7 @@ double diffTimeout = 1.0; List diffMain( String text1, String text2, { - bool checklines = true, + bool checkLines = true, DateTime? deadline, }) { // Set a deadline by which time the diff must be complete. @@ -104,26 +104,26 @@ List diffMain( } // Trim off common prefix (speedup). - var commonlength = diffCommonPrefix(text1, text2); - var commonprefix = text1.substring(0, commonlength); - text1 = text1.substring(commonlength); - text2 = text2.substring(commonlength); + var commonLength = diffCommonPrefix(text1, text2); + var commonPrefix = text1.substring(0, commonLength); + text1 = text1.substring(commonLength); + text2 = text2.substring(commonLength); // Trim off common suffix (speedup). - commonlength = diffCommonSuffix(text1, text2); - var commonsuffix = text1.substring(text1.length - commonlength); - text1 = text1.substring(0, text1.length - commonlength); - text2 = text2.substring(0, text2.length - commonlength); + commonLength = diffCommonSuffix(text1, text2); + var commonSuffix = text1.substring(text1.length - commonLength); + text1 = text1.substring(0, text1.length - commonLength); + text2 = text2.substring(0, text2.length - commonLength); // Compute the diff on the middle block. - diffs = diffCompute(text1, text2, checklines, deadline); + diffs = diffCompute(text1, text2, checkLines, deadline); // Restore the prefix and suffix. - if (commonprefix.isNotEmpty) { - diffs.insert(0, Diff(Operation.equal, commonprefix)); + if (commonPrefix.isNotEmpty) { + diffs.insert(0, Diff(Operation.equal, commonPrefix)); } - if (commonsuffix.isNotEmpty) { - diffs.add(Diff(Operation.equal, commonsuffix)); + if (commonSuffix.isNotEmpty) { + diffs.add(Diff(Operation.equal, commonSuffix)); } diffCleanupMerge(diffs); @@ -135,7 +135,7 @@ List diffMain( /// Assumes that the texts do not have any common prefix or suffix. /// [text1] is the old string to be diffed. /// [text2] is the new string to be diffed. -/// [checklines] is a speedup flag. If false, then don't run a +/// [checkLines] is a speedup flag. If false, then don't run a /// line-level diff first to identify the changed areas. /// If true, then run a faster slightly less optimal diff. /// [deadline] is the time when the diff should be complete by. @@ -144,7 +144,7 @@ List diffMain( List diffCompute( String text1, String text2, - bool checklines, + bool checkLines, DateTime deadline, ) { var diffs = []; @@ -199,14 +199,14 @@ List diffCompute( final diffsA = diffMain( text1A, text2A, - checklines: checklines, + checkLines: checkLines, deadline: deadline, ); final diffsB = diffMain( textB, text2B, - checklines: checklines, + checkLines: checkLines, deadline: deadline, ); @@ -217,7 +217,7 @@ List diffCompute( return diffs; } - if (checklines && text1.length > 100 && text2.length > 100) { + if (checkLines && text1.length > 100 && text2.length > 100) { return _diffLineMode(text1, text2, deadline); } @@ -237,7 +237,7 @@ void diffCleanupMerge(List diffs) { var countInsert = 0; var textDelete = ''; var textInsert = ''; - int commonlength; + int commonLength; while (pointer < diffs.length) { switch (diffs[pointer].operation) { @@ -256,35 +256,35 @@ void diffCleanupMerge(List diffs) { if (countDelete + countInsert > 1) { if (countDelete != 0 && countInsert != 0) { // Factor out any common prefixes. - commonlength = diffCommonPrefix(textInsert, textDelete); - if (commonlength != 0) { + commonLength = diffCommonPrefix(textInsert, textDelete); + if (commonLength != 0) { if ((pointer - countDelete - countInsert) > 0 && diffs[pointer - countDelete - countInsert - 1].operation == Operation.equal) { final i = pointer - countDelete - countInsert - 1; diffs[i].text = - diffs[i].text + textInsert.substring(0, commonlength); + diffs[i].text + textInsert.substring(0, commonLength); } else { diffs.insert( 0, Diff(Operation.equal, - textInsert.substring(0, commonlength))); + textInsert.substring(0, commonLength))); pointer++; } - textInsert = textInsert.substring(commonlength); - textDelete = textDelete.substring(commonlength); + textInsert = textInsert.substring(commonLength); + textDelete = textDelete.substring(commonLength); } // Factor out any common suffixes. - commonlength = diffCommonSuffix(textInsert, textDelete); - if (commonlength != 0) { + commonLength = diffCommonSuffix(textInsert, textDelete); + if (commonLength != 0) { diffs[pointer].text = - textInsert.substring(textInsert.length - commonlength) + + textInsert.substring(textInsert.length - commonLength) + diffs[pointer].text; textInsert = - textInsert.substring(0, textInsert.length - commonlength); + textInsert.substring(0, textInsert.length - commonLength); textDelete = - textDelete.substring(0, textDelete.length - commonlength); + textDelete.substring(0, textDelete.length - commonLength); } } // Delete the offending records and add the merged ones. @@ -378,25 +378,25 @@ List? diffHalfMatch(String text1, String text2) { return null; } - final longtext = text1.length > text2.length ? text1 : text2; - final shorttext = text1.length > text2.length ? text2 : text1; + final longText = text1.length > text2.length ? text1 : text2; + final shortText = text1.length > text2.length ? text2 : text1; - if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { + if (longText.length < 4 || shortText.length * 2 < longText.length) { return null; // Pointless. } // First check if the second quarter is the seed for a half-match. final hm1 = _diffHalfMatchI( - longtext, - shorttext, - ((longtext.length + 3) / 4).ceil().toInt(), + longText, + shortText, + ((longText.length + 3) / 4).ceil().toInt(), ); // Check again based on the third quarter. final hm2 = _diffHalfMatchI( - longtext, - shorttext, - ((longtext.length + 1) / 2).ceil().toInt(), + longText, + shortText, + ((longText.length + 1) / 2).ceil().toInt(), ); List? hm; @@ -420,7 +420,8 @@ List? diffHalfMatch(String text1, String text2) { } } -/// Do a quick line-level diff on both strings, then rediff the parts for greater accuracy. +/// Do a quick line-level diff on both strings, +/// then rediff the parts for greater accuracy. /// /// This speedup can produce non-minimal diffs. /// [text1] is the old string to be diffed. @@ -429,21 +430,17 @@ List? diffHalfMatch(String text1, String text2) { /// Returns a List of Diff objects. List _diffLineMode(String text1, String text2, DateTime deadline) { // Scan the text on a line-by-line basis first. - final a = diffLinesToChars(text1, text2); - - final linearray = a['lineArray'] as List; - text1 = a['chars1'] as String; - text2 = a['chars2'] as String; + final (:chars1, :chars2, :lineArray) = diffLinesToChars(text1, text2); final diffs = diffMain( - text1, - text2, - checklines: false, + chars1, + chars2, + checkLines: false, deadline: deadline, ); // Convert the diff back to original text. - diffCharsToLines(diffs, linearray); + diffCharsToLines(diffs, lineArray); // Eliminate freak matches (e.g. blank lines) diffCleanupSemantic(diffs); @@ -479,7 +476,7 @@ List _diffLineMode(String text1, String text2, DateTime deadline) { final subDiff = diffMain( textDelete.toString(), textInsert.toString(), - checklines: false, + checkLines: false, deadline: deadline, ); @@ -630,44 +627,44 @@ List diffBisect(String text1, String text2, DateTime deadline) { return [Diff(Operation.delete, text1), Diff(Operation.insert, text2)]; } -/// Does a substring of shorttext exist within longtext such that the -/// substring is at least half the length of longtext? +/// Does a substring of [shortText] exist within [longText] such that the +/// substring is at least half the length of [longText]? /// -/// [longtext] is the longer string. [shorttext] is the shorter string. -/// [i] Start index of quarter length substring within longtext. -/// Returns a five element String array, containing the prefix of longtext, -/// the suffix of longtext, the prefix of shorttext, the suffix of -/// shorttext and the common middle. Or null if there was no match. -List? _diffHalfMatchI(String longtext, String shorttext, int i) { +/// [longText] is the longer string. [shortText] is the shorter string. +/// [i] Start index of quarter length substring within [longText]. +/// Returns a five element String array, containing the prefix of [longText], +/// the suffix of [longText], the prefix of [shortText], the suffix of +/// [shortText] and the common middle. Or `null` if there was no match. +List? _diffHalfMatchI(String longText, String shortText, int i) { // Start with a 1/4 length substring at position i as a seed. - final seed = longtext.substring(i, i + (longtext.length / 4).floor().toInt()); + final seed = longText.substring(i, i + (longText.length / 4).floor().toInt()); var j = -1; var bestCommon = ''; var bestLongtextA = '', bestLongtextB = ''; var bestShortTextA = '', bestShortTextB = ''; - while ((j = shorttext.indexOf(seed, j + 1)) != -1) { + while ((j = shortText.indexOf(seed, j + 1)) != -1) { final prefixLength = diffCommonPrefix( - longtext.substring(i), - shorttext.substring(j), + longText.substring(i), + shortText.substring(j), ); final suffixLength = diffCommonSuffix( - longtext.substring(0, i), - shorttext.substring(0, j), + longText.substring(0, i), + shortText.substring(0, j), ); if (bestCommon.length < suffixLength + prefixLength) { - bestCommon = shorttext.substring(j - suffixLength, j) + - shorttext.substring(j, j + prefixLength); - bestLongtextA = longtext.substring(0, i - suffixLength); - bestLongtextB = longtext.substring(i + prefixLength); - bestShortTextA = shorttext.substring(0, j - suffixLength); - bestShortTextB = shorttext.substring(j + prefixLength); + bestCommon = shortText.substring(j - suffixLength, j) + + shortText.substring(j, j + prefixLength); + bestLongtextA = longText.substring(0, i - suffixLength); + bestLongtextB = longText.substring(i + prefixLength); + bestShortTextA = shortText.substring(0, j - suffixLength); + bestShortTextB = shortText.substring(j + prefixLength); } } - if (bestCommon.length * 2 >= longtext.length) { + if (bestCommon.length * 2 >= longText.length) { return [ bestLongtextA, bestLongtextB, @@ -680,17 +677,18 @@ List? _diffHalfMatchI(String longtext, String shorttext, int i) { } } -/// Rehydrate the text in a diff from a string of line hashes to real lines of text. +/// Rehydrate the text in a diff from a +/// string of line hashes to real lines of text. /// /// [diffs] is a List of Diff objects. /// [lineArray] is a List of unique strings. @visibleForTesting -void diffCharsToLines(List diffs, List? lineArray) { +void diffCharsToLines(List diffs, List lineArray) { final text = StringBuffer(); for (var diff in diffs) { for (var j = 0; j < diff.text.length; j++) { - text.write(lineArray![diff.text.codeUnitAt(j)]); + text.write(lineArray[diff.text.codeUnitAt(j)]); } diff.text = text.toString(); text.clear(); @@ -722,13 +720,13 @@ List _diffBisectSplit( final diffs = diffMain( text1a, text2a, - checklines: false, + checkLines: false, deadline: deadline, ); final diffsb = diffMain( text1b, text2b, - checklines: false, + checkLines: false, deadline: deadline, ); diff --git a/lib/src/third_party/diff_match_patch/test.dart b/lib/src/third_party/diff_match_patch/test.dart index a7c00f410..151bf2367 100644 --- a/lib/src/third_party/diff_match_patch/test.dart +++ b/lib/src/third_party/diff_match_patch/test.dart @@ -210,7 +210,7 @@ void testDiffMain() { diffMain(a, b), a, b, - checklines: true, + checkLines: true, ); a = '123 4567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'; @@ -221,7 +221,7 @@ void testDiffMain() { diffMain(a, b), a, b, - checklines: true, + checkLines: true, ); a = '1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n'; @@ -231,7 +231,7 @@ void testDiffMain() { final textsLineMode = diffRebuildtexts(diffMain( a, b, - checklines: true, + checkLines: true, )); final textsTextMode = diffRebuildtexts(diffMain( a, @@ -427,29 +427,29 @@ void testDiffLineToChars() { 'Shared lines', 'alpha\nbeta\nalpha\n', 'beta\nalpha\nbeta\n', - { - 'chars1': '\u0001\u0002\u0001', - 'chars2': '\u0002\u0001\u0002', - 'lineArray': ['', 'alpha\n', 'beta\n'] - }, + ( + chars1: '\u0001\u0002\u0001', + chars2: '\u0002\u0001\u0002', + lineArray: ['', 'alpha\n', 'beta\n'], + ), ); _testDiffLineToChars( 'Empty string and blank lines.', '', 'alpha\r\nbeta\r\n\r\n\r\n', - { - 'chars1': '', - 'chars2': '\u0001\u0002\u0003\u0003', - 'lineArray': ['', 'alpha\r\n', 'beta\r\n', '\r\n'] - }, + ( + chars1: '', + chars2: '\u0001\u0002\u0003\u0003', + lineArray: ['', 'alpha\r\n', 'beta\r\n', '\r\n'], + ), ); - _testDiffLineToChars('No linebreaks.', 'a', 'b', { - 'chars1': '\u0001', - 'chars2': '\u0002', - 'lineArray': ['', 'a', 'b'] - }); + _testDiffLineToChars('No linebreaks.', 'a', 'b', ( + chars1: '\u0001', + chars2: '\u0002', + lineArray: ['', 'a', 'b'], + )); // More than 256 to reveal any 8-bit limitations. var n = 300; @@ -479,7 +479,7 @@ void testDiffLineToChars() { 'More than 256.', lines, '', - {'chars1': chars, 'chars2': '', 'lineArray': lineList}, + (chars1: chars, chars2: '', lineArray: lineList), ); }); } @@ -544,8 +544,8 @@ void testDiffCharsToLines() { chars = lineList.join(); final results = diffLinesToChars(chars, ''); - diffs = [Diff(Operation.insert, results['chars1'] as String)]; - diffCharsToLines(diffs, results['lineArray'] as List); + diffs = [Diff(Operation.insert, results.chars1)]; + diffCharsToLines(diffs, results.lineArray); test('More than 65536.', () => expect(chars, diffs[0].text)); }); } @@ -1026,10 +1026,10 @@ void _testDiffMain( List expected, String text1, String text2, { - bool checklines = false, + bool checkLines = false, }) => test(name, () { - _testOutput(diffMain(text1, text2, checklines: checklines), expected); + _testOutput(diffMain(text1, text2, checkLines: checkLines), expected); }); void _testCommonPrefix( @@ -1099,26 +1099,22 @@ void _testDiffLineToChars( String name, String text1, String text2, - Map expected, + ({String chars1, String chars2, List lineArray}) expected, ) { test(name, () { final actual = diffLinesToChars(text1, text2); expect( - actual['chars1'], - expected['chars1'], + actual.chars1, + expected.chars1, ); expect( - actual['chars2'], - expected['chars2'], + actual.chars2, + expected.chars2, ); expect( - actual['lineArray'].length, - expected['lineArray'].length, + actual.lineArray, + expected.lineArray, ); - - for (var i = 0; i < (actual['lineArray'] as List).length; i++) { - expect(actual['lineArray'][i], expected['lineArray'][i]); - } }); } diff --git a/lib/src/third_party/diff_match_patch/utils.dart b/lib/src/third_party/diff_match_patch/utils.dart index 351ea30d4..eb11d90a4 100644 --- a/lib/src/third_party/diff_match_patch/utils.dart +++ b/lib/src/third_party/diff_match_patch/utils.dart @@ -70,8 +70,8 @@ String _diffLinesToCharsMunge( } line = text.substring(lineStart, lineEnd + 1); - if (lineHash.containsKey(line)) { - chars.writeCharCode(lineHash[line]!); + if (lineHash[line] case final lineHashIndex?) { + chars.writeCharCode(lineHashIndex); } else { if (lineArray.length == maxLines) { // Bail out at 65535 because @@ -99,7 +99,8 @@ String _diffLinesToCharsMunge( /// the List of unique strings. The zeroth element of the List of /// unique strings is intentionally blank. @visibleForTesting -Map diffLinesToChars(String text1, String text2) { +({String chars1, String chars2, List lineArray}) diffLinesToChars( + String text1, String text2) { final lineArray = []; final lineHash = HashMap(); // e.g. linearray[4] == 'Hello\n' @@ -113,7 +114,7 @@ Map diffLinesToChars(String text1, String text2) { final chars1 = _diffLinesToCharsMunge(text1, lineArray, lineHash, 40000); final chars2 = _diffLinesToCharsMunge(text2, lineArray, lineHash, 65535); - return {'chars1': chars1, 'chars2': chars2, 'lineArray': lineArray}; + return (chars1: chars1, chars2: chars2, lineArray: lineArray); } int diffLevenshteinWord(Iterable diffs) { @@ -350,7 +351,7 @@ void diffCleanupSemantic(List diffs) { // Throw away the equality we just deleted. equalities.removeLast(); - // Throw away the previous equality (it needs to be revaluated). + // Throw away the previous equality (it needs to be reevaluated). if (equalities.isNotEmpty) { equalities.removeLast(); } @@ -453,14 +454,14 @@ void diffCleanupSemanticLossless(List diffs) { // rather than force total conformity. final char1 = one[one.length - 1]; final char2 = two[0]; - final nonAlphaNumeric1 = char1.contains(nonAlphaNumericRegex); - final nonAlphaNumeric2 = char2.contains(nonAlphaNumericRegex); - final whitespace1 = nonAlphaNumeric1 && char1.contains(whitespaceRegex); - final whitespace2 = nonAlphaNumeric2 && char2.contains(whitespaceRegex); - final lineBreak1 = whitespace1 && char1.contains(linebreakRegex); - final lineBreak2 = whitespace2 && char2.contains(linebreakRegex); - final blankLine1 = lineBreak1 && one.contains(blanklineEndRegex); - final blankLine2 = lineBreak2 && two.contains(blanklineStartRegex); + final nonAlphaNumeric1 = char1.contains(_nonAlphaNumericRegex); + final nonAlphaNumeric2 = char2.contains(_nonAlphaNumericRegex); + final whitespace1 = nonAlphaNumeric1 && char1.contains(_whitespaceRegex); + final whitespace2 = nonAlphaNumeric2 && char2.contains(_whitespaceRegex); + final lineBreak1 = whitespace1 && char1.contains(_lineBreakRegex); + final lineBreak2 = whitespace2 && char2.contains(_lineBreakRegex); + final blankLine1 = lineBreak1 && one.contains(_blankLineEndRegex); + final blankLine2 = lineBreak2 && two.contains(_blankLineStartRegex); if (blankLine1 || blankLine2) { // Five points for blank lines. @@ -546,8 +547,8 @@ void diffCleanupSemanticLossless(List diffs) { } } -final nonAlphaNumericRegex = RegExp(r'[^a-zA-Z0-9]'); -final whitespaceRegex = RegExp(r'\s'); -final linebreakRegex = RegExp(r'[\r\n]'); -final blanklineEndRegex = RegExp(r'\n\r?\n$'); -final blanklineStartRegex = RegExp(r'^\r?\n\r?\n'); +final _nonAlphaNumericRegex = RegExp(r'[^a-zA-Z0-9]'); +final _whitespaceRegex = RegExp(r'\s'); +final _lineBreakRegex = RegExp(r'[\r\n]'); +final _blankLineEndRegex = RegExp(r'\n\r?\n$'); +final _blankLineStartRegex = RegExp(r'^\r?\n\r?\n'); diff --git a/test/diff_test.dart b/test/diff_test.dart index e5abaa7d0..9994a0eeb 100644 --- a/test/diff_test.dart +++ b/test/diff_test.dart @@ -1,6 +1,6 @@ import 'package:pana/src/third_party/diff_match_patch/test.dart'; -// The follwing test cases were forked from the google's diff-match-patch +// The following test cases were forked from the google's diff-match-patch // dart library and hence the actual tests are in a separate "third_-party" // directory and we only call those tests from here. void main() {