Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts
3296 views
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 * as DOM from '../../../../../base/browser/dom.js';
7
import { createFastDomNode, FastDomNode } from '../../../../../base/browser/fastDomNode.js';
8
import { PixelRatio } from '../../../../../base/browser/pixelRatio.js';
9
import { Color } from '../../../../../base/common/color.js';
10
import { DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
11
import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved } from '../../../../../platform/theme/common/colorRegistry.js';
12
import { IColorTheme, IThemeService, Themable } from '../../../../../platform/theme/common/themeService.js';
13
import { IDiffElementViewModelBase } from './diffElementViewModel.js';
14
import { NotebookDiffEditorEventDispatcher } from './eventDispatcher.js';
15
import { INotebookTextDiffEditor } from './notebookDiffEditorBrowser.js';
16
17
const MINIMUM_SLIDER_SIZE = 20;
18
19
export class NotebookDiffOverviewRuler extends Themable {
20
private readonly _domNode: FastDomNode<HTMLCanvasElement>;
21
private readonly _overviewViewportDomElement: FastDomNode<HTMLElement>;
22
23
private _diffElementViewModels: readonly IDiffElementViewModelBase[] = [];
24
private _lanes = 2;
25
26
private _insertColor: Color | null;
27
private _insertColorHex: string | null;
28
private _removeColor: Color | null;
29
private _removeColorHex: string | null;
30
31
private readonly _disposables: DisposableStore;
32
private _renderAnimationFrame: IDisposable | null;
33
34
constructor(readonly notebookEditor: INotebookTextDiffEditor, readonly width: number, container: HTMLElement, @IThemeService themeService: IThemeService) {
35
super(themeService);
36
this._insertColor = null;
37
this._removeColor = null;
38
this._insertColorHex = null;
39
this._removeColorHex = null;
40
this._disposables = this._register(new DisposableStore());
41
this._renderAnimationFrame = null;
42
this._domNode = createFastDomNode(document.createElement('canvas'));
43
this._domNode.setPosition('relative');
44
this._domNode.setLayerHinting(true);
45
this._domNode.setContain('strict');
46
47
container.appendChild(this._domNode.domNode);
48
49
this._overviewViewportDomElement = createFastDomNode(document.createElement('div'));
50
this._overviewViewportDomElement.setClassName('diffViewport');
51
this._overviewViewportDomElement.setPosition('absolute');
52
this._overviewViewportDomElement.setWidth(width);
53
container.appendChild(this._overviewViewportDomElement.domNode);
54
55
this._register(PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).onDidChange(() => {
56
this._scheduleRender();
57
}));
58
59
this._register(this.themeService.onDidColorThemeChange(e => {
60
const colorChanged = this.applyColors(e);
61
if (colorChanged) {
62
this._scheduleRender();
63
}
64
}));
65
this.applyColors(this.themeService.getColorTheme());
66
67
this._register(this.notebookEditor.onDidScroll(() => {
68
this._renderOverviewViewport();
69
}));
70
71
this._register(DOM.addStandardDisposableListener(container, DOM.EventType.POINTER_DOWN, (e) => {
72
this.notebookEditor.delegateVerticalScrollbarPointerDown(e);
73
}));
74
}
75
76
private applyColors(theme: IColorTheme): boolean {
77
const newInsertColor = theme.getColor(diffOverviewRulerInserted) || (theme.getColor(diffInserted) || defaultInsertColor).transparent(2);
78
const newRemoveColor = theme.getColor(diffOverviewRulerRemoved) || (theme.getColor(diffRemoved) || defaultRemoveColor).transparent(2);
79
const hasChanges = !newInsertColor.equals(this._insertColor) || !newRemoveColor.equals(this._removeColor);
80
this._insertColor = newInsertColor;
81
this._removeColor = newRemoveColor;
82
if (this._insertColor) {
83
this._insertColorHex = Color.Format.CSS.formatHexA(this._insertColor);
84
}
85
86
if (this._removeColor) {
87
this._removeColorHex = Color.Format.CSS.formatHexA(this._removeColor);
88
}
89
90
return hasChanges;
91
}
92
93
layout() {
94
this._layoutNow();
95
}
96
97
updateViewModels(elements: readonly IDiffElementViewModelBase[], eventDispatcher: NotebookDiffEditorEventDispatcher | undefined) {
98
this._disposables.clear();
99
100
this._diffElementViewModels = elements;
101
102
if (eventDispatcher) {
103
this._disposables.add(eventDispatcher.onDidChangeLayout(() => {
104
this._scheduleRender();
105
}));
106
107
this._disposables.add(eventDispatcher.onDidChangeCellLayout(() => {
108
this._scheduleRender();
109
}));
110
}
111
112
this._scheduleRender();
113
}
114
115
private _scheduleRender(): void {
116
if (this._renderAnimationFrame === null) {
117
this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(DOM.getWindow(this._domNode.domNode), this._onRenderScheduled.bind(this), 16);
118
}
119
}
120
121
private _onRenderScheduled(): void {
122
this._renderAnimationFrame = null;
123
this._layoutNow();
124
}
125
126
private _layoutNow() {
127
const layoutInfo = this.notebookEditor.getLayoutInfo();
128
const height = layoutInfo.height;
129
const contentHeight = this._diffElementViewModels.map(view => view.totalHeight).reduce((a, b) => a + b, 0);
130
const ratio = PixelRatio.getInstance(DOM.getWindow(this._domNode.domNode)).value;
131
this._domNode.setWidth(this.width);
132
this._domNode.setHeight(height);
133
this._domNode.domNode.width = this.width * ratio;
134
this._domNode.domNode.height = height * ratio;
135
const ctx = this._domNode.domNode.getContext('2d')!;
136
ctx.clearRect(0, 0, this.width * ratio, height * ratio);
137
this._renderCanvas(ctx, this.width * ratio, height * ratio, contentHeight * ratio, ratio);
138
this._renderOverviewViewport();
139
}
140
141
private _renderOverviewViewport(): void {
142
const layout = this._computeOverviewViewport();
143
if (!layout) {
144
this._overviewViewportDomElement.setTop(0);
145
this._overviewViewportDomElement.setHeight(0);
146
} else {
147
this._overviewViewportDomElement.setTop(layout.top);
148
this._overviewViewportDomElement.setHeight(layout.height);
149
}
150
}
151
152
private _computeOverviewViewport(): { height: number; top: number } | null {
153
const layoutInfo = this.notebookEditor.getLayoutInfo();
154
if (!layoutInfo) {
155
return null;
156
}
157
158
const scrollTop = this.notebookEditor.getScrollTop();
159
const scrollHeight = this.notebookEditor.getScrollHeight();
160
161
const computedAvailableSize = Math.max(0, layoutInfo.height);
162
const computedRepresentableSize = Math.max(0, computedAvailableSize - 2 * 0);
163
const visibleSize = layoutInfo.height;
164
const computedSliderSize = Math.round(Math.max(MINIMUM_SLIDER_SIZE, Math.floor(visibleSize * computedRepresentableSize / scrollHeight)));
165
const computedSliderRatio = (computedRepresentableSize - computedSliderSize) / (scrollHeight - visibleSize);
166
const computedSliderPosition = Math.round(scrollTop * computedSliderRatio);
167
168
return {
169
height: computedSliderSize,
170
top: computedSliderPosition
171
};
172
}
173
174
private _renderCanvas(ctx: CanvasRenderingContext2D, width: number, height: number, scrollHeight: number, ratio: number) {
175
if (!this._insertColorHex || !this._removeColorHex) {
176
// no op when colors are not yet known
177
return;
178
}
179
180
const laneWidth = width / this._lanes;
181
let currentFrom = 0;
182
for (let i = 0; i < this._diffElementViewModels.length; i++) {
183
const element = this._diffElementViewModels[i];
184
185
const cellHeight = Math.round((element.totalHeight / scrollHeight) * ratio * height);
186
switch (element.type) {
187
case 'insert':
188
ctx.fillStyle = this._insertColorHex;
189
ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight);
190
break;
191
case 'delete':
192
ctx.fillStyle = this._removeColorHex;
193
ctx.fillRect(0, currentFrom, laneWidth, cellHeight);
194
break;
195
case 'unchanged':
196
case 'unchangedMetadata':
197
break;
198
case 'modified':
199
case 'modifiedMetadata':
200
ctx.fillStyle = this._removeColorHex;
201
ctx.fillRect(0, currentFrom, laneWidth, cellHeight);
202
ctx.fillStyle = this._insertColorHex;
203
ctx.fillRect(laneWidth, currentFrom, laneWidth, cellHeight);
204
break;
205
}
206
207
208
currentFrom += cellHeight;
209
}
210
}
211
212
override dispose() {
213
if (this._renderAnimationFrame !== null) {
214
this._renderAnimationFrame.dispose();
215
this._renderAnimationFrame = null;
216
}
217
218
super.dispose();
219
}
220
}
221
222