monaco-editor
Version:
A browser based code editor
382 lines (381 loc) • 21.1 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { LcsDiff } from '../../../base/common/diff/diff.js';
import * as strings from '../../../base/common/strings.js';
var MAXIMUM_RUN_TIME = 5000; // 5 seconds
var MINIMUM_MATCHING_CHARACTER_LENGTH = 3;
function computeDiff(originalSequence, modifiedSequence, continueProcessingPredicate, pretty) {
var diffAlgo = new LcsDiff(originalSequence, modifiedSequence, continueProcessingPredicate);
return diffAlgo.ComputeDiff(pretty);
}
var LineMarkerSequence = /** @class */ (function () {
function LineMarkerSequence(lines) {
var startColumns = [];
var endColumns = [];
for (var i = 0, length_1 = lines.length; i < length_1; i++) {
startColumns[i] = LineMarkerSequence._getFirstNonBlankColumn(lines[i], 1);
endColumns[i] = LineMarkerSequence._getLastNonBlankColumn(lines[i], 1);
}
this._lines = lines;
this._startColumns = startColumns;
this._endColumns = endColumns;
}
LineMarkerSequence.prototype.getLength = function () {
return this._lines.length;
};
LineMarkerSequence.prototype.getElementAtIndex = function (i) {
return this._lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1);
};
LineMarkerSequence.prototype.getStartLineNumber = function (i) {
return i + 1;
};
LineMarkerSequence.prototype.getEndLineNumber = function (i) {
return i + 1;
};
LineMarkerSequence._getFirstNonBlankColumn = function (txt, defaultValue) {
var r = strings.firstNonWhitespaceIndex(txt);
if (r === -1) {
return defaultValue;
}
return r + 1;
};
LineMarkerSequence._getLastNonBlankColumn = function (txt, defaultValue) {
var r = strings.lastNonWhitespaceIndex(txt);
if (r === -1) {
return defaultValue;
}
return r + 2;
};
LineMarkerSequence.prototype.getCharSequence = function (shouldIgnoreTrimWhitespace, startIndex, endIndex) {
var charCodes = [];
var lineNumbers = [];
var columns = [];
var len = 0;
for (var index = startIndex; index <= endIndex; index++) {
var lineContent = this._lines[index];
var startColumn = (shouldIgnoreTrimWhitespace ? this._startColumns[index] : 1);
var endColumn = (shouldIgnoreTrimWhitespace ? this._endColumns[index] : lineContent.length + 1);
for (var col = startColumn; col < endColumn; col++) {
charCodes[len] = lineContent.charCodeAt(col - 1);
lineNumbers[len] = index + 1;
columns[len] = col;
len++;
}
}
return new CharSequence(charCodes, lineNumbers, columns);
};
return LineMarkerSequence;
}());
var CharSequence = /** @class */ (function () {
function CharSequence(charCodes, lineNumbers, columns) {
this._charCodes = charCodes;
this._lineNumbers = lineNumbers;
this._columns = columns;
}
CharSequence.prototype.getLength = function () {
return this._charCodes.length;
};
CharSequence.prototype.getElementAtIndex = function (i) {
return this._charCodes[i];
};
CharSequence.prototype.getStartLineNumber = function (i) {
return this._lineNumbers[i];
};
CharSequence.prototype.getStartColumn = function (i) {
return this._columns[i];
};
CharSequence.prototype.getEndLineNumber = function (i) {
return this._lineNumbers[i];
};
CharSequence.prototype.getEndColumn = function (i) {
return this._columns[i] + 1;
};
return CharSequence;
}());
var CharChange = /** @class */ (function () {
function CharChange(originalStartLineNumber, originalStartColumn, originalEndLineNumber, originalEndColumn, modifiedStartLineNumber, modifiedStartColumn, modifiedEndLineNumber, modifiedEndColumn) {
this.originalStartLineNumber = originalStartLineNumber;
this.originalStartColumn = originalStartColumn;
this.originalEndLineNumber = originalEndLineNumber;
this.originalEndColumn = originalEndColumn;
this.modifiedStartLineNumber = modifiedStartLineNumber;
this.modifiedStartColumn = modifiedStartColumn;
this.modifiedEndLineNumber = modifiedEndLineNumber;
this.modifiedEndColumn = modifiedEndColumn;
}
CharChange.createFromDiffChange = function (diffChange, originalCharSequence, modifiedCharSequence) {
var originalStartLineNumber;
var originalStartColumn;
var originalEndLineNumber;
var originalEndColumn;
var modifiedStartLineNumber;
var modifiedStartColumn;
var modifiedEndLineNumber;
var modifiedEndColumn;
if (diffChange.originalLength === 0) {
originalStartLineNumber = 0;
originalStartColumn = 0;
originalEndLineNumber = 0;
originalEndColumn = 0;
}
else {
originalStartLineNumber = originalCharSequence.getStartLineNumber(diffChange.originalStart);
originalStartColumn = originalCharSequence.getStartColumn(diffChange.originalStart);
originalEndLineNumber = originalCharSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);
originalEndColumn = originalCharSequence.getEndColumn(diffChange.originalStart + diffChange.originalLength - 1);
}
if (diffChange.modifiedLength === 0) {
modifiedStartLineNumber = 0;
modifiedStartColumn = 0;
modifiedEndLineNumber = 0;
modifiedEndColumn = 0;
}
else {
modifiedStartLineNumber = modifiedCharSequence.getStartLineNumber(diffChange.modifiedStart);
modifiedStartColumn = modifiedCharSequence.getStartColumn(diffChange.modifiedStart);
modifiedEndLineNumber = modifiedCharSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);
modifiedEndColumn = modifiedCharSequence.getEndColumn(diffChange.modifiedStart + diffChange.modifiedLength - 1);
}
return new CharChange(originalStartLineNumber, originalStartColumn, originalEndLineNumber, originalEndColumn, modifiedStartLineNumber, modifiedStartColumn, modifiedEndLineNumber, modifiedEndColumn);
};
return CharChange;
}());
function postProcessCharChanges(rawChanges) {
if (rawChanges.length <= 1) {
return rawChanges;
}
var result = [rawChanges[0]];
var prevChange = result[0];
for (var i = 1, len = rawChanges.length; i < len; i++) {
var currChange = rawChanges[i];
var originalMatchingLength = currChange.originalStart - (prevChange.originalStart + prevChange.originalLength);
var modifiedMatchingLength = currChange.modifiedStart - (prevChange.modifiedStart + prevChange.modifiedLength);
// Both of the above should be equal, but the continueProcessingPredicate may prevent this from being true
var matchingLength = Math.min(originalMatchingLength, modifiedMatchingLength);
if (matchingLength < MINIMUM_MATCHING_CHARACTER_LENGTH) {
// Merge the current change into the previous one
prevChange.originalLength = (currChange.originalStart + currChange.originalLength) - prevChange.originalStart;
prevChange.modifiedLength = (currChange.modifiedStart + currChange.modifiedLength) - prevChange.modifiedStart;
}
else {
// Add the current change
result.push(currChange);
prevChange = currChange;
}
}
return result;
}
var LineChange = /** @class */ (function () {
function LineChange(originalStartLineNumber, originalEndLineNumber, modifiedStartLineNumber, modifiedEndLineNumber, charChanges) {
this.originalStartLineNumber = originalStartLineNumber;
this.originalEndLineNumber = originalEndLineNumber;
this.modifiedStartLineNumber = modifiedStartLineNumber;
this.modifiedEndLineNumber = modifiedEndLineNumber;
this.charChanges = charChanges;
}
LineChange.createFromDiffResult = function (shouldIgnoreTrimWhitespace, diffChange, originalLineSequence, modifiedLineSequence, continueProcessingPredicate, shouldComputeCharChanges, shouldPostProcessCharChanges) {
var originalStartLineNumber;
var originalEndLineNumber;
var modifiedStartLineNumber;
var modifiedEndLineNumber;
var charChanges = undefined;
if (diffChange.originalLength === 0) {
originalStartLineNumber = originalLineSequence.getStartLineNumber(diffChange.originalStart) - 1;
originalEndLineNumber = 0;
}
else {
originalStartLineNumber = originalLineSequence.getStartLineNumber(diffChange.originalStart);
originalEndLineNumber = originalLineSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);
}
if (diffChange.modifiedLength === 0) {
modifiedStartLineNumber = modifiedLineSequence.getStartLineNumber(diffChange.modifiedStart) - 1;
modifiedEndLineNumber = 0;
}
else {
modifiedStartLineNumber = modifiedLineSequence.getStartLineNumber(diffChange.modifiedStart);
modifiedEndLineNumber = modifiedLineSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);
}
if (shouldComputeCharChanges && diffChange.originalLength !== 0 && diffChange.modifiedLength !== 0 && continueProcessingPredicate()) {
var originalCharSequence = originalLineSequence.getCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
var modifiedCharSequence = modifiedLineSequence.getCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
var rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, true);
if (shouldPostProcessCharChanges) {
rawChanges = postProcessCharChanges(rawChanges);
}
charChanges = [];
for (var i = 0, length_2 = rawChanges.length; i < length_2; i++) {
charChanges.push(CharChange.createFromDiffChange(rawChanges[i], originalCharSequence, modifiedCharSequence));
}
}
return new LineChange(originalStartLineNumber, originalEndLineNumber, modifiedStartLineNumber, modifiedEndLineNumber, charChanges);
};
return LineChange;
}());
var DiffComputer = /** @class */ (function () {
function DiffComputer(originalLines, modifiedLines, opts) {
this.shouldComputeCharChanges = opts.shouldComputeCharChanges;
this.shouldPostProcessCharChanges = opts.shouldPostProcessCharChanges;
this.shouldIgnoreTrimWhitespace = opts.shouldIgnoreTrimWhitespace;
this.shouldMakePrettyDiff = opts.shouldMakePrettyDiff;
this.maximumRunTimeMs = MAXIMUM_RUN_TIME;
this.originalLines = originalLines;
this.modifiedLines = modifiedLines;
this.original = new LineMarkerSequence(originalLines);
this.modified = new LineMarkerSequence(modifiedLines);
}
DiffComputer.prototype.computeDiff = function () {
if (this.original.getLength() === 1 && this.original.getElementAtIndex(0).length === 0) {
// empty original => fast path
return [{
originalStartLineNumber: 1,
originalEndLineNumber: 1,
modifiedStartLineNumber: 1,
modifiedEndLineNumber: this.modified.getLength(),
charChanges: [{
modifiedEndColumn: 0,
modifiedEndLineNumber: 0,
modifiedStartColumn: 0,
modifiedStartLineNumber: 0,
originalEndColumn: 0,
originalEndLineNumber: 0,
originalStartColumn: 0,
originalStartLineNumber: 0
}]
}];
}
if (this.modified.getLength() === 1 && this.modified.getElementAtIndex(0).length === 0) {
// empty modified => fast path
return [{
originalStartLineNumber: 1,
originalEndLineNumber: this.original.getLength(),
modifiedStartLineNumber: 1,
modifiedEndLineNumber: 1,
charChanges: [{
modifiedEndColumn: 0,
modifiedEndLineNumber: 0,
modifiedStartColumn: 0,
modifiedStartLineNumber: 0,
originalEndColumn: 0,
originalEndLineNumber: 0,
originalStartColumn: 0,
originalStartLineNumber: 0
}]
}];
}
this.computationStartTime = (new Date()).getTime();
var rawChanges = computeDiff(this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldMakePrettyDiff);
// The diff is always computed with ignoring trim whitespace
// This ensures we get the prettiest diff
if (this.shouldIgnoreTrimWhitespace) {
var lineChanges = [];
for (var i = 0, length_3 = rawChanges.length; i < length_3; i++) {
lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
}
return lineChanges;
}
// Need to post-process and introduce changes where the trim whitespace is different
// Note that we are looping starting at -1 to also cover the lines before the first change
var result = [];
var originalLineIndex = 0;
var modifiedLineIndex = 0;
for (var i = -1 /* !!!! */, len = rawChanges.length; i < len; i++) {
var nextChange = (i + 1 < len ? rawChanges[i + 1] : null);
var originalStop = (nextChange ? nextChange.originalStart : this.originalLines.length);
var modifiedStop = (nextChange ? nextChange.modifiedStart : this.modifiedLines.length);
while (originalLineIndex < originalStop && modifiedLineIndex < modifiedStop) {
var originalLine = this.originalLines[originalLineIndex];
var modifiedLine = this.modifiedLines[modifiedLineIndex];
if (originalLine !== modifiedLine) {
// These lines differ only in trim whitespace
// Check the leading whitespace
{
var originalStartColumn = LineMarkerSequence._getFirstNonBlankColumn(originalLine, 1);
var modifiedStartColumn = LineMarkerSequence._getFirstNonBlankColumn(modifiedLine, 1);
while (originalStartColumn > 1 && modifiedStartColumn > 1) {
var originalChar = originalLine.charCodeAt(originalStartColumn - 2);
var modifiedChar = modifiedLine.charCodeAt(modifiedStartColumn - 2);
if (originalChar !== modifiedChar) {
break;
}
originalStartColumn--;
modifiedStartColumn--;
}
if (originalStartColumn > 1 || modifiedStartColumn > 1) {
this._pushTrimWhitespaceCharChange(result, originalLineIndex + 1, 1, originalStartColumn, modifiedLineIndex + 1, 1, modifiedStartColumn);
}
}
// Check the trailing whitespace
{
var originalEndColumn = LineMarkerSequence._getLastNonBlankColumn(originalLine, 1);
var modifiedEndColumn = LineMarkerSequence._getLastNonBlankColumn(modifiedLine, 1);
var originalMaxColumn = originalLine.length + 1;
var modifiedMaxColumn = modifiedLine.length + 1;
while (originalEndColumn < originalMaxColumn && modifiedEndColumn < modifiedMaxColumn) {
var originalChar = originalLine.charCodeAt(originalEndColumn - 1);
var modifiedChar = originalLine.charCodeAt(modifiedEndColumn - 1);
if (originalChar !== modifiedChar) {
break;
}
originalEndColumn++;
modifiedEndColumn++;
}
if (originalEndColumn < originalMaxColumn || modifiedEndColumn < modifiedMaxColumn) {
this._pushTrimWhitespaceCharChange(result, originalLineIndex + 1, originalEndColumn, originalMaxColumn, modifiedLineIndex + 1, modifiedEndColumn, modifiedMaxColumn);
}
}
}
originalLineIndex++;
modifiedLineIndex++;
}
if (nextChange) {
// Emit the actual change
result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
originalLineIndex += nextChange.originalLength;
modifiedLineIndex += nextChange.modifiedLength;
}
}
return result;
};
DiffComputer.prototype._pushTrimWhitespaceCharChange = function (result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn) {
if (this._mergeTrimWhitespaceCharChange(result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn)) {
// Merged into previous
return;
}
var charChanges = undefined;
if (this.shouldComputeCharChanges) {
charChanges = [new CharChange(originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn)];
}
result.push(new LineChange(originalLineNumber, originalLineNumber, modifiedLineNumber, modifiedLineNumber, charChanges));
};
DiffComputer.prototype._mergeTrimWhitespaceCharChange = function (result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn) {
var len = result.length;
if (len === 0) {
return false;
}
var prevChange = result[len - 1];
if (prevChange.originalEndLineNumber === 0 || prevChange.modifiedEndLineNumber === 0) {
// Don't merge with inserts/deletes
return false;
}
if (prevChange.originalEndLineNumber + 1 === originalLineNumber && prevChange.modifiedEndLineNumber + 1 === modifiedLineNumber) {
prevChange.originalEndLineNumber = originalLineNumber;
prevChange.modifiedEndLineNumber = modifiedLineNumber;
if (this.shouldComputeCharChanges) {
prevChange.charChanges.push(new CharChange(originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn));
}
return true;
}
return false;
};
DiffComputer.prototype._continueProcessingPredicate = function () {
if (this.maximumRunTimeMs === 0) {
return true;
}
var now = (new Date()).getTime();
return now - this.computationStartTime < this.maximumRunTimeMs;
};
return DiffComputer;
}());
export { DiffComputer };