Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/view/viewOverlays.ts
3294 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 { FastDomNode, createFastDomNode } from '../../../base/browser/fastDomNode.js';
7
import { applyFontInfo } from '../config/domFontInfo.js';
8
import { DynamicViewOverlay } from './dynamicViewOverlay.js';
9
import { IVisibleLine, VisibleLinesCollection } from './viewLayer.js';
10
import { ViewPart } from './viewPart.js';
11
import { StringBuilder } from '../../common/core/stringBuilder.js';
12
import { RenderingContext, RestrictedRenderingContext } from './renderingContext.js';
13
import { ViewContext } from '../../common/viewModel/viewContext.js';
14
import * as viewEvents from '../../common/viewEvents.js';
15
import { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js';
16
import { EditorOption } from '../../common/config/editorOptions.js';
17
18
export class ViewOverlays extends ViewPart {
19
private readonly _visibleLines: VisibleLinesCollection<ViewOverlayLine>;
20
protected readonly domNode: FastDomNode<HTMLElement>;
21
private _dynamicOverlays: DynamicViewOverlay[] = [];
22
private _isFocused: boolean = false;
23
24
constructor(context: ViewContext) {
25
super(context);
26
27
this._visibleLines = new VisibleLinesCollection(this._context, {
28
createLine: () => new ViewOverlayLine(this._dynamicOverlays)
29
});
30
this.domNode = this._visibleLines.domNode;
31
32
const options = this._context.configuration.options;
33
const fontInfo = options.get(EditorOption.fontInfo);
34
applyFontInfo(this.domNode, fontInfo);
35
36
this.domNode.setClassName('view-overlays');
37
}
38
39
public override shouldRender(): boolean {
40
if (super.shouldRender()) {
41
return true;
42
}
43
44
for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) {
45
const dynamicOverlay = this._dynamicOverlays[i];
46
if (dynamicOverlay.shouldRender()) {
47
return true;
48
}
49
}
50
51
return false;
52
}
53
54
public override dispose(): void {
55
super.dispose();
56
57
for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) {
58
const dynamicOverlay = this._dynamicOverlays[i];
59
dynamicOverlay.dispose();
60
}
61
this._dynamicOverlays = [];
62
}
63
64
public getDomNode(): FastDomNode<HTMLElement> {
65
return this.domNode;
66
}
67
68
public addDynamicOverlay(overlay: DynamicViewOverlay): void {
69
this._dynamicOverlays.push(overlay);
70
}
71
72
// ----- event handlers
73
74
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
75
this._visibleLines.onConfigurationChanged(e);
76
77
const options = this._context.configuration.options;
78
const fontInfo = options.get(EditorOption.fontInfo);
79
applyFontInfo(this.domNode, fontInfo);
80
81
return true;
82
}
83
public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
84
return this._visibleLines.onFlushed(e);
85
}
86
public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
87
this._isFocused = e.isFocused;
88
return true;
89
}
90
public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
91
return this._visibleLines.onLinesChanged(e);
92
}
93
public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
94
return this._visibleLines.onLinesDeleted(e);
95
}
96
public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
97
return this._visibleLines.onLinesInserted(e);
98
}
99
public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
100
return this._visibleLines.onScrollChanged(e) || true;
101
}
102
public override onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
103
return this._visibleLines.onTokensChanged(e);
104
}
105
public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
106
return this._visibleLines.onZonesChanged(e);
107
}
108
109
// ----- end event handlers
110
111
public prepareRender(ctx: RenderingContext): void {
112
const toRender = this._dynamicOverlays.filter(overlay => overlay.shouldRender());
113
114
for (let i = 0, len = toRender.length; i < len; i++) {
115
const dynamicOverlay = toRender[i];
116
dynamicOverlay.prepareRender(ctx);
117
dynamicOverlay.onDidRender();
118
}
119
}
120
121
public render(ctx: RestrictedRenderingContext): void {
122
// Overwriting to bypass `shouldRender` flag
123
this._viewOverlaysRender(ctx);
124
125
this.domNode.toggleClassName('focused', this._isFocused);
126
}
127
128
_viewOverlaysRender(ctx: RestrictedRenderingContext): void {
129
this._visibleLines.renderLines(ctx.viewportData);
130
}
131
}
132
133
export class ViewOverlayLine implements IVisibleLine {
134
135
private readonly _dynamicOverlays: DynamicViewOverlay[];
136
private _domNode: FastDomNode<HTMLElement> | null;
137
private _renderedContent: string | null;
138
139
constructor(dynamicOverlays: DynamicViewOverlay[]) {
140
this._dynamicOverlays = dynamicOverlays;
141
142
this._domNode = null;
143
this._renderedContent = null;
144
}
145
146
public getDomNode(): HTMLElement | null {
147
if (!this._domNode) {
148
return null;
149
}
150
return this._domNode.domNode;
151
}
152
public setDomNode(domNode: HTMLElement): void {
153
this._domNode = createFastDomNode(domNode);
154
}
155
156
public onContentChanged(): void {
157
// Nothing
158
}
159
public onTokensChanged(): void {
160
// Nothing
161
}
162
163
public renderLine(lineNumber: number, deltaTop: number, lineHeight: number, viewportData: ViewportData, sb: StringBuilder): boolean {
164
let result = '';
165
for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) {
166
const dynamicOverlay = this._dynamicOverlays[i];
167
result += dynamicOverlay.render(viewportData.startLineNumber, lineNumber);
168
}
169
170
if (this._renderedContent === result) {
171
// No rendering needed
172
return false;
173
}
174
175
this._renderedContent = result;
176
177
sb.appendString('<div style="top:');
178
sb.appendString(String(deltaTop));
179
sb.appendString('px;height:');
180
sb.appendString(String(lineHeight));
181
sb.appendString('px;line-height:');
182
sb.appendString(String(lineHeight));
183
sb.appendString('px;">');
184
sb.appendString(result);
185
sb.appendString('</div>');
186
187
return true;
188
}
189
190
public layoutLine(lineNumber: number, deltaTop: number, lineHeight: number): void {
191
if (this._domNode) {
192
this._domNode.setTop(deltaTop);
193
this._domNode.setHeight(lineHeight);
194
this._domNode.setLineHeight(lineHeight);
195
}
196
}
197
}
198
199
export class ContentViewOverlays extends ViewOverlays {
200
201
private _contentWidth: number;
202
203
constructor(context: ViewContext) {
204
super(context);
205
const options = this._context.configuration.options;
206
const layoutInfo = options.get(EditorOption.layoutInfo);
207
this._contentWidth = layoutInfo.contentWidth;
208
209
this.domNode.setHeight(0);
210
}
211
212
// --- begin event handlers
213
214
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
215
const options = this._context.configuration.options;
216
const layoutInfo = options.get(EditorOption.layoutInfo);
217
this._contentWidth = layoutInfo.contentWidth;
218
return super.onConfigurationChanged(e) || true;
219
}
220
public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
221
return super.onScrollChanged(e) || e.scrollWidthChanged;
222
}
223
224
// --- end event handlers
225
226
override _viewOverlaysRender(ctx: RestrictedRenderingContext): void {
227
super._viewOverlaysRender(ctx);
228
229
this.domNode.setWidth(Math.max(ctx.scrollWidth, this._contentWidth));
230
}
231
}
232
233
export class MarginViewOverlays extends ViewOverlays {
234
235
private _contentLeft: number;
236
237
constructor(context: ViewContext) {
238
super(context);
239
240
const options = this._context.configuration.options;
241
const layoutInfo = options.get(EditorOption.layoutInfo);
242
this._contentLeft = layoutInfo.contentLeft;
243
244
this.domNode.setClassName('margin-view-overlays');
245
this.domNode.setWidth(1);
246
247
applyFontInfo(this.domNode, options.get(EditorOption.fontInfo));
248
}
249
250
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
251
const options = this._context.configuration.options;
252
applyFontInfo(this.domNode, options.get(EditorOption.fontInfo));
253
const layoutInfo = options.get(EditorOption.layoutInfo);
254
this._contentLeft = layoutInfo.contentLeft;
255
return super.onConfigurationChanged(e) || true;
256
}
257
258
public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
259
return super.onScrollChanged(e) || e.scrollHeightChanged;
260
}
261
262
override _viewOverlaysRender(ctx: RestrictedRenderingContext): void {
263
super._viewOverlaysRender(ctx);
264
const height = Math.min(ctx.scrollHeight, 1000000);
265
this.domNode.setHeight(height);
266
this.domNode.setWidth(this._contentLeft);
267
}
268
}
269
270