feat: 添加vscode编辑器
This commit is contained in:
@@ -0,0 +1,421 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
const MINIMUM_MATCHING_CHARACTER_LENGTH = 3;
|
||||
function computeDiff(originalSequence, modifiedSequence, continueProcessingPredicate, pretty) {
|
||||
const diffAlgo = new LcsDiff(originalSequence, modifiedSequence, continueProcessingPredicate);
|
||||
return diffAlgo.ComputeDiff(pretty);
|
||||
}
|
||||
class LineSequence {
|
||||
constructor(lines) {
|
||||
const startColumns = [];
|
||||
const endColumns = [];
|
||||
for (let i = 0, length = lines.length; i < length; i++) {
|
||||
startColumns[i] = getFirstNonBlankColumn(lines[i], 1);
|
||||
endColumns[i] = getLastNonBlankColumn(lines[i], 1);
|
||||
}
|
||||
this.lines = lines;
|
||||
this._startColumns = startColumns;
|
||||
this._endColumns = endColumns;
|
||||
}
|
||||
getElements() {
|
||||
const elements = [];
|
||||
for (let i = 0, len = this.lines.length; i < len; i++) {
|
||||
elements[i] = this.lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1);
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
getStrictElement(index) {
|
||||
return this.lines[index];
|
||||
}
|
||||
getStartLineNumber(i) {
|
||||
return i + 1;
|
||||
}
|
||||
getEndLineNumber(i) {
|
||||
return i + 1;
|
||||
}
|
||||
createCharSequence(shouldIgnoreTrimWhitespace, startIndex, endIndex) {
|
||||
const charCodes = [];
|
||||
const lineNumbers = [];
|
||||
const columns = [];
|
||||
let len = 0;
|
||||
for (let index = startIndex; index <= endIndex; index++) {
|
||||
const lineContent = this.lines[index];
|
||||
const startColumn = (shouldIgnoreTrimWhitespace ? this._startColumns[index] : 1);
|
||||
const endColumn = (shouldIgnoreTrimWhitespace ? this._endColumns[index] : lineContent.length + 1);
|
||||
for (let col = startColumn; col < endColumn; col++) {
|
||||
charCodes[len] = lineContent.charCodeAt(col - 1);
|
||||
lineNumbers[len] = index + 1;
|
||||
columns[len] = col;
|
||||
len++;
|
||||
}
|
||||
if (!shouldIgnoreTrimWhitespace && index < endIndex) {
|
||||
// Add \n if trim whitespace is not ignored
|
||||
charCodes[len] = 10 /* CharCode.LineFeed */;
|
||||
lineNumbers[len] = index + 1;
|
||||
columns[len] = lineContent.length + 1;
|
||||
len++;
|
||||
}
|
||||
}
|
||||
return new CharSequence(charCodes, lineNumbers, columns);
|
||||
}
|
||||
}
|
||||
class CharSequence {
|
||||
constructor(charCodes, lineNumbers, columns) {
|
||||
this._charCodes = charCodes;
|
||||
this._lineNumbers = lineNumbers;
|
||||
this._columns = columns;
|
||||
}
|
||||
toString() {
|
||||
return ('[' + this._charCodes.map((s, idx) => (s === 10 /* CharCode.LineFeed */ ? '\\n' : String.fromCharCode(s)) + `-(${this._lineNumbers[idx]},${this._columns[idx]})`).join(', ') + ']');
|
||||
}
|
||||
_assertIndex(index, arr) {
|
||||
if (index < 0 || index >= arr.length) {
|
||||
throw new Error(`Illegal index`);
|
||||
}
|
||||
}
|
||||
getElements() {
|
||||
return this._charCodes;
|
||||
}
|
||||
getStartLineNumber(i) {
|
||||
if (i > 0 && i === this._lineNumbers.length) {
|
||||
// the start line number of the element after the last element
|
||||
// is the end line number of the last element
|
||||
return this.getEndLineNumber(i - 1);
|
||||
}
|
||||
this._assertIndex(i, this._lineNumbers);
|
||||
return this._lineNumbers[i];
|
||||
}
|
||||
getEndLineNumber(i) {
|
||||
if (i === -1) {
|
||||
// the end line number of the element before the first element
|
||||
// is the start line number of the first element
|
||||
return this.getStartLineNumber(i + 1);
|
||||
}
|
||||
this._assertIndex(i, this._lineNumbers);
|
||||
if (this._charCodes[i] === 10 /* CharCode.LineFeed */) {
|
||||
return this._lineNumbers[i] + 1;
|
||||
}
|
||||
return this._lineNumbers[i];
|
||||
}
|
||||
getStartColumn(i) {
|
||||
if (i > 0 && i === this._columns.length) {
|
||||
// the start column of the element after the last element
|
||||
// is the end column of the last element
|
||||
return this.getEndColumn(i - 1);
|
||||
}
|
||||
this._assertIndex(i, this._columns);
|
||||
return this._columns[i];
|
||||
}
|
||||
getEndColumn(i) {
|
||||
if (i === -1) {
|
||||
// the end column of the element before the first element
|
||||
// is the start column of the first element
|
||||
return this.getStartColumn(i + 1);
|
||||
}
|
||||
this._assertIndex(i, this._columns);
|
||||
if (this._charCodes[i] === 10 /* CharCode.LineFeed */) {
|
||||
return 1;
|
||||
}
|
||||
return this._columns[i] + 1;
|
||||
}
|
||||
}
|
||||
class CharChange {
|
||||
constructor(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;
|
||||
}
|
||||
static createFromDiffChange(diffChange, originalCharSequence, modifiedCharSequence) {
|
||||
const originalStartLineNumber = originalCharSequence.getStartLineNumber(diffChange.originalStart);
|
||||
const originalStartColumn = originalCharSequence.getStartColumn(diffChange.originalStart);
|
||||
const originalEndLineNumber = originalCharSequence.getEndLineNumber(diffChange.originalStart + diffChange.originalLength - 1);
|
||||
const originalEndColumn = originalCharSequence.getEndColumn(diffChange.originalStart + diffChange.originalLength - 1);
|
||||
const modifiedStartLineNumber = modifiedCharSequence.getStartLineNumber(diffChange.modifiedStart);
|
||||
const modifiedStartColumn = modifiedCharSequence.getStartColumn(diffChange.modifiedStart);
|
||||
const modifiedEndLineNumber = modifiedCharSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);
|
||||
const modifiedEndColumn = modifiedCharSequence.getEndColumn(diffChange.modifiedStart + diffChange.modifiedLength - 1);
|
||||
return new CharChange(originalStartLineNumber, originalStartColumn, originalEndLineNumber, originalEndColumn, modifiedStartLineNumber, modifiedStartColumn, modifiedEndLineNumber, modifiedEndColumn);
|
||||
}
|
||||
}
|
||||
function postProcessCharChanges(rawChanges) {
|
||||
if (rawChanges.length <= 1) {
|
||||
return rawChanges;
|
||||
}
|
||||
const result = [rawChanges[0]];
|
||||
let prevChange = result[0];
|
||||
for (let i = 1, len = rawChanges.length; i < len; i++) {
|
||||
const currChange = rawChanges[i];
|
||||
const originalMatchingLength = currChange.originalStart - (prevChange.originalStart + prevChange.originalLength);
|
||||
const modifiedMatchingLength = currChange.modifiedStart - (prevChange.modifiedStart + prevChange.modifiedLength);
|
||||
// Both of the above should be equal, but the continueProcessingPredicate may prevent this from being true
|
||||
const 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;
|
||||
}
|
||||
class LineChange {
|
||||
constructor(originalStartLineNumber, originalEndLineNumber, modifiedStartLineNumber, modifiedEndLineNumber, charChanges) {
|
||||
this.originalStartLineNumber = originalStartLineNumber;
|
||||
this.originalEndLineNumber = originalEndLineNumber;
|
||||
this.modifiedStartLineNumber = modifiedStartLineNumber;
|
||||
this.modifiedEndLineNumber = modifiedEndLineNumber;
|
||||
this.charChanges = charChanges;
|
||||
}
|
||||
static createFromDiffResult(shouldIgnoreTrimWhitespace, diffChange, originalLineSequence, modifiedLineSequence, continueCharDiff, shouldComputeCharChanges, shouldPostProcessCharChanges) {
|
||||
let originalStartLineNumber;
|
||||
let originalEndLineNumber;
|
||||
let modifiedStartLineNumber;
|
||||
let modifiedEndLineNumber;
|
||||
let 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.originalLength < 20 && diffChange.modifiedLength > 0 && diffChange.modifiedLength < 20 && continueCharDiff()) {
|
||||
// Compute character changes for diff chunks of at most 20 lines...
|
||||
const originalCharSequence = originalLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
|
||||
const modifiedCharSequence = modifiedLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
|
||||
if (originalCharSequence.getElements().length > 0 && modifiedCharSequence.getElements().length > 0) {
|
||||
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueCharDiff, true).changes;
|
||||
if (shouldPostProcessCharChanges) {
|
||||
rawChanges = postProcessCharChanges(rawChanges);
|
||||
}
|
||||
charChanges = [];
|
||||
for (let i = 0, length = rawChanges.length; i < length; i++) {
|
||||
charChanges.push(CharChange.createFromDiffChange(rawChanges[i], originalCharSequence, modifiedCharSequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LineChange(originalStartLineNumber, originalEndLineNumber, modifiedStartLineNumber, modifiedEndLineNumber, charChanges);
|
||||
}
|
||||
}
|
||||
export class DiffComputer {
|
||||
constructor(originalLines, modifiedLines, opts) {
|
||||
this.shouldComputeCharChanges = opts.shouldComputeCharChanges;
|
||||
this.shouldPostProcessCharChanges = opts.shouldPostProcessCharChanges;
|
||||
this.shouldIgnoreTrimWhitespace = opts.shouldIgnoreTrimWhitespace;
|
||||
this.shouldMakePrettyDiff = opts.shouldMakePrettyDiff;
|
||||
this.originalLines = originalLines;
|
||||
this.modifiedLines = modifiedLines;
|
||||
this.original = new LineSequence(originalLines);
|
||||
this.modified = new LineSequence(modifiedLines);
|
||||
this.continueLineDiff = createContinueProcessingPredicate(opts.maxComputationTime);
|
||||
this.continueCharDiff = createContinueProcessingPredicate(opts.maxComputationTime === 0 ? 0 : Math.min(opts.maxComputationTime, 5000)); // never run after 5s for character changes...
|
||||
}
|
||||
computeDiff() {
|
||||
if (this.original.lines.length === 1 && this.original.lines[0].length === 0) {
|
||||
// empty original => fast path
|
||||
if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {
|
||||
return {
|
||||
quitEarly: false,
|
||||
changes: []
|
||||
};
|
||||
}
|
||||
return {
|
||||
quitEarly: false,
|
||||
changes: [{
|
||||
originalStartLineNumber: 1,
|
||||
originalEndLineNumber: 1,
|
||||
modifiedStartLineNumber: 1,
|
||||
modifiedEndLineNumber: this.modified.lines.length,
|
||||
charChanges: [{
|
||||
modifiedEndColumn: 0,
|
||||
modifiedEndLineNumber: 0,
|
||||
modifiedStartColumn: 0,
|
||||
modifiedStartLineNumber: 0,
|
||||
originalEndColumn: 0,
|
||||
originalEndLineNumber: 0,
|
||||
originalStartColumn: 0,
|
||||
originalStartLineNumber: 0
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {
|
||||
// empty modified => fast path
|
||||
return {
|
||||
quitEarly: false,
|
||||
changes: [{
|
||||
originalStartLineNumber: 1,
|
||||
originalEndLineNumber: this.original.lines.length,
|
||||
modifiedStartLineNumber: 1,
|
||||
modifiedEndLineNumber: 1,
|
||||
charChanges: [{
|
||||
modifiedEndColumn: 0,
|
||||
modifiedEndLineNumber: 0,
|
||||
modifiedStartColumn: 0,
|
||||
modifiedStartLineNumber: 0,
|
||||
originalEndColumn: 0,
|
||||
originalEndLineNumber: 0,
|
||||
originalStartColumn: 0,
|
||||
originalStartLineNumber: 0
|
||||
}]
|
||||
}]
|
||||
};
|
||||
}
|
||||
const diffResult = computeDiff(this.original, this.modified, this.continueLineDiff, this.shouldMakePrettyDiff);
|
||||
const rawChanges = diffResult.changes;
|
||||
const quitEarly = diffResult.quitEarly;
|
||||
// The diff is always computed with ignoring trim whitespace
|
||||
// This ensures we get the prettiest diff
|
||||
if (this.shouldIgnoreTrimWhitespace) {
|
||||
const lineChanges = [];
|
||||
for (let i = 0, length = rawChanges.length; i < length; i++) {
|
||||
lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
|
||||
}
|
||||
return {
|
||||
quitEarly: quitEarly,
|
||||
changes: 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
|
||||
const result = [];
|
||||
let originalLineIndex = 0;
|
||||
let modifiedLineIndex = 0;
|
||||
for (let i = -1 /* !!!! */, len = rawChanges.length; i < len; i++) {
|
||||
const nextChange = (i + 1 < len ? rawChanges[i + 1] : null);
|
||||
const originalStop = (nextChange ? nextChange.originalStart : this.originalLines.length);
|
||||
const modifiedStop = (nextChange ? nextChange.modifiedStart : this.modifiedLines.length);
|
||||
while (originalLineIndex < originalStop && modifiedLineIndex < modifiedStop) {
|
||||
const originalLine = this.originalLines[originalLineIndex];
|
||||
const modifiedLine = this.modifiedLines[modifiedLineIndex];
|
||||
if (originalLine !== modifiedLine) {
|
||||
// These lines differ only in trim whitespace
|
||||
// Check the leading whitespace
|
||||
{
|
||||
let originalStartColumn = getFirstNonBlankColumn(originalLine, 1);
|
||||
let modifiedStartColumn = getFirstNonBlankColumn(modifiedLine, 1);
|
||||
while (originalStartColumn > 1 && modifiedStartColumn > 1) {
|
||||
const originalChar = originalLine.charCodeAt(originalStartColumn - 2);
|
||||
const 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
|
||||
{
|
||||
let originalEndColumn = getLastNonBlankColumn(originalLine, 1);
|
||||
let modifiedEndColumn = getLastNonBlankColumn(modifiedLine, 1);
|
||||
const originalMaxColumn = originalLine.length + 1;
|
||||
const modifiedMaxColumn = modifiedLine.length + 1;
|
||||
while (originalEndColumn < originalMaxColumn && modifiedEndColumn < modifiedMaxColumn) {
|
||||
const originalChar = originalLine.charCodeAt(originalEndColumn - 1);
|
||||
const 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.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
|
||||
originalLineIndex += nextChange.originalLength;
|
||||
modifiedLineIndex += nextChange.modifiedLength;
|
||||
}
|
||||
}
|
||||
return {
|
||||
quitEarly: quitEarly,
|
||||
changes: result
|
||||
};
|
||||
}
|
||||
_pushTrimWhitespaceCharChange(result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn) {
|
||||
if (this._mergeTrimWhitespaceCharChange(result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn)) {
|
||||
// Merged into previous
|
||||
return;
|
||||
}
|
||||
let 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));
|
||||
}
|
||||
_mergeTrimWhitespaceCharChange(result, originalLineNumber, originalStartColumn, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedEndColumn) {
|
||||
const len = result.length;
|
||||
if (len === 0) {
|
||||
return false;
|
||||
}
|
||||
const 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) {
|
||||
prevChange.charChanges.push(new CharChange(originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn, modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function getFirstNonBlankColumn(txt, defaultValue) {
|
||||
const r = strings.firstNonWhitespaceIndex(txt);
|
||||
if (r === -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return r + 1;
|
||||
}
|
||||
function getLastNonBlankColumn(txt, defaultValue) {
|
||||
const r = strings.lastNonWhitespaceIndex(txt);
|
||||
if (r === -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return r + 2;
|
||||
}
|
||||
function createContinueProcessingPredicate(maximumRuntime) {
|
||||
if (maximumRuntime === 0) {
|
||||
return () => true;
|
||||
}
|
||||
const startTime = Date.now();
|
||||
return () => {
|
||||
return Date.now() - startTime < maximumRuntime;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user