ng-diff-match-patch
Version:
A Diff-Match-Patch component for your Angular 2 + applications
1,220 lines (1,219 loc) • 283 kB
JavaScript
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,extraRequire,uselessCode} checked by tsc
*/
/** @enum {number} */
const DiffOp = {
Delete: -1,
Equal: 0,
Insert: 1,
};
export { DiffOp };
/** @typedef {?} */
var Diff;
export { Diff };
/**
* Class containing the diff, match and patch methods.
*/
class DiffMatchPatch {
constructor() {
// Defaults.
// Redefine these in your program to override the defaults.
// Number of seconds to map a diff before giving up (0 for infinity).
this.Diff_Timeout = 1.0;
// Cost of an empty edit operation in terms of edit characters.
this.Diff_EditCost = 4;
// At what point is no match declared (0.0 = perfection, 1.0 = very loose).
this.Match_Threshold = 0.5;
// How far to search for a match (0 = exact location, 1000+ = broad match).
// A match this many characters away from the expected location will add
// 1.0 to the score (0.0 is a perfect match).
this.Match_Distance = 1000;
// When deleting a large block of text (over ~64 characters), how close do
// the contents have to be to match the expected contents. (0.0 = perfection,
// 1.0 = very loose). Note that Match_Threshold controls how closely the
// end points of a delete need to match.
this.Patch_DeleteThreshold = 0.5;
// Chunk size for context length.
this.Patch_Margin = 4;
// The number of bits in an int.
this.Match_MaxBits = 32;
/**
* The data structure representing a diff is an array of tuples:
* [[DiffOp.Delete, 'Hello'], [DiffOp.Insert, 'Goodbye'], [DiffOp.Equal, ' world.']]
* which means: delete 'Hello', add 'Goodbye' and keep ' world.'
*/
this.whitespaceRegex_ = new RegExp('/\s/');
this.linebreakRegex_ = new RegExp('/[\r\n]/');
this.blanklineEndRegex_ = new RegExp('/\n\r?\n$/');
this.blanklineStartRegex_ = new RegExp('/^\r?\n\r?\n/');
/**
* Convert a diff array into a pretty HTML report.
* @param diffs Array of diff tuples.
* @return HTML representation.
*/
this.diff_prettyHtml = function (diffs) {
/** @type {?} */
const html = [];
/** @type {?} */
const pattern_amp = /&/g;
/** @type {?} */
const pattern_lt = /</g;
/** @type {?} */
const pattern_gt = />/g;
/** @type {?} */
const pattern_para = /\n/g;
for (let x = 0; x < diffs.length; x++) {
/** @type {?} */
const op = diffs[x][0];
/** @type {?} */
const data = diffs[x][1];
/** @type {?} */
const text = data.replace(pattern_amp, '&').replace(pattern_lt, '<')
.replace(pattern_gt, '>').replace(pattern_para, '¶<br>');
switch (op) {
case 1 /* Insert */:
html[x] = '<ins style="background:#e6ffe6;">' + text + '</ins>';
break;
case -1 /* Delete */:
html[x] = '<del style="background:#ffe6e6;">' + text + '</del>';
break;
case 0 /* Equal */:
html[x] = '<span>' + text + '</span>';
break;
}
}
return html.join('');
};
/**
* Look through the patches and break up any which are longer than the maximum
* limit of the match algorithm.
* Intended to be called only from within patch_apply.
* @param patches Array of Patch objects.
*/
this.patch_splitMax = function (patches) {
/** @type {?} */
const patch_size = this.Match_MaxBits;
for (let x = 0; x < patches.length; x++) {
if (patches[x].length1 <= patch_size) {
continue;
}
/** @type {?} */
const bigpatch = patches[x];
// Remove the big old patch.
patches.splice(x--, 1);
/** @type {?} */
let start1 = bigpatch.start1;
/** @type {?} */
let start2 = bigpatch.start2;
/** @type {?} */
let precontext = '';
while (bigpatch.diffs.length !== 0) {
/** @type {?} */
const patch = new patch_obj();
/** @type {?} */
let empty = true;
patch.start1 = start1 - precontext.length;
patch.start2 = start2 - precontext.length;
if (precontext !== '') {
patch.length1 = patch.length2 = precontext.length;
patch.diffs.push([0 /* Equal */, precontext]);
}
while (bigpatch.diffs.length !== 0 &&
patch.length1 < patch_size - this.Patch_Margin) {
/** @type {?} */
const diff_type = bigpatch.diffs[0][0];
/** @type {?} */
let diff_text = bigpatch.diffs[0][1];
if (diff_type === 1 /* Insert */) {
// Insertions are harmless.
patch.length2 += diff_text.length;
start2 += diff_text.length;
patch.diffs.push(bigpatch.diffs.shift());
empty = false;
}
else if (diff_type === -1 /* Delete */ && patch.diffs.length == 1 &&
patch.diffs[0][0] == 0 /* Equal */ &&
diff_text.length > 2 * patch_size) {
// This is a large deletion. Let it pass in one chunk.
patch.length1 += diff_text.length;
start1 += diff_text.length;
empty = false;
patch.diffs.push([diff_type, diff_text]);
bigpatch.diffs.shift();
}
else {
// Deletion or equality. Only take as much as we can stomach.
diff_text = diff_text.substring(0, patch_size - patch.length1 - this.Patch_Margin);
patch.length1 += diff_text.length;
start1 += diff_text.length;
if (diff_type === 0 /* Equal */) {
patch.length2 += diff_text.length;
start2 += diff_text.length;
}
else {
empty = false;
}
patch.diffs.push([diff_type, diff_text]);
if (diff_text == bigpatch.diffs[0][1]) {
bigpatch.diffs.shift();
}
else {
bigpatch.diffs[0][1] =
bigpatch.diffs[0][1].substring(diff_text.length);
}
}
}
// Compute the head context for the next patch.
precontext = this.diff_text2(patch.diffs);
precontext =
precontext.substring(precontext.length - this.Patch_Margin);
/** @type {?} */
const postcontext = this.diff_text1(bigpatch.diffs)
.substring(0, this.Patch_Margin);
if (postcontext !== '') {
patch.length1 += postcontext.length;
patch.length2 += postcontext.length;
if (patch.diffs.length !== 0 &&
patch.diffs[patch.diffs.length - 1][0] === 0 /* Equal */) {
patch.diffs[patch.diffs.length - 1][1] += postcontext;
}
else {
patch.diffs.push([0 /* Equal */, postcontext]);
}
}
if (!empty) {
patches.splice(++x, 0, patch);
}
}
}
};
}
/**
* Find the differences between two texts. Simplifies the problem by stripping
* any common prefix or suffix off the texts before diffing.
* @param {?} text1 Old string to be diffed.
* @param {?} text2 New string to be diffed.
* @param {?=} opt_checklines 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.
* @param {?=} opt_deadline Optional time when the diff should be complete
* by. Used internally for recursive calls. Users should set DiffTimeout
* instead.
* @return {?} Array of diff tuples.
*/
diff_main(text1, text2, opt_checklines, opt_deadline) {
// Set a deadline by which time the diff must be complete.
if (typeof opt_deadline == 'undefined') {
if (this.Diff_Timeout <= 0) {
opt_deadline = Number.MAX_VALUE;
}
else {
opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000;
}
}
/** @type {?} */
const deadline = opt_deadline;
// Check for null inputs.
if (text1 == null || text2 == null) {
throw new Error('Null input. (diff_main)');
}
// Check for equality (speedup).
if (text1 == text2) {
if (text1) {
return [[0 /* Equal */, text1]];
}
return [];
}
if (typeof opt_checklines == 'undefined') {
opt_checklines = true;
}
/** @type {?} */
const checklines = opt_checklines;
/** @type {?} */
let commonlength = this.diff_commonPrefix(text1, text2);
/** @type {?} */
const commonprefix = text1.substring(0, commonlength);
text1 = text1.substring(commonlength);
text2 = text2.substring(commonlength);
// Trim off common suffix (speedup).
commonlength = this.diff_commonSuffix(text1, text2);
/** @type {?} */
const commonsuffix = text1.substring(text1.length - commonlength);
text1 = text1.substring(0, text1.length - commonlength);
text2 = text2.substring(0, text2.length - commonlength);
/** @type {?} */
const diffs = this.diff_compute_(text1, text2, checklines, deadline);
// Restore the prefix and suffix.
if (commonprefix) {
diffs.unshift([0 /* Equal */, commonprefix]);
}
if (commonsuffix) {
diffs.push([0 /* Equal */, commonsuffix]);
}
this.diff_cleanupMerge(diffs);
return diffs;
}
;
/**
* Find the differences between two texts. Assumes that the texts do not
* have any common prefix or suffix.
* @param {?} text1 Old string to be diffed.
* @param {?} text2 New string to be diffed.
* @param {?} checklines 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.
* @param {?} deadline Time when the diff should be complete by.
* @return {?} Array of diff tuples.
*/
diff_compute_(text1, text2, checklines, deadline) {
/** @type {?} */
let diffs;
if (!text1) {
// Just add some text (speedup).
return [[1 /* Insert */, text2]];
}
if (!text2) {
// Just delete some text (speedup).
return [[-1 /* Delete */, text1]];
}
/** @type {?} */
const longtext = text1.length > text2.length ? text1 : text2;
/** @type {?} */
const shorttext = text1.length > text2.length ? text2 : text1;
/** @type {?} */
const i = longtext.indexOf(shorttext);
if (i != -1) {
// Shorter text is inside the longer text (speedup).
diffs = [[1 /* Insert */, longtext.substring(0, i)],
[0 /* Equal */, shorttext],
[1 /* Insert */, longtext.substring(i + shorttext.length)]];
// Swap insertions for deletions if diff is reversed.
if (text1.length > text2.length) {
diffs[0][0] = diffs[2][0] = -1 /* Delete */;
}
return diffs;
}
if (shorttext.length == 1) {
// Single character string.
// After the previous speedup, the character can't be an equality.
return [[-1 /* Delete */, text1], [1 /* Insert */, text2]];
}
/** @type {?} */
const hm = this.diff_halfMatch_(text1, text2);
if (hm) {
/** @type {?} */
const text1_a = hm[0];
/** @type {?} */
const text1_b = hm[1];
/** @type {?} */
const text2_a = hm[2];
/** @type {?} */
const text2_b = hm[3];
/** @type {?} */
const mid_common = hm[4];
/** @type {?} */
const diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline);
/** @type {?} */
const diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline);
// Merge the results.
return diffs_a.concat([[0 /* Equal */, mid_common]], diffs_b);
}
if (checklines && text1.length > 100 && text2.length > 100) {
return this.diff_lineMode_(text1, text2, deadline);
}
return this.diff_bisect_(text1, text2, deadline);
}
;
/**
* Do a quick line-level diff on both strings, then rediff the parts for
* greater accuracy.
* This speedup can produce non-minimal diffs.
* @param {?} text1 Old string to be diffed.
* @param {?} text2 New string to be diffed.
* @param {?} deadline Time when the diff should be complete by.
* @return {?} Array of diff tuples.
*/
diff_lineMode_(text1, text2, deadline) {
/** @type {?} */
const a = this.diff_linesToChars_(text1, text2);
text1 = a.chars1;
text2 = a.chars2;
/** @type {?} */
const linearray = a.lineArray;
/** @type {?} */
const diffs = this.diff_main(text1, text2, false, deadline);
// Convert the diff back to original text.
this.diff_charsToLines_(diffs, linearray);
// Eliminate freak matches (e.g. blank lines)
this.diff_cleanupSemantic(diffs);
// Rediff any replacement blocks, this time character-by-character.
// Add a dummy entry at the end.
diffs.push([0 /* Equal */, '']);
/** @type {?} */
let pointer = 0;
/** @type {?} */
let count_delete = 0;
/** @type {?} */
let count_insert = 0;
/** @type {?} */
let text_delete = '';
/** @type {?} */
let text_insert = '';
while (pointer < diffs.length) {
switch (diffs[pointer][0]) {
case 1 /* Insert */:
count_insert++;
text_insert += diffs[pointer][1];
break;
case -1 /* Delete */:
count_delete++;
text_delete += diffs[pointer][1];
break;
case 0 /* Equal */:
// Upon reaching an equality, check for prior redundancies.
if (count_delete >= 1 && count_insert >= 1) {
// Delete the offending records and add the merged ones.
diffs.splice(pointer - count_delete - count_insert, count_delete + count_insert);
pointer = pointer - count_delete - count_insert;
/** @type {?} */
const b = this.diff_main(text_delete, text_insert, false, deadline);
for (let j = b.length - 1; j >= 0; j--) {
diffs.splice(pointer, 0, b[j]);
}
pointer = pointer + b.length;
}
count_insert = 0;
count_delete = 0;
text_delete = '';
text_insert = '';
break;
}
pointer++;
}
diffs.pop(); // Remove the dummy entry at the end.
return diffs;
}
;
/**
* Find the 'middle snake' of a diff, split the problem in two
* and return the recursively constructed diff.
* See Myers 1986 paper: An O(ND) Difference Algorithm and Its constiations.
* @param {?} text1 Old string to be diffed.
* @param {?} text2 New string to be diffed.
* @param {?} deadline Time at which to bail if not yet complete.
* @return {?} Array of diff tuples.
*/
diff_bisect_(text1, text2, deadline) {
/** @type {?} */
const text1_length = text1.length;
/** @type {?} */
const text2_length = text2.length;
/** @type {?} */
const max_d = Math.ceil((text1_length + text2_length) / 2);
/** @type {?} */
const v_offset = max_d;
/** @type {?} */
const v_length = 2 * max_d;
/** @type {?} */
const v1 = new Array(v_length);
/** @type {?} */
const v2 = new Array(v_length);
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
// integers and undefined.
for (let x = 0; x < v_length; x++) {
v1[x] = -1;
v2[x] = -1;
}
v1[v_offset + 1] = 0;
v2[v_offset + 1] = 0;
/** @type {?} */
const delta = text1_length - text2_length;
/** @type {?} */
const front = (delta % 2 != 0);
/** @type {?} */
let k1start = 0;
/** @type {?} */
let k1end = 0;
/** @type {?} */
let k2start = 0;
/** @type {?} */
let k2end = 0;
for (let d = 0; d < max_d; d++) {
// Bail out if deadline is reached.
if ((new Date()).getTime() > deadline) {
break;
}
// Walk the front path one step.
for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
/** @type {?} */
const k1_offset = v_offset + k1;
/** @type {?} */
let x1;
if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) {
x1 = v1[k1_offset + 1];
}
else {
x1 = v1[k1_offset - 1] + 1;
}
/** @type {?} */
let y1 = x1 - k1;
while (x1 < text1_length && y1 < text2_length &&
text1.charAt(x1) == text2.charAt(y1)) {
x1++;
y1++;
}
v1[k1_offset] = x1;
if (x1 > text1_length) {
// Ran off the right of the graph.
k1end += 2;
}
else if (y1 > text2_length) {
// Ran off the bottom of the graph.
k1start += 2;
}
else if (front) {
/** @type {?} */
const k2_offset = v_offset + delta - k1;
if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) {
/** @type {?} */
const x2 = text1_length - v2[k2_offset];
if (x1 >= x2) {
// Overlap detected.
return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);
}
}
}
}
// Walk the reverse path one step.
for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
/** @type {?} */
const k2_offset = v_offset + k2;
/** @type {?} */
let x2;
if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) {
x2 = v2[k2_offset + 1];
}
else {
x2 = v2[k2_offset - 1] + 1;
}
/** @type {?} */
let y2 = x2 - k2;
while (x2 < text1_length && y2 < text2_length &&
text1.charAt(text1_length - x2 - 1) ==
text2.charAt(text2_length - y2 - 1)) {
x2++;
y2++;
}
v2[k2_offset] = x2;
if (x2 > text1_length) {
// Ran off the left of the graph.
k2end += 2;
}
else if (y2 > text2_length) {
// Ran off the top of the graph.
k2start += 2;
}
else if (!front) {
/** @type {?} */
const k1_offset = v_offset + delta - k2;
if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) {
/** @type {?} */
const x1 = v1[k1_offset];
/** @type {?} */
const y1 = v_offset + x1 - k1_offset;
// Mirror x2 onto top-left coordinate system.
x2 = text1_length - x2;
if (x1 >= x2) {
// Overlap detected.
return this.diff_bisectSplit_(text1, text2, x1, y1, deadline);
}
}
}
}
}
// Diff took too long and hit the deadline or
// number of diffs equals number of characters, no commonality at all.
return [[-1 /* Delete */, text1], [1 /* Insert */, text2]];
}
;
/**
* Given the location of the 'middle snake', split the diff in two parts
* and recurse.
* @param {?} text1 Old string to be diffed.
* @param {?} text2 New string to be diffed.
* @param {?} x Index of split point in text1.
* @param {?} y Index of split point in text2.
* @param {?} deadline Time at which to bail if not yet complete.
* @return {?} Array of diff tuples.
*/
diff_bisectSplit_(text1, text2, x, y, deadline) {
/** @type {?} */
const text1a = text1.substring(0, x);
/** @type {?} */
const text2a = text2.substring(0, y);
/** @type {?} */
const text1b = text1.substring(x);
/** @type {?} */
const text2b = text2.substring(y);
/** @type {?} */
const diffs = this.diff_main(text1a, text2a, false, deadline);
/** @type {?} */
const diffsb = this.diff_main(text1b, text2b, false, deadline);
return diffs.concat(diffsb);
}
;
/**
* Split two texts into an array of strings. Reduce the texts to a string of
* hashes where each Unicode character represents one line.
* @param {?} text1 First string.
* @param {?} text2 Second string.
* @return {?} }
* An object containing the encoded text1, the encoded text2 and
* the array of unique strings.
* The zeroth element of the array of unique strings is intentionally blank.
*/
diff_linesToChars_(text1, text2) {
/** @type {?} */
const lineArray = [];
/** @type {?} */
const lineHash = {}; // e.g. lineHash['Hello\n'] == 4
// '\x00' is a valid character, but constious debuggers don't like it.
// So we'll insert a junk entry to avoid generating a null character.
lineArray[0] = '';
/** @type {?} */
const chars1 = this.diff_linesToCharsMunge_(text1, lineArray, lineHash);
/** @type {?} */
const chars2 = this.diff_linesToCharsMunge_(text2, lineArray, lineHash);
return { chars1: chars1, chars2: chars2, lineArray: lineArray };
}
;
/**
* Split a text into an array of strings. Reduce the texts to a string of
* hashes where each Unicode character represents one line.
* Modifies linearray and linehash through being a closure.
* @param {?} text String to encode.
* @param {?} lineArray
* @param {?} lineHash
* @return {?} Encoded string.
*/
diff_linesToCharsMunge_(text, lineArray, lineHash) {
/** @type {?} */
let chars = '';
/** @type {?} */
let lineStart = 0;
/** @type {?} */
let lineEnd = -1;
/** @type {?} */
let lineArrayLength = lineArray.length;
while (lineEnd < text.length - 1) {
lineEnd = text.indexOf('\n', lineStart);
if (lineEnd == -1) {
lineEnd = text.length - 1;
}
/** @type {?} */
const line = text.substring(lineStart, lineEnd + 1);
lineStart = lineEnd + 1;
if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
(lineHash[line] !== undefined)) {
chars += String.fromCharCode(lineHash[line]);
}
else {
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
}
return chars;
}
/**
* Rehydrate the text in a diff from a string of line hashes to real lines of
* text.
* @param {?} diffs Array of diff tuples.
* @param {?} lineArray Array of unique strings.
* @return {?}
*/
diff_charsToLines_(diffs, lineArray) {
for (let x = 0; x < diffs.length; x++) {
/** @type {?} */
const chars = diffs[x][1];
/** @type {?} */
const text = [];
for (let y = 0; y < chars.length; y++) {
text[y] = lineArray[chars.charCodeAt(y)];
}
diffs[x][1] = text.join('');
}
}
;
/**
* Determine the common prefix of two strings.
* @param {?} text1 First string.
* @param {?} text2 Second string.
* @return {?} The number of characters common to the start of each
* string.
*/
diff_commonPrefix(text1, text2) {
// Quick check for common null cases.
if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
return 0;
}
/** @type {?} */
let pointermin = 0;
/** @type {?} */
let pointermax = Math.min(text1.length, text2.length);
/** @type {?} */
let pointermid = pointermax;
/** @type {?} */
let pointerstart = 0;
while (pointermin < pointermid) {
if (text1.substring(pointerstart, pointermid) ==
text2.substring(pointerstart, pointermid)) {
pointermin = pointermid;
pointerstart = pointermin;
}
else {
pointermax = pointermid;
}
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
}
return pointermid;
}
;
/**
* Determine the common suffix of two strings.
* @param {?} text1 First string.
* @param {?} text2 Second string.
* @return {?} The number of characters common to the end of each string.
*/
diff_commonSuffix(text1, text2) {
// Quick check for common null cases.
if (!text1 || !text2 ||
text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) {
return 0;
}
/** @type {?} */
let pointermin = 0;
/** @type {?} */
let pointermax = Math.min(text1.length, text2.length);
/** @type {?} */
let pointermid = pointermax;
/** @type {?} */
let pointerend = 0;
while (pointermin < pointermid) {
if (text1.substring(text1.length - pointermid, text1.length - pointerend) ==
text2.substring(text2.length - pointermid, text2.length - pointerend)) {
pointermin = pointermid;
pointerend = pointermin;
}
else {
pointermax = pointermid;
}
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
}
return pointermid;
}
;
/**
* Determine if the suffix of one string is the prefix of another.
* @param {?} text1 First string.
* @param {?} text2 Second string.
* @return {?} The number of characters common to the end of the first
* string and the start of the second string.
*/
diff_commonOverlap_(text1, text2) {
/** @type {?} */
const text1_length = text1.length;
/** @type {?} */
const text2_length = text2.length;
// Eliminate the null case.
if (text1_length == 0 || text2_length == 0) {
return 0;
}
// Truncate the longer string.
if (text1_length > text2_length) {
text1 = text1.substring(text1_length - text2_length);
}
else if (text1_length < text2_length) {
text2 = text2.substring(0, text1_length);
}
/** @type {?} */
const text_length = Math.min(text1_length, text2_length);
// Quick check for the worst case.
if (text1 == text2) {
return text_length;
}
/** @type {?} */
let best = 0;
/** @type {?} */
let length = 1;
while (true) {
/** @type {?} */
const pattern = text1.substring(text_length - length);
/** @type {?} */
const found = text2.indexOf(pattern);
if (found == -1) {
return best;
}
length += found;
if (found == 0 || text1.substring(text_length - length) ==
text2.substring(0, length)) {
best = length;
length++;
}
}
}
;
/**
* Do the two texts share a substring which is at least half the length of the
* longer text?
* This speedup can produce non-minimal diffs.
* @param {?} text1 First string.
* @param {?} text2 Second string.
* @return {?} Five element Array, containing the prefix of
* text1, the suffix of text1, the prefix of text2, the suffix of
* text2 and the common middle. Or null if there was no match.
*/
diff_halfMatch_(text1, text2) {
if (this.Diff_Timeout <= 0) {
// Don't risk returning a non-optimal diff if we have unlimited time.
return null;
}
/** @type {?} */
const longtext = text1.length > text2.length ? text1 : text2;
/** @type {?} */
const shorttext = text1.length > text2.length ? text2 : text1;
if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
return null; // Pointless.
}
/** @type {?} */
const dmp = this;
/** @type {?} */
const hm1 = this.diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 4), dmp);
/** @type {?} */
const hm2 = this.diff_halfMatchI_(longtext, shorttext, Math.ceil(longtext.length / 2), dmp);
/** @type {?} */
let hm;
if (!hm1 && !hm2) {
return null;
}
else if (!hm2) {
hm = hm1;
}
else if (!hm1) {
hm = hm2;
}
else {
// Both matched. Select the longest.
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
}
/** @type {?} */
let text1_a;
/** @type {?} */
let text1_b;
/** @type {?} */
let text2_a;
/** @type {?} */
let text2_b;
if (text1.length > text2.length) {
text1_a = hm[0];
text1_b = hm[1];
text2_a = hm[2];
text2_b = hm[3];
}
else {
text2_a = hm[0];
text2_b = hm[1];
text1_a = hm[2];
text1_b = hm[3];
}
/** @type {?} */
const mid_common = hm[4];
return [text1_a, text1_b, text2_a, text2_b, mid_common];
}
;
/**
* Does a substring of shorttext exist within longtext such that the substring
* is at least half the length of longtext?
* Closure, but does not reference any external constiables.
* @param {?} longtext Longer string.
* @param {?} shorttext Shorter string.
* @param {?} i Start index of quarter length substring within longtext.
* @param {?} dmp
* @return {?} Five element 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.
*/
diff_halfMatchI_(longtext, shorttext, i, dmp) {
/** @type {?} */
const seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
/** @type {?} */
let j = -1;
/** @type {?} */
let best_common = '';
/** @type {?} */
let best_longtext_a;
/** @type {?} */
let best_longtext_b;
/** @type {?} */
let best_shorttext_a;
/** @type {?} */
let best_shorttext_b;
while ((j = shorttext.indexOf(seed, j + 1)) != -1) {
/** @type {?} */
const prefixLength = dmp.diff_commonPrefix(longtext.substring(i), shorttext.substring(j));
/** @type {?} */
const suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i), shorttext.substring(0, j));
if (best_common.length < suffixLength + prefixLength) {
best_common = shorttext.substring(j - suffixLength, j) +
shorttext.substring(j, j + prefixLength);
best_longtext_a = longtext.substring(0, i - suffixLength);
best_longtext_b = longtext.substring(i + prefixLength);
best_shorttext_a = shorttext.substring(0, j - suffixLength);
best_shorttext_b = shorttext.substring(j + prefixLength);
}
}
if (best_common.length * 2 >= longtext.length) {
return [best_longtext_a, best_longtext_b,
best_shorttext_a, best_shorttext_b, best_common];
}
else {
return null;
}
}
/**
* Reduce the number of edits by eliminating semantically trivial equalities.
* @param {?} diffs Array of diff tuples.
* @return {?}
*/
diff_cleanupSemantic(diffs) {
/** @type {?} */
let changes = false;
/** @type {?} */
const equalities = [];
/** @type {?} */
let equalitiesLength = 0;
/** @type {?} */
let lastequality = null;
/** @type {?} */
let pointer = 0;
/** @type {?} */
let length_insertions1 = 0;
/** @type {?} */
let length_deletions1 = 0;
/** @type {?} */
let length_insertions2 = 0;
/** @type {?} */
let length_deletions2 = 0;
while (pointer < diffs.length) {
if (diffs[pointer][0] == 0 /* Equal */) {
// Equality found.
equalities[equalitiesLength++] = pointer;
length_insertions1 = length_insertions2;
length_deletions1 = length_deletions2;
length_insertions2 = 0;
length_deletions2 = 0;
lastequality = diffs[pointer][1];
}
else {
// An insertion or deletion.
if (diffs[pointer][0] == 1 /* Insert */) {
length_insertions2 += diffs[pointer][1].length;
}
else {
length_deletions2 += diffs[pointer][1].length;
}
// Eliminate an equality that is smaller or equal to the edits on both
// sides of it.
if (lastequality && (lastequality.length <=
Math.max(length_insertions1, length_deletions1)) &&
(lastequality.length <= Math.max(length_insertions2, length_deletions2))) {
// Duplicate record.
diffs.splice(equalities[equalitiesLength - 1], 0, [-1 /* Delete */, lastequality]);
// Change second copy to insert.
diffs[equalities[equalitiesLength - 1] + 1][0] = 1 /* Insert */;
// Throw away the equality we just deleted.
equalitiesLength--;
// Throw away the previous equality (it needs to be reevaluated).
equalitiesLength--;
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
length_insertions1 = 0; // Reset the counters.
length_deletions1 = 0;
length_insertions2 = 0;
length_deletions2 = 0;
lastequality = null;
changes = true;
}
}
pointer++;
}
// Normalize the diff.
if (changes) {
this.diff_cleanupMerge(diffs);
}
this.diff_cleanupSemanticLossless(diffs);
// Find any overlaps between deletions and insertions.
// e.g: <del>abcxxx</del><ins>xxxdef</ins>
// -> <del>abc</del>xxx<ins>def</ins>
// e.g: <del>xxxabc</del><ins>defxxx</ins>
// -> <ins>def</ins>xxx<del>abc</del>
// Only extract an overlap if it is as big as the edit ahead or behind it.
pointer = 1;
while (pointer < diffs.length) {
if (diffs[pointer - 1][0] == -1 /* Delete */ &&
diffs[pointer][0] == 1 /* Insert */) {
/** @type {?} */
const deletion = diffs[pointer - 1][1];
/** @type {?} */
const insertion = diffs[pointer][1];
/** @type {?} */
const overlap_length1 = this.diff_commonOverlap_(deletion, insertion);
/** @type {?} */
const overlap_length2 = this.diff_commonOverlap_(insertion, deletion);
if (overlap_length1 >= overlap_length2) {
if (overlap_length1 >= deletion.length / 2 ||
overlap_length1 >= insertion.length / 2) {
// Overlap found. Insert an equality and trim the surrounding edits.
diffs.splice(pointer, 0, [0 /* Equal */, insertion.substring(0, overlap_length1)]);
diffs[pointer - 1][1] =
deletion.substring(0, deletion.length - overlap_length1);
diffs[pointer + 1][1] = insertion.substring(overlap_length1);
pointer++;
}
}
else {
if (overlap_length2 >= deletion.length / 2 ||
overlap_length2 >= insertion.length / 2) {
// Reverse overlap found.
// Insert an equality and swap and trim the surrounding edits.
diffs.splice(pointer, 0, [0 /* Equal */, deletion.substring(0, overlap_length2)]);
diffs[pointer - 1][0] = 1 /* Insert */;
diffs[pointer - 1][1] =
insertion.substring(0, insertion.length - overlap_length2);
diffs[pointer + 1][0] = -1 /* Delete */;
diffs[pointer + 1][1] =
deletion.substring(overlap_length2);
pointer++;
}
}
pointer++;
}
pointer++;
}
}
;
/**
* Look for single edits surrounded on both sides by equalities
* which can be shifted sideways to align the edit to a word boundary.
* e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
* @param {?} diffs Array of diff tuples.
* @return {?}
*/
diff_cleanupSemanticLossless(diffs) {
/**
* Given two strings, compute a score representing whether the internal
* boundary falls on logical boundaries.
* Scores range from 6 (best) to 0 (worst).
* Closure, but does not reference any external constiables.
* @param {?} one First string.
* @param {?} two Second string.
* @return {?} The score.
*/
function diff_cleanupSemanticScore_(one, two) {
if (!one || !two) {
// Edges are the best.
return 6;
}
/** @type {?} */
const nonAlphaNumericRegex_ = new RegExp('/[^a-zA-Z0-9]/');
/** @type {?} */
const char1 = one.charAt(one.length - 1);
/** @type {?} */
const char2 = two.charAt(0);
/** @type {?} */
const nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);
/** @type {?} */
const nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_);
/** @type {?} */
const whitespace1 = nonAlphaNumeric1 &&
char1.match(this.whitespaceRegex_);
/** @type {?} */
const whitespace2 = nonAlphaNumeric2 &&
char2.match(this.whitespaceRegex_);
/** @type {?} */
const lineBreak1 = whitespace1 &&
char1.match(this.linebreakRegex_);
/** @type {?} */
const lineBreak2 = whitespace2 &&
char2.match(this.linebreakRegex_);
/** @type {?} */
const blankLine1 = lineBreak1 &&
one.match(this.blanklineEndRegex_);
/** @type {?} */
const blankLine2 = lineBreak2 &&
two.match(this.blanklineStartRegex_);
if (blankLine1 || blankLine2) {
// Five points for blank lines.
return 5;
}
else if (lineBreak1 || lineBreak2) {
// Four points for line breaks.
return 4;
}
else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
// Three points for end of sentences.
return 3;
}
else if (whitespace1 || whitespace2) {
// Two points for whitespace.
return 2;
}
else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
// One point for non-alphanumeric.
return 1;
}
return 0;
}
/** @type {?} */
let pointer = 1;
// Intentionally ignore the first and last element (don't need checking).
while (pointer < diffs.length - 1) {
if (diffs[pointer - 1][0] == 0 /* Equal */ &&
diffs[pointer + 1][0] == 0 /* Equal */) {
/** @type {?} */
let equality1 = diffs[pointer - 1][1];
/** @type {?} */
let edit = diffs[pointer][1];
/** @type {?} */
let equality2 = diffs[pointer + 1][1];
/** @type {?} */
const commonOffset = this.diff_commonSuffix(equality1, edit);
if (commonOffset) {
/** @type {?} */
const commonString = edit.substring(edit.length - commonOffset);
equality1 = equality1.substring(0, equality1.length - commonOffset);
edit = commonString + edit.substring(0, edit.length - commonOffset);
equality2 = commonString + equality2;
}
/** @type {?} */
let bestEquality1 = equality1;
/** @type {?} */
let bestEdit = edit;
/** @type {?} */
let bestEquality2 = equality2;
/** @type {?} */
let bestScore = diff_cleanupSemanticScore_(equality1, edit) +
diff_cleanupSemanticScore_(edit, equality2);
while (edit.charAt(0) === equality2.charAt(0)) {
equality1 += edit.charAt(0);
edit = edit.substring(1) + equality2.charAt(0);
equality2 = equality2.substring(1);
/** @type {?} */
const score = diff_cleanupSemanticScore_(equality1, edit) +
diff_cleanupSemanticScore_(edit, equality2);
// The >= encourages trailing rather than leading whitespace on edits.
if (score >= bestScore) {
bestScore = score;
bestEquality1 = equality1;
bestEdit = edit;
bestEquality2 = equality2;
}
}
if (diffs[pointer - 1][1] != bestEquality1) {
// We have an improvement, save it back to the diff.
if (bestEquality1) {
diffs[pointer - 1][1] = bestEquality1;
}
else {
diffs.splice(pointer - 1, 1);
pointer--;
}
diffs[pointer][1] = bestEdit;
if (bestEquality2) {
diffs[pointer + 1][1] = bestEquality2;
}
else {
diffs.splice(pointer + 1, 1);
pointer--;
}
}
}
pointer++;
}
}
;
/**
* Reduce the number of edits by eliminating operationally trivial equalities.
* @param {?} diffs Array of diff tuples.
* @return {?}
*/
diff_cleanupEfficiency(diffs) {
/** @type {?} */
let changes = false;
/** @type {?} */
const equalities = [];
/** @type {?} */
let equalitiesLength = 0;
/** @type {?} */
let lastequality = null;
/** @type {?} */
let pointer = 0;
/** @type {?} */
let pre_ins = false;
/** @type {?} */
let pre_del = false;
/** @type {?} */
let post_ins = false;
/** @type {?} */
let post_del = false;
while (pointer < diffs.length) {
if (diffs[pointer][0] == 0 /* Equal */) {
// Equality found.
if (diffs[pointer][1].length < this.Diff_EditCost &&
(post_ins || post_del)) {
// Candidate found.
equalities[equalitiesLength++] = pointer;
pre_ins = post_ins;
pre_del = post_del;
lastequality = diffs[pointer][1];
}
else {
// Not a candidate, and can never become one.
equalitiesLength = 0;
lastequality = null;
}
post_ins = post_del = false;
}
else {
// An insertion or deletion.
if (diffs[pointer][0] == -1 /* Delete */) {
post_del = true;
}
else {
post_ins = true;
}
/*
* Five types to be split:
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
* <ins>A</ins>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<ins>C</ins>
* <ins>A</del>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<del>C</del>
*/
if (lastequality && ((pre_ins && pre_del && post_ins && post_del) ||
((lastequality.length < this.Diff_EditCost / 2) &&
((pre_ins ? 1 : 0) + (pre_del ? 1 : 0) + (post_ins ? 1 : 0) + (post_del ? 1 : 0) == 3)))) {
// Duplicate record.
diffs.splice(equalities[equalitiesLength - 1], 0, [-1 /* Delete */, lastequality]);
// Change second copy to insert.
diffs[equalities[equalitiesLength - 1] + 1][0] = 1 /* Insert */;
equalitiesLength--; // Throw away the equality we just deleted;
lastequality = null;
if (pre_ins && pre_del) {
// No changes made which could affect previous entry, keep going.
post_ins = post_del = true;
equalitiesLength = 0;
}
else {
equalitiesLength--; // Throw away the previous equality.
pointer = equalitiesLength > 0 ?