Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/view.ts
3292 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 { FastDomNode, createFastDomNode } from '../../base/browser/fastDomNode.js';
8
import { IMouseWheelEvent } from '../../base/browser/mouseEvent.js';
9
import { inputLatency } from '../../base/browser/performance.js';
10
import { CodeWindow } from '../../base/browser/window.js';
11
import { BugIndicatingError, onUnexpectedError } from '../../base/common/errors.js';
12
import { Disposable, IDisposable } from '../../base/common/lifecycle.js';
13
import { IPointerHandlerHelper } from './controller/mouseHandler.js';
14
import { PointerHandlerLastRenderData } from './controller/mouseTarget.js';
15
import { PointerHandler } from './controller/pointerHandler.js';
16
import { IContentWidget, IContentWidgetPosition, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition, IMouseTarget, IOverlayWidget, IOverlayWidgetPosition, IViewZoneChangeAccessor } from './editorBrowser.js';
17
import { LineVisibleRanges, RenderingContext, RestrictedRenderingContext } from './view/renderingContext.js';
18
import { ICommandDelegate, ViewController } from './view/viewController.js';
19
import { ContentViewOverlays, MarginViewOverlays } from './view/viewOverlays.js';
20
import { PartFingerprint, PartFingerprints, ViewPart } from './view/viewPart.js';
21
import { ViewUserInputEvents } from './view/viewUserInputEvents.js';
22
import { BlockDecorations } from './viewParts/blockDecorations/blockDecorations.js';
23
import { ViewContentWidgets } from './viewParts/contentWidgets/contentWidgets.js';
24
import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from './viewParts/currentLineHighlight/currentLineHighlight.js';
25
import { DecorationsOverlay } from './viewParts/decorations/decorations.js';
26
import { EditorScrollbar } from './viewParts/editorScrollbar/editorScrollbar.js';
27
import { GlyphMarginWidgets } from './viewParts/glyphMargin/glyphMargin.js';
28
import { IndentGuidesOverlay } from './viewParts/indentGuides/indentGuides.js';
29
import { LineNumbersOverlay } from './viewParts/lineNumbers/lineNumbers.js';
30
import { ViewLines } from './viewParts/viewLines/viewLines.js';
31
import { LinesDecorationsOverlay } from './viewParts/linesDecorations/linesDecorations.js';
32
import { Margin } from './viewParts/margin/margin.js';
33
import { MarginViewLineDecorationsOverlay } from './viewParts/marginDecorations/marginDecorations.js';
34
import { Minimap } from './viewParts/minimap/minimap.js';
35
import { ViewOverlayWidgets } from './viewParts/overlayWidgets/overlayWidgets.js';
36
import { DecorationsOverviewRuler } from './viewParts/overviewRuler/decorationsOverviewRuler.js';
37
import { OverviewRuler } from './viewParts/overviewRuler/overviewRuler.js';
38
import { Rulers } from './viewParts/rulers/rulers.js';
39
import { ScrollDecorationViewPart } from './viewParts/scrollDecoration/scrollDecoration.js';
40
import { SelectionsOverlay } from './viewParts/selections/selections.js';
41
import { ViewCursors } from './viewParts/viewCursors/viewCursors.js';
42
import { ViewZones } from './viewParts/viewZones/viewZones.js';
43
import { WhitespaceOverlay } from './viewParts/whitespace/whitespace.js';
44
import { IEditorConfiguration } from '../common/config/editorConfiguration.js';
45
import { EditorOption } from '../common/config/editorOptions.js';
46
import { Position } from '../common/core/position.js';
47
import { Range } from '../common/core/range.js';
48
import { Selection } from '../common/core/selection.js';
49
import { ScrollType } from '../common/editorCommon.js';
50
import { GlyphMarginLane, IGlyphMarginLanesModel } from '../common/model.js';
51
import { ViewEventHandler } from '../common/viewEventHandler.js';
52
import * as viewEvents from '../common/viewEvents.js';
53
import { ViewportData } from '../common/viewLayout/viewLinesViewportData.js';
54
import { IViewModel } from '../common/viewModel.js';
55
import { ViewContext } from '../common/viewModel/viewContext.js';
56
import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js';
57
import { IColorTheme, getThemeTypeSelector } from '../../platform/theme/common/themeService.js';
58
import { ViewGpuContext } from './gpu/viewGpuContext.js';
59
import { ViewLinesGpu } from './viewParts/viewLinesGpu/viewLinesGpu.js';
60
import { AbstractEditContext } from './controller/editContext/editContext.js';
61
import { IVisibleRangeProvider, TextAreaEditContext } from './controller/editContext/textArea/textAreaEditContext.js';
62
import { NativeEditContext } from './controller/editContext/native/nativeEditContext.js';
63
import { RulersGpu } from './viewParts/rulersGpu/rulersGpu.js';
64
import { GpuMarkOverlay } from './viewParts/gpuMark/gpuMark.js';
65
import { AccessibilitySupport } from '../../platform/accessibility/common/accessibility.js';
66
import { Event, Emitter } from '../../base/common/event.js';
67
68
69
export interface IContentWidgetData {
70
widget: IContentWidget;
71
position: IContentWidgetPosition | null;
72
}
73
74
export interface IOverlayWidgetData {
75
widget: IOverlayWidget;
76
position: IOverlayWidgetPosition | null;
77
}
78
79
export interface IGlyphMarginWidgetData {
80
widget: IGlyphMarginWidget;
81
position: IGlyphMarginWidgetPosition;
82
}
83
84
export class View extends ViewEventHandler {
85
86
private _widgetFocusTracker: CodeEditorWidgetFocusTracker;
87
88
private readonly _scrollbar: EditorScrollbar;
89
private readonly _context: ViewContext;
90
private readonly _viewGpuContext?: ViewGpuContext;
91
private _selections: Selection[];
92
93
// The view lines
94
private readonly _viewLines: ViewLines;
95
private readonly _viewLinesGpu?: ViewLinesGpu;
96
97
// These are parts, but we must do some API related calls on them, so we keep a reference
98
private readonly _viewZones: ViewZones;
99
private readonly _contentWidgets: ViewContentWidgets;
100
private readonly _overlayWidgets: ViewOverlayWidgets;
101
private readonly _glyphMarginWidgets: GlyphMarginWidgets;
102
private readonly _viewCursors: ViewCursors;
103
private readonly _viewParts: ViewPart[];
104
private readonly _viewController: ViewController;
105
106
private _editContextEnabled: boolean;
107
private _accessibilitySupport: AccessibilitySupport;
108
private _editContext: AbstractEditContext;
109
private readonly _pointerHandler: PointerHandler;
110
111
// Dom nodes
112
private readonly _linesContent: FastDomNode<HTMLElement>;
113
public readonly domNode: FastDomNode<HTMLElement>;
114
private readonly _overflowGuardContainer: FastDomNode<HTMLElement>;
115
116
// Actual mutable state
117
private _shouldRecomputeGlyphMarginLanes: boolean = false;
118
private _renderAnimationFrame: IDisposable | null;
119
private _ownerID: string;
120
121
constructor(
122
editorContainer: HTMLElement,
123
ownerID: string,
124
commandDelegate: ICommandDelegate,
125
configuration: IEditorConfiguration,
126
colorTheme: IColorTheme,
127
model: IViewModel,
128
userInputEvents: ViewUserInputEvents,
129
overflowWidgetsDomNode: HTMLElement | undefined,
130
@IInstantiationService private readonly _instantiationService: IInstantiationService
131
) {
132
super();
133
this._ownerID = ownerID;
134
135
this._widgetFocusTracker = this._register(
136
new CodeEditorWidgetFocusTracker(editorContainer, overflowWidgetsDomNode)
137
);
138
this._register(this._widgetFocusTracker.onChange(() => {
139
this._context.viewModel.setHasWidgetFocus(this._widgetFocusTracker.hasFocus());
140
}));
141
142
this._selections = [new Selection(1, 1, 1, 1)];
143
this._renderAnimationFrame = null;
144
145
this._overflowGuardContainer = createFastDomNode(document.createElement('div'));
146
PartFingerprints.write(this._overflowGuardContainer, PartFingerprint.OverflowGuard);
147
this._overflowGuardContainer.setClassName('overflow-guard');
148
149
this._viewController = new ViewController(configuration, model, userInputEvents, commandDelegate);
150
151
// The view context is passed on to most classes (basically to reduce param. counts in ctors)
152
this._context = new ViewContext(configuration, colorTheme, model);
153
154
// Ensure the view is the first event handler in order to update the layout
155
this._context.addEventHandler(this);
156
157
this._viewParts = [];
158
159
// Keyboard handler
160
this._editContextEnabled = this._context.configuration.options.get(EditorOption.effectiveEditContext);
161
this._accessibilitySupport = this._context.configuration.options.get(EditorOption.accessibilitySupport);
162
this._editContext = this._instantiateEditContext();
163
164
this._viewParts.push(this._editContext);
165
166
// These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.)
167
this._linesContent = createFastDomNode(document.createElement('div'));
168
this._linesContent.setClassName('lines-content' + ' monaco-editor-background');
169
this._linesContent.setPosition('absolute');
170
171
this.domNode = createFastDomNode(document.createElement('div'));
172
this.domNode.setClassName(this._getEditorClassName());
173
// Set role 'code' for better screen reader support https://github.com/microsoft/vscode/issues/93438
174
this.domNode.setAttribute('role', 'code');
175
176
if (this._context.configuration.options.get(EditorOption.experimentalGpuAcceleration) === 'on') {
177
this._viewGpuContext = this._instantiationService.createInstance(ViewGpuContext, this._context);
178
}
179
180
this._scrollbar = new EditorScrollbar(this._context, this._linesContent, this.domNode, this._overflowGuardContainer);
181
this._viewParts.push(this._scrollbar);
182
183
// View Lines
184
this._viewLines = new ViewLines(this._context, this._viewGpuContext, this._linesContent);
185
if (this._viewGpuContext) {
186
this._viewLinesGpu = this._instantiationService.createInstance(ViewLinesGpu, this._context, this._viewGpuContext);
187
}
188
189
// View Zones
190
this._viewZones = new ViewZones(this._context);
191
this._viewParts.push(this._viewZones);
192
193
// Decorations overview ruler
194
const decorationsOverviewRuler = new DecorationsOverviewRuler(this._context);
195
this._viewParts.push(decorationsOverviewRuler);
196
197
198
const scrollDecoration = new ScrollDecorationViewPart(this._context);
199
this._viewParts.push(scrollDecoration);
200
201
const contentViewOverlays = new ContentViewOverlays(this._context);
202
this._viewParts.push(contentViewOverlays);
203
contentViewOverlays.addDynamicOverlay(new CurrentLineHighlightOverlay(this._context));
204
contentViewOverlays.addDynamicOverlay(new SelectionsOverlay(this._context));
205
contentViewOverlays.addDynamicOverlay(new IndentGuidesOverlay(this._context));
206
contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context));
207
contentViewOverlays.addDynamicOverlay(new WhitespaceOverlay(this._context));
208
209
const marginViewOverlays = new MarginViewOverlays(this._context);
210
this._viewParts.push(marginViewOverlays);
211
marginViewOverlays.addDynamicOverlay(new CurrentLineMarginHighlightOverlay(this._context));
212
marginViewOverlays.addDynamicOverlay(new MarginViewLineDecorationsOverlay(this._context));
213
marginViewOverlays.addDynamicOverlay(new LinesDecorationsOverlay(this._context));
214
marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context));
215
if (this._viewGpuContext) {
216
marginViewOverlays.addDynamicOverlay(new GpuMarkOverlay(this._context, this._viewGpuContext));
217
}
218
219
// Glyph margin widgets
220
this._glyphMarginWidgets = new GlyphMarginWidgets(this._context);
221
this._viewParts.push(this._glyphMarginWidgets);
222
223
const margin = new Margin(this._context);
224
margin.getDomNode().appendChild(this._viewZones.marginDomNode);
225
margin.getDomNode().appendChild(marginViewOverlays.getDomNode());
226
margin.getDomNode().appendChild(this._glyphMarginWidgets.domNode);
227
this._viewParts.push(margin);
228
229
// Content widgets
230
this._contentWidgets = new ViewContentWidgets(this._context, this.domNode);
231
this._viewParts.push(this._contentWidgets);
232
233
this._viewCursors = new ViewCursors(this._context);
234
this._viewParts.push(this._viewCursors);
235
236
// Overlay widgets
237
this._overlayWidgets = new ViewOverlayWidgets(this._context, this.domNode);
238
this._viewParts.push(this._overlayWidgets);
239
240
const rulers = this._viewGpuContext
241
? new RulersGpu(this._context, this._viewGpuContext)
242
: new Rulers(this._context);
243
this._viewParts.push(rulers);
244
245
const blockOutline = new BlockDecorations(this._context);
246
this._viewParts.push(blockOutline);
247
248
const minimap = new Minimap(this._context);
249
this._viewParts.push(minimap);
250
251
// -------------- Wire dom nodes up
252
253
if (decorationsOverviewRuler) {
254
const overviewRulerData = this._scrollbar.getOverviewRulerLayoutInfo();
255
overviewRulerData.parent.insertBefore(decorationsOverviewRuler.getDomNode(), overviewRulerData.insertBefore);
256
}
257
258
this._linesContent.appendChild(contentViewOverlays.getDomNode());
259
if ('domNode' in rulers) {
260
this._linesContent.appendChild(rulers.domNode);
261
}
262
this._linesContent.appendChild(this._viewZones.domNode);
263
this._linesContent.appendChild(this._viewLines.getDomNode());
264
this._linesContent.appendChild(this._contentWidgets.domNode);
265
this._linesContent.appendChild(this._viewCursors.getDomNode());
266
this._overflowGuardContainer.appendChild(margin.getDomNode());
267
this._overflowGuardContainer.appendChild(this._scrollbar.getDomNode());
268
if (this._viewGpuContext) {
269
this._overflowGuardContainer.appendChild(this._viewGpuContext.canvas);
270
}
271
this._overflowGuardContainer.appendChild(scrollDecoration.getDomNode());
272
this._overflowGuardContainer.appendChild(this._overlayWidgets.getDomNode());
273
this._overflowGuardContainer.appendChild(minimap.getDomNode());
274
this._overflowGuardContainer.appendChild(blockOutline.domNode);
275
this.domNode.appendChild(this._overflowGuardContainer);
276
277
if (overflowWidgetsDomNode) {
278
overflowWidgetsDomNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode.domNode);
279
overflowWidgetsDomNode.appendChild(this._overlayWidgets.overflowingOverlayWidgetsDomNode.domNode);
280
} else {
281
this.domNode.appendChild(this._contentWidgets.overflowingContentWidgetsDomNode);
282
this.domNode.appendChild(this._overlayWidgets.overflowingOverlayWidgetsDomNode);
283
}
284
285
this._applyLayout();
286
287
// Pointer handler
288
this._pointerHandler = this._register(new PointerHandler(this._context, this._viewController, this._createPointerHandlerHelper()));
289
}
290
291
private _instantiateEditContext(): AbstractEditContext {
292
const usingExperimentalEditContext = this._context.configuration.options.get(EditorOption.effectiveEditContext);
293
if (usingExperimentalEditContext) {
294
return this._instantiationService.createInstance(NativeEditContext, this._ownerID, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper());
295
} else {
296
return this._instantiationService.createInstance(TextAreaEditContext, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper());
297
}
298
}
299
300
private _updateEditContext(): void {
301
const editContextEnabled = this._context.configuration.options.get(EditorOption.effectiveEditContext);
302
const accessibilitySupport = this._context.configuration.options.get(EditorOption.accessibilitySupport);
303
if (this._editContextEnabled === editContextEnabled && this._accessibilitySupport === accessibilitySupport) {
304
return;
305
}
306
this._editContextEnabled = editContextEnabled;
307
this._accessibilitySupport = accessibilitySupport;
308
const isEditContextFocused = this._editContext.isFocused();
309
const indexOfEditContext = this._viewParts.indexOf(this._editContext);
310
this._editContext.dispose();
311
this._editContext = this._instantiateEditContext();
312
if (isEditContextFocused) {
313
this._editContext.focus();
314
}
315
if (indexOfEditContext !== -1) {
316
this._viewParts.splice(indexOfEditContext, 1, this._editContext);
317
}
318
}
319
320
private _computeGlyphMarginLanes(): IGlyphMarginLanesModel {
321
const model = this._context.viewModel.model;
322
const laneModel = this._context.viewModel.glyphLanes;
323
type Glyph = { range: Range; lane: GlyphMarginLane; persist?: boolean };
324
let glyphs: Glyph[] = [];
325
let maxLineNumber = 0;
326
327
// Add all margin decorations
328
glyphs = glyphs.concat(model.getAllMarginDecorations().map((decoration) => {
329
const lane = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Center;
330
maxLineNumber = Math.max(maxLineNumber, decoration.range.endLineNumber);
331
return { range: decoration.range, lane, persist: decoration.options.glyphMargin?.persistLane };
332
}));
333
334
// Add all glyph margin widgets
335
glyphs = glyphs.concat(this._glyphMarginWidgets.getWidgets().map((widget) => {
336
const range = model.validateRange(widget.preference.range);
337
maxLineNumber = Math.max(maxLineNumber, range.endLineNumber);
338
return { range, lane: widget.preference.lane };
339
}));
340
341
// Sorted by their start position
342
glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
343
344
laneModel.reset(maxLineNumber);
345
for (const glyph of glyphs) {
346
laneModel.push(glyph.lane, glyph.range, glyph.persist);
347
}
348
349
return laneModel;
350
}
351
352
private _createPointerHandlerHelper(): IPointerHandlerHelper {
353
return {
354
viewDomNode: this.domNode.domNode,
355
linesContentDomNode: this._linesContent.domNode,
356
viewLinesDomNode: this._viewLines.getDomNode().domNode,
357
viewLinesGpu: this._viewLinesGpu,
358
359
focusTextArea: () => {
360
this.focus();
361
},
362
363
dispatchTextAreaEvent: (event: CustomEvent) => {
364
this._editContext.domNode.domNode.dispatchEvent(event);
365
},
366
367
getLastRenderData: (): PointerHandlerLastRenderData => {
368
const lastViewCursorsRenderData = this._viewCursors.getLastRenderData() || [];
369
const lastTextareaPosition = this._editContext.getLastRenderData();
370
return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition);
371
},
372
renderNow: (): void => {
373
this.render(true, false);
374
},
375
shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => {
376
return this._viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId);
377
},
378
shouldSuppressMouseDownOnWidget: (widgetId: string) => {
379
return this._contentWidgets.shouldSuppressMouseDownOnWidget(widgetId);
380
},
381
getPositionFromDOMInfo: (spanNode: HTMLElement, offset: number) => {
382
this._flushAccumulatedAndRenderNow();
383
return this._viewLines.getPositionFromDOMInfo(spanNode, offset);
384
},
385
386
visibleRangeForPosition: (lineNumber: number, column: number) => {
387
this._flushAccumulatedAndRenderNow();
388
const position = new Position(lineNumber, column);
389
return this._viewLines.visibleRangeForPosition(position) ?? this._viewLinesGpu?.visibleRangeForPosition(position) ?? null;
390
},
391
392
getLineWidth: (lineNumber: number) => {
393
this._flushAccumulatedAndRenderNow();
394
if (this._viewLinesGpu) {
395
const result = this._viewLinesGpu.getLineWidth(lineNumber);
396
if (result !== undefined) {
397
return result;
398
}
399
}
400
return this._viewLines.getLineWidth(lineNumber);
401
}
402
};
403
}
404
405
private _createTextAreaHandlerHelper(): IVisibleRangeProvider {
406
return {
407
visibleRangeForPosition: (position: Position) => {
408
this._flushAccumulatedAndRenderNow();
409
return this._viewLines.visibleRangeForPosition(position);
410
},
411
linesVisibleRangesForRange: (range: Range, includeNewLines: boolean): LineVisibleRanges[] | null => {
412
this._flushAccumulatedAndRenderNow();
413
return this._viewLines.linesVisibleRangesForRange(range, includeNewLines);
414
}
415
};
416
}
417
418
private _applyLayout(): void {
419
const options = this._context.configuration.options;
420
const layoutInfo = options.get(EditorOption.layoutInfo);
421
422
this.domNode.setWidth(layoutInfo.width);
423
this.domNode.setHeight(layoutInfo.height);
424
425
this._overflowGuardContainer.setWidth(layoutInfo.width);
426
this._overflowGuardContainer.setHeight(layoutInfo.height);
427
428
// https://stackoverflow.com/questions/38905916/content-in-google-chrome-larger-than-16777216-px-not-being-rendered
429
this._linesContent.setWidth(16777216);
430
this._linesContent.setHeight(16777216);
431
}
432
433
private _getEditorClassName() {
434
const focused = this._editContext.isFocused() ? ' focused' : '';
435
return this._context.configuration.options.get(EditorOption.editorClassName) + ' ' + getThemeTypeSelector(this._context.theme.type) + focused;
436
}
437
438
// --- begin event handlers
439
public override handleEvents(events: viewEvents.ViewEvent[]): void {
440
super.handleEvents(events);
441
this._scheduleRender();
442
}
443
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
444
this.domNode.setClassName(this._getEditorClassName());
445
this._updateEditContext();
446
this._applyLayout();
447
return false;
448
}
449
public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
450
this._selections = e.selections;
451
return false;
452
}
453
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
454
if (e.affectsGlyphMargin) {
455
this._shouldRecomputeGlyphMarginLanes = true;
456
}
457
return false;
458
}
459
public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
460
this.domNode.setClassName(this._getEditorClassName());
461
return false;
462
}
463
public override onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
464
this._context.theme.update(e.theme);
465
this.domNode.setClassName(this._getEditorClassName());
466
return false;
467
}
468
469
// --- end event handlers
470
471
public override dispose(): void {
472
if (this._renderAnimationFrame !== null) {
473
this._renderAnimationFrame.dispose();
474
this._renderAnimationFrame = null;
475
}
476
477
this._contentWidgets.overflowingContentWidgetsDomNode.domNode.remove();
478
this._overlayWidgets.overflowingOverlayWidgetsDomNode.domNode.remove();
479
480
this._context.removeEventHandler(this);
481
this._viewGpuContext?.dispose();
482
483
this._viewLines.dispose();
484
this._viewLinesGpu?.dispose();
485
486
// Destroy view parts
487
for (const viewPart of this._viewParts) {
488
viewPart.dispose();
489
}
490
491
super.dispose();
492
}
493
494
private _scheduleRender(): void {
495
if (this._store.isDisposed) {
496
throw new BugIndicatingError();
497
}
498
if (this._renderAnimationFrame === null) {
499
// TODO: workaround fix for https://github.com/microsoft/vscode/issues/229825
500
if (this._editContext instanceof NativeEditContext) {
501
this._editContext.setEditContextOnDomNode();
502
}
503
const rendering = this._createCoordinatedRendering();
504
this._renderAnimationFrame = EditorRenderingCoordinator.INSTANCE.scheduleCoordinatedRendering({
505
window: dom.getWindow(this.domNode?.domNode),
506
prepareRenderText: () => {
507
if (this._store.isDisposed) {
508
throw new BugIndicatingError();
509
}
510
try {
511
return rendering.prepareRenderText();
512
} finally {
513
this._renderAnimationFrame = null;
514
}
515
},
516
renderText: () => {
517
if (this._store.isDisposed) {
518
throw new BugIndicatingError();
519
}
520
return rendering.renderText();
521
},
522
prepareRender: (viewParts: ViewPart[], ctx: RenderingContext) => {
523
if (this._store.isDisposed) {
524
throw new BugIndicatingError();
525
}
526
return rendering.prepareRender(viewParts, ctx);
527
},
528
render: (viewParts: ViewPart[], ctx: RestrictedRenderingContext) => {
529
if (this._store.isDisposed) {
530
throw new BugIndicatingError();
531
}
532
return rendering.render(viewParts, ctx);
533
}
534
});
535
}
536
}
537
538
private _flushAccumulatedAndRenderNow(): void {
539
const rendering = this._createCoordinatedRendering();
540
safeInvokeNoArg(() => rendering.prepareRenderText());
541
const data = safeInvokeNoArg(() => rendering.renderText());
542
if (data) {
543
const [viewParts, ctx] = data;
544
safeInvokeNoArg(() => rendering.prepareRender(viewParts, ctx));
545
safeInvokeNoArg(() => rendering.render(viewParts, ctx));
546
}
547
}
548
549
private _getViewPartsToRender(): ViewPart[] {
550
const result: ViewPart[] = [];
551
let resultLen = 0;
552
for (const viewPart of this._viewParts) {
553
if (viewPart.shouldRender()) {
554
result[resultLen++] = viewPart;
555
}
556
}
557
return result;
558
}
559
560
private _createCoordinatedRendering() {
561
return {
562
prepareRenderText: () => {
563
if (this._shouldRecomputeGlyphMarginLanes) {
564
this._shouldRecomputeGlyphMarginLanes = false;
565
const model = this._computeGlyphMarginLanes();
566
this._context.configuration.setGlyphMarginDecorationLaneCount(model.requiredLanes);
567
}
568
inputLatency.onRenderStart();
569
},
570
renderText: (): [ViewPart[], RenderingContext] | null => {
571
if (!this.domNode.domNode.isConnected) {
572
return null;
573
}
574
let viewPartsToRender = this._getViewPartsToRender();
575
if (!this._viewLines.shouldRender() && viewPartsToRender.length === 0) {
576
// Nothing to render
577
return null;
578
}
579
const partialViewportData = this._context.viewLayout.getLinesViewportData();
580
this._context.viewModel.setViewport(partialViewportData.startLineNumber, partialViewportData.endLineNumber, partialViewportData.centeredLineNumber);
581
582
const viewportData = new ViewportData(
583
this._selections,
584
partialViewportData,
585
this._context.viewLayout.getWhitespaceViewportData(),
586
this._context.viewModel
587
);
588
589
if (this._contentWidgets.shouldRender()) {
590
// Give the content widgets a chance to set their max width before a possible synchronous layout
591
this._contentWidgets.onBeforeRender(viewportData);
592
}
593
594
if (this._viewLines.shouldRender()) {
595
this._viewLines.renderText(viewportData);
596
this._viewLines.onDidRender();
597
598
// Rendering of viewLines might cause scroll events to occur, so collect view parts to render again
599
viewPartsToRender = this._getViewPartsToRender();
600
}
601
602
if (this._viewLinesGpu?.shouldRender()) {
603
this._viewLinesGpu.renderText(viewportData);
604
this._viewLinesGpu.onDidRender();
605
}
606
607
return [viewPartsToRender, new RenderingContext(this._context.viewLayout, viewportData, this._viewLines, this._viewLinesGpu)];
608
},
609
prepareRender: (viewPartsToRender: ViewPart[], ctx: RenderingContext) => {
610
for (const viewPart of viewPartsToRender) {
611
viewPart.prepareRender(ctx);
612
}
613
},
614
render: (viewPartsToRender: ViewPart[], ctx: RestrictedRenderingContext) => {
615
for (const viewPart of viewPartsToRender) {
616
viewPart.render(ctx);
617
viewPart.onDidRender();
618
}
619
}
620
};
621
}
622
623
// --- BEGIN CodeEditor helpers
624
625
public delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent): void {
626
this._scrollbar.delegateVerticalScrollbarPointerDown(browserEvent);
627
}
628
629
public delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
630
this._scrollbar.delegateScrollFromMouseWheelEvent(browserEvent);
631
}
632
633
public restoreState(scrollPosition: { scrollLeft: number; scrollTop: number }): void {
634
this._context.viewModel.viewLayout.setScrollPosition({
635
scrollTop: scrollPosition.scrollTop,
636
scrollLeft: scrollPosition.scrollLeft
637
}, ScrollType.Immediate);
638
this._context.viewModel.visibleLinesStabilized();
639
}
640
641
public getOffsetForColumn(modelLineNumber: number, modelColumn: number): number {
642
const modelPosition = this._context.viewModel.model.validatePosition({
643
lineNumber: modelLineNumber,
644
column: modelColumn
645
});
646
const viewPosition = this._context.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
647
this._flushAccumulatedAndRenderNow();
648
const visibleRange = this._viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column));
649
if (!visibleRange) {
650
return -1;
651
}
652
return visibleRange.left;
653
}
654
655
public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null {
656
const mouseTarget = this._pointerHandler.getTargetAtClientPoint(clientX, clientY);
657
if (!mouseTarget) {
658
return null;
659
}
660
return ViewUserInputEvents.convertViewToModelMouseTarget(mouseTarget, this._context.viewModel.coordinatesConverter);
661
}
662
663
public createOverviewRuler(cssClassName: string): OverviewRuler {
664
return new OverviewRuler(this._context, cssClassName);
665
}
666
667
public change(callback: (changeAccessor: IViewZoneChangeAccessor) => any): void {
668
this._viewZones.changeViewZones(callback);
669
this._scheduleRender();
670
}
671
672
public render(now: boolean, everything: boolean): void {
673
if (everything) {
674
// Force everything to render...
675
this._viewLines.forceShouldRender();
676
for (const viewPart of this._viewParts) {
677
viewPart.forceShouldRender();
678
}
679
}
680
if (now) {
681
this._flushAccumulatedAndRenderNow();
682
} else {
683
this._scheduleRender();
684
}
685
}
686
687
public writeScreenReaderContent(reason: string): void {
688
this._editContext.writeScreenReaderContent(reason);
689
}
690
691
public focus(): void {
692
this._editContext.focus();
693
}
694
695
public isFocused(): boolean {
696
return this._editContext.isFocused();
697
}
698
699
public isWidgetFocused(): boolean {
700
return this._widgetFocusTracker.hasFocus();
701
}
702
703
public refreshFocusState() {
704
this._editContext.refreshFocusState();
705
this._widgetFocusTracker.refreshState();
706
}
707
708
public setAriaOptions(options: IEditorAriaOptions): void {
709
this._editContext.setAriaOptions(options);
710
}
711
712
public addContentWidget(widgetData: IContentWidgetData): void {
713
this._contentWidgets.addWidget(widgetData.widget);
714
this.layoutContentWidget(widgetData);
715
this._scheduleRender();
716
}
717
718
public layoutContentWidget(widgetData: IContentWidgetData): void {
719
this._contentWidgets.setWidgetPosition(
720
widgetData.widget,
721
widgetData.position?.position ?? null,
722
widgetData.position?.secondaryPosition ?? null,
723
widgetData.position?.preference ?? null,
724
widgetData.position?.positionAffinity ?? null
725
);
726
this._scheduleRender();
727
}
728
729
public removeContentWidget(widgetData: IContentWidgetData): void {
730
this._contentWidgets.removeWidget(widgetData.widget);
731
this._scheduleRender();
732
}
733
734
public addOverlayWidget(widgetData: IOverlayWidgetData): void {
735
this._overlayWidgets.addWidget(widgetData.widget);
736
this.layoutOverlayWidget(widgetData);
737
this._scheduleRender();
738
}
739
740
public layoutOverlayWidget(widgetData: IOverlayWidgetData): void {
741
const shouldRender = this._overlayWidgets.setWidgetPosition(widgetData.widget, widgetData.position);
742
if (shouldRender) {
743
this._scheduleRender();
744
}
745
}
746
747
public removeOverlayWidget(widgetData: IOverlayWidgetData): void {
748
this._overlayWidgets.removeWidget(widgetData.widget);
749
this._scheduleRender();
750
}
751
752
public addGlyphMarginWidget(widgetData: IGlyphMarginWidgetData): void {
753
this._glyphMarginWidgets.addWidget(widgetData.widget);
754
this._shouldRecomputeGlyphMarginLanes = true;
755
this._scheduleRender();
756
}
757
758
public layoutGlyphMarginWidget(widgetData: IGlyphMarginWidgetData): void {
759
const newPreference = widgetData.position;
760
const shouldRender = this._glyphMarginWidgets.setWidgetPosition(widgetData.widget, newPreference);
761
if (shouldRender) {
762
this._shouldRecomputeGlyphMarginLanes = true;
763
this._scheduleRender();
764
}
765
}
766
767
public removeGlyphMarginWidget(widgetData: IGlyphMarginWidgetData): void {
768
this._glyphMarginWidgets.removeWidget(widgetData.widget);
769
this._shouldRecomputeGlyphMarginLanes = true;
770
this._scheduleRender();
771
}
772
773
// --- END CodeEditor helpers
774
775
}
776
777
function safeInvokeNoArg<T>(func: () => T): T | null {
778
try {
779
return func();
780
} catch (e) {
781
onUnexpectedError(e);
782
return null;
783
}
784
}
785
786
interface ICoordinatedRendering {
787
readonly window: CodeWindow;
788
prepareRenderText(): void;
789
renderText(): [ViewPart[], RenderingContext] | null;
790
prepareRender(viewParts: ViewPart[], ctx: RenderingContext): void;
791
render(viewParts: ViewPart[], ctx: RestrictedRenderingContext): void;
792
}
793
794
class EditorRenderingCoordinator {
795
796
public static INSTANCE = new EditorRenderingCoordinator();
797
798
private _coordinatedRenderings: ICoordinatedRendering[] = [];
799
private _animationFrameRunners = new Map<CodeWindow, IDisposable>();
800
801
private constructor() { }
802
803
scheduleCoordinatedRendering(rendering: ICoordinatedRendering): IDisposable {
804
this._coordinatedRenderings.push(rendering);
805
this._scheduleRender(rendering.window);
806
return {
807
dispose: () => {
808
const renderingIndex = this._coordinatedRenderings.indexOf(rendering);
809
if (renderingIndex === -1) {
810
return;
811
}
812
this._coordinatedRenderings.splice(renderingIndex, 1);
813
814
if (this._coordinatedRenderings.length === 0) {
815
// There are no more renderings to coordinate => cancel animation frames
816
for (const [_, disposable] of this._animationFrameRunners) {
817
disposable.dispose();
818
}
819
this._animationFrameRunners.clear();
820
}
821
}
822
};
823
}
824
825
private _scheduleRender(window: CodeWindow): void {
826
if (!this._animationFrameRunners.has(window)) {
827
const runner = () => {
828
this._animationFrameRunners.delete(window);
829
this._onRenderScheduled();
830
};
831
this._animationFrameRunners.set(window, dom.runAtThisOrScheduleAtNextAnimationFrame(window, runner, 100));
832
}
833
}
834
835
private _onRenderScheduled(): void {
836
const coordinatedRenderings = this._coordinatedRenderings.slice(0);
837
this._coordinatedRenderings = [];
838
839
for (const rendering of coordinatedRenderings) {
840
safeInvokeNoArg(() => rendering.prepareRenderText());
841
}
842
843
const datas: ([ViewPart[], RenderingContext] | null)[] = [];
844
for (let i = 0, len = coordinatedRenderings.length; i < len; i++) {
845
const rendering = coordinatedRenderings[i];
846
datas[i] = safeInvokeNoArg(() => rendering.renderText());
847
}
848
849
for (let i = 0, len = coordinatedRenderings.length; i < len; i++) {
850
const rendering = coordinatedRenderings[i];
851
const data = datas[i];
852
if (!data) {
853
continue;
854
}
855
const [viewParts, ctx] = data;
856
safeInvokeNoArg(() => rendering.prepareRender(viewParts, ctx));
857
}
858
859
for (let i = 0, len = coordinatedRenderings.length; i < len; i++) {
860
const rendering = coordinatedRenderings[i];
861
const data = datas[i];
862
if (!data) {
863
continue;
864
}
865
const [viewParts, ctx] = data;
866
safeInvokeNoArg(() => rendering.render(viewParts, ctx));
867
}
868
}
869
}
870
871
class CodeEditorWidgetFocusTracker extends Disposable {
872
873
private _hasDomElementFocus: boolean;
874
private readonly _domFocusTracker: dom.IFocusTracker;
875
private readonly _overflowWidgetsDomNode: dom.IFocusTracker | undefined;
876
877
private readonly _onChange: Emitter<void> = this._register(new Emitter<void>());
878
public readonly onChange: Event<void> = this._onChange.event;
879
880
private _overflowWidgetsDomNodeHasFocus: boolean;
881
882
private _hadFocus: boolean | undefined = undefined;
883
884
constructor(domElement: HTMLElement, overflowWidgetsDomNode: HTMLElement | undefined) {
885
super();
886
887
this._hasDomElementFocus = false;
888
this._domFocusTracker = this._register(dom.trackFocus(domElement));
889
890
this._overflowWidgetsDomNodeHasFocus = false;
891
892
this._register(this._domFocusTracker.onDidFocus(() => {
893
this._hasDomElementFocus = true;
894
this._update();
895
}));
896
this._register(this._domFocusTracker.onDidBlur(() => {
897
this._hasDomElementFocus = false;
898
this._update();
899
}));
900
901
if (overflowWidgetsDomNode) {
902
this._overflowWidgetsDomNode = this._register(dom.trackFocus(overflowWidgetsDomNode));
903
this._register(this._overflowWidgetsDomNode.onDidFocus(() => {
904
this._overflowWidgetsDomNodeHasFocus = true;
905
this._update();
906
}));
907
this._register(this._overflowWidgetsDomNode.onDidBlur(() => {
908
this._overflowWidgetsDomNodeHasFocus = false;
909
this._update();
910
}));
911
}
912
}
913
914
private _update() {
915
const focused = this._hasDomElementFocus || this._overflowWidgetsDomNodeHasFocus;
916
if (this._hadFocus !== focused) {
917
this._hadFocus = focused;
918
this._onChange.fire(undefined);
919
}
920
}
921
922
public hasFocus(): boolean {
923
return this._hadFocus ?? false;
924
}
925
926
public refreshState(): void {
927
this._domFocusTracker.refreshState();
928
this._overflowWidgetsDomNode?.refreshState?.();
929
}
930
}
931
932