You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
30 KiB
305 lines
30 KiB
|
3 years ago
|
/*istanbul ignore start*/
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
Object.defineProperty(exports, "__esModule", {
|
||
|
|
value: true
|
||
|
|
});
|
||
|
|
exports.default = Diff;
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
function Diff() {}
|
||
|
|
|
||
|
|
Diff.prototype = {
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
diff: function diff(oldString, newString) {
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
var
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
||
|
|
var callback = options.callback;
|
||
|
|
|
||
|
|
if (typeof options === 'function') {
|
||
|
|
callback = options;
|
||
|
|
options = {};
|
||
|
|
}
|
||
|
|
|
||
|
|
this.options = options;
|
||
|
|
var self = this;
|
||
|
|
|
||
|
|
function done(value) {
|
||
|
|
if (callback) {
|
||
|
|
setTimeout(function () {
|
||
|
|
callback(undefined, value);
|
||
|
|
}, 0);
|
||
|
|
return true;
|
||
|
|
} else {
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
} // Allow subclasses to massage the input prior to running
|
||
|
|
|
||
|
|
|
||
|
|
oldString = this.castInput(oldString);
|
||
|
|
newString = this.castInput(newString);
|
||
|
|
oldString = this.removeEmpty(this.tokenize(oldString));
|
||
|
|
newString = this.removeEmpty(this.tokenize(newString));
|
||
|
|
var newLen = newString.length,
|
||
|
|
oldLen = oldString.length;
|
||
|
|
var editLength = 1;
|
||
|
|
var maxEditLength = newLen + oldLen;
|
||
|
|
var bestPath = [{
|
||
|
|
newPos: -1,
|
||
|
|
components: []
|
||
|
|
}]; // Seed editLength = 0, i.e. the content starts with the same values
|
||
|
|
|
||
|
|
var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0);
|
||
|
|
|
||
|
|
if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) {
|
||
|
|
// Identity per the equality and tokenizer
|
||
|
|
return done([{
|
||
|
|
value: this.join(newString),
|
||
|
|
count: newString.length
|
||
|
|
}]);
|
||
|
|
} // Main worker method. checks all permutations of a given edit length for acceptance.
|
||
|
|
|
||
|
|
|
||
|
|
function execEditLength() {
|
||
|
|
for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) {
|
||
|
|
var basePath =
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
void 0
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
;
|
||
|
|
|
||
|
|
var addPath = bestPath[diagonalPath - 1],
|
||
|
|
removePath = bestPath[diagonalPath + 1],
|
||
|
|
_oldPos = (removePath ? removePath.newPos : 0) - diagonalPath;
|
||
|
|
|
||
|
|
if (addPath) {
|
||
|
|
// No one else is going to attempt to use this value, clear it
|
||
|
|
bestPath[diagonalPath - 1] = undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
var canAdd = addPath && addPath.newPos + 1 < newLen,
|
||
|
|
canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen;
|
||
|
|
|
||
|
|
if (!canAdd && !canRemove) {
|
||
|
|
// If this path is a terminal then prune
|
||
|
|
bestPath[diagonalPath] = undefined;
|
||
|
|
continue;
|
||
|
|
} // Select the diagonal that we want to branch from. We select the prior
|
||
|
|
// path whose position in the new string is the farthest from the origin
|
||
|
|
// and does not pass the bounds of the diff graph
|
||
|
|
|
||
|
|
|
||
|
|
if (!canAdd || canRemove && addPath.newPos < removePath.newPos) {
|
||
|
|
basePath = clonePath(removePath);
|
||
|
|
self.pushComponent(basePath.components, undefined, true);
|
||
|
|
} else {
|
||
|
|
basePath = addPath; // No need to clone, we've pulled it from the list
|
||
|
|
|
||
|
|
basePath.newPos++;
|
||
|
|
self.pushComponent(basePath.components, true, undefined);
|
||
|
|
}
|
||
|
|
|
||
|
|
_oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done
|
||
|
|
|
||
|
|
if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) {
|
||
|
|
return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken));
|
||
|
|
} else {
|
||
|
|
// Otherwise track this path as a potential candidate and continue.
|
||
|
|
bestPath[diagonalPath] = basePath;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
editLength++;
|
||
|
|
} // Performs the length of edit iteration. Is a bit fugly as this has to support the
|
||
|
|
// sync and async mode which is never fun. Loops over execEditLength until a value
|
||
|
|
// is produced.
|
||
|
|
|
||
|
|
|
||
|
|
if (callback) {
|
||
|
|
(function exec() {
|
||
|
|
setTimeout(function () {
|
||
|
|
// This should not happen, but we want to be safe.
|
||
|
|
|
||
|
|
/* istanbul ignore next */
|
||
|
|
if (editLength > maxEditLength) {
|
||
|
|
return callback();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!execEditLength()) {
|
||
|
|
exec();
|
||
|
|
}
|
||
|
|
}, 0);
|
||
|
|
})();
|
||
|
|
} else {
|
||
|
|
while (editLength <= maxEditLength) {
|
||
|
|
var ret = execEditLength();
|
||
|
|
|
||
|
|
if (ret) {
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
pushComponent: function pushComponent(components, added, removed) {
|
||
|
|
var last = components[components.length - 1];
|
||
|
|
|
||
|
|
if (last && last.added === added && last.removed === removed) {
|
||
|
|
// We need to clone here as the component clone operation is just
|
||
|
|
// as shallow array clone
|
||
|
|
components[components.length - 1] = {
|
||
|
|
count: last.count + 1,
|
||
|
|
added: added,
|
||
|
|
removed: removed
|
||
|
|
};
|
||
|
|
} else {
|
||
|
|
components.push({
|
||
|
|
count: 1,
|
||
|
|
added: added,
|
||
|
|
removed: removed
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) {
|
||
|
|
var newLen = newString.length,
|
||
|
|
oldLen = oldString.length,
|
||
|
|
newPos = basePath.newPos,
|
||
|
|
oldPos = newPos - diagonalPath,
|
||
|
|
commonCount = 0;
|
||
|
|
|
||
|
|
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) {
|
||
|
|
newPos++;
|
||
|
|
oldPos++;
|
||
|
|
commonCount++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (commonCount) {
|
||
|
|
basePath.components.push({
|
||
|
|
count: commonCount
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
basePath.newPos = newPos;
|
||
|
|
return oldPos;
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
equals: function equals(left, right) {
|
||
|
|
if (this.options.comparator) {
|
||
|
|
return this.options.comparator(left, right);
|
||
|
|
} else {
|
||
|
|
return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase();
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
removeEmpty: function removeEmpty(array) {
|
||
|
|
var ret = [];
|
||
|
|
|
||
|
|
for (var i = 0; i < array.length; i++) {
|
||
|
|
if (array[i]) {
|
||
|
|
ret.push(array[i]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
castInput: function castInput(value) {
|
||
|
|
return value;
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
tokenize: function tokenize(value) {
|
||
|
|
return value.split('');
|
||
|
|
},
|
||
|
|
|
||
|
|
/*istanbul ignore start*/
|
||
|
|
|
||
|
|
/*istanbul ignore end*/
|
||
|
|
join: function join(chars) {
|
||
|
|
return chars.join('');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
function buildValues(diff, components, newString, oldString, useLongestToken) {
|
||
|
|
var componentPos = 0,
|
||
|
|
componentLen = components.length,
|
||
|
|
newPos = 0,
|
||
|
|
oldPos = 0;
|
||
|
|
|
||
|
|
for (; componentPos < componentLen; componentPos++) {
|
||
|
|
var component = components[componentPos];
|
||
|
|
|
||
|
|
if (!component.removed) {
|
||
|
|
if (!component.added && useLongestToken) {
|
||
|
|
var value = newString.slice(newPos, newPos + component.count);
|
||
|
|
value = value.map(function (value, i) {
|
||
|
|
var oldValue = oldString[oldPos + i];
|
||
|
|
return oldValue.length > value.length ? oldValue : value;
|
||
|
|
});
|
||
|
|
component.value = diff.join(value);
|
||
|
|
} else {
|
||
|
|
component.value = diff.join(newString.slice(newPos, newPos + component.count));
|
||
|
|
}
|
||
|
|
|
||
|
|
newPos += component.count; // Common case
|
||
|
|
|
||
|
|
if (!component.added) {
|
||
|
|
oldPos += component.count;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
|
||
|
|
oldPos += component.count; // Reverse add and remove so removes are output first to match common convention
|
||
|
|
// The diffing algorithm is tied to add then remove output and this is the simplest
|
||
|
|
// route to get the desired output with minimal overhead.
|
||
|
|
|
||
|
|
if (componentPos && components[componentPos - 1].added) {
|
||
|
|
var tmp = components[componentPos - 1];
|
||
|
|
components[componentPos - 1] = components[componentPos];
|
||
|
|
components[componentPos] = tmp;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} // Special case handle for when one terminal is ignored (i.e. whitespace).
|
||
|
|
// For this case we merge the terminal into the prior string and drop the change.
|
||
|
|
// This is only available for string mode.
|
||
|
|
|
||
|
|
|
||
|
|
var lastComponent = components[componentLen - 1];
|
||
|
|
|
||
|
|
if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) {
|
||
|
|
components[componentLen - 2].value += lastComponent.value;
|
||
|
|
components.pop();
|
||
|
|
}
|
||
|
|
|
||
|
|
return components;
|
||
|
|
}
|
||
|
|
|
||
|
|
function clonePath(path) {
|
||
|
|
return {
|
||
|
|
newPos: path.newPos,
|
||
|
|
components: path.components.slice(0)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9kaWZmL2Jhc2UuanMiXSwibmFtZXMiOlsiRGlmZiIsInByb3RvdHlwZSIsImRpZmYiLCJvbGRTdHJpbmciLCJuZXdTdHJpbmciLCJvcHRpb25zIiwiY2FsbGJhY2siLCJzZWxmIiwiZG9uZSIsInZhbHVlIiwic2V0VGltZW91dCIsInVuZGVmaW5lZCIsImNhc3RJbnB1dCIsInJlbW92ZUVtcHR5IiwidG9rZW5pemUiLCJuZXdMZW4iLCJsZW5ndGgiLCJvbGRMZW4iLCJlZGl0TGVuZ3RoIiwibWF4RWRpdExlbmd0aCIsImJlc3RQYXRoIiwibmV3UG9zIiwiY29tcG9uZW50cyIsIm9sZFBvcyIsImV4dHJhY3RDb21tb24iLCJqb2luIiwiY291bnQiLCJleGVjRWRpdExlbmd0aCIsImRpYWdvbmFsUGF0aCIsImJhc2VQYXRoIiwiYWRkUGF0aCIsInJlbW92ZVBhdGgiLCJjYW5BZGQiLCJjYW5SZW1vdmUiLCJjbG9uZVBhdGgiLCJwdXNoQ29tcG9uZW50IiwiYnVpbGRWYWx1ZXMiLCJ1c2VMb25nZXN0VG9rZW4iLCJleGVjIiwicmV0IiwiYWRkZWQiLCJyZW1vdmVkIiwibGFzdCIsInB1c2giLCJjb21tb25Db3VudCIsImVxdWFscyIsImxlZnQiLCJyaWdodCIsImNvbXBhcmF0b3IiLCJpZ25vcmVDYXNlIiwidG9Mb3dlckNhc2UiLCJhcnJheSIsImkiLCJzcGxpdCIsImNoYXJzIiwiY29tcG9uZW50UG9zIiwiY29tcG9uZW50TGVuIiwiY29tcG9uZW50Iiwic2xpY2UiLCJtYXAiLCJvbGRWYWx1ZSIsInRtcCIsImxhc3RDb21wb25lbnQiLCJwb3AiLCJwYXRoIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7QUFBZSxTQUFTQSxJQUFULEdBQWdCLENBQUU7O0FBRWpDQSxJQUFJLENBQUNDLFNBQUwsR0FBaUI7QUFBQTs7QUFBQTtBQUNmQyxFQUFBQSxJQURlLGdCQUNWQyxTQURVLEVBQ0NDLFNBREQsRUFDMEI7QUFBQTtBQUFBO0FBQUE7QUFBZEMs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
|