Skip to content

Commit ec8c12d

Browse files
authored
Improves diff rendering in new diff editor (microsoft#185877)
1 parent be94045 commit ec8c12d

File tree

11 files changed

+203
-118
lines changed

11 files changed

+203
-118
lines changed

src/vs/editor/browser/view/renderingContext.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@ export class LineVisibleRanges {
122122
constructor(
123123
public readonly outsideRenderedLine: boolean,
124124
public readonly lineNumber: number,
125-
public readonly ranges: HorizontalRange[]
125+
public readonly ranges: HorizontalRange[],
126+
/**
127+
* Indicates if the requested range does not end in this line, but continues on the next line.
128+
*/
129+
public readonly continuesOnNextLine: boolean,
126130
) { }
127131
}
128132

src/vs/editor/browser/viewParts/decorations/decorations.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55

66
import 'vs/css!./decorations';
77
import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay';
8-
import { Range } from 'vs/editor/common/core/range';
98
import { HorizontalRange, RenderingContext } from 'vs/editor/browser/view/renderingContext';
10-
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
9+
import { EditorOption } from 'vs/editor/common/config/editorOptions';
10+
import { Range } from 'vs/editor/common/core/range';
1111
import * as viewEvents from 'vs/editor/common/viewEvents';
1212
import { ViewModelDecoration } from 'vs/editor/common/viewModel';
13-
import { EditorOption } from 'vs/editor/common/config/editorOptions';
13+
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
1414

1515
export class DecorationsOverlay extends DynamicViewOverlay {
1616

@@ -151,6 +151,7 @@ export class DecorationsOverlay extends DynamicViewOverlay {
151151
let prevClassName: string | null = null;
152152
let prevShowIfCollapsed: boolean = false;
153153
let prevRange: Range | null = null;
154+
let prevShouldFillLineOnLineBreak: boolean = false;
154155

155156
for (let i = 0, lenI = decorations.length; i < lenI; i++) {
156157
const d = decorations[i];
@@ -175,20 +176,21 @@ export class DecorationsOverlay extends DynamicViewOverlay {
175176

176177
// flush previous decoration
177178
if (prevClassName !== null) {
178-
this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output);
179+
this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output);
179180
}
180181

181182
prevClassName = className;
182183
prevShowIfCollapsed = showIfCollapsed;
183184
prevRange = range;
185+
prevShouldFillLineOnLineBreak = d.options.shouldFillLineOnLineBreak ?? false;
184186
}
185187

186188
if (prevClassName !== null) {
187-
this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output);
189+
this._renderNormalDecoration(ctx, prevRange!, prevClassName, prevShouldFillLineOnLineBreak, prevShowIfCollapsed, lineHeight, visibleStartLineNumber, output);
188190
}
189191
}
190192

191-
private _renderNormalDecoration(ctx: RenderingContext, range: Range, className: string, showIfCollapsed: boolean, lineHeight: string, visibleStartLineNumber: number, output: string[]): void {
193+
private _renderNormalDecoration(ctx: RenderingContext, range: Range, className: string, shouldFillLineOnLineBreak: boolean, showIfCollapsed: boolean, lineHeight: string, visibleStartLineNumber: number, output: string[]): void {
192194
const linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch');
193195
if (!linesVisibleRanges) {
194196
return;
@@ -213,15 +215,17 @@ export class DecorationsOverlay extends DynamicViewOverlay {
213215
}
214216

215217
for (let k = 0, lenK = lineVisibleRanges.ranges.length; k < lenK; k++) {
218+
const expandToLeft = shouldFillLineOnLineBreak && lineVisibleRanges.continuesOnNextLine && lenK === 1;
216219
const visibleRange = lineVisibleRanges.ranges[k];
217220
const decorationOutput = (
218221
'<div class="cdr '
219222
+ className
220223
+ '" style="left:'
221224
+ String(visibleRange.left)
222-
+ 'px;width:'
223-
+ String(visibleRange.width)
224-
+ 'px;height:'
225+
+ (expandToLeft ?
226+
'px;width:100%;height:' :
227+
('px;width:' + String(visibleRange.width) + 'px;height:')
228+
)
225229
+ lineHeight
226230
+ 'px;"></div>'
227231
);

src/vs/editor/browser/viewParts/lines/viewLines.ts

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

6-
import 'vs/css!./viewLines';
7-
import * as platform from 'vs/base/common/platform';
86
import { FastDomNode } from 'vs/base/browser/fastDomNode';
7+
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
98
import { RunOnceScheduler } from 'vs/base/common/async';
9+
import * as platform from 'vs/base/common/platform';
10+
import { Constants } from 'vs/base/common/uint';
11+
import 'vs/css!./viewLines';
1012
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
13+
import { HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, VisibleRanges } from 'vs/editor/browser/view/renderingContext';
1114
import { IVisibleLinesHost, VisibleLinesCollection } from 'vs/editor/browser/view/viewLayer';
1215
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
16+
import { DomReadingContext } from 'vs/editor/browser/viewParts/lines/domReadingContext';
1317
import { ViewLine, ViewLineOptions } from 'vs/editor/browser/viewParts/lines/viewLine';
18+
import { EditorOption } from 'vs/editor/common/config/editorOptions';
1419
import { Position } from 'vs/editor/common/core/position';
1520
import { Range } from 'vs/editor/common/core/range';
1621
import { Selection } from 'vs/editor/common/core/selection';
1722
import { ScrollType } from 'vs/editor/common/editorCommon';
18-
import { IViewLines, LineVisibleRanges, VisibleRanges, HorizontalPosition, HorizontalRange } from 'vs/editor/browser/view/renderingContext';
19-
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
2023
import * as viewEvents from 'vs/editor/common/viewEvents';
2124
import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
2225
import { Viewport } from 'vs/editor/common/viewModel';
23-
import { EditorOption } from 'vs/editor/common/config/editorOptions';
24-
import { Constants } from 'vs/base/common/uint';
25-
import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor/mouseCursor';
26-
import { DomReadingContext } from 'vs/editor/browser/viewParts/lines/domReadingContext';
26+
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
2727

2828
class LastRenderedData {
2929

@@ -443,7 +443,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
443443
}
444444

445445
const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1;
446-
const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.viewModel.getLineMaxColumn(lineNumber);
446+
const continuesInNextLine = lineNumber !== range.endLineNumber;
447+
const endColumn = continuesInNextLine ? this._context.viewModel.getLineMaxColumn(lineNumber) : range.endColumn;
447448
const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(lineNumber, startColumn, endColumn, domReadingContext);
448449

449450
if (!visibleRangesForLine) {
@@ -459,7 +460,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
459460
}
460461
}
461462

462-
visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges));
463+
visibleRanges[visibleRangesLen++] = new LineVisibleRanges(visibleRangesForLine.outsideRenderedLine, lineNumber, HorizontalRange.from(visibleRangesForLine.ranges), continuesInNextLine);
463464
}
464465

465466
this._updateLineWidthsSlowIfDomDidLayout(domReadingContext);

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ 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 diffFullLineAddDecoration = ModelDecorationOptions.register({
16+
export const diffLineAddDecorationBackground = 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 diffFullLineDeleteDecoration = ModelDecorationOptions.register({
24+
export const diffLineDeleteDecorationBackground = ModelDecorationOptions.register({
2525
className: 'line-delete',
2626
description: 'line-delete',
2727
isWholeLine: true,
@@ -32,13 +32,38 @@ export const diffFullLineDeleteDecoration = ModelDecorationOptions.register({
3232
export const diffAddDecoration = ModelDecorationOptions.register({
3333
className: 'char-insert',
3434
description: 'char-insert',
35+
shouldFillLineOnLineBreak: true,
36+
});
37+
38+
export const diffWholeLineAddDecoration = ModelDecorationOptions.register({
39+
className: 'char-insert',
40+
description: 'char-insert',
41+
isWholeLine: true,
42+
});
43+
44+
export const diffAddDecorationEmpty = ModelDecorationOptions.register({
45+
className: 'char-insert diff-range-empty',
46+
description: 'char-insert diff-range-empty',
3547
});
3648

3749
export const diffDeleteDecoration = ModelDecorationOptions.register({
3850
className: 'char-delete',
3951
description: 'char-delete',
52+
shouldFillLineOnLineBreak: true,
4053
});
4154

55+
export const diffWholeLineDeleteDecoration = ModelDecorationOptions.register({
56+
className: 'char-delete',
57+
description: 'char-delete',
58+
isWholeLine: true,
59+
});
60+
61+
export const diffDeleteDecorationEmpty = ModelDecorationOptions.register({
62+
className: 'char-delete diff-range-empty',
63+
description: 'char-delete diff-range-empty',
64+
});
65+
66+
4267
export const arrowRevertChange = ModelDecorationOptions.register({
4368
description: 'diff-editor-arrow-revert-change',
4469
glyphMarginHoverMessage: new MarkdownString(undefined, { isTrusted: true, supportThemeIcons: true }).appendMarkdown(localize('revertChangeHoverMessage', 'Click to revert change')),
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
6+
import { Disposable } from 'vs/base/common/lifecycle';
7+
import { IObservable, derived } from 'vs/base/common/observable';
8+
import { isDefined } from 'vs/base/common/types';
9+
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';
12+
import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditorWidget2/movedBlocksLines';
13+
import { applyObservableDecorations } from 'vs/editor/browser/widget/diffEditorWidget2/utils';
14+
import { LineRange } from 'vs/editor/common/core/lineRange';
15+
import { Position } from 'vs/editor/common/core/position';
16+
import { Range } from 'vs/editor/common/core/range';
17+
import { IModelDeltaDecoration } from 'vs/editor/common/model';
18+
19+
export class DiffEditorDecorations extends Disposable {
20+
constructor(
21+
private readonly _originalEditor: CodeEditorWidget,
22+
private readonly _modifiedEditor: CodeEditorWidget,
23+
private readonly _diffModel: IObservable<DiffModel | undefined>,
24+
private readonly _renderSideBySide: IObservable<boolean>,
25+
) {
26+
super();
27+
28+
this._register(applyObservableDecorations(this._originalEditor, this._decorations.map(d => d?.originalDecorations || [])));
29+
this._register(applyObservableDecorations(this._modifiedEditor, this._decorations.map(d => d?.modifiedDecorations || [])));
30+
}
31+
32+
33+
private readonly _decorations = derived('decorations', (reader) => {
34+
const diff = this._diffModel.read(reader)?.diff.read(reader);
35+
if (!diff) {
36+
return null;
37+
}
38+
39+
const currentMove = this._diffModel.read(reader)!.syncedMovedTexts.read(reader);
40+
41+
const originalDecorations: IModelDeltaDecoration[] = [];
42+
const modifiedDecorations: IModelDeltaDecoration[] = [];
43+
for (const m of diff.mappings) {
44+
const fullRangeOriginal = LineRange.subtract(m.lineRangeMapping.originalRange, currentMove?.lineRangeMapping.originalRange)
45+
.map(i => i.toInclusiveRange()).filter(isDefined);
46+
for (const range of fullRangeOriginal) {
47+
originalDecorations.push({ range, options: diffLineDeleteDecorationBackground });
48+
}
49+
50+
const fullRangeModified = LineRange.subtract(m.lineRangeMapping.modifiedRange, currentMove?.lineRangeMapping.modifiedRange)
51+
.map(i => i.toInclusiveRange()).filter(isDefined);
52+
for (const range of fullRangeModified) {
53+
modifiedDecorations.push({ range, options: diffLineAddDecorationBackground });
54+
}
55+
56+
for (const i of m.lineRangeMapping.innerChanges || []) {
57+
if (currentMove
58+
&& (currentMove.lineRangeMapping.originalRange.intersect(new LineRange(i.originalRange.startLineNumber, i.originalRange.endLineNumber))
59+
|| currentMove.lineRangeMapping.modifiedRange.intersect(new LineRange(i.modifiedRange.startLineNumber, i.modifiedRange.endLineNumber)))) {
60+
continue;
61+
}
62+
63+
originalDecorations.push({ range: i.originalRange, options: i.originalRange.isEmpty() ? diffDeleteDecorationEmpty : diffDeleteDecoration });
64+
modifiedDecorations.push({ range: i.modifiedRange, options: i.modifiedRange.isEmpty() ? diffAddDecorationEmpty : diffAddDecoration });
65+
}
66+
67+
if (!m.lineRangeMapping.modifiedRange.isEmpty && this._renderSideBySide.read(reader) && !currentMove) {
68+
modifiedDecorations.push({ range: Range.fromPositions(new Position(m.lineRangeMapping.modifiedRange.startLineNumber, 1)), options: arrowRevertChange });
69+
}
70+
}
71+
72+
if (currentMove) {
73+
for (const m of currentMove.changes) {
74+
const fullRangeOriginal = m.originalRange.toInclusiveRange();
75+
if (fullRangeOriginal) {
76+
originalDecorations.push({ range: fullRangeOriginal, options: diffLineDeleteDecorationBackground });
77+
}
78+
const fullRangeModified = m.modifiedRange.toInclusiveRange();
79+
if (fullRangeModified) {
80+
modifiedDecorations.push({ range: fullRangeModified, options: diffLineAddDecorationBackground });
81+
}
82+
83+
for (const i of m.innerChanges || []) {
84+
originalDecorations.push({ range: i.originalRange, options: diffDeleteDecoration });
85+
modifiedDecorations.push({ range: i.modifiedRange, options: diffAddDecoration });
86+
}
87+
}
88+
}
89+
90+
for (const m of diff.movedTexts) {
91+
originalDecorations.push({
92+
range: m.lineRangeMapping.originalRange.toInclusiveRange()!, options: {
93+
description: 'moved',
94+
blockClassName: 'movedOriginal',
95+
blockPadding: [MovedBlocksLinesPart.movedCodeBlockPadding, 0, MovedBlocksLinesPart.movedCodeBlockPadding, MovedBlocksLinesPart.movedCodeBlockPadding],
96+
}
97+
});
98+
99+
modifiedDecorations.push({
100+
range: m.lineRangeMapping.modifiedRange.toInclusiveRange()!, options: {
101+
description: 'moved',
102+
blockClassName: 'movedModified',
103+
blockPadding: [4, 0, 4, 4],
104+
}
105+
});
106+
}
107+
108+
return { originalDecorations, modifiedDecorations };
109+
});
110+
}

0 commit comments

Comments
 (0)