Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows to pass in a custom diffing algorithm when instantiating the monaco diff editor. #165179

Merged
merged 2 commits into from Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/monaco/monaco.d.ts.recipe
Expand Up @@ -73,6 +73,8 @@ export interface ICommandHandler {
#include(vs/editor/common/core/wordHelper): IWordAtPosition
#includeAll(vs/editor/common/model): IScrollEvent
#include(vs/editor/common/diff/smartLinesDiffComputer): IChange, ICharChange, ILineChange
#include(vs/editor/common/diff/documentDiffProvider): IDocumentDiffProvider, IDocumentDiffProviderOptions, IDocumentDiff
#include(vs/editor/common/diff/linesDiffComputer): LineRangeMapping, LineRange, RangeMapping
#include(vs/editor/common/core/dimension): IDimension
#includeAll(vs/editor/common/editorCommon): IScrollEvent
#includeAll(vs/editor/common/textModelEvents):
Expand Down
36 changes: 30 additions & 6 deletions src/vs/editor/browser/services/editorWorkerService.ts
Expand Up @@ -14,7 +14,7 @@ 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 { IDiffComputationResult, IEditorWorkerService, IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorker';
import { DiffAlgorithmName, 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,7 +26,8 @@ import { UnicodeHighlighterOptions } from 'vs/editor/common/services/unicodeText
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';
import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/diff/documentDiffProvider';
import { LineRangeMapping, LineRange, RangeMapping } from 'vs/editor/common/diff/linesDiffComputer';

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

public computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions): Promise<IDiffComputationResult | null> {
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options));
public async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDocumentDiff | null> {
const result = await this._workerManager.withWorker().then(client => client.computeDiff(original, modified, options, algorithm));
if (!result) {
return null;
}
// Convert from space efficient JSON data to rich objects.
const diff: IDocumentDiff = {
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])
)
)
)
),
};
return diff;
}

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

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

Expand Down
69 changes: 37 additions & 32 deletions src/vs/editor/browser/widget/diffEditorWidget.ts
Expand Up @@ -3,61 +3,60 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/diffEditor';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { createFastDomNode, FastDomNode } from 'vs/base/browser/fastDomNode';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash';
import * as assert from 'vs/base/common/assert';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { ISashEvent, IVerticalSashLayoutProvider, Sash, SashState, Orientation } from 'vs/base/browser/ui/sash/sash';
import { RunOnceScheduler } from 'vs/base/common/async';
import { Codicon } from 'vs/base/common/codicons';
import { Color } from 'vs/base/common/color';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Constants } from 'vs/base/common/uint';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/diffEditor';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll';
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
import { DiffReview } from 'vs/editor/browser/widget/diffReview';
import { IDiffEditorOptions, EditorLayoutInfo, EditorOption, EditorOptions, EditorFontLigatures, stringSet as validateStringSetOption, boolean as validateBooleanOption, ValidDiffEditorBaseOptions, clampedInt } from 'vs/editor/common/config/editorOptions';
import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin';
import { WorkerBasedDocumentDiffProvider } from 'vs/editor/browser/widget/workerBasedDocumentDiffProvider';
import { boolean as validateBooleanOption, clampedInt, EditorFontLigatures, EditorLayoutInfo, EditorOption, EditorOptions, IDiffEditorOptions, stringSet as validateStringSetOption, ValidDiffEditorBaseOptions } from 'vs/editor/common/config/editorOptions';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { IDimension } from 'vs/editor/common/core/dimension';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { StringBuilder } from 'vs/editor/common/core/stringBuilder';
import { IChange, ICharChange, IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager';
import { ILineBreaksComputer } from 'vs/editor/common/modelLineProjectionData';
import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations';
import { RenderLineInput, renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { IEditorWhitespace, InlineDecoration, InlineDecorationType, IViewModel, ViewLineRenderingData } from 'vs/editor/common/viewModel';
import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager';
import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { defaultInsertColor, defaultRemoveColor, diffBorder, diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, scrollbarShadow, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, diffDiagonalFill, diffInsertedLineGutter, diffRemovedLineGutter, diffInsertedLine, diffRemovedLine, diffOverviewRulerInserted, diffOverviewRulerRemoved } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, IThemeService, getThemeTypeSelector, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDiffLinesChange, InlineDiffMargin } from 'vs/editor/browser/widget/inlineDiffMargin';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { Constants } from 'vs/base/common/uint';
import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IEditorProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver';
import { Codicon } from 'vs/base/common/codicons';
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { defaultInsertColor, defaultRemoveColor, diffBorder, diffDiagonalFill, diffInserted, diffInsertedLine, diffInsertedLineGutter, diffInsertedOutline, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved, diffRemovedLine, diffRemovedLineGutter, diffRemovedOutline, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
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/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';
import { getThemeTypeSelector, IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';

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

private readonly _updateDecorationsRunner: RunOnceScheduler;

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

this._documentDiffProvider = instantiationService.createInstance(WorkerBasedDocumentDiffProvider);
this._documentDiffProvider = this._register(instantiationService.createInstance(WorkerBasedDocumentDiffProvider, options));
this._register(this._documentDiffProvider.onDidChange(e => this._beginUpdateDecorationsSoon()));

this._codeEditorService = codeEditorService;
this._contextKeyService = this._register(contextKeyService.createScoped(domElement));
this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService]));
Expand Down Expand Up @@ -757,7 +758,8 @@ 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 || changed.diffAlgorithm));
const beginUpdateDecorationsSoon = (this._isVisible && (changed.maxComputationTime || changed.maxFileSize));
this._documentDiffProvider.setOptions(newOptions);

if (beginUpdateDecorations) {
this._beginUpdateDecorations();
Expand Down Expand Up @@ -1087,7 +1089,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
}

private _beginUpdateDecorations(): void {
this._beginUpdateDecorationsTimeout = -1;
if (this._beginUpdateDecorationsTimeout !== -1) {
// Cancel any pending requests in case this method is called directly
window.clearTimeout(this._beginUpdateDecorationsTimeout);
this._beginUpdateDecorationsTimeout = -1;
}
const currentOriginalModel = this._originalEditor.getModel();
const currentModifiedModel = this._modifiedEditor.getModel();
if (!currentOriginalModel || !currentModifiedModel) {
Expand Down Expand Up @@ -1121,8 +1127,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this._setState(editorBrowser.DiffEditorState.ComputingDiff);
this._documentDiffProvider.computeDiff(currentOriginalModel, currentModifiedModel, {
ignoreTrimWhitespace: this._options.ignoreTrimWhitespace,
maxComputationTime: this._options.maxComputationTime,
diffAlgorithm: this._options.diffAlgorithm,
maxComputationTimeMs: this._options.maxComputationTime,
}).then(result => {
if (currentToken === this._diffComputationToken
&& currentOriginalModel === this._originalEditor.getModel()
Expand Down
70 changes: 45 additions & 25 deletions src/vs/editor/browser/widget/workerBasedDocumentDiffProvider.ts
Expand Up @@ -3,43 +3,63 @@
* 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 { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
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';
import { DiffAlgorithmName, IEditorWorkerService } from 'vs/editor/common/services/editorWorker';

export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider, IDisposable {
private onDidChangeEventEmitter = new Emitter<void>();
public readonly onDidChange: Event<void> = this.onDidChangeEventEmitter.event;

private diffAlgorithm: DiffAlgorithmName | IDocumentDiffProvider = 'smart';
private diffAlgorithmOnDidChangeSubscription: IDisposable | undefined = undefined;

export class WorkerBasedDocumentDiffProvider implements IDocumentDiffProvider {
constructor(
options: IWorkerBasedDocumentDiffProviderOptions,
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService,
) {
this.setOptions(options);
}

public dispose(): void {
this.diffAlgorithmOnDidChangeSubscription?.dispose();
}

async computeDiff(original: ITextModel, modified: ITextModel, options: IDocumentDiffProviderOptions): Promise<IDocumentDiff> {
const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options);
if (typeof this.diffAlgorithm !== 'string') {
return this.diffAlgorithm.computeDiff(original, modified, options);
}

const result = await this.editorWorkerService.computeDiff(original.uri, modified.uri, options, this.diffAlgorithm);
if (!result) {
throw new Error('no diff result available');
}

// Convert from space efficient JSON data to rich objects.
const diff: IDocumentDiff = {
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])
)
)
)
),
};
return diff;
return result;
}

public setOptions(newOptions: IWorkerBasedDocumentDiffProviderOptions): void {
let didChange = false;
if (newOptions.diffAlgorithm) {
if (this.diffAlgorithm !== newOptions.diffAlgorithm) {
this.diffAlgorithmOnDidChangeSubscription?.dispose();
this.diffAlgorithmOnDidChangeSubscription = undefined;

this.diffAlgorithm = newOptions.diffAlgorithm;
if (typeof newOptions.diffAlgorithm !== 'string') {
this.diffAlgorithmOnDidChangeSubscription = newOptions.diffAlgorithm.onDidChange(() => this.onDidChangeEventEmitter.fire());
}
didChange = true;
}
}
if (didChange) {
this.onDidChangeEventEmitter.fire();
}
}
}

interface IWorkerBasedDocumentDiffProviderOptions {
readonly diffAlgorithm?: 'smart' | 'experimental' | IDocumentDiffProvider;
}
3 changes: 2 additions & 1 deletion src/vs/editor/common/config/editorOptions.ts
Expand Up @@ -15,6 +15,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as arrays from 'vs/base/common/arrays';
import * as objects from 'vs/base/common/objects';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/core/textModelDefaults';
import { IDocumentDiffProvider } from 'vs/editor/common/diff/documentDiffProvider';

//#region typed options

Expand Down Expand Up @@ -739,7 +740,7 @@ export interface IDiffEditorBaseOptions {
/**
* Diff Algorithm
*/
diffAlgorithm?: 'smart' | 'experimental';
diffAlgorithm?: 'smart' | 'experimental' | IDocumentDiffProvider;
}

/**
Expand Down