cleanupSemantic.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.cleanupSemantic = exports.DIFF_INSERT = exports.DIFF_DELETE = exports.DIFF_EQUAL = exports.Diff = void 0;
  6. function _defineProperty(obj, key, value) {
  7. if (key in obj) {
  8. Object.defineProperty(obj, key, {
  9. value: value,
  10. enumerable: true,
  11. configurable: true,
  12. writable: true
  13. });
  14. } else {
  15. obj[key] = value;
  16. }
  17. return obj;
  18. }
  19. /**
  20. * Diff Match and Patch
  21. * Copyright 2018 The diff-match-patch Authors.
  22. * https://github.com/google/diff-match-patch
  23. *
  24. * Licensed under the Apache License, Version 2.0 (the "License");
  25. * you may not use this file except in compliance with the License.
  26. * You may obtain a copy of the License at
  27. *
  28. * http://www.apache.org/licenses/LICENSE-2.0
  29. *
  30. * Unless required by applicable law or agreed to in writing, software
  31. * distributed under the License is distributed on an "AS IS" BASIS,
  32. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  33. * See the License for the specific language governing permissions and
  34. * limitations under the License.
  35. */
  36. /**
  37. * @fileoverview Computes the difference between two texts to create a patch.
  38. * Applies the patch onto another text, allowing for errors.
  39. * @author fraser@google.com (Neil Fraser)
  40. */
  41. /**
  42. * CHANGES by pedrottimark to diff_match_patch_uncompressed.ts file:
  43. *
  44. * 1. Delete anything not needed to use diff_cleanupSemantic method
  45. * 2. Convert from prototype properties to var declarations
  46. * 3. Convert Diff to class from constructor and prototype
  47. * 4. Add type annotations for arguments and return values
  48. * 5. Add exports
  49. */
  50. /**
  51. * The data structure representing a diff is an array of tuples:
  52. * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
  53. * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
  54. */
  55. var DIFF_DELETE = -1;
  56. exports.DIFF_DELETE = DIFF_DELETE;
  57. var DIFF_INSERT = 1;
  58. exports.DIFF_INSERT = DIFF_INSERT;
  59. var DIFF_EQUAL = 0;
  60. /**
  61. * Class representing one diff tuple.
  62. * Attempts to look like a two-element array (which is what this used to be).
  63. * @param {number} op Operation, one of: DIFF_DELETE, DIFF_INSERT, DIFF_EQUAL.
  64. * @param {string} text Text to be deleted, inserted, or retained.
  65. * @constructor
  66. */
  67. exports.DIFF_EQUAL = DIFF_EQUAL;
  68. class Diff {
  69. constructor(op, text) {
  70. _defineProperty(this, 0, void 0);
  71. _defineProperty(this, 1, void 0);
  72. this[0] = op;
  73. this[1] = text;
  74. }
  75. }
  76. /**
  77. * Determine the common prefix of two strings.
  78. * @param {string} text1 First string.
  79. * @param {string} text2 Second string.
  80. * @return {number} The number of characters common to the start of each
  81. * string.
  82. */
  83. exports.Diff = Diff;
  84. var diff_commonPrefix = function diff_commonPrefix(text1, text2) {
  85. // Quick check for common null cases.
  86. if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
  87. return 0;
  88. } // Binary search.
  89. // Performance analysis: https://neil.fraser.name/news/2007/10/09/
  90. var pointermin = 0;
  91. var pointermax = Math.min(text1.length, text2.length);
  92. var pointermid = pointermax;
  93. var pointerstart = 0;
  94. while (pointermin < pointermid) {
  95. if (
  96. text1.substring(pointerstart, pointermid) ==
  97. text2.substring(pointerstart, pointermid)
  98. ) {
  99. pointermin = pointermid;
  100. pointerstart = pointermin;
  101. } else {
  102. pointermax = pointermid;
  103. }
  104. pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
  105. }
  106. return pointermid;
  107. };
  108. /**
  109. * Determine the common suffix of two strings.
  110. * @param {string} text1 First string.
  111. * @param {string} text2 Second string.
  112. * @return {number} The number of characters common to the end of each string.
  113. */
  114. var diff_commonSuffix = function diff_commonSuffix(text1, text2) {
  115. // Quick check for common null cases.
  116. if (
  117. !text1 ||
  118. !text2 ||
  119. text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)
  120. ) {
  121. return 0;
  122. } // Binary search.
  123. // Performance analysis: https://neil.fraser.name/news/2007/10/09/
  124. var pointermin = 0;
  125. var pointermax = Math.min(text1.length, text2.length);
  126. var pointermid = pointermax;
  127. var pointerend = 0;
  128. while (pointermin < pointermid) {
  129. if (
  130. text1.substring(text1.length - pointermid, text1.length - pointerend) ==
  131. text2.substring(text2.length - pointermid, text2.length - pointerend)
  132. ) {
  133. pointermin = pointermid;
  134. pointerend = pointermin;
  135. } else {
  136. pointermax = pointermid;
  137. }
  138. pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
  139. }
  140. return pointermid;
  141. };
  142. /**
  143. * Determine if the suffix of one string is the prefix of another.
  144. * @param {string} text1 First string.
  145. * @param {string} text2 Second string.
  146. * @return {number} The number of characters common to the end of the first
  147. * string and the start of the second string.
  148. * @private
  149. */
  150. var diff_commonOverlap_ = function diff_commonOverlap_(text1, text2) {
  151. // Cache the text lengths to prevent multiple calls.
  152. var text1_length = text1.length;
  153. var text2_length = text2.length; // Eliminate the null case.
  154. if (text1_length == 0 || text2_length == 0) {
  155. return 0;
  156. } // Truncate the longer string.
  157. if (text1_length > text2_length) {
  158. text1 = text1.substring(text1_length - text2_length);
  159. } else if (text1_length < text2_length) {
  160. text2 = text2.substring(0, text1_length);
  161. }
  162. var text_length = Math.min(text1_length, text2_length); // Quick check for the worst case.
  163. if (text1 == text2) {
  164. return text_length;
  165. } // Start by looking for a single character match
  166. // and increase length until no match is found.
  167. // Performance analysis: https://neil.fraser.name/news/2010/11/04/
  168. var best = 0;
  169. var length = 1;
  170. while (true) {
  171. var pattern = text1.substring(text_length - length);
  172. var found = text2.indexOf(pattern);
  173. if (found == -1) {
  174. return best;
  175. }
  176. length += found;
  177. if (
  178. found == 0 ||
  179. text1.substring(text_length - length) == text2.substring(0, length)
  180. ) {
  181. best = length;
  182. length++;
  183. }
  184. }
  185. };
  186. /**
  187. * Reduce the number of edits by eliminating semantically trivial equalities.
  188. * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
  189. */
  190. var diff_cleanupSemantic = function diff_cleanupSemantic(diffs) {
  191. var changes = false;
  192. var equalities = []; // Stack of indices where equalities are found.
  193. var equalitiesLength = 0; // Keeping our own length var is faster in JS.
  194. /** @type {?string} */
  195. var lastEquality = null; // Always equal to diffs[equalities[equalitiesLength - 1]][1]
  196. var pointer = 0; // Index of current position.
  197. // Number of characters that changed prior to the equality.
  198. var length_insertions1 = 0;
  199. var length_deletions1 = 0; // Number of characters that changed after the equality.
  200. var length_insertions2 = 0;
  201. var length_deletions2 = 0;
  202. while (pointer < diffs.length) {
  203. if (diffs[pointer][0] == DIFF_EQUAL) {
  204. // Equality found.
  205. equalities[equalitiesLength++] = pointer;
  206. length_insertions1 = length_insertions2;
  207. length_deletions1 = length_deletions2;
  208. length_insertions2 = 0;
  209. length_deletions2 = 0;
  210. lastEquality = diffs[pointer][1];
  211. } else {
  212. // An insertion or deletion.
  213. if (diffs[pointer][0] == DIFF_INSERT) {
  214. length_insertions2 += diffs[pointer][1].length;
  215. } else {
  216. length_deletions2 += diffs[pointer][1].length;
  217. } // Eliminate an equality that is smaller or equal to the edits on both
  218. // sides of it.
  219. if (
  220. lastEquality &&
  221. lastEquality.length <=
  222. Math.max(length_insertions1, length_deletions1) &&
  223. lastEquality.length <= Math.max(length_insertions2, length_deletions2)
  224. ) {
  225. // Duplicate record.
  226. diffs.splice(
  227. equalities[equalitiesLength - 1],
  228. 0,
  229. new Diff(DIFF_DELETE, lastEquality)
  230. ); // Change second copy to insert.
  231. diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; // Throw away the equality we just deleted.
  232. equalitiesLength--; // Throw away the previous equality (it needs to be reevaluated).
  233. equalitiesLength--;
  234. pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
  235. length_insertions1 = 0; // Reset the counters.
  236. length_deletions1 = 0;
  237. length_insertions2 = 0;
  238. length_deletions2 = 0;
  239. lastEquality = null;
  240. changes = true;
  241. }
  242. }
  243. pointer++;
  244. } // Normalize the diff.
  245. if (changes) {
  246. diff_cleanupMerge(diffs);
  247. }
  248. diff_cleanupSemanticLossless(diffs); // Find any overlaps between deletions and insertions.
  249. // e.g: <del>abcxxx</del><ins>xxxdef</ins>
  250. // -> <del>abc</del>xxx<ins>def</ins>
  251. // e.g: <del>xxxabc</del><ins>defxxx</ins>
  252. // -> <ins>def</ins>xxx<del>abc</del>
  253. // Only extract an overlap if it is as big as the edit ahead or behind it.
  254. pointer = 1;
  255. while (pointer < diffs.length) {
  256. if (
  257. diffs[pointer - 1][0] == DIFF_DELETE &&
  258. diffs[pointer][0] == DIFF_INSERT
  259. ) {
  260. var deletion = diffs[pointer - 1][1];
  261. var insertion = diffs[pointer][1];
  262. var overlap_length1 = diff_commonOverlap_(deletion, insertion);
  263. var overlap_length2 = diff_commonOverlap_(insertion, deletion);
  264. if (overlap_length1 >= overlap_length2) {
  265. if (
  266. overlap_length1 >= deletion.length / 2 ||
  267. overlap_length1 >= insertion.length / 2
  268. ) {
  269. // Overlap found. Insert an equality and trim the surrounding edits.
  270. diffs.splice(
  271. pointer,
  272. 0,
  273. new Diff(DIFF_EQUAL, insertion.substring(0, overlap_length1))
  274. );
  275. diffs[pointer - 1][1] = deletion.substring(
  276. 0,
  277. deletion.length - overlap_length1
  278. );
  279. diffs[pointer + 1][1] = insertion.substring(overlap_length1);
  280. pointer++;
  281. }
  282. } else {
  283. if (
  284. overlap_length2 >= deletion.length / 2 ||
  285. overlap_length2 >= insertion.length / 2
  286. ) {
  287. // Reverse overlap found.
  288. // Insert an equality and swap and trim the surrounding edits.
  289. diffs.splice(
  290. pointer,
  291. 0,
  292. new Diff(DIFF_EQUAL, deletion.substring(0, overlap_length2))
  293. );
  294. diffs[pointer - 1][0] = DIFF_INSERT;
  295. diffs[pointer - 1][1] = insertion.substring(
  296. 0,
  297. insertion.length - overlap_length2
  298. );
  299. diffs[pointer + 1][0] = DIFF_DELETE;
  300. diffs[pointer + 1][1] = deletion.substring(overlap_length2);
  301. pointer++;
  302. }
  303. }
  304. pointer++;
  305. }
  306. pointer++;
  307. }
  308. };
  309. /**
  310. * Look for single edits surrounded on both sides by equalities
  311. * which can be shifted sideways to align the edit to a word boundary.
  312. * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
  313. * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
  314. */
  315. exports.cleanupSemantic = diff_cleanupSemantic;
  316. var diff_cleanupSemanticLossless = function diff_cleanupSemanticLossless(
  317. diffs
  318. ) {
  319. /**
  320. * Given two strings, compute a score representing whether the internal
  321. * boundary falls on logical boundaries.
  322. * Scores range from 6 (best) to 0 (worst).
  323. * Closure, but does not reference any external variables.
  324. * @param {string} one First string.
  325. * @param {string} two Second string.
  326. * @return {number} The score.
  327. * @private
  328. */
  329. function diff_cleanupSemanticScore_(one, two) {
  330. if (!one || !two) {
  331. // Edges are the best.
  332. return 6;
  333. } // Each port of this function behaves slightly differently due to
  334. // subtle differences in each language's definition of things like
  335. // 'whitespace'. Since this function's purpose is largely cosmetic,
  336. // the choice has been made to use each language's native features
  337. // rather than force total conformity.
  338. var char1 = one.charAt(one.length - 1);
  339. var char2 = two.charAt(0);
  340. var nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);
  341. var nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_);
  342. var whitespace1 = nonAlphaNumeric1 && char1.match(whitespaceRegex_);
  343. var whitespace2 = nonAlphaNumeric2 && char2.match(whitespaceRegex_);
  344. var lineBreak1 = whitespace1 && char1.match(linebreakRegex_);
  345. var lineBreak2 = whitespace2 && char2.match(linebreakRegex_);
  346. var blankLine1 = lineBreak1 && one.match(blanklineEndRegex_);
  347. var blankLine2 = lineBreak2 && two.match(blanklineStartRegex_);
  348. if (blankLine1 || blankLine2) {
  349. // Five points for blank lines.
  350. return 5;
  351. } else if (lineBreak1 || lineBreak2) {
  352. // Four points for line breaks.
  353. return 4;
  354. } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
  355. // Three points for end of sentences.
  356. return 3;
  357. } else if (whitespace1 || whitespace2) {
  358. // Two points for whitespace.
  359. return 2;
  360. } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
  361. // One point for non-alphanumeric.
  362. return 1;
  363. }
  364. return 0;
  365. }
  366. var pointer = 1; // Intentionally ignore the first and last element (don't need checking).
  367. while (pointer < diffs.length - 1) {
  368. if (
  369. diffs[pointer - 1][0] == DIFF_EQUAL &&
  370. diffs[pointer + 1][0] == DIFF_EQUAL
  371. ) {
  372. // This is a single edit surrounded by equalities.
  373. var equality1 = diffs[pointer - 1][1];
  374. var edit = diffs[pointer][1];
  375. var equality2 = diffs[pointer + 1][1]; // First, shift the edit as far left as possible.
  376. var commonOffset = diff_commonSuffix(equality1, edit);
  377. if (commonOffset) {
  378. var commonString = edit.substring(edit.length - commonOffset);
  379. equality1 = equality1.substring(0, equality1.length - commonOffset);
  380. edit = commonString + edit.substring(0, edit.length - commonOffset);
  381. equality2 = commonString + equality2;
  382. } // Second, step character by character right, looking for the best fit.
  383. var bestEquality1 = equality1;
  384. var bestEdit = edit;
  385. var bestEquality2 = equality2;
  386. var bestScore =
  387. diff_cleanupSemanticScore_(equality1, edit) +
  388. diff_cleanupSemanticScore_(edit, equality2);
  389. while (edit.charAt(0) === equality2.charAt(0)) {
  390. equality1 += edit.charAt(0);
  391. edit = edit.substring(1) + equality2.charAt(0);
  392. equality2 = equality2.substring(1);
  393. var score =
  394. diff_cleanupSemanticScore_(equality1, edit) +
  395. diff_cleanupSemanticScore_(edit, equality2); // The >= encourages trailing rather than leading whitespace on edits.
  396. if (score >= bestScore) {
  397. bestScore = score;
  398. bestEquality1 = equality1;
  399. bestEdit = edit;
  400. bestEquality2 = equality2;
  401. }
  402. }
  403. if (diffs[pointer - 1][1] != bestEquality1) {
  404. // We have an improvement, save it back to the diff.
  405. if (bestEquality1) {
  406. diffs[pointer - 1][1] = bestEquality1;
  407. } else {
  408. diffs.splice(pointer - 1, 1);
  409. pointer--;
  410. }
  411. diffs[pointer][1] = bestEdit;
  412. if (bestEquality2) {
  413. diffs[pointer + 1][1] = bestEquality2;
  414. } else {
  415. diffs.splice(pointer + 1, 1);
  416. pointer--;
  417. }
  418. }
  419. }
  420. pointer++;
  421. }
  422. }; // Define some regex patterns for matching boundaries.
  423. var nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
  424. var whitespaceRegex_ = /\s/;
  425. var linebreakRegex_ = /[\r\n]/;
  426. var blanklineEndRegex_ = /\n\r?\n$/;
  427. var blanklineStartRegex_ = /^\r?\n\r?\n/;
  428. /**
  429. * Reorder and merge like edit sections. Merge equalities.
  430. * Any edit section can move as long as it doesn't cross an equality.
  431. * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
  432. */
  433. var diff_cleanupMerge = function diff_cleanupMerge(diffs) {
  434. // Add a dummy entry at the end.
  435. diffs.push(new Diff(DIFF_EQUAL, ''));
  436. var pointer = 0;
  437. var count_delete = 0;
  438. var count_insert = 0;
  439. var text_delete = '';
  440. var text_insert = '';
  441. var commonlength;
  442. while (pointer < diffs.length) {
  443. switch (diffs[pointer][0]) {
  444. case DIFF_INSERT:
  445. count_insert++;
  446. text_insert += diffs[pointer][1];
  447. pointer++;
  448. break;
  449. case DIFF_DELETE:
  450. count_delete++;
  451. text_delete += diffs[pointer][1];
  452. pointer++;
  453. break;
  454. case DIFF_EQUAL:
  455. // Upon reaching an equality, check for prior redundancies.
  456. if (count_delete + count_insert > 1) {
  457. if (count_delete !== 0 && count_insert !== 0) {
  458. // Factor out any common prefixies.
  459. commonlength = diff_commonPrefix(text_insert, text_delete);
  460. if (commonlength !== 0) {
  461. if (
  462. pointer - count_delete - count_insert > 0 &&
  463. diffs[pointer - count_delete - count_insert - 1][0] ==
  464. DIFF_EQUAL
  465. ) {
  466. diffs[
  467. pointer - count_delete - count_insert - 1
  468. ][1] += text_insert.substring(0, commonlength);
  469. } else {
  470. diffs.splice(
  471. 0,
  472. 0,
  473. new Diff(DIFF_EQUAL, text_insert.substring(0, commonlength))
  474. );
  475. pointer++;
  476. }
  477. text_insert = text_insert.substring(commonlength);
  478. text_delete = text_delete.substring(commonlength);
  479. } // Factor out any common suffixies.
  480. commonlength = diff_commonSuffix(text_insert, text_delete);
  481. if (commonlength !== 0) {
  482. diffs[pointer][1] =
  483. text_insert.substring(text_insert.length - commonlength) +
  484. diffs[pointer][1];
  485. text_insert = text_insert.substring(
  486. 0,
  487. text_insert.length - commonlength
  488. );
  489. text_delete = text_delete.substring(
  490. 0,
  491. text_delete.length - commonlength
  492. );
  493. }
  494. } // Delete the offending records and add the merged ones.
  495. pointer -= count_delete + count_insert;
  496. diffs.splice(pointer, count_delete + count_insert);
  497. if (text_delete.length) {
  498. diffs.splice(pointer, 0, new Diff(DIFF_DELETE, text_delete));
  499. pointer++;
  500. }
  501. if (text_insert.length) {
  502. diffs.splice(pointer, 0, new Diff(DIFF_INSERT, text_insert));
  503. pointer++;
  504. }
  505. pointer++;
  506. } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) {
  507. // Merge this equality with the previous one.
  508. diffs[pointer - 1][1] += diffs[pointer][1];
  509. diffs.splice(pointer, 1);
  510. } else {
  511. pointer++;
  512. }
  513. count_insert = 0;
  514. count_delete = 0;
  515. text_delete = '';
  516. text_insert = '';
  517. break;
  518. }
  519. }
  520. if (diffs[diffs.length - 1][1] === '') {
  521. diffs.pop(); // Remove the dummy entry at the end.
  522. } // Second pass: look for single edits surrounded on both sides by equalities
  523. // which can be shifted sideways to eliminate an equality.
  524. // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
  525. var changes = false;
  526. pointer = 1; // Intentionally ignore the first and last element (don't need checking).
  527. while (pointer < diffs.length - 1) {
  528. if (
  529. diffs[pointer - 1][0] == DIFF_EQUAL &&
  530. diffs[pointer + 1][0] == DIFF_EQUAL
  531. ) {
  532. // This is a single edit surrounded by equalities.
  533. if (
  534. diffs[pointer][1].substring(
  535. diffs[pointer][1].length - diffs[pointer - 1][1].length
  536. ) == diffs[pointer - 1][1]
  537. ) {
  538. // Shift the edit over the previous equality.
  539. diffs[pointer][1] =
  540. diffs[pointer - 1][1] +
  541. diffs[pointer][1].substring(
  542. 0,
  543. diffs[pointer][1].length - diffs[pointer - 1][1].length
  544. );
  545. diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
  546. diffs.splice(pointer - 1, 1);
  547. changes = true;
  548. } else if (
  549. diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==
  550. diffs[pointer + 1][1]
  551. ) {
  552. // Shift the edit over the next equality.
  553. diffs[pointer - 1][1] += diffs[pointer + 1][1];
  554. diffs[pointer][1] =
  555. diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
  556. diffs[pointer + 1][1];
  557. diffs.splice(pointer + 1, 1);
  558. changes = true;
  559. }
  560. }
  561. pointer++;
  562. } // If shifts were made, the diff needs reordering and another shift sweep.
  563. if (changes) {
  564. diff_cleanupMerge(diffs);
  565. }
  566. };