Files
openide/plugins/evaluation-plugin/core/resources/diff.js
Berkay Özerbay 22c95bd6da fix:Refactor diff.js in evaluation-plugin
GitOrigin-RevId: 9454dc73b294062f99cba51e866543a266bc1206
2024-06-20 12:51:08 +00:00

354 lines
9.8 KiB
JavaScript

class Diff {
diff(oldString, newString, options = {}) {
let callback = options.callback;
if (typeof options === 'function') {
callback = options;
options = {};
}
let self = this;
function done(value) {
value = self.postProcess(value, options);
if (callback) {
setTimeout(function() { callback(value); }, 0);
return true;
} else {
return value;
}
}
oldString = this.castInput(oldString, options);
newString = this.castInput(newString, options);
oldString = this.removeEmpty(this.tokenize(oldString, options));
newString = this.removeEmpty(this.tokenize(newString, options));
let newLen = newString.length, oldLen = oldString.length;
let editLength = 1;
let maxEditLength = newLen + oldLen;
if(options.maxEditLength != null) {
maxEditLength = Math.min(maxEditLength, options.maxEditLength);
}
const maxExecutionTime = options.timeout ?? Infinity;
const abortAfterTimestamp = Date.now() + maxExecutionTime;
let bestPath = [{ oldPos: -1, lastComponent: undefined }];
let newPos = this.extractCommon(bestPath[0], newString, oldString, 0, options);
if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
return done(buildValues(self, bestPath[0].lastComponent, newString, oldString, self.useLongestToken));
}
let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
function execEditLength() {
for (
let diagonalPath = Math.max(minDiagonalToConsider, -editLength);
diagonalPath <= Math.min(maxDiagonalToConsider, editLength);
diagonalPath += 2
) {
let basePath;
let removePath = bestPath[diagonalPath - 1],
addPath = bestPath[diagonalPath + 1];
if (removePath) {
bestPath[diagonalPath - 1] = undefined;
}
let canAdd = false;
if (addPath) {
const addPathNewPos = addPath.oldPos - diagonalPath;
canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
}
let canRemove = removePath && removePath.oldPos + 1 < oldLen;
if (!canAdd && !canRemove) {
bestPath[diagonalPath] = undefined;
continue;
}
if (!canRemove || (canAdd && removePath.oldPos < addPath.oldPos)) {
basePath = self.addToPath(addPath, true, false, 0, options);
} else {
basePath = self.addToPath(removePath, false, true, 1, options);
}
newPos = self.extractCommon(basePath, newString, oldString, diagonalPath, options);
if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
return done(buildValues(self, basePath.lastComponent, newString, oldString, self.useLongestToken));
} else {
bestPath[diagonalPath] = basePath;
if (basePath.oldPos + 1 >= oldLen) {
maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
}
if (newPos + 1 >= newLen) {
minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
}
}
}
editLength++;
}
if (callback) {
(function exec() {
setTimeout(function() {
if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
return callback();
}
if (!execEditLength()) {
exec();
}
}, 0);
}());
} else {
while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
let ret = execEditLength();
if (ret) {
return ret;
}
}
}
}
addToPath(path, added, removed, oldPosInc, options) {
let last = path.lastComponent;
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
return {
oldPos: path.oldPos + oldPosInc,
lastComponent: {count: last.count + 1, added: added, removed: removed, previousComponent: last.previousComponent }
};
} else {
return {
oldPos: path.oldPos + oldPosInc,
lastComponent: {count: 1, added: added, removed: removed, previousComponent: last }
};
}
}
extractCommon(basePath, newString, oldString, diagonalPath, options) {
let newLen = newString.length,
oldLen = oldString.length,
oldPos = basePath.oldPos,
newPos = oldPos - diagonalPath,
commonCount = 0;
while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldString[oldPos + 1], newString[newPos + 1], options)) {
newPos++;
oldPos++;
commonCount++;
if (options.oneChangePerToken) {
basePath.lastComponent = {count: 1, previousComponent: basePath.lastComponent, added: false, removed: false};
}
}
if (commonCount && !options.oneChangePerToken) {
basePath.lastComponent = {count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false};
}
basePath.oldPos = oldPos;
return newPos;
}
baseEquals(left, right, options) {
if (options.comparator) {
return options.comparator(left, right);
} else {
return left === right || (options.ignoreCase && left.toLowerCase() === right.toLowerCase());
}
}
removeEmpty(array) {
let ret = [];
for (let i = 0; i < array.length; i++) {
if (array[i]) {
ret.push(array[i]);
}
}
return ret;
}
castInput(value) {
return value;
}
tokenize(value) {
return Array.from(value);
}
join(chars) {
return chars.join('');
}
postProcess(changeObjects) {
return changeObjects;
}
tokenize(value, options) {
if (options && options.stripTrailingCr) {
value = value.replace(/\r\n/g, '\n');
}
let retLines = [],
linesAndNewlines = value.split(/(\n|\r\n)/);
if (!linesAndNewlines[linesAndNewlines.length - 1]) {
linesAndNewlines.pop();
}
for (let i = 0; i < linesAndNewlines.length; i++) {
let line = linesAndNewlines[i];
if (i % 2 && (!options || !options.newlineIsToken)) {
retLines[retLines.length - 1] += line;
} else {
retLines.push(line);
}
}
return retLines;
}
equals(left, right, options) {
if (options.ignoreWhitespace) {
if (!options.newlineIsToken || !left.includes('\n')) {
left = left.trim();
}
if (!options.newlineIsToken || !right.includes('\n')) {
right = right.trim();
}
}
return this.baseEquals(left, right, options);
}
diffLines(oldStr, newStr, callback) {
return this.diff(oldStr, newStr, { callback });
}
generateOptions(options, defaults) {
if (typeof options === 'function') {
defaults.callback = options;
} else if (options) {
for (let name in options) {
if (options.hasOwnProperty(name)) {
defaults[name] = options[name];
}
}
}
return defaults;
}
computeDiff(oldCode, newCode) {
const changes = this.diffLines(oldCode || "", newCode);
let oldIndex = -1;
return changes.map(({ value, count, removed, added }) => {
const lines = value.split(/\r\n|\r|\n/);
const lastLine = lines.pop();
if (lastLine) {
lines.push(lastLine);
}
const result = {
oldIndex,
lines,
count,
removed,
added
};
if (!added) {
oldIndex += count || 0;
}
return result;
});
}
unifiedSlideDiff(prevCode, currCode, slideIndex) {
const changes = this.computeDiff(prevCode, currCode);
const unifiedDiff = [];
let oldLineNumber = 1;
let newLineNumber = 1;
changes.forEach(change => {
const { lines, added, removed } = change;
lines.forEach(line => {
if (added) {
unifiedDiff.push({
content: `+ ${line}`,
type: "added",
oldLineNumber: '',
newLineNumber: newLineNumber,
slideIndex
});
newLineNumber++;
} else if (removed) {
unifiedDiff.push({
content: `- ${line}`,
type: "removed",
oldLineNumber: oldLineNumber,
newLineNumber: '',
slideIndex
});
oldLineNumber++;
} else {
unifiedDiff.push({
content: ` ${line}`,
type: "unchanged",
oldLineNumber: oldLineNumber,
newLineNumber: newLineNumber,
slideIndex
});
oldLineNumber++;
newLineNumber++;
}
});
});
return unifiedDiff;
}
}
function buildValues(diff, lastComponent, newString, oldString, useLongestToken) {
const components = [];
let nextComponent;
while (lastComponent) {
components.push(lastComponent);
nextComponent = lastComponent.previousComponent;
delete lastComponent.previousComponent;
lastComponent = nextComponent;
}
components.reverse();
let componentPos = 0,
componentLen = components.length,
newPos = 0,
oldPos = 0;
for (; componentPos < componentLen; componentPos++) {
let component = components[componentPos];
if (!component.removed) {
if (!component.added && useLongestToken) {
let value = newString.slice(newPos, newPos + component.count);
value = value.map(function(value, i) {
let 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;
if (!component.added) {
oldPos += component.count;
}
} else {
component.value = diff.join(oldString.slice(oldPos, oldPos + component.count));
oldPos += component.count;
}
}
return components;
}