📄 diff.js
字号:
// Diff_Match_Patch v1.3
// Computes the difference between two texts to create a patch.
// Applies the patch onto another text, allowing for errors.
// Copyright (C) 2006 Neil Fraser
// http://neil.fraser.name/software/diff_match_patch/
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License (www.gnu.org) for more details.
// Constants.
// Redefine these in your program to override the defaults.
// Number of seconds to map a diff before giving up. (0 for infinity)
var DIFF_TIMEOUT = 1.0;
// Cost of an empty edit operation in terms of edit characters.
var DIFF_EDIT_COST = 4;
// Tweak the relative importance (0.0 = accuracy, 1.0 = proximity)
var MATCH_BALANCE = 0.5;
// At what point is no match declared (0.0 = perfection, 1.0 = very loose)
var MATCH_THRESHOLD = 0.5;
// The min and max cutoffs used when computing text lengths.
var MATCH_MINLENGTH = 100;
var MATCH_MAXLENGTH = 1000;
// Chunk size for context length.
var PATCH_MARGIN = 4;
//////////////////////////////////////////////////////////////////////
// Diff //
//////////////////////////////////////////////////////////////////////
// The data structure representing a diff is an array of tuples:
// [[-1, "Hello"], [1, "Goodbye"], [0, " world."]]
// which means: delete "Hello", add "Goodbye" and keep " world."
function diff_main(text1, text2, checklines) {
// Find the differences between two texts. Return an array of changes.
// If checklines is present and false, then don't run a line-level diff first to identify the changed areas.
// Check for equality (speedup)
if (text1 == text2)
return [[0, text1]];
if (typeof checklines == 'undefined')
checklines = true;
var a;
// Trim off common prefix (speedup)
a = diff_prefix(text1, text2);
text1 = a[0];
text2 = a[1];
var commonprefix = a[2];
// Trim off common suffix (speedup)
a = diff_suffix(text1, text2);
text1 = a[0];
text2 = a[1];
var commonsuffix = a[2];
var diff, i;
var longtext = text1.length > text2.length ? text1 : text2;
var shorttext = text1.length > text2.length ? text2 : text1;
if (!text1) { // Just add some text (speedup)
diff = [[1, text2]];
} else if (!text2) { // Just delete some text (speedup)
diff = [[-1, text1]];
} else if ((i = longtext.indexOf(shorttext)) != -1) {
// Shorter text is inside the longer text (speedup)
diff = [[1, longtext.substring(0, i)], [0, shorttext], [1, longtext.substring(i+shorttext.length)]];
// Swap insertions for deletions if diff is reversed.
if (text1.length > text2.length)
diff[0][0] = diff[2][0] = -1;
} else {
longtext = shorttext = null; // Garbage collect
// Check to see if the problem can be split in two.
var hm = diff_halfmatch(text1, text2);
if (hm) {
// A half-match was found, sort out the return data.
var text1_a = hm[0];
var text1_b = hm[1];
var text2_a = hm[2];
var text2_b = hm[3];
var mid_common = hm[4];
// Send both pairs off for separate processing.
var diff_a = diff_main(text1_a, text2_a, checklines);
var diff_b = diff_main(text1_b, text2_b, checklines);
// Merge the results.
diff = diff_a.concat([[0, mid_common]], diff_b);
} else {
// Perform a real diff.
if (checklines && text1.length + text2.length < 250)
checklines = false; // Too trivial for the overhead.
if (checklines) {
// Scan the text on a line-by-line basis first.
a = diff_lines2chars(text1, text2);
text1 = a[0];
text2 = a[1];
var linearray = a[2];
}
diff = diff_map(text1, text2);
if (!diff) // No acceptable result.
diff = [[-1, text1], [1, text2]];
if (checklines) {
diff_chars2lines(diff, linearray); // Convert the diff back to original text.
diff_cleanup_semantic(diff); // Eliminate freak matches (e.g. blank lines)
// Rediff any replacement blocks, this time on character-by-character basis.
diff.push([0, '']); // Add a dummy entry at the end.
var pointer = 0;
var count_delete = 0;
var count_insert = 0;
var text_delete = '';
var text_insert = '';
while(pointer < diff.length) {
if (diff[pointer][0] == 1) {
count_insert++;
text_insert += diff[pointer][1];
} else if (diff[pointer][0] == -1) {
count_delete++;
text_delete += diff[pointer][1];
} else { // Upon reaching an equality, check for prior redundancies.
if (count_delete >= 1 && count_insert >= 1) {
// Delete the offending records and add the merged ones.
a = diff_main(text_delete, text_insert, false);
diff.splice(pointer - count_delete - count_insert, count_delete + count_insert);
pointer = pointer - count_delete - count_insert;
for (i=a.length-1; i>=0; i--)
diff.splice(pointer, 0, a[i]);
pointer = pointer + a.length;
}
count_insert = 0;
count_delete = 0;
text_delete = '';
text_insert = '';
}
pointer++;
}
diff.pop(); // Remove the dummy entry at the end.
}
}
}
if (commonprefix)
diff.unshift([0, commonprefix]);
if (commonsuffix)
diff.push([0, commonsuffix]);
diff_cleanup_merge(diff);
return diff;
}
function diff_lines2chars(text1, text2) {
// Split text into an array of strings.
// Reduce the texts to a string of hashes where each character represents one line.
var linearray = new Array(); // linearray[4] == "Hello\n"
var linehash = new Object(); // linehash["Hello\n"] == 4
// "\x00" is a valid JavaScript character, but the Venkman debugger doesn't like it (bug 335098)
// So we'll insert a junk entry to avoid generating a null character.
linearray.push('');
function diff_lines2chars_munge(text) {
// My first ever closure!
var i, line;
var chars = '';
while (text) {
i = text.indexOf('\n');
if (i == -1)
i = text.length;
line = text.substring(0, i+1);
text = text.substring(i+1);
if (linehash.hasOwnProperty ? linehash.hasOwnProperty(line) : (linehash[line] !== undefined)) {
chars += String.fromCharCode(linehash[line]);
} else {
linearray.push(line);
linehash[line] = linearray.length - 1;
chars += String.fromCharCode(linearray.length - 1);
}
}
return chars;
}
var chars1 = diff_lines2chars_munge(text1);
var chars2 = diff_lines2chars_munge(text2);
return [chars1, chars2, linearray];
}
function diff_chars2lines(diff, linearray) {
// Rehydrate the text in a diff from a string of line hashes to real lines of text.
var chars, text;
for (var x=0; x<diff.length; x++) {
chars = diff[x][1];
text = '';
for (var y=0; y<chars.length; y++)
text += linearray[chars.charCodeAt(y)];
diff[x][1] = text;
}
}
function diff_map(text1, text2) {
// Explore the intersection points between the two texts.
var now = new Date();
var ms_end = now.getTime() + DIFF_TIMEOUT * 1000; // Don't run for too long.
var max = (text1.length + text2.length) / 2;
var v_map1 = new Array();
var v_map2 = new Array();
var v1 = new Object();
var v2 = new Object();
v1[1] = 0;
v2[1] = 0;
var x, y;
var footstep; // Used to track overlapping paths.
var footsteps = new Object();
var done = false;
var hasOwnProperty = !!(footsteps.hasOwnProperty);
// If the total number of characters is odd, then the front path will collide with the reverse path.
var front = (text1.length + text2.length) % 2;
for (var d=0; d<max; d++) {
now = new Date();
if (DIFF_TIMEOUT > 0 && now.getTime() > ms_end) // Timeout reached
return null;
// Walk the front path one step.
v_map1[d] = new Object();
for (var k=-d; k<=d; k+=2) {
if (k == -d || k != d && v1[k-1] < v1[k+1])
x = v1[k+1];
else
x = v1[k-1]+1;
y = x - k;
footstep = x+","+y;
if (front && (hasOwnProperty ? footsteps.hasOwnProperty(footstep) : (footsteps[footstep] !== undefined)))
done = true;
if (!front)
footsteps[footstep] = d;
while (!done && x < text1.length && y < text2.length && text1.charAt(x) == text2.charAt(y)) {
x++; y++;
footstep = x+","+y;
if (front && (hasOwnProperty ? footsteps.hasOwnProperty(footstep) : (footsteps[footstep] !== undefined)))
done = true;
if (!front)
footsteps[footstep] = d;
}
v1[k] = x;
v_map1[d][x+","+y] = true;
if (done) {
// Front path ran over reverse path.
v_map2 = v_map2.slice(0, footsteps[footstep]+1);
var a = diff_path1(v_map1, text1.substring(0, x), text2.substring(0, y));
return a.concat(diff_path2(v_map2, text1.substring(x), text2.substring(y)));
}
}
// Walk the reverse path one step.
v_map2[d] = new Object();
for (var k=-d; k<=d; k+=2) {
if (k == -d || k != d && v2[k-1] < v2[k+1])
x = v2[k+1];
else
x = v2[k-1]+1;
y = x - k;
footstep = (text1.length-x)+","+(text2.length-y);
if (!front && (hasOwnProperty ? footsteps.hasOwnProperty(footstep) : (footsteps[footstep] !== undefined)))
done = true;
if (front)
footsteps[footstep] = d;
while (!done && x < text1.length && y < text2.length && text1.charAt(text1.length-x-1) == text2.charAt(text2.length-y-1)) {
x++; y++;
footstep = (text1.length-x)+","+(text2.length-y);
if (!front && (hasOwnProperty ? footsteps.hasOwnProperty(footstep) : (footsteps[footstep] !== undefined)))
done = true;
if (front)
footsteps[footstep] = d;
}
v2[k] = x;
v_map2[d][x+","+y] = true;
if (done) {
// Reverse path ran over front path.
v_map1 = v_map1.slice(0, footsteps[footstep]+1);
var a = diff_path1(v_map1, text1.substring(0, text1.length-x), text2.substring(0, text2.length-y));
return a.concat(diff_path2(v_map2, text1.substring(text1.length-x), text2.substring(text2.length-y)));
}
}
}
// Number of diffs equals number of characters, no commonality at all.
return null;
}
function diff_path1(v_map, text1, text2) {
// Work from the middle back to the start to determine the path.
var path = [];
var x = text1.length;
var y = text2.length;
var last_op = null;
for (var d=v_map.length-2; d>=0; d--) {
while(1) {
if (v_map[d].hasOwnProperty ? v_map[d].hasOwnProperty((x-1)+","+y) : (v_map[d][(x-1)+","+y] !== undefined)) {
x--;
if (last_op === -1)
path[0][1] = text1.charAt(x) + path[0][1];
else
path.unshift([-1, text1.charAt(x)]);
last_op = -1;
break;
} else if (v_map[d].hasOwnProperty ? v_map[d].hasOwnProperty(x+","+(y-1)) : (v_map[d][x+","+(y-1)] !== undefined)) {
y--;
if (last_op === 1)
path[0][1] = text2.charAt(y) + path[0][1];
else
path.unshift([1, text2.charAt(y)]);
last_op = 1;
break;
} else {
x--;
y--;
//if (text1.charAt(x) != text2.charAt(y))
// return alert("No diagonal. Can't happen. (diff_path1)");
if (last_op === 0)
path[0][1] = text1.charAt(x) + path[0][1];
else
path.unshift([0, text1.charAt(x)]);
last_op = 0;
}
}
}
return path;
}
function diff_path2(v_map, text1, text2) {
// Work from the middle back to the end to determine the path.
var path = [];
var x = text1.length;
var y = text2.length;
var last_op = null;
for (var d=v_map.length-2; d>=0; d--) {
while(1) {
if (v_map[d].hasOwnProperty ? v_map[d].hasOwnProperty((x-1)+","+y) : (v_map[d][(x-1)+","+y] !== undefined)) {
x--;
if (last_op === -1)
path[path.length-1][1] += text1.charAt(text1.length-x-1);
else
path.push([-1, text1.charAt(text1.length-x-1)]);
last_op = -1;
break;
} else if (v_map[d].hasOwnProperty ? v_map[d].hasOwnProperty(x+","+(y-1)) : (v_map[d][x+","+(y-1)] !== undefined)) {
y--;
if (last_op === 1)
path[path.length-1][1] += text2.charAt(text2.length-y-1);
else
path.push([1, text2.charAt(text2.length-y-1)]);
last_op = 1;
break;
} else {
x--;
y--;
//if (text1.charAt(text1.length-x-1) != text2.charAt(text2.length-y-1))
// return alert("No diagonal. Can't happen. (diff_path2)");
if (last_op === 0)
path[path.length-1][1] += text1.charAt(text1.length-x-1);
else
path.push([0, text1.charAt(text1.length-x-1)]);
last_op = 0;
}
}
}
return path;
}
function diff_prefix(text1, text2) {
// Trim off common prefix
var pointermin = 0;
var pointermax = Math.min(text1.length, text2.length);
var pointermid = pointermax;
while(pointermin < pointermid) {
if (text1.substring(0, pointermid) == text2.substring(0, pointermid))
pointermin = pointermid;
else
pointermax = pointermid;
pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
}
var commonprefix = text1.substring(0, pointermid);
text1 = text1.substring(pointermid);
text2 = text2.substring(pointermid);
return [text1, text2, commonprefix];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -