diff-match-patch-es
Version:
ESM and TypeScript rewrite of Google's diff-match-patch
1,343 lines (1,338 loc) • 47.1 kB
JavaScript
const defaultOptions = /* @__PURE__ */ Object.freeze({
diffTimeout: 1,
diffEditCost: 4,
matchThreshold: 0.5,
matchDistance: 1e3,
patchDeleteThreshold: 0.5,
patchMargin: 4,
matchMaxBits: 32
});
function resolveOptions(options) {
if (options?.__resolved)
return options;
const resolved = {
...defaultOptions,
...options
};
Object.defineProperty(resolved, "__resolved", { value: true, enumerable: false });
return resolved;
}
const DIFF_DELETE = -1;
const DIFF_INSERT = 1;
const DIFF_EQUAL = 0;
function createDiff(op, text) {
return [op, text];
}
function diffMain(text1, text2, options, opt_checklines = true, opt_deadline) {
const resolved = resolveOptions(options);
if (typeof opt_deadline == "undefined") {
if (resolved.diffTimeout <= 0)
opt_deadline = Number.MAX_VALUE;
else
opt_deadline = (/* @__PURE__ */ new Date()).getTime() + resolved.diffTimeout * 1e3;
}
const deadline = opt_deadline;
if (text1 == null || text2 == null)
throw new Error("Null input. (diff_main)");
if (text1 === text2) {
if (text1)
return [createDiff(DIFF_EQUAL, text1)];
return [];
}
const checklines = opt_checklines;
let commonlength = diffCommonPrefix(text1, text2);
const commonprefix = text1.substring(0, commonlength);
text1 = text1.substring(commonlength);
text2 = text2.substring(commonlength);
commonlength = diffCommonSuffix(text1, text2);
const commonsuffix = text1.substring(text1.length - commonlength);
text1 = text1.substring(0, text1.length - commonlength);
text2 = text2.substring(0, text2.length - commonlength);
const diffs = diffCompute(text1, text2, resolved, checklines, deadline);
if (commonprefix)
diffs.unshift(createDiff(DIFF_EQUAL, commonprefix));
if (commonsuffix)
diffs.push(createDiff(DIFF_EQUAL, commonsuffix));
diffCleanupMerge(diffs);
return diffs;
}
function diffCompute(text1, text2, options, checklines, deadline) {
let diffs;
if (!text1) {
return [createDiff(DIFF_INSERT, text2)];
}
if (!text2) {
return [createDiff(DIFF_DELETE, text1)];
}
const longtext = text1.length > text2.length ? text1 : text2;
const shorttext = text1.length > text2.length ? text2 : text1;
const i = longtext.indexOf(shorttext);
if (i !== -1) {
diffs = [createDiff(DIFF_INSERT, longtext.substring(0, i)), createDiff(DIFF_EQUAL, shorttext), createDiff(DIFF_INSERT, longtext.substring(i + shorttext.length))];
if (text1.length > text2.length)
diffs[0][0] = diffs[2][0] = DIFF_DELETE;
return diffs;
}
if (shorttext.length === 1) {
return [createDiff(DIFF_DELETE, text1), createDiff(DIFF_INSERT, text2)];
}
const hm = diffHalfMatch(text1, text2, options);
if (hm) {
const text1_a = hm[0];
const text1_b = hm[1];
const text2_a = hm[2];
const text2_b = hm[3];
const mid_common = hm[4];
const diffs_a = diffMain(text1_a, text2_a, options, checklines, deadline);
const diffs_b = diffMain(text1_b, text2_b, options, checklines, deadline);
return diffs_a.concat([createDiff(DIFF_EQUAL, mid_common)], diffs_b);
}
if (checklines && text1.length > 100 && text2.length > 100)
return diffLineMode(text1, text2, options, deadline);
return diffBisect(text1, text2, options, deadline);
}
function diffLineMode(text1, text2, options, deadline) {
const a = diffLinesToChars(text1, text2);
text1 = a.chars1;
text2 = a.chars2;
const linearray = a.lineArray;
const diffs = diffMain(text1, text2, options, false, deadline);
diffCharsToLines(diffs, linearray);
diffCleanupSemantic(diffs);
diffs.push(createDiff(DIFF_EQUAL, ""));
let pointer = 0;
let count_delete = 0;
let count_insert = 0;
let text_delete = "";
let text_insert = "";
while (pointer < diffs.length) {
switch (diffs[pointer][0]) {
case DIFF_INSERT:
count_insert++;
text_insert += diffs[pointer][1];
break;
case DIFF_DELETE:
count_delete++;
text_delete += diffs[pointer][1];
break;
case DIFF_EQUAL:
if (count_delete >= 1 && count_insert >= 1) {
diffs.splice(pointer - count_delete - count_insert, count_delete + count_insert);
pointer = pointer - count_delete - count_insert;
const subDiff = diffMain(text_delete, text_insert, options, false, deadline);
for (let j = subDiff.length - 1; j >= 0; j--)
diffs.splice(pointer, 0, subDiff[j]);
pointer = pointer + subDiff.length;
}
count_insert = 0;
count_delete = 0;
text_delete = "";
text_insert = "";
break;
}
pointer++;
}
diffs.pop();
return diffs;
}
function diffBisect(text1, text2, options, deadline) {
const text1_length = text1.length;
const text2_length = text2.length;
const max_d = Math.ceil((text1_length + text2_length) / 2);
const v_offset = max_d;
const v_length = 2 * max_d;
const v1 = new Array(v_length);
const v2 = new Array(v_length);
for (let x = 0; x < v_length; x++) {
v1[x] = -1;
v2[x] = -1;
}
v1[v_offset + 1] = 0;
v2[v_offset + 1] = 0;
const delta = text1_length - text2_length;
const front = delta % 2 !== 0;
let k1start = 0;
let k1end = 0;
let k2start = 0;
let k2end = 0;
for (let d = 0; d < max_d; d++) {
if ((/* @__PURE__ */ new Date()).getTime() > deadline)
break;
for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
const k1_offset = v_offset + k1;
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;
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) {
k1end += 2;
} else if (y1 > text2_length) {
k1start += 2;
} else if (front) {
const k2_offset = v_offset + delta - k1;
if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] !== -1) {
const x2 = text1_length - v2[k2_offset];
if (x1 >= x2) {
return diffBisectSplit(text1, text2, options, x1, y1, deadline);
}
}
}
}
for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
const k2_offset = v_offset + k2;
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;
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) {
k2end += 2;
} else if (y2 > text2_length) {
k2start += 2;
} else if (!front) {
const k1_offset = v_offset + delta - k2;
if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] !== -1) {
const x1 = v1[k1_offset];
const y1 = v_offset + x1 - k1_offset;
x2 = text1_length - x2;
if (x1 >= x2) {
return diffBisectSplit(text1, text2, options, x1, y1, deadline);
}
}
}
}
}
return [createDiff(DIFF_DELETE, text1), createDiff(DIFF_INSERT, text2)];
}
function diffBisectSplit(text1, text2, options, x, y, deadline) {
const text1a = text1.substring(0, x);
const text2a = text2.substring(0, y);
const text1b = text1.substring(x);
const text2b = text2.substring(y);
const diffs = diffMain(text1a, text2a, options, false, deadline);
const diffsb = diffMain(text1b, text2b, options, false, deadline);
return diffs.concat(diffsb);
}
function diffLinesToChars(text1, text2) {
const lineArray = [];
const lineHash = {};
let maxLines = 4e4;
lineArray[0] = "";
function diffLinesToCharsMunge(text) {
let chars = "";
let lineStart = 0;
let lineEnd = -1;
let lineArrayLength = lineArray.length;
while (lineEnd < text.length - 1) {
lineEnd = text.indexOf("\n", lineStart);
if (lineEnd === -1)
lineEnd = text.length - 1;
let line = text.substring(lineStart, lineEnd + 1);
if (lineHash.hasOwnProperty ? Object.prototype.hasOwnProperty.call(lineHash, line) : lineHash[line] !== undefined) {
chars += String.fromCharCode(lineHash[line]);
} else {
if (lineArrayLength === maxLines) {
line = text.substring(lineStart);
lineEnd = text.length;
}
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
lineStart = lineEnd + 1;
}
return chars;
}
const chars1 = diffLinesToCharsMunge(text1);
maxLines = 65535;
const chars2 = diffLinesToCharsMunge(text2);
return { chars1, chars2, lineArray };
}
function diffCharsToLines(diffs, lineArray) {
for (let i = 0; i < diffs.length; i++) {
const chars = diffs[i][1];
const text = [];
for (let j = 0; j < chars.length; j++)
text[j] = lineArray[chars.charCodeAt(j)];
diffs[i][1] = text.join("");
}
}
function diffCommonPrefix(text1, text2) {
if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0))
return 0;
let pointermin = 0;
let pointermax = Math.min(text1.length, text2.length);
let pointermid = pointermax;
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;
}
function diffCommonSuffix(text1, text2) {
if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
return 0;
}
let pointermin = 0;
let pointermax = Math.min(text1.length, text2.length);
let pointermid = pointermax;
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;
}
function diffCommonOverlap(text1, text2) {
const text1_length = text1.length;
const text2_length = text2.length;
if (text1_length === 0 || text2_length === 0)
return 0;
if (text1_length > text2_length)
text1 = text1.substring(text1_length - text2_length);
else if (text1_length < text2_length)
text2 = text2.substring(0, text1_length);
const text_length = Math.min(text1_length, text2_length);
if (text1 === text2)
return text_length;
let best = 0;
let length = 1;
while (true) {
const pattern = text1.substring(text_length - length);
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++;
}
}
}
function diffHalfMatch(text1, text2, options) {
if (options.diffTimeout <= 0) {
return null;
}
const longtext = text1.length > text2.length ? text1 : text2;
const shorttext = text1.length > text2.length ? text2 : text1;
if (longtext.length < 4 || shorttext.length * 2 < longtext.length)
return null;
function diffHalfMatchI(longtext2, shorttext2, i) {
const seed = longtext2.substring(i, i + Math.floor(longtext2.length / 4));
let j = -1;
let best_common = "";
let best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b;
while ((j = shorttext2.indexOf(seed, j + 1)) !== -1) {
const prefixLength = diffCommonPrefix(longtext2.substring(i), shorttext2.substring(j));
const suffixLength = diffCommonSuffix(longtext2.substring(0, i), shorttext2.substring(0, j));
if (best_common.length < suffixLength + prefixLength) {
best_common = shorttext2.substring(j - suffixLength, j) + shorttext2.substring(j, j + prefixLength);
best_longtext_a = longtext2.substring(0, i - suffixLength);
best_longtext_b = longtext2.substring(i + prefixLength);
best_shorttext_a = shorttext2.substring(0, j - suffixLength);
best_shorttext_b = shorttext2.substring(j + prefixLength);
}
}
if (best_common.length * 2 >= longtext2.length)
return [best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b, best_common];
else
return null;
}
const hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4));
const hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2));
let hm;
if (!hm1 && !hm2) {
return null;
} else if (!hm2) {
hm = hm1;
} else if (!hm1) {
hm = hm2;
} else {
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
}
let text1_a, text1_b, text2_a, 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];
}
const mid_common = hm[4];
return [text1_a, text1_b, text2_a, text2_b, mid_common];
}
function diffCleanupSemantic(diffs) {
let changes = false;
const equalities = [];
let equalitiesLength = 0;
let lastEquality = null;
let pointer = 0;
let length_insertions1 = 0;
let length_deletions1 = 0;
let length_insertions2 = 0;
let length_deletions2 = 0;
while (pointer < diffs.length) {
if (diffs[pointer][0] === DIFF_EQUAL) {
equalities[equalitiesLength++] = pointer;
length_insertions1 = length_insertions2;
length_deletions1 = length_deletions2;
length_insertions2 = 0;
length_deletions2 = 0;
lastEquality = diffs[pointer][1];
} else {
if (diffs[pointer][0] === DIFF_INSERT)
length_insertions2 += diffs[pointer][1].length;
else
length_deletions2 += diffs[pointer][1].length;
if (lastEquality && lastEquality.length <= Math.max(length_insertions1, length_deletions1) && lastEquality.length <= Math.max(length_insertions2, length_deletions2)) {
diffs.splice(equalities[equalitiesLength - 1], 0, createDiff(DIFF_DELETE, lastEquality));
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
equalitiesLength--;
equalitiesLength--;
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
length_insertions1 = 0;
length_deletions1 = 0;
length_insertions2 = 0;
length_deletions2 = 0;
lastEquality = null;
changes = true;
}
}
pointer++;
}
if (changes)
diffCleanupMerge(diffs);
diffCleanupSemanticLossless(diffs);
pointer = 1;
while (pointer < diffs.length) {
if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
const deletion = diffs[pointer - 1][1];
const insertion = diffs[pointer][1];
const overlap_length1 = diffCommonOverlap(deletion, insertion);
const overlap_length2 = diffCommonOverlap(insertion, deletion);
if (overlap_length1 >= overlap_length2) {
if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) {
diffs.splice(pointer, 0, createDiff(DIFF_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) {
diffs.splice(pointer, 0, createDiff(DIFF_EQUAL, deletion.substring(0, overlap_length2)));
diffs[pointer - 1][0] = DIFF_INSERT;
diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlap_length2);
diffs[pointer + 1][0] = DIFF_DELETE;
diffs[pointer + 1][1] = deletion.substring(overlap_length2);
pointer++;
}
}
pointer++;
}
pointer++;
}
}
const nonAlphaNumericRegex_ = /[^a-z0-9]/i;
const whitespaceRegex_ = /\s/;
const linebreakRegex_ = /[\r\n]/;
const blanklineEndRegex_ = /\n\r?\n$/;
const blanklineStartRegex_ = /^\r?\n\r?\n/;
function diffCleanupSemanticLossless(diffs) {
function diffCleanupSemanticScore(one, two) {
if (!one || !two) {
return 6;
}
const char1 = one.charAt(one.length - 1);
const char2 = two.charAt(0);
const nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);
const nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_);
const whitespace1 = nonAlphaNumeric1 && char1.match(whitespaceRegex_);
const whitespace2 = nonAlphaNumeric2 && char2.match(whitespaceRegex_);
const lineBreak1 = whitespace1 && char1.match(linebreakRegex_);
const lineBreak2 = whitespace2 && char2.match(linebreakRegex_);
const blankLine1 = lineBreak1 && one.match(blanklineEndRegex_);
const blankLine2 = lineBreak2 && two.match(blanklineStartRegex_);
if (blankLine1 || blankLine2) {
return 5;
} else if (lineBreak1 || lineBreak2) {
return 4;
} else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
return 3;
} else if (whitespace1 || whitespace2) {
return 2;
} else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
return 1;
}
return 0;
}
let pointer = 1;
while (pointer < diffs.length - 1) {
if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
let equality1 = diffs[pointer - 1][1];
let edit = diffs[pointer][1];
let equality2 = diffs[pointer + 1][1];
const commonOffset = diffCommonSuffix(equality1, edit);
if (commonOffset) {
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;
}
let bestEquality1 = equality1;
let bestEdit = edit;
let bestEquality2 = equality2;
let bestScore = diffCleanupSemanticScore(equality1, edit) + diffCleanupSemanticScore(edit, equality2);
while (edit.charAt(0) === equality2.charAt(0)) {
equality1 += edit.charAt(0);
edit = edit.substring(1) + equality2.charAt(0);
equality2 = equality2.substring(1);
const score = diffCleanupSemanticScore(equality1, edit) + diffCleanupSemanticScore(edit, equality2);
if (score >= bestScore) {
bestScore = score;
bestEquality1 = equality1;
bestEdit = edit;
bestEquality2 = equality2;
}
}
if (diffs[pointer - 1][1] !== bestEquality1) {
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++;
}
}
function diffCleanupEfficiency(diffs, options = {}) {
const {
diffEditCost = defaultOptions.diffEditCost
} = options;
let changes = false;
const equalities = [];
let equalitiesLength = 0;
let lastEquality = null;
let pointer = 0;
let pre_ins = false;
let pre_del = false;
let post_ins = false;
let post_del = false;
while (pointer < diffs.length) {
if (diffs[pointer][0] === DIFF_EQUAL) {
if (diffs[pointer][1].length < diffEditCost && (post_ins || post_del)) {
equalities[equalitiesLength++] = pointer;
pre_ins = post_ins;
pre_del = post_del;
lastEquality = diffs[pointer][1];
} else {
equalitiesLength = 0;
lastEquality = null;
}
post_ins = post_del = false;
} else {
let booleanCount = function(...args) {
return args.filter(Boolean).length;
};
if (diffs[pointer][0] === DIFF_DELETE)
post_del = true;
else
post_ins = true;
if (lastEquality && (pre_ins && pre_del && post_ins && post_del || lastEquality.length < diffEditCost / 2 && booleanCount(pre_ins, pre_del, post_ins, post_del) === 3)) {
diffs.splice(equalities[equalitiesLength - 1], 0, createDiff(DIFF_DELETE, lastEquality));
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
equalitiesLength--;
lastEquality = null;
if (pre_ins && pre_del) {
post_ins = post_del = true;
equalitiesLength = 0;
} else {
equalitiesLength--;
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
post_ins = post_del = false;
}
changes = true;
}
}
pointer++;
}
if (changes)
diffCleanupMerge(diffs);
}
function diffCleanupMerge(diffs) {
diffs.push(createDiff(DIFF_EQUAL, ""));
let pointer = 0;
let count_delete = 0;
let count_insert = 0;
let text_delete = "";
let text_insert = "";
let commonlength;
while (pointer < diffs.length) {
switch (diffs[pointer][0]) {
case DIFF_INSERT:
count_insert++;
text_insert += diffs[pointer][1];
pointer++;
break;
case DIFF_DELETE:
count_delete++;
text_delete += diffs[pointer][1];
pointer++;
break;
case DIFF_EQUAL:
if (count_delete + count_insert > 1) {
if (count_delete !== 0 && count_insert !== 0) {
commonlength = diffCommonPrefix(text_insert, text_delete);
if (commonlength !== 0) {
if (pointer - count_delete - count_insert > 0 && diffs[pointer - count_delete - count_insert - 1][0] === DIFF_EQUAL) {
diffs[pointer - count_delete - count_insert - 1][1] += text_insert.substring(0, commonlength);
} else {
diffs.splice(0, 0, createDiff(DIFF_EQUAL, text_insert.substring(0, commonlength)));
pointer++;
}
text_insert = text_insert.substring(commonlength);
text_delete = text_delete.substring(commonlength);
}
commonlength = diffCommonSuffix(text_insert, text_delete);
if (commonlength !== 0) {
diffs[pointer][1] = text_insert.substring(text_insert.length - commonlength) + diffs[pointer][1];
text_insert = text_insert.substring(0, text_insert.length - commonlength);
text_delete = text_delete.substring(0, text_delete.length - commonlength);
}
}
pointer -= count_delete + count_insert;
diffs.splice(pointer, count_delete + count_insert);
if (text_delete.length) {
diffs.splice(pointer, 0, createDiff(DIFF_DELETE, text_delete));
pointer++;
}
if (text_insert.length) {
diffs.splice(pointer, 0, createDiff(DIFF_INSERT, text_insert));
pointer++;
}
pointer++;
} else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
diffs[pointer - 1][1] += diffs[pointer][1];
diffs.splice(pointer, 1);
} else {
pointer++;
}
count_insert = 0;
count_delete = 0;
text_delete = "";
text_insert = "";
break;
}
}
if (diffs[diffs.length - 1][1] === "")
diffs.pop();
let changes = false;
pointer = 1;
while (pointer < diffs.length - 1) {
if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
if (diffs[pointer][1].substring(diffs[pointer][1].length - diffs[pointer - 1][1].length) === diffs[pointer - 1][1]) {
diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
diffs.splice(pointer - 1, 1);
changes = true;
} else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
diffs[pointer - 1][1] += diffs[pointer + 1][1];
diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
diffs.splice(pointer + 1, 1);
changes = true;
}
}
pointer++;
}
if (changes)
diffCleanupMerge(diffs);
}
function diffXIndex(diffs, loc) {
let chars1 = 0;
let chars2 = 0;
let last_chars1 = 0;
let last_chars2 = 0;
let x;
for (x = 0; x < diffs.length; x++) {
if (diffs[x][0] !== DIFF_INSERT) {
chars1 += diffs[x][1].length;
}
if (diffs[x][0] !== DIFF_DELETE) {
chars2 += diffs[x][1].length;
}
if (chars1 > loc) {
break;
}
last_chars1 = chars1;
last_chars2 = chars2;
}
if (diffs.length !== x && diffs[x][0] === DIFF_DELETE)
return last_chars2;
return last_chars2 + (loc - last_chars1);
}
function diffPrettyHtml(diffs) {
const html = [];
const pattern_amp = /&/g;
const pattern_lt = /</g;
const pattern_gt = />/g;
const pattern_para = /\n/g;
for (let x = 0; x < diffs.length; x++) {
const op = diffs[x][0];
const data = diffs[x][1];
const text = data.replace(pattern_amp, "&").replace(pattern_lt, "<").replace(pattern_gt, ">").replace(pattern_para, "¶<br>");
switch (op) {
case DIFF_INSERT:
html[x] = `<ins style="background:#e6ffe6;">${text}</ins>`;
break;
case DIFF_DELETE:
html[x] = `<del style="background:#ffe6e6;">${text}</del>`;
break;
case DIFF_EQUAL:
html[x] = `<span>${text}</span>`;
break;
}
}
return html.join("");
}
function diffText1(diffs) {
const text = [];
for (let x = 0; x < diffs.length; x++) {
if (diffs[x][0] !== DIFF_INSERT)
text[x] = diffs[x][1];
}
return text.join("");
}
function diffText2(diffs) {
const text = [];
for (let x = 0; x < diffs.length; x++) {
if (diffs[x][0] !== DIFF_DELETE)
text[x] = diffs[x][1];
}
return text.join("");
}
function diffLevenshtein(diffs) {
let levenshtein = 0;
let insertions = 0;
let deletions = 0;
for (let x = 0; x < diffs.length; x++) {
const op = diffs[x][0];
const data = diffs[x][1];
switch (op) {
case DIFF_INSERT:
insertions += data.length;
break;
case DIFF_DELETE:
deletions += data.length;
break;
case DIFF_EQUAL:
levenshtein += Math.max(insertions, deletions);
insertions = 0;
deletions = 0;
break;
}
}
levenshtein += Math.max(insertions, deletions);
return levenshtein;
}
function diffToDelta(diffs) {
const text = [];
for (let x = 0; x < diffs.length; x++) {
switch (diffs[x][0]) {
case DIFF_INSERT:
text[x] = `+${encodeURI(diffs[x][1])}`;
break;
case DIFF_DELETE:
text[x] = `-${diffs[x][1].length}`;
break;
case DIFF_EQUAL:
text[x] = `=${diffs[x][1].length}`;
break;
}
}
return text.join(" ").replace(/%20/g, " ");
}
function diffFromDelta(text1, delta) {
const diffs = [];
let diffsLength = 0;
let pointer = 0;
const tokens = delta.split(/\t/g);
for (let x = 0; x < tokens.length; x++) {
const param = tokens[x].substring(1);
switch (tokens[x].charAt(0)) {
case "+":
try {
diffs[diffsLength++] = createDiff(DIFF_INSERT, decodeURI(param));
} catch {
throw new Error(`Illegal escape in diff_fromDelta: ${param}`);
}
break;
case "-":
// Fall through.
case "=": {
const n = Number.parseInt(param, 10);
if (Number.isNaN(n) || n < 0)
throw new Error(`Invalid number in diff_fromDelta: ${param}`);
const text = text1.substring(pointer, pointer += n);
if (tokens[x].charAt(0) === "=")
diffs[diffsLength++] = createDiff(DIFF_EQUAL, text);
else
diffs[diffsLength++] = createDiff(DIFF_DELETE, text);
break;
}
default:
if (tokens[x])
throw new Error(`Invalid diff operation in diff_fromDelta: ${tokens[x]}`);
}
}
if (pointer !== text1.length)
throw new Error(`Delta length (${pointer}) does not equal source text length (${text1.length}).`);
return diffs;
}
function matchMain(text, pattern, loc, options) {
if (text == null || pattern == null || loc == null)
throw new Error("Null input. (match_main)");
loc = Math.max(0, Math.min(loc, text.length));
if (text === pattern) {
return 0;
} else if (!text.length) {
return -1;
} else if (text.substring(loc, loc + pattern.length) === pattern) {
return loc;
} else {
return matchBitap(text, pattern, loc, options);
}
}
function matchBitap(text, pattern, loc, options) {
const resolved = resolveOptions(options);
if (pattern.length > resolved.matchMaxBits)
throw new Error("Pattern too long for this browser.");
const s = matchAlphabet(pattern);
function matchBitapScore(e, x) {
const accuracy = e / pattern.length;
const proximity = Math.abs(loc - x);
if (!resolved.matchDistance) {
return proximity ? 1 : accuracy;
}
return accuracy + proximity / resolved.matchDistance;
}
let score_threshold = resolved.matchThreshold;
let best_loc = text.indexOf(pattern, loc);
if (best_loc !== -1) {
score_threshold = Math.min(matchBitapScore(0, best_loc), score_threshold);
best_loc = text.lastIndexOf(pattern, loc + pattern.length);
if (best_loc !== -1)
score_threshold = Math.min(matchBitapScore(0, best_loc), score_threshold);
}
const matchmask = 1 << pattern.length - 1;
best_loc = -1;
let bin_min, bin_mid;
let bin_max = pattern.length + text.length;
let last_rd = [];
for (let d = 0; d < pattern.length; d++) {
bin_min = 0;
bin_mid = bin_max;
while (bin_min < bin_mid) {
if (matchBitapScore(d, loc + bin_mid) <= score_threshold)
bin_min = bin_mid;
else
bin_max = bin_mid;
bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min);
}
bin_max = bin_mid;
let start = Math.max(1, loc - bin_mid + 1);
const finish = Math.min(loc + bin_mid, text.length) + pattern.length;
const rd = new Array(finish + 2);
rd[finish + 1] = (1 << d) - 1;
for (let j = finish; j >= start; j--) {
const charMatch = s[text.charAt(j - 1)];
if (d === 0) {
rd[j] = (rd[j + 1] << 1 | 1) & charMatch;
} else {
rd[j] = (rd[j + 1] << 1 | 1) & charMatch | ((last_rd[j + 1] | last_rd[j]) << 1 | 1) | last_rd[j + 1];
}
if (rd[j] & matchmask) {
const score = matchBitapScore(d, j - 1);
if (score <= score_threshold) {
score_threshold = score;
best_loc = j - 1;
if (best_loc > loc) {
start = Math.max(1, 2 * loc - best_loc);
} else {
break;
}
}
}
}
if (matchBitapScore(d + 1, loc) > score_threshold)
break;
last_rd = rd;
}
return best_loc;
}
function matchAlphabet(pattern) {
const s = {};
for (let i = 0; i < pattern.length; i++)
s[pattern.charAt(i)] = 0;
for (let i = 0; i < pattern.length; i++)
s[pattern.charAt(i)] |= 1 << pattern.length - i - 1;
return s;
}
function patchAddContext(patch, text, options) {
if (text.length === 0)
return;
if (patch.start2 === null)
throw new Error("patch not initialized");
const {
matchMaxBits = defaultOptions.matchMaxBits,
patchMargin = defaultOptions.patchMargin
} = options;
let pattern = text.substring(patch.start2, patch.start2 + patch.length1);
let padding = 0;
while (text.indexOf(pattern) !== text.lastIndexOf(pattern) && pattern.length < matchMaxBits - patchMargin - patchMargin) {
padding += patchMargin;
pattern = text.substring(patch.start2 - padding, patch.start2 + patch.length1 + padding);
}
padding += patchMargin;
const prefix = text.substring(patch.start2 - padding, patch.start2);
if (prefix)
patch.diffs.unshift(createDiff(DIFF_EQUAL, prefix));
const suffix = text.substring(patch.start2 + patch.length1, patch.start2 + patch.length1 + padding);
if (suffix)
patch.diffs.push(createDiff(DIFF_EQUAL, suffix));
patch.start1 -= prefix.length;
patch.start2 -= prefix.length;
patch.length1 += prefix.length + suffix.length;
patch.length2 += prefix.length + suffix.length;
}
function patchMake(a, opt_b, opt_c, options = {}) {
const resolved = {
...defaultOptions,
...options
};
let text1, diffs;
if (typeof a == "string" && typeof opt_b == "string" && typeof opt_c == "undefined") {
text1 = a;
diffs = diffMain(text1, opt_b, resolved, true);
if (diffs.length > 2) {
diffCleanupSemantic(diffs);
diffCleanupEfficiency(diffs);
}
} else if (a && typeof a == "object" && typeof opt_b == "undefined" && typeof opt_c == "undefined") {
diffs = /** @type {Diff[]} */
a;
text1 = diffText1(diffs);
} else if (typeof a == "string" && opt_b && typeof opt_b == "object" && typeof opt_c == "undefined") {
text1 = /** @type {string} */
a;
diffs = /** @type {Diff[]} */
opt_b;
} else if (typeof a == "string" && typeof opt_b == "string" && opt_c && typeof opt_c == "object") {
text1 = /** @type {string} */
a;
diffs = /** @type {Diff[]} */
opt_c;
} else {
throw new Error("Unknown call format to patch_make.");
}
if (diffs.length === 0)
return [];
const patches = [];
let patch = createPatch();
let patchDiffLength = 0;
let char_count1 = 0;
let char_count2 = 0;
let prepatch_text = text1;
let postpatch_text = text1;
for (let x = 0; x < diffs.length; x++) {
const diff_type = diffs[x][0];
const diff_text = diffs[x][1];
if (!patchDiffLength && diff_type !== DIFF_EQUAL) {
patch.start1 = char_count1;
patch.start2 = char_count2;
}
switch (diff_type) {
case DIFF_INSERT:
patch.diffs[patchDiffLength++] = diffs[x];
patch.length2 += diff_text.length;
postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + postpatch_text.substring(char_count2);
break;
case DIFF_DELETE:
patch.length1 += diff_text.length;
patch.diffs[patchDiffLength++] = diffs[x];
postpatch_text = postpatch_text.substring(0, char_count2) + postpatch_text.substring(char_count2 + diff_text.length);
break;
case DIFF_EQUAL:
if (diff_text.length <= 2 * resolved.patchMargin && patchDiffLength && diffs.length !== x + 1) {
patch.diffs[patchDiffLength++] = diffs[x];
patch.length1 += diff_text.length;
patch.length2 += diff_text.length;
} else if (diff_text.length >= 2 * resolved.patchMargin) {
if (patchDiffLength) {
patchAddContext(patch, prepatch_text, resolved);
patches.push(patch);
patch = createPatch();
patchDiffLength = 0;
prepatch_text = postpatch_text;
char_count1 = char_count2;
}
}
break;
}
if (diff_type !== DIFF_INSERT)
char_count1 += diff_text.length;
if (diff_type !== DIFF_DELETE)
char_count2 += diff_text.length;
}
if (patchDiffLength) {
patchAddContext(patch, prepatch_text, resolved);
patches.push(patch);
}
return patches;
}
function patchDeepCopy(patches) {
const patchesCopy = [];
for (let x = 0; x < patches.length; x++) {
const patch = patches[x];
const patchCopy = createPatch();
patchCopy.diffs = [];
for (let y = 0; y < patch.diffs.length; y++) {
patchCopy.diffs[y] = createDiff(patch.diffs[y][0], patch.diffs[y][1]);
}
patchCopy.start1 = patch.start1;
patchCopy.start2 = patch.start2;
patchCopy.length1 = patch.length1;
patchCopy.length2 = patch.length2;
patchesCopy[x] = patchCopy;
}
return patchesCopy;
}
function patchApply(patches, text, options) {
if (patches.length === 0)
return [text, []];
patches = patchDeepCopy(patches);
const resolved = resolveOptions(options);
const nullPadding = patchAddPadding(patches, resolved);
text = nullPadding + text + nullPadding;
patchSplitMax(patches, resolved);
let delta = 0;
const results = [];
for (let x = 0; x < patches.length; x++) {
const expected_loc = patches[x].start2 + delta;
const text1 = diffText1(patches[x].diffs);
let start_loc;
let end_loc = -1;
if (text1.length > resolved.matchMaxBits) {
start_loc = matchMain(
text,
text1.substring(0, resolved.matchMaxBits),
expected_loc,
options
);
if (start_loc !== -1) {
end_loc = matchMain(
text,
text1.substring(text1.length - resolved.matchMaxBits),
expected_loc + text1.length - resolved.matchMaxBits,
options
);
if (end_loc === -1 || start_loc >= end_loc) {
start_loc = -1;
}
}
} else {
start_loc = matchMain(text, text1, expected_loc, options);
}
if (start_loc === -1) {
results[x] = false;
delta -= patches[x].length2 - patches[x].length1;
} else {
results[x] = true;
delta = start_loc - expected_loc;
let text2;
if (end_loc === -1)
text2 = text.substring(start_loc, start_loc + text1.length);
else
text2 = text.substring(start_loc, end_loc + resolved.matchMaxBits);
if (text1 === text2) {
text = text.substring(0, start_loc) + diffText2(patches[x].diffs) + text.substring(start_loc + text1.length);
} else {
const diffs = diffMain(text1, text2, options, false);
if (text1.length > resolved.matchMaxBits && diffLevenshtein(diffs) / text1.length > resolved.patchDeleteThreshold) {
results[x] = false;
} else {
diffCleanupSemanticLossless(diffs);
let index1 = 0;
let index2 = 0;
for (let y = 0; y < patches[x].diffs.length; y++) {
const mod = patches[x].diffs[y];
if (mod[0] !== DIFF_EQUAL)
index2 = diffXIndex(diffs, index1);
if (mod[0] === DIFF_INSERT) {
text = text.substring(0, start_loc + index2) + mod[1] + text.substring(start_loc + index2);
} else if (mod[0] === DIFF_DELETE) {
text = text.substring(0, start_loc + index2) + text.substring(start_loc + diffXIndex(diffs, index1 + mod[1].length));
}
if (mod[0] !== DIFF_DELETE)
index1 += mod[1].length;
}
}
}
}
}
text = text.substring(nullPadding.length, text.length - nullPadding.length);
return [text, results];
}
function patchAddPadding(patches, options = {}) {
const {
patchMargin: paddingLength = defaultOptions.patchMargin
} = options;
let nullPadding = "";
for (let x = 1; x <= paddingLength; x++)
nullPadding += String.fromCharCode(x);
for (let x = 0; x < patches.length; x++) {
patches[x].start1 += paddingLength;
patches[x].start2 += paddingLength;
}
let patch = patches[0];
let diffs = patch.diffs;
if (diffs.length === 0 || diffs[0][0] !== DIFF_EQUAL) {
diffs.unshift(createDiff(DIFF_EQUAL, nullPadding));
patch.start1 -= paddingLength;
patch.start2 -= paddingLength;
patch.length1 += paddingLength;
patch.length2 += paddingLength;
} else if (paddingLength > diffs[0][1].length) {
const extraLength = paddingLength - diffs[0][1].length;
diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1];
patch.start1 -= extraLength;
patch.start2 -= extraLength;
patch.length1 += extraLength;
patch.length2 += extraLength;
}
patch = patches[patches.length - 1];
diffs = patch.diffs;
if (diffs.length === 0 || diffs[diffs.length - 1][0] !== DIFF_EQUAL) {
diffs.push(createDiff(DIFF_EQUAL, nullPadding));
patch.length1 += paddingLength;
patch.length2 += paddingLength;
} else if (paddingLength > diffs[diffs.length - 1][1].length) {
const extraLength = paddingLength - diffs[diffs.length - 1][1].length;
diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength);
patch.length1 += extraLength;
patch.length2 += extraLength;
}
return nullPadding;
}
function patchSplitMax(patches, options) {
const resolved = resolveOptions(options);
for (let x = 0; x < patches.length; x++) {
if (patches[x].length1 <= resolved.matchMaxBits)
continue;
const bigpatch = patches[x];
patches.splice(x--, 1);
let start1 = bigpatch.start1;
let start2 = bigpatch.start2;
let precontext = "";
while (bigpatch.diffs.length !== 0) {
const patch = createPatch();
let empty = true;
patch.start1 = start1 - precontext.length;
patch.start2 = start2 - precontext.length;
if (precontext !== "") {
patch.length1 = patch.length2 = precontext.length;
patch.diffs.push(createDiff(DIFF_EQUAL, precontext));
}
while (bigpatch.diffs.length !== 0 && patch.length1 < resolved.matchMaxBits - resolved.patchMargin) {
const diff_type = bigpatch.diffs[0][0];
let diff_text = bigpatch.diffs[0][1];
if (diff_type === DIFF_INSERT) {
patch.length2 += diff_text.length;
start2 += diff_text.length;
patch.diffs.push(bigpatch.diffs.shift());
empty = false;
} else if (diff_type === DIFF_DELETE && patch.diffs.length === 1 && patch.diffs[0][0] === DIFF_EQUAL && diff_text.length > 2 * resolved.matchMaxBits) {
patch.length1 += diff_text.length;
start1 += diff_text.length;
empty = false;
patch.diffs.push(createDiff(diff_type, diff_text));
bigpatch.diffs.shift();
} else {
diff_text = diff_text.substring(0, resolved.matchMaxBits - patch.length1 - resolved.patchMargin);
patch.length1 += diff_text.length;
start1 += diff_text.length;
if (diff_type === DIFF_EQUAL) {
patch.length2 += diff_text.length;
start2 += diff_text.length;
} else {
empty = false;
}
patch.diffs.push(createDiff(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);
}
}
}
precontext = diffText2(patch.diffs);
precontext = precontext.substring(precontext.length - resolved.patchMargin);
const postcontext = diffText1(bigpatch.diffs).substring(0, resolved.patchMargin);
if (postcontext !== "") {
patch.length1 += postcontext.length;
patch.length2 += postcontext.length;
if (patch.diffs.length !== 0 && patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) {
patch.diffs[patch.diffs.length - 1][1] += postcontext;
} else {
patch.diffs.push(createDiff(DIFF_EQUAL, postcontext));
}
}
if (!empty)
patches.splice(++x, 0, patch);
}
}
}
function patchToText(patches) {
const text = [];
for (let x = 0; x < patches.length; x++)
text[x] = patches[x];
return text.join("");
}
function patchFromText(textline) {
const patches = [];
if (!textline)
return patches;
const text = textline.split("\n");
let textPointer = 0;
const patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/;
while (textPointer < text.length) {
const m = text[textPointer].match(patchHeader);
if (!m)
throw new Error(`Invalid patch string: ${text[textPointer]}`);
const patch = createPatch();
patches.push(patch);
patch.start1 = Number.parseInt(m[1], 10);
if (m[2] === "") {
patch.start1--;
patch.length1 = 1;
} else if (m[2] === "0") {
patch.length1 = 0;
} else {
patch.start1--;
patch.length1 = Number.parseInt(m[2], 10);
}
patch.start2 = Number.parseInt(m[3], 10);
if (m[4] === "") {
patch.start2--;
patch.length2 = 1;
} else if (m[4] === "0") {
patch.length2 = 0;
} else {
patch.start2--;
patch.length2 = Number.parseInt(m[4], 10);
}
textPointer++;
while (textPointer < text.length) {
const sign = text[textPointer].charAt(0);
let line = "";
try {
line = decodeURI(text[textPointer].substring(1));
} catch {
throw new Error(`Illegal escape in patch_fromText: ${line}`);
}
if (sign === "-") {
patch.diffs.push(createDiff(DIFF_DELETE, line));
} else if (sign === "+") {
patch.diffs.push(createDiff(DIFF_INSERT, line));
} else if (sign === " ") {
patch.diffs.push(createDiff(DIFF_EQUAL, line));
} else if (sign === "@") {
break;
} else if (sign === "") ; else {
throw new Error(`Invalid patch mode "${sign}" in: ${line}`);
}
textPointer++;
}
}
return patches;
}
function createPatch() {
const patch = {
diffs: [],
start1: null,
start2: null,
length1: 0,
length2: 0
};
patch.toString = function() {
let coords1, coords2;
if (this.length1 === 0)
coords1 = `${this.start1},0`;
else if (this.length1 === 1)
coords1 = this.start1 + 1;
else
coords1 = `${this.start1 + 1},${this.length1}`;
if (this.length2 === 0)
coords2 = `${this.start2},0`;
else if (this.length2 === 1)
coords2 = this.start2 + 1;
else
coords2 = `${this.start2 + 1},${this.length2}`;
const text = [`@@ -${coords1} +${coords2} @@
`];
let op;
for (let x = 0; x < this.diffs.length; x++) {
switch (this.diffs[x][0]) {
case DIFF_INSERT:
op = "+";
break;
case DIFF_DELETE:
op = "-";
break;
case DIFF_EQUAL:
op = " ";
break;
}
text[x + 1] = `${op + encodeURI(this.diffs[x][1])}
`;
}
return text.join("").replace(/%20/g, " ");
};
return patch;
}
export { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, defaultOptions, diffMain as diff, diffCharsToLines, diffCleanupEfficiency, diffCleanupMerge, diffCleanupSemantic, diffCleanupSemanticLossless, diffCommonPrefix, diffCommonSuffix, diffFromDelta, diffLevenshtein, diffLinesToChars, diffMain, diffPrettyHtml, diffText1, diffText2, diffToDelta, diffXIndex, matchMain as match, matchAlphabet, matchBitap, matchMain, patchMake as patch, patchAddPadding, patchApply, patchDeepCopy, patchFromText, patchMake, patchSplitMax, patchToText, resolveOptions };