123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- /*---------------------------------------------------------------------------------------------
- * 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++;
- }
- }
- return new CharSequence(charCodes, lineNumbers, columns);
- }
- }
- class CharSequence {
- constructor(charCodes, lineNumbers, columns) {
- this._charCodes = charCodes;
- this._lineNumbers = lineNumbers;
- this._columns = columns;
- }
- getElements() {
- return this._charCodes;
- }
- getStartLineNumber(i) {
- return this._lineNumbers[i];
- }
- getStartColumn(i) {
- return this._columns[i];
- }
- getEndLineNumber(i) {
- return this._lineNumbers[i];
- }
- getEndColumn(i) {
- 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) {
- let originalStartLineNumber;
- let originalStartColumn;
- let originalEndLineNumber;
- let originalEndColumn;
- let modifiedStartLineNumber;
- let modifiedStartColumn;
- let modifiedEndLineNumber;
- let 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);
- }
- }
- 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);
- 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;
- };
- }
|