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