UNPKG

monaco-editor

Version:
382 lines (381 loc) • 21.1 kB
/*--------------------------------------------------------------------------------------------- * 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 };