Skip to content

Commit a572746

Browse files
authored
diff editor refactoring (microsoft#185928)
1 parent 5ee4551 commit a572746

15 files changed

+569
-457
lines changed

src/vs/editor/browser/editorBrowser.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,26 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { Event } from 'vs/base/common/event';
76
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
87
import { IMouseEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
9-
import { OverviewRulerPosition, ConfigurationChangedEvent, EditorLayoutInfo, IComputedEditorOptions, EditorOption, FindComputedEditorOptionValueById, IEditorOptions, IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
10-
import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents';
8+
import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
9+
import { Event } from 'vs/base/common/event';
10+
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
11+
import { ConfigurationChangedEvent, EditorLayoutInfo, EditorOption, FindComputedEditorOptionValueById, IComputedEditorOptions, IDiffEditorOptions, IEditorOptions, OverviewRulerPosition } from 'vs/editor/common/config/editorOptions';
12+
import { IDimension } from 'vs/editor/common/core/dimension';
1113
import { IPosition, Position } from 'vs/editor/common/core/position';
1214
import { IRange, Range } from 'vs/editor/common/core/range';
1315
import { Selection } from 'vs/editor/common/core/selection';
14-
import * as editorCommon from 'vs/editor/common/editorCommon';
15-
import { IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, ICursorStateComputer, PositionAffinity, GlyphMarginLane } from 'vs/editor/common/model';
1616
import { IWordAtPosition } from 'vs/editor/common/core/wordHelper';
17+
import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents';
18+
import { IDiffComputationResult, ILineChange } from 'vs/editor/common/diff/smartLinesDiffComputer';
19+
import * as editorCommon from 'vs/editor/common/editorCommon';
20+
import { GlyphMarginLane, ICursorStateComputer, IIdentifiedSingleEditOperation, IModelDecoration, IModelDeltaDecoration, ITextModel, PositionAffinity } from 'vs/editor/common/model';
21+
import { InjectedText } from 'vs/editor/common/modelLineProjectionData';
1722
import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents';
23+
import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel';
1824
import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager';
1925
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
20-
import { IEditorWhitespace, IViewModel } from 'vs/editor/common/viewModel';
21-
import { InjectedText } from 'vs/editor/common/modelLineProjectionData';
22-
import { ILineChange, IDiffComputationResult } from 'vs/editor/common/diff/smartLinesDiffComputer';
23-
import { IDimension } from 'vs/editor/common/core/dimension';
24-
import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash';
2526

2627
/**
2728
* A view zone is a full horizontal rectangle that 'pushes' text down.
@@ -505,12 +506,7 @@ export interface IEditorAriaOptions {
505506
role?: string;
506507
}
507508

508-
export interface IDiffEditorConstructionOptions extends IDiffEditorOptions {
509-
/**
510-
* The initial editor dimension (to avoid measuring the container).
511-
*/
512-
dimension?: IDimension;
513-
509+
export interface IDiffEditorConstructionOptions extends IDiffEditorOptions, IEditorConstructionOptions {
514510
/**
515511
* Place overflow widgets inside an external DOM node.
516512
* Defaults to an internal DOM node.

src/vs/editor/browser/widget/diffEditorWidget.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
298298
experimental: {
299299
collapseUnchangedRegions: false,
300300
},
301+
isInEmbeddedEditor: false,
301302
});
302303

303304
this.isEmbeddedDiffEditorKey = EditorContextKeys.isEmbeddedDiffEditor.bindTo(this._contextKeyService);
@@ -2741,6 +2742,7 @@ function validateDiffEditorOptions(options: Readonly<IDiffEditorOptions>, defaul
27412742
experimental: {
27422743
collapseUnchangedRegions: false,
27432744
},
2745+
isInEmbeddedEditor: validateBooleanOption(options.isInEmbeddedEditor, defaults.isInEmbeddedEditor),
27442746
};
27452747
}
27462748

src/vs/editor/browser/widget/diffEditorWidget2/decorations.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,36 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
1313
export const diffInsertIcon = registerIcon('diff-insert', Codicon.add, localize('diffInsertIcon', 'Line decoration for inserts in the diff editor.'));
1414
export const diffRemoveIcon = registerIcon('diff-remove', Codicon.remove, localize('diffRemoveIcon', 'Line decoration for removals in the diff editor.'));
1515

16-
export const diffLineAddDecorationBackground = ModelDecorationOptions.register({
16+
export const diffLineAddDecorationBackgroundWithIndicator = ModelDecorationOptions.register({
1717
className: 'line-insert',
1818
description: 'line-insert',
1919
isWholeLine: true,
2020
linesDecorationsClassName: 'insert-sign ' + ThemeIcon.asClassName(diffInsertIcon),
2121
marginClassName: 'gutter-insert',
2222
});
2323

24-
export const diffLineDeleteDecorationBackground = ModelDecorationOptions.register({
24+
export const diffLineDeleteDecorationBackgroundWithIndicator = ModelDecorationOptions.register({
2525
className: 'line-delete',
2626
description: 'line-delete',
2727
isWholeLine: true,
2828
linesDecorationsClassName: 'delete-sign ' + ThemeIcon.asClassName(diffRemoveIcon),
2929
marginClassName: 'gutter-delete',
3030
});
3131

32+
export const diffLineAddDecorationBackground = ModelDecorationOptions.register({
33+
className: 'line-insert',
34+
description: 'line-insert',
35+
isWholeLine: true,
36+
marginClassName: 'gutter-insert',
37+
});
38+
39+
export const diffLineDeleteDecorationBackground = ModelDecorationOptions.register({
40+
className: 'line-delete',
41+
description: 'line-delete',
42+
isWholeLine: true,
43+
marginClassName: 'gutter-delete',
44+
});
45+
3246
export const diffAddDecoration = ModelDecorationOptions.register({
3347
className: 'char-insert',
3448
description: 'char-insert',

src/vs/editor/browser/widget/diffEditorWidget2/diffEditorDecorations.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import { Disposable } from 'vs/base/common/lifecycle';
77
import { IObservable, derived } from 'vs/base/common/observable';
88
import { isDefined } from 'vs/base/common/types';
99
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
10-
import { arrowRevertChange, diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackground, diffLineDeleteDecorationBackground } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
11-
import { DiffModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffModel';
10+
import { arrowRevertChange, diffAddDecoration, diffAddDecorationEmpty, diffDeleteDecoration, diffDeleteDecorationEmpty, diffLineAddDecorationBackground, diffLineAddDecorationBackgroundWithIndicator, diffLineDeleteDecorationBackground, diffLineDeleteDecorationBackgroundWithIndicator } from 'vs/editor/browser/widget/diffEditorWidget2/decorations';
11+
import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorOptions';
12+
import { DiffEditorViewModel } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorViewModel';
1213
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditorWidget2/movedBlocksLines';
1314
import { applyObservableDecorations } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
1415
import { LineRange } from 'vs/editor/common/core/lineRange';
@@ -20,37 +21,37 @@ export class DiffEditorDecorations extends Disposable {
2021
constructor(
2122
private readonly _originalEditor: CodeEditorWidget,
2223
private readonly _modifiedEditor: CodeEditorWidget,
23-
private readonly _diffModel: IObservable<DiffModel | undefined>,
24-
private readonly _shouldRenderRevertArrows: IObservable<boolean>,
24+
private readonly _diffModel: IObservable<DiffEditorViewModel | undefined>,
25+
private readonly _options: DiffEditorOptions,
2526
) {
2627
super();
2728

2829
this._register(applyObservableDecorations(this._originalEditor, this._decorations.map(d => d?.originalDecorations || [])));
2930
this._register(applyObservableDecorations(this._modifiedEditor, this._decorations.map(d => d?.modifiedDecorations || [])));
3031
}
3132

32-
3333
private readonly _decorations = derived('decorations', (reader) => {
3434
const diff = this._diffModel.read(reader)?.diff.read(reader);
3535
if (!diff) {
3636
return null;
3737
}
3838

3939
const currentMove = this._diffModel.read(reader)!.syncedMovedTexts.read(reader);
40+
const renderIndicators = this._options.renderIndicators.read(reader);
4041

4142
const originalDecorations: IModelDeltaDecoration[] = [];
4243
const modifiedDecorations: IModelDeltaDecoration[] = [];
4344
for (const m of diff.mappings) {
4445
const fullRangeOriginal = LineRange.subtract(m.lineRangeMapping.originalRange, currentMove?.lineRangeMapping.originalRange)
4546
.map(i => i.toInclusiveRange()).filter(isDefined);
4647
for (const range of fullRangeOriginal) {
47-
originalDecorations.push({ range, options: diffLineDeleteDecorationBackground });
48+
originalDecorations.push({ range, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
4849
}
4950

5051
const fullRangeModified = LineRange.subtract(m.lineRangeMapping.modifiedRange, currentMove?.lineRangeMapping.modifiedRange)
5152
.map(i => i.toInclusiveRange()).filter(isDefined);
5253
for (const range of fullRangeModified) {
53-
modifiedDecorations.push({ range, options: diffLineAddDecorationBackground });
54+
modifiedDecorations.push({ range, options: renderIndicators ? diffLineAddDecorationBackgroundWithIndicator : diffLineAddDecorationBackground });
5455
}
5556

5657
for (const i of m.lineRangeMapping.innerChanges || []) {
@@ -64,7 +65,7 @@ export class DiffEditorDecorations extends Disposable {
6465
modifiedDecorations.push({ range: i.modifiedRange, options: i.modifiedRange.isEmpty() ? diffAddDecorationEmpty : diffAddDecoration });
6566
}
6667

67-
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._shouldRenderRevertArrows.read(reader) && !currentMove) {
68+
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._options.shouldRenderRevertArrows.read(reader) && !currentMove) {
6869
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modifiedRange.startLineNumber, 1)), options: arrowRevertChange });
6970
}
7071
}
@@ -73,11 +74,11 @@ export class DiffEditorDecorations extends Disposable {
7374
for (const m of currentMove.changes) {
7475
const fullRangeOriginal = m.originalRange.toInclusiveRange();
7576
if (fullRangeOriginal) {
76-
originalDecorations.push({ range: fullRangeOriginal, options: diffLineDeleteDecorationBackground });
77+
originalDecorations.push({ range: fullRangeOriginal, options: renderIndicators ? diffLineDeleteDecorationBackgroundWithIndicator : diffLineDeleteDecorationBackground });
7778
}
7879
const fullRangeModified = m.modifiedRange.toInclusiveRange();
7980
if (fullRangeModified) {
80-
modifiedDecorations.push({ range: fullRangeModified, options: diffLineAddDecorationBackground });
81+
modifiedDecorations.push({ range: fullRangeModified, options: renderIndicators ? diffLineAddDecorationBackgroundWithIndicator : diffLineAddDecorationBackground });
8182
}
8283

8384
for (const i of m.innerChanges || []) {
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
import { Emitter } from 'vs/base/common/event';
6+
import { Disposable } from 'vs/base/common/lifecycle';
7+
import { autorunHandleChanges } from 'vs/base/common/observableImpl/autorun';
8+
import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
9+
import { IDiffEditorConstructionOptions } from 'vs/editor/browser/editorBrowser';
10+
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
11+
import { IDiffCodeEditorWidgetOptions } from 'vs/editor/browser/widget/diffEditorWidget';
12+
import { OverviewRulerPart } from 'vs/editor/browser/widget/diffEditorWidget2/overviewRulerPart';
13+
import { EditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
14+
import { IContentSizeChangedEvent } from 'vs/editor/common/editorCommon';
15+
import { localize } from 'vs/nls';
16+
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
17+
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
18+
import { DiffEditorOptions } from './diffEditorOptions';
19+
20+
export class DiffEditorEditors extends Disposable {
21+
public readonly modified: CodeEditorWidget;
22+
public readonly original: CodeEditorWidget;
23+
24+
private readonly _onDidContentSizeChange = this._register(new Emitter<IContentSizeChangedEvent>());
25+
public get onDidContentSizeChange() { return this._onDidContentSizeChange.event; }
26+
27+
constructor(
28+
private readonly originalEditorElement: HTMLElement,
29+
private readonly modifiedEditorElement: HTMLElement,
30+
private readonly _options: DiffEditorOptions,
31+
codeEditorWidgetOptions: IDiffCodeEditorWidgetOptions,
32+
private readonly _createInnerEditor: (instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorOptions>, editorWidgetOptions: ICodeEditorWidgetOptions) => CodeEditorWidget,
33+
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
34+
@IInstantiationService private readonly _instantiationService: IInstantiationService
35+
) {
36+
super();
37+
38+
this.original = this._createLeftHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.originalEditor || {});
39+
this.modified = this._createRightHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.modifiedEditor || {});
40+
41+
this._register(autorunHandleChanges('update editor options', {
42+
createEmptyChangeSummary: () => ({}),
43+
handleChange: (ctx, changeSummary) => {
44+
if (ctx.didChange(_options.editorOptions)) {
45+
Object.assign(changeSummary, ctx.change);
46+
}
47+
return true;
48+
}
49+
}, (reader, changeSummary) => {
50+
_options.editorOptions.read(reader);
51+
52+
this.modified.updateOptions(this._adjustOptionsForRightHandSide(changeSummary));
53+
this.original.updateOptions(this._adjustOptionsForLeftHandSide(changeSummary));
54+
}));
55+
}
56+
57+
private _createLeftHandSideEditor(options: Readonly<IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
58+
const editor = this._constructInnerEditor(this._instantiationService, this.originalEditorElement, this._adjustOptionsForLeftHandSide(options), codeEditorWidgetOptions);
59+
const isInDiffLeftEditorKey = this._contextKeyService.createKey<boolean>('isInDiffLeftEditor', editor.hasWidgetFocus());
60+
this._register(editor.onDidFocusEditorWidget(() => isInDiffLeftEditorKey.set(true)));
61+
this._register(editor.onDidBlurEditorWidget(() => isInDiffLeftEditorKey.set(false)));
62+
return editor;
63+
}
64+
65+
private _createRightHandSideEditor(options: Readonly<IDiffEditorConstructionOptions>, codeEditorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
66+
const editor = this._constructInnerEditor(this._instantiationService, this.modifiedEditorElement, this._adjustOptionsForRightHandSide(options), codeEditorWidgetOptions);
67+
const isInDiffRightEditorKey = this._contextKeyService.createKey<boolean>('isInDiffRightEditor', editor.hasWidgetFocus());
68+
this._register(editor.onDidFocusEditorWidget(() => isInDiffRightEditorKey.set(true)));
69+
this._register(editor.onDidBlurEditorWidget(() => isInDiffRightEditorKey.set(false)));
70+
return editor;
71+
}
72+
73+
private _constructInnerEditor(instantiationService: IInstantiationService, container: HTMLElement, options: Readonly<IEditorConstructionOptions>, editorWidgetOptions: ICodeEditorWidgetOptions): CodeEditorWidget {
74+
const editor = this._createInnerEditor(instantiationService, container, options, editorWidgetOptions);
75+
76+
this._register(editor.onDidContentSizeChange(e => {
77+
const width = this.original.getContentWidth() + this.modified.getContentWidth() + OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH;
78+
const height = Math.max(this.modified.getContentHeight(), this.original.getContentHeight());
79+
80+
this._onDidContentSizeChange.fire({
81+
contentHeight: height,
82+
contentWidth: width,
83+
contentHeightChanged: e.contentHeightChanged,
84+
contentWidthChanged: e.contentWidthChanged
85+
});
86+
}));
87+
return editor;
88+
}
89+
90+
private _adjustOptionsForLeftHandSide(changedOptions: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
91+
const result = this._adjustOptionsForSubEditor(changedOptions);
92+
if (!this._options.renderSideBySide.get()) {
93+
// never wrap hidden editor
94+
result.wordWrapOverride1 = 'off';
95+
result.wordWrapOverride2 = 'off';
96+
result.stickyScroll = { enabled: false };
97+
} else {
98+
result.wordWrapOverride1 = this._options.diffWordWrap.get();
99+
}
100+
if (changedOptions.originalAriaLabel) {
101+
result.ariaLabel = changedOptions.originalAriaLabel;
102+
}
103+
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
104+
result.readOnly = !this._options.originalEditable.get();
105+
result.dropIntoEditor = { enabled: !result.readOnly };
106+
result.extraEditorClassName = 'original-in-monaco-diff-editor';
107+
return result;
108+
}
109+
110+
private _adjustOptionsForRightHandSide(changedOptions: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
111+
const result = this._adjustOptionsForSubEditor(changedOptions);
112+
if (changedOptions.modifiedAriaLabel) {
113+
result.ariaLabel = changedOptions.modifiedAriaLabel;
114+
}
115+
result.ariaLabel = this._updateAriaLabel(result.ariaLabel);
116+
result.wordWrapOverride1 = this._options.diffWordWrap.get();
117+
result.revealHorizontalRightPadding = EditorOptions.revealHorizontalRightPadding.defaultValue + OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH;
118+
result.scrollbar!.verticalHasArrows = false;
119+
result.extraEditorClassName = 'modified-in-monaco-diff-editor';
120+
return result;
121+
}
122+
123+
private _adjustOptionsForSubEditor(options: Readonly<IDiffEditorConstructionOptions>): IEditorConstructionOptions {
124+
const clonedOptions = {
125+
...options,
126+
dimension: {
127+
height: 0,
128+
width: 0
129+
},
130+
};
131+
clonedOptions.inDiffEditor = true;
132+
clonedOptions.automaticLayout = false;
133+
// Clone scrollbar options before changing them
134+
clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) };
135+
clonedOptions.scrollbar.vertical = 'visible';
136+
clonedOptions.folding = false;
137+
clonedOptions.codeLens = this._options.diffCodeLens.get();
138+
clonedOptions.fixedOverflowWidgets = true;
139+
// clonedOptions.lineDecorationsWidth = '2ch';
140+
// Clone minimap options before changing them
141+
clonedOptions.minimap = { ...(clonedOptions.minimap || {}) };
142+
clonedOptions.minimap.enabled = false;
143+
144+
if (this._options.collapseUnchangedRegions.get()) {
145+
clonedOptions.stickyScroll = { enabled: false };
146+
} else {
147+
clonedOptions.stickyScroll = this._options.editorOptions.get().stickyScroll;
148+
}
149+
return clonedOptions;
150+
}
151+
152+
private _updateAriaLabel(ariaLabel: string | undefined): string | undefined {
153+
const ariaNavigationTip = localize('diff-aria-navigation-tip', ' use Shift + F7 to navigate changes');
154+
if (this._options.accessibilityVerbose.get()) {
155+
return ariaLabel + ariaNavigationTip;
156+
} else if (ariaLabel) {
157+
return ariaLabel.replaceAll(ariaNavigationTip, '');
158+
}
159+
return undefined;
160+
}
161+
}

0 commit comments

Comments
 (0)