Skip to content

Commit

Permalink
Introduces diffing infrastructure & experimental diffing algorithm. (m…
Browse files Browse the repository at this point in the history
…icrosoft#157646)

* Introduces diffing infrastructure & experimental diffing algorithm.

* Fixes CI

* Fixes unit test

* Fixes CI tests.
  • Loading branch information
hediet authored Aug 9, 2022
1 parent 8bf8281 commit 516f0d1
Show file tree
Hide file tree
Showing 38 changed files with 1,072 additions and 193 deletions.
2 changes: 1 addition & 1 deletion build/monaco/monaco.d.ts.recipe
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface ICommandHandler {
#include(vs/editor/common/core/editOperation): ISingleEditOperation
#include(vs/editor/common/core/wordHelper): IWordAtPosition
#includeAll(vs/editor/common/model): IScrollEvent
#include(vs/editor/common/diff/diffComputer): IChange, ICharChange, ILineChange
#include(vs/editor/common/diff/smartLinesDiffComputer): IChange, ICharChange, ILineChange
#include(vs/editor/common/core/dimension): IDimension
#includeAll(vs/editor/common/editorCommon): IScrollEvent
#includeAll(vs/editor/common/textModelEvents):
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/browser/editorBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManage
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel';
import { InjectedText } from 'vs/editor/common/modelLineProjectionData';
import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/diffComputer';
import { ILineChange, IDiffComputationResult } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { IDimension } from 'vs/editor/common/core/dimension';

/**
Expand Down
13 changes: 7 additions & 6 deletions src/vs/editor/browser/services/editorWorkerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/b
import { DefaultWorkerFactory } from 'vs/base/browser/defaultWorkerFactory';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IChange, IDiffComputationResult } from 'vs/editor/common/diff/diffComputer';
import { ITextModel } from 'vs/editor/common/model';
import * as languages from 'vs/editor/common/languages';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { IModelService } from 'vs/editor/common/services/model';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
import { regExpFlags } from 'vs/base/common/strings';
Expand All @@ -26,6 +25,8 @@ import { canceled } from 'vs/base/common/errors';
import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeTextModelHighlighter';
import { IEditorWorkerHost } from 'vs/editor/common/services/editorWorkerHost';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';

/**
* Stop syncing a model to the worker if it was not needed for 1 min.
Expand Down Expand Up @@ -94,8 +95,8 @@ export class EditorWorkerService extends Disposable implements IEditorWorkerServ
return this._workerManager.withWorker().then(client => client.computedUnicodeHighlights(uri, options, range));
}

public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime));
public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise<IDiffComputationResult | null> {
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options));
}

public canComputeDirtyDiff(original: URI, modified: URI): boolean {
Expand Down Expand Up @@ -491,9 +492,9 @@ export class EditorWorkerClient extends Disposable implements IEditorWorkerClien
});
}

public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise<IDiffComputationResult | null> {
return this._withSyncedResources([original, modified], /* forceLargeModels */true).then(proxy => {
return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace, maxComputationTime);
return proxy.computeDiff(original.toString(), modified.toString(), options);
});
}

Expand Down
71 changes: 64 additions & 7 deletions src/vs/editor/browser/widget/diffEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { ILineBreaksComputer } from 'vs/editor/common/modelLineProjectionData';
import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/diffComputer';
import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { IDimension } from 'vs/editor/common/core/dimension';
import { isHighContrast } from 'vs/platform/theme/common/theme';
import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';
import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider';

export interface IDiffCodeEditorWidgetOptions {
originalEditor?: ICodeEditorWidgetOptions;
Expand Down Expand Up @@ -221,7 +223,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE

private readonly _updateDecorationsRunner: RunOnceScheduler;

private readonly _editorWorkerService: IEditorWorkerService;
private readonly _documentDiffProvider: IDocumentDiffProvider;
private readonly _contextKeyService: IContextKeyService;
private readonly _instantiationService: IInstantiationService;
private readonly _codeEditorService: ICodeEditorService;
Expand All @@ -246,7 +248,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
) {
super();

this._editorWorkerService = editorWorkerService;
this._documentDiffProvider = new WorkerBasedDocumentDiffProvider(editorWorkerService);
this._codeEditorService = codeEditorService;
this._contextKeyService = this._register(contextKeyService.createScoped(domElement));
this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]));
Expand All @@ -272,7 +274,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
originalEditable: false,
diffCodeLens: false,
renderOverviewRuler: true,
diffWordWrap: 'inherit'
diffWordWrap: 'inherit',
diffAlgorithm: 'smart',
});

if (typeof options.isInEmbeddedEditor !== 'undefined') {
Expand Down Expand Up @@ -751,7 +754,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this._options = newOptions;

const beginUpdateDecorations = (changed.ignoreTrimWhitespace || changed.renderIndicators || changed.renderMarginRevertIcon);
const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize));
const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize || changed.diffAlgorithm));

if (beginUpdateDecorations) {
this._beginUpdateDecorations();
Expand Down Expand Up @@ -1111,13 +1114,65 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
}

this._setState(editorBrowser.DiffEditorState.ComputingDiff);
this._editorWorkerService.computeDiff(currentOriginalModel.uri, currentModifiedModel.uri, this._options.ignoreTrimWhitespace, this._options.maxComputationTime).then((result) => {
this._documentDiffProvider.computeDiff(currentOriginalModel, currentModifiedModel, {
ignoreTrimWhitespace: this._options.ignoreTrimWhitespace,
maxComputationTime: this._options.maxComputationTime,
diffAlgorithm: this._options.diffAlgorithm,
}).then(result => {
if (currentToken === this._diffComputationToken
&& currentOriginalModel === this._originalEditor.getModel()
&& currentModifiedModel === this._modifiedEditor.getModel()
) {
this._setState(editorBrowser.DiffEditorState.DiffComputed);
this._diffComputationResult = result;
this._diffComputationResult = {
identical: result.identical,
quitEarly: result.quitEarly,
changes: result.changes.map(m => {
// TODO don't do this translation, but use the diff result directly
let originalStartLineNumber: number;
let originalEndLineNumber: number;
let modifiedStartLineNumber: number;
let modifiedEndLineNumber: number;
let innerChanges = m.innerChanges;

if (m.originalRange.isEmpty) {
// Insertion
originalStartLineNumber = m.originalRange.startLineNumber - 1;
originalEndLineNumber = 0;
innerChanges = undefined;
} else {
originalStartLineNumber = m.originalRange.startLineNumber;
originalEndLineNumber = m.originalRange.endLineNumberExclusive - 1;
}

if (m.modifiedRange.isEmpty) {
// Deletion
modifiedStartLineNumber = m.modifiedRange.startLineNumber - 1;
modifiedEndLineNumber = 0;
innerChanges = undefined;
} else {
modifiedStartLineNumber = m.modifiedRange.startLineNumber;
modifiedEndLineNumber = m.modifiedRange.endLineNumberExclusive - 1;
}

return {
originalStartLineNumber,
originalEndLineNumber,
modifiedStartLineNumber,
modifiedEndLineNumber,
charChanges: innerChanges?.map(m => ({
originalStartLineNumber: m.originalRange.startLineNumber,
originalStartColumn: m.originalRange.startColumn,
originalEndLineNumber: m.originalRange.endLineNumber,
originalEndColumn: m.originalRange.endColumn,
modifiedStartLineNumber: m.modifiedRange.startLineNumber,
modifiedStartColumn: m.modifiedRange.startColumn,
modifiedEndLineNumber: m.modifiedRange.endLineNumber,
modifiedEndColumn: m.modifiedRange.endColumn,
}))
};
})
};
this._updateDecorationsRunner.schedule();
this._onDidUpdateDiff.fire();
}
Expand Down Expand Up @@ -2655,6 +2710,7 @@ function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaul
diffCodeLens: validateBooleanOption(options.diffCodeLens, defaults.diffCodeLens),
renderOverviewRuler: validateBooleanOption(options.renderOverviewRuler, defaults.renderOverviewRuler),
diffWordWrap: validateDiffWordWrap(options.diffWordWrap, defaults.diffWordWrap),
diffAlgorithm: validateStringSetOption(options.diffAlgorithm, defaults.diffAlgorithm, ['smart', 'experimental']),
};
}

Expand All @@ -2671,6 +2727,7 @@ function changedDiffEditorOptions(a: ValidDiffEditorBaseOptions, b: ValidDiffEdi
diffCodeLens: (a.diffCodeLens !== b.diffCodeLens),
renderOverviewRuler: (a.renderOverviewRuler !== b.renderOverviewRuler),
diffWordWrap: (a.diffWordWrap !== b.diffWordWrap),
diffAlgorithm: (a.diffAlgorithm !== b.diffAlgorithm),
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/browser/widget/diffNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as objects from 'vs/base/common/objects';
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
import { Range } from 'vs/editor/common/core/range';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import { ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
import { ScrollType } from 'vs/editor/common/editorCommon';


Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/browser/widget/diffReview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { ILanguageIdCodec } from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import { ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';

const DIFF_LINES_PADDING = 3;

Expand Down
44 changes: 44 additions & 0 deletions src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { LineRange, LineRangeMapping, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';
import { Range } from 'vs/editor/common/core/range';
import { IDocumentDiff, IDocumentDiffProvider, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
import { ITextModel } from 'vs/editor/common/model';

export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider {
constructor(
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService,
) {
}

async computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions): Promise<IDocumentDiff> {
const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options);
if (!result) {
throw new Error('no diff result available');
}

// Convert from space efficient JSON data to rich objects.
return {
identical: result.identical,
quitEarly: result.quitEarly,
changes: result.changes.map(
(c) =>
new LineRangeMapping(
new LineRange(c[0], c[1]),
new LineRange(c[2], c[3]),
c[4]?.map(
(c) =>
new RangeMapping(
new Range(c[0], c[1], c[2], c[3]),
new Range(c[4], c[5], c[6], c[7])
)
)
)
),
};
}
}
11 changes: 10 additions & 1 deletion src/vs/editor/common/config/editorConfigurationSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,16 @@ const editorConfiguration: IConfigurationNode = {
nls.localize('wordWrap.on', "Lines will wrap at the viewport width."),
nls.localize('wordWrap.inherit', "Lines will wrap according to the `#editor.wordWrap#` setting."),
]
}
},
'diffEditor.diffAlgorithm': {
type: 'string',
enum: ['smart', 'experimental'],
default: 'smart',
markdownEnumDescriptions: [
nls.localize('diffAlgorithm.smart', "Uses the default diffing algorithm."),
nls.localize('diffAlgorithm.experimental', "Uses an experimental diffing algorithm."),
]
},
}
};

Expand Down
4 changes: 4 additions & 0 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,10 @@ export interface IDiffEditorBaseOptions {
* Control the wrapping of the diff editor.
*/
diffWordWrap?: 'off' | 'on' | 'inherit';
/**
* Diff Algorithm
*/
diffAlgorithm?: 'smart' | 'experimental';
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/vs/editor/common/core/range.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,13 @@ export class Range {
return Range.collapseToStart(this);
}

/**
* Moves the range by the given amount of lines.
*/
public delta(lineCount: number): Range {
return new Range(this.startLineNumber + lineCount, this.startColumn, this.endLineNumber + lineCount, this.endColumn);
}

/**
* Create a new empty range using this range's start position.
*/
Expand Down
54 changes: 54 additions & 0 deletions src/vs/editor/common/diff/algorithms/diffAlgorithm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

/**
* Represents a synchronous diff algorithm. Should be executed in a worker.
*/
export interface IDiffAlgorithm {
compute(sequence1: ISequence, sequence2: ISequence): SequenceDiff[];
}

export class SequenceDiff {
constructor(
public readonly seq1Range: OffsetRange,
public readonly seq2Range: OffsetRange
) { }
}

/**
* Todo move this class to some top level utils.
*/
export class OffsetRange {
constructor(public readonly start: number, public readonly endExclusive: number) { }

get isEmpty(): boolean {
return this.start === this.endExclusive;
}

public delta(offset: number): OffsetRange {
return new OffsetRange(this.start + offset, this.endExclusive + offset);
}

public get length(): number {
return this.endExclusive - this.start;
}
}

export interface ISequence {
getElement(offset: number): number;
get length(): number;
}

export class SequenceFromIntArray implements ISequence {
constructor(private readonly arr: number[]) { }

getElement(offset: number): number {
return this.arr[offset];
}

get length(): number {
return this.arr.length;
}
}
Loading

0 comments on commit 516f0d1

Please sign in to comment.