Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts
5263 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 { ContentWidgetPositionPreference, IContentWidget, IContentWidgetRenderedCoordinate } from '../../editorBrowser.js';
9
import { PartFingerprint, PartFingerprints, ViewPart } from '../../view/viewPart.js';
10
import { RenderingContext, RestrictedRenderingContext } from '../../view/renderingContext.js';
11
import { ViewContext } from '../../../common/viewModel/viewContext.js';
12
import * as viewEvents from '../../../common/viewEvents.js';
13
import { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js';
14
import { EditorOption } from '../../../common/config/editorOptions.js';
15
import { IDimension } from '../../../common/core/2d/dimension.js';
16
import { PositionAffinity } from '../../../common/model.js';
17
import { IPosition, Position } from '../../../common/core/position.js';
18
import { IViewModel } from '../../../common/viewModel.js';
19
20
/**
21
* This view part is responsible for rendering the content widgets, which are
22
* used for rendering elements that are associated to an editor position,
23
* such as suggestions or the parameter hints.
24
*/
25
export class ViewContentWidgets extends ViewPart {
26
27
private readonly _viewDomNode: FastDomNode<HTMLElement>;
28
private _widgets: { [key: string]: Widget };
29
30
public domNode: FastDomNode<HTMLElement>;
31
public overflowingContentWidgetsDomNode: FastDomNode<HTMLElement>;
32
33
constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>) {
34
super(context);
35
this._viewDomNode = viewDomNode;
36
this._widgets = {};
37
38
this.domNode = createFastDomNode(document.createElement('div'));
39
PartFingerprints.write(this.domNode, PartFingerprint.ContentWidgets);
40
this.domNode.setClassName('contentWidgets');
41
this.domNode.setPosition('absolute');
42
this.domNode.setTop(0);
43
44
this.overflowingContentWidgetsDomNode = createFastDomNode(document.createElement('div'));
45
PartFingerprints.write(this.overflowingContentWidgetsDomNode, PartFingerprint.OverflowingContentWidgets);
46
this.overflowingContentWidgetsDomNode.setClassName('overflowingContentWidgets');
47
}
48
49
public override dispose(): void {
50
super.dispose();
51
this._widgets = {};
52
}
53
54
// --- begin event handlers
55
56
public override onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
57
const keys = Object.keys(this._widgets);
58
for (const widgetId of keys) {
59
this._widgets[widgetId].onConfigurationChanged(e);
60
}
61
return true;
62
}
63
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
64
// true for inline decorations that can end up relayouting text
65
return true;
66
}
67
public override onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
68
return true;
69
}
70
public override onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean {
71
this._updateAnchorsViewPositions();
72
return true;
73
}
74
public override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
75
this._updateAnchorsViewPositions();
76
return true;
77
}
78
public override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean {
79
this._updateAnchorsViewPositions();
80
return true;
81
}
82
public override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean {
83
this._updateAnchorsViewPositions();
84
return true;
85
}
86
public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
87
return true;
88
}
89
public override onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
90
return true;
91
}
92
93
// ---- end view event handlers
94
95
private _updateAnchorsViewPositions(): void {
96
const keys = Object.keys(this._widgets);
97
for (const widgetId of keys) {
98
this._widgets[widgetId].updateAnchorViewPosition();
99
}
100
}
101
102
public addWidget(_widget: IContentWidget): void {
103
const myWidget = new Widget(this._context, this._viewDomNode, _widget);
104
this._widgets[myWidget.id] = myWidget;
105
106
if (myWidget.allowEditorOverflow) {
107
this.overflowingContentWidgetsDomNode.appendChild(myWidget.domNode);
108
} else {
109
this.domNode.appendChild(myWidget.domNode);
110
}
111
112
this.setShouldRender();
113
}
114
115
public setWidgetPosition(widget: IContentWidget, primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void {
116
const myWidget = this._widgets[widget.getId()];
117
myWidget.setPosition(primaryAnchor, secondaryAnchor, preference, affinity);
118
119
if (!myWidget.useDisplayNone) {
120
this.setShouldRender();
121
}
122
}
123
124
public removeWidget(widget: IContentWidget): void {
125
const widgetId = widget.getId();
126
if (this._widgets.hasOwnProperty(widgetId)) {
127
const myWidget = this._widgets[widgetId];
128
delete this._widgets[widgetId];
129
130
const domNode = myWidget.domNode.domNode;
131
domNode.remove();
132
domNode.removeAttribute('monaco-visible-content-widget');
133
134
this.setShouldRender();
135
}
136
}
137
138
public shouldSuppressMouseDownOnWidget(widgetId: string): boolean {
139
if (this._widgets.hasOwnProperty(widgetId)) {
140
return this._widgets[widgetId].suppressMouseDown;
141
}
142
return false;
143
}
144
145
public override onBeforeRender(viewportData: ViewportData): void {
146
const keys = Object.keys(this._widgets);
147
for (const widgetId of keys) {
148
this._widgets[widgetId].onBeforeRender(viewportData);
149
}
150
}
151
152
public prepareRender(ctx: RenderingContext): void {
153
const keys = Object.keys(this._widgets);
154
for (const widgetId of keys) {
155
this._widgets[widgetId].prepareRender(ctx);
156
}
157
}
158
159
public render(ctx: RestrictedRenderingContext): void {
160
const keys = Object.keys(this._widgets);
161
for (const widgetId of keys) {
162
this._widgets[widgetId].render(ctx);
163
}
164
}
165
}
166
167
interface IBoxLayoutResult {
168
fitsAbove: boolean;
169
aboveTop: number;
170
171
fitsBelow: boolean;
172
belowTop: number;
173
174
left: number;
175
}
176
177
interface IOffViewportRenderData {
178
kind: 'offViewport';
179
preserveFocus: boolean;
180
}
181
182
interface IInViewportRenderData {
183
kind: 'inViewport';
184
coordinate: Coordinate;
185
position: ContentWidgetPositionPreference;
186
}
187
188
type IRenderData = IInViewportRenderData | IOffViewportRenderData;
189
190
class Widget {
191
private readonly _context: ViewContext;
192
private readonly _viewDomNode: FastDomNode<HTMLElement>;
193
private readonly _actual: IContentWidget;
194
195
public readonly domNode: FastDomNode<HTMLElement>;
196
public readonly id: string;
197
public readonly allowEditorOverflow: boolean;
198
public readonly suppressMouseDown: boolean;
199
200
private readonly _fixedOverflowWidgets: boolean;
201
private _contentWidth: number;
202
private _contentLeft: number;
203
204
private _primaryAnchor: PositionPair = new PositionPair(null, null);
205
private _secondaryAnchor: PositionPair = new PositionPair(null, null);
206
private _affinity: PositionAffinity | null;
207
private _preference: ContentWidgetPositionPreference[] | null;
208
private _cachedDomNodeOffsetWidth: number;
209
private _cachedDomNodeOffsetHeight: number;
210
private _maxWidth: number;
211
private _isVisible: boolean;
212
213
private _renderData: IRenderData | null;
214
public readonly useDisplayNone: boolean;
215
216
constructor(context: ViewContext, viewDomNode: FastDomNode<HTMLElement>, actual: IContentWidget) {
217
this._context = context;
218
this._viewDomNode = viewDomNode;
219
this._actual = actual;
220
221
const options = this._context.configuration.options;
222
const layoutInfo = options.get(EditorOption.layoutInfo);
223
const allowOverflow = options.get(EditorOption.allowOverflow);
224
225
this.domNode = createFastDomNode(this._actual.getDomNode());
226
this.id = this._actual.getId();
227
this.allowEditorOverflow = (this._actual.allowEditorOverflow || false) && allowOverflow;
228
this.suppressMouseDown = this._actual.suppressMouseDown || false;
229
this.useDisplayNone = this._actual.useDisplayNone || false;
230
231
this._fixedOverflowWidgets = options.get(EditorOption.fixedOverflowWidgets);
232
this._contentWidth = layoutInfo.contentWidth;
233
this._contentLeft = layoutInfo.contentLeft;
234
235
this._affinity = null;
236
this._preference = [];
237
this._cachedDomNodeOffsetWidth = -1;
238
this._cachedDomNodeOffsetHeight = -1;
239
this._maxWidth = this._getMaxWidth();
240
this._isVisible = false;
241
this._renderData = null;
242
243
this.domNode.setPosition((this._fixedOverflowWidgets && this.allowEditorOverflow) ? 'fixed' : 'absolute');
244
this.domNode.setDisplay('none');
245
this.domNode.setVisibility('hidden');
246
this.domNode.setAttribute('widgetId', this.id);
247
this.domNode.setMaxWidth(this._maxWidth);
248
}
249
250
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): void {
251
const options = this._context.configuration.options;
252
if (e.hasChanged(EditorOption.layoutInfo)) {
253
const layoutInfo = options.get(EditorOption.layoutInfo);
254
this._contentLeft = layoutInfo.contentLeft;
255
this._contentWidth = layoutInfo.contentWidth;
256
this._maxWidth = this._getMaxWidth();
257
}
258
}
259
260
public updateAnchorViewPosition(): void {
261
this._setPosition(this._affinity, this._primaryAnchor.modelPosition, this._secondaryAnchor.modelPosition);
262
}
263
264
private _setPosition(affinity: PositionAffinity | null, primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null): void {
265
this._affinity = affinity;
266
this._primaryAnchor = getValidPositionPair(primaryAnchor, this._context.viewModel, this._affinity);
267
this._secondaryAnchor = getValidPositionPair(secondaryAnchor, this._context.viewModel, this._affinity);
268
269
function getValidPositionPair(position: IPosition | null, viewModel: IViewModel, affinity: PositionAffinity | null): PositionPair {
270
if (!position) {
271
return new PositionPair(null, null);
272
}
273
// Do not trust that widgets give a valid position
274
const validModelPosition = viewModel.model.validatePosition(position);
275
if (viewModel.coordinatesConverter.modelPositionIsVisible(validModelPosition)) {
276
const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(validModelPosition, affinity ?? undefined);
277
return new PositionPair(position, viewPosition);
278
}
279
return new PositionPair(position, null);
280
}
281
}
282
283
private _getMaxWidth(): number {
284
const elDocument = this.domNode.domNode.ownerDocument;
285
const elWindow = elDocument.defaultView;
286
return (
287
this.allowEditorOverflow
288
? elWindow?.innerWidth || elDocument.documentElement.offsetWidth || elDocument.body.offsetWidth
289
: this._contentWidth
290
);
291
}
292
293
public setPosition(primaryAnchor: IPosition | null, secondaryAnchor: IPosition | null, preference: ContentWidgetPositionPreference[] | null, affinity: PositionAffinity | null): void {
294
this._setPosition(affinity, primaryAnchor, secondaryAnchor);
295
this._preference = preference;
296
if (!this.useDisplayNone && this._primaryAnchor.viewPosition && this._preference && this._preference.length > 0) {
297
// this content widget would like to be visible if possible
298
// we change it from `display:none` to `display:block` even if it
299
// might be outside the viewport such that we can measure its size
300
// in `prepareRender`
301
this.domNode.setDisplay('block');
302
} else {
303
this.domNode.setDisplay('none');
304
}
305
this._cachedDomNodeOffsetWidth = -1;
306
this._cachedDomNodeOffsetHeight = -1;
307
}
308
309
private _layoutBoxInViewport(anchor: AnchorCoordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult {
310
// Our visible box is split horizontally by the current line => 2 boxes
311
312
// a) the box above the line
313
const aboveLineTop = anchor.top;
314
const heightAvailableAboveLine = aboveLineTop;
315
316
// b) the box under the line
317
const underLineTop = anchor.top + anchor.height;
318
const heightAvailableUnderLine = ctx.viewportHeight - underLineTop;
319
320
const aboveTop = aboveLineTop - height;
321
const fitsAbove = (heightAvailableAboveLine >= height);
322
const belowTop = underLineTop;
323
const fitsBelow = (heightAvailableUnderLine >= height);
324
325
// And its left
326
let left = anchor.left;
327
if (left + width > ctx.scrollLeft + ctx.viewportWidth) {
328
left = ctx.scrollLeft + ctx.viewportWidth - width;
329
}
330
if (left < ctx.scrollLeft) {
331
left = ctx.scrollLeft;
332
}
333
334
return { fitsAbove, aboveTop, fitsBelow, belowTop, left };
335
}
336
337
private _layoutHorizontalSegmentInPage(windowSize: dom.Dimension, domNodePosition: dom.IDomNodePagePosition, left: number, width: number): [number, number] {
338
// Leave some clearance to the left/right
339
const LEFT_PADDING = 15;
340
const RIGHT_PADDING = 15;
341
342
// Initially, the limits are defined as the dom node limits
343
const MIN_LIMIT = Math.max(LEFT_PADDING, domNodePosition.left - width);
344
const MAX_LIMIT = Math.min(domNodePosition.left + domNodePosition.width + width, windowSize.width - RIGHT_PADDING);
345
346
const elDocument = this._viewDomNode.domNode.ownerDocument;
347
const elWindow = elDocument.defaultView;
348
let absoluteLeft = domNodePosition.left + left - (elWindow?.scrollX ?? 0);
349
350
if (absoluteLeft + width > MAX_LIMIT) {
351
const delta = absoluteLeft - (MAX_LIMIT - width);
352
absoluteLeft -= delta;
353
left -= delta;
354
}
355
356
if (absoluteLeft < MIN_LIMIT) {
357
const delta = absoluteLeft - MIN_LIMIT;
358
absoluteLeft -= delta;
359
left -= delta;
360
}
361
362
return [left, absoluteLeft];
363
}
364
365
private _layoutBoxInPage(anchor: AnchorCoordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult | null {
366
const aboveTop = anchor.top - height;
367
const belowTop = anchor.top + anchor.height;
368
369
const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode);
370
const elDocument = this._viewDomNode.domNode.ownerDocument;
371
const elWindow = elDocument.defaultView;
372
const absoluteAboveTop = domNodePosition.top + aboveTop - (elWindow?.scrollY ?? 0);
373
const absoluteBelowTop = domNodePosition.top + belowTop - (elWindow?.scrollY ?? 0);
374
375
const windowSize = dom.getClientArea(elDocument.body);
376
const [left, absoluteAboveLeft] = this._layoutHorizontalSegmentInPage(windowSize, domNodePosition, anchor.left - ctx.scrollLeft + this._contentLeft, width);
377
378
// Leave some clearance to the top/bottom
379
const TOP_PADDING = 22;
380
const BOTTOM_PADDING = 22;
381
382
const fitsAbove = (absoluteAboveTop >= TOP_PADDING);
383
const fitsBelow = (absoluteBelowTop + height <= windowSize.height - BOTTOM_PADDING);
384
385
if (this._fixedOverflowWidgets) {
386
return {
387
fitsAbove,
388
aboveTop: Math.max(absoluteAboveTop, TOP_PADDING),
389
fitsBelow,
390
belowTop: absoluteBelowTop,
391
left: absoluteAboveLeft
392
};
393
}
394
395
return { fitsAbove, aboveTop, fitsBelow, belowTop, left };
396
}
397
398
private _prepareRenderWidgetAtExactPositionOverflowing(topLeft: Coordinate): Coordinate {
399
return new Coordinate(topLeft.top, topLeft.left + this._contentLeft);
400
}
401
402
/**
403
* Compute the coordinates above and below the primary and secondary anchors.
404
* The content widget *must* touch the primary anchor.
405
* The content widget should touch if possible the secondary anchor.
406
*/
407
private _getAnchorsCoordinates(ctx: RenderingContext): { primary: AnchorCoordinate | null; secondary: AnchorCoordinate | null } {
408
const primary = getCoordinates(this._primaryAnchor.viewPosition, this._affinity);
409
const secondaryViewPosition = (this._secondaryAnchor.viewPosition?.lineNumber === this._primaryAnchor.viewPosition?.lineNumber ? this._secondaryAnchor.viewPosition : null);
410
const secondary = getCoordinates(secondaryViewPosition, this._affinity);
411
return { primary, secondary };
412
413
function getCoordinates(position: Position | null, affinity: PositionAffinity | null): AnchorCoordinate | null {
414
if (!position) {
415
return null;
416
}
417
418
const horizontalPosition = ctx.visibleRangeForPosition(position);
419
if (!horizontalPosition) {
420
return null;
421
}
422
423
// Left-align widgets that should appear :before content
424
const left = (position.column === 1 && affinity === PositionAffinity.LeftOfInjectedText ? 0 : horizontalPosition.left);
425
const top = ctx.getVerticalOffsetForLineNumber(position.lineNumber) - ctx.scrollTop;
426
const lineHeight = ctx.getLineHeightForLineNumber(position.lineNumber);
427
return new AnchorCoordinate(top, left, lineHeight);
428
}
429
}
430
431
private _reduceAnchorCoordinates(primary: AnchorCoordinate, secondary: AnchorCoordinate | null, width: number): AnchorCoordinate {
432
if (!secondary) {
433
return primary;
434
}
435
436
const fontInfo = this._context.configuration.options.get(EditorOption.fontInfo);
437
438
let left = secondary.left;
439
if (left < primary.left) {
440
left = Math.max(left, primary.left - width + fontInfo.typicalFullwidthCharacterWidth);
441
} else {
442
left = Math.min(left, primary.left + width - fontInfo.typicalFullwidthCharacterWidth);
443
}
444
return new AnchorCoordinate(primary.top, left, primary.height);
445
}
446
447
private _prepareRenderWidget(ctx: RenderingContext): IRenderData | null {
448
if (!this._preference || this._preference.length === 0) {
449
return null;
450
}
451
452
const { primary, secondary } = this._getAnchorsCoordinates(ctx);
453
if (!primary) {
454
return {
455
kind: 'offViewport',
456
preserveFocus: this.domNode.domNode.contains(this.domNode.domNode.ownerDocument.activeElement)
457
};
458
// return null;
459
}
460
461
if (this._cachedDomNodeOffsetWidth === -1 || this._cachedDomNodeOffsetHeight === -1) {
462
463
let preferredDimensions: IDimension | null = null;
464
if (typeof this._actual.beforeRender === 'function') {
465
preferredDimensions = safeInvoke(this._actual.beforeRender, this._actual);
466
}
467
if (preferredDimensions) {
468
this._cachedDomNodeOffsetWidth = preferredDimensions.width;
469
this._cachedDomNodeOffsetHeight = preferredDimensions.height;
470
} else {
471
const domNode = this.domNode.domNode;
472
const clientRect = domNode.getBoundingClientRect();
473
this._cachedDomNodeOffsetWidth = Math.round(clientRect.width);
474
this._cachedDomNodeOffsetHeight = Math.round(clientRect.height);
475
}
476
}
477
478
const anchor = this._reduceAnchorCoordinates(primary, secondary, this._cachedDomNodeOffsetWidth);
479
480
let placement: IBoxLayoutResult | null;
481
if (this.allowEditorOverflow) {
482
placement = this._layoutBoxInPage(anchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
483
} else {
484
placement = this._layoutBoxInViewport(anchor, this._cachedDomNodeOffsetWidth, this._cachedDomNodeOffsetHeight, ctx);
485
}
486
487
// Do two passes, first for perfect fit, second picks first option
488
for (let pass = 1; pass <= 2; pass++) {
489
for (const pref of this._preference) {
490
// placement
491
if (pref === ContentWidgetPositionPreference.ABOVE) {
492
if (!placement) {
493
// Widget outside of viewport
494
return null;
495
}
496
if (pass === 2 || placement.fitsAbove) {
497
return {
498
kind: 'inViewport',
499
coordinate: new Coordinate(placement.aboveTop, placement.left),
500
position: ContentWidgetPositionPreference.ABOVE
501
};
502
}
503
} else if (pref === ContentWidgetPositionPreference.BELOW) {
504
if (!placement) {
505
// Widget outside of viewport
506
return null;
507
}
508
if (pass === 2 || placement.fitsBelow) {
509
return {
510
kind: 'inViewport',
511
coordinate: new Coordinate(placement.belowTop, placement.left),
512
position: ContentWidgetPositionPreference.BELOW
513
};
514
}
515
} else {
516
if (this.allowEditorOverflow) {
517
return {
518
kind: 'inViewport',
519
coordinate: this._prepareRenderWidgetAtExactPositionOverflowing(new Coordinate(anchor.top, anchor.left)),
520
position: ContentWidgetPositionPreference.EXACT
521
};
522
} else {
523
return {
524
kind: 'inViewport',
525
coordinate: new Coordinate(anchor.top, anchor.left),
526
position: ContentWidgetPositionPreference.EXACT
527
};
528
}
529
}
530
}
531
}
532
533
return null;
534
}
535
536
/**
537
* On this first pass, we ensure that the content widget (if it is in the viewport) has the max width set correctly.
538
*/
539
public onBeforeRender(viewportData: ViewportData): void {
540
if (!this._primaryAnchor.viewPosition || !this._preference) {
541
return;
542
}
543
544
if (this._primaryAnchor.viewPosition.lineNumber < viewportData.startLineNumber || this._primaryAnchor.viewPosition.lineNumber > viewportData.endLineNumber) {
545
// Outside of viewport
546
return;
547
}
548
549
this.domNode.setMaxWidth(this._maxWidth);
550
}
551
552
public prepareRender(ctx: RenderingContext): void {
553
this._renderData = this._prepareRenderWidget(ctx);
554
}
555
556
public render(ctx: RestrictedRenderingContext): void {
557
if (!this._renderData || this._renderData.kind === 'offViewport') {
558
// This widget should be invisible
559
if (this._isVisible) {
560
this.domNode.removeAttribute('monaco-visible-content-widget');
561
this._isVisible = false;
562
563
if (this._renderData?.kind === 'offViewport' && this._renderData.preserveFocus) {
564
// widget wants to be shown, but it is outside of the viewport and it
565
// has focus which we need to preserve
566
this.domNode.setTop(-1000);
567
} else {
568
this.domNode.setVisibility('hidden');
569
}
570
}
571
572
if (typeof this._actual.afterRender === 'function') {
573
safeInvoke(this._actual.afterRender, this._actual, null, null);
574
}
575
return;
576
}
577
578
// This widget should be visible
579
if (this.allowEditorOverflow) {
580
this.domNode.setTop(this._renderData.coordinate.top);
581
this.domNode.setLeft(this._renderData.coordinate.left);
582
} else {
583
this.domNode.setTop(this._renderData.coordinate.top + ctx.scrollTop - ctx.bigNumbersDelta);
584
this.domNode.setLeft(this._renderData.coordinate.left);
585
}
586
587
if (!this._isVisible) {
588
this.domNode.setVisibility('inherit');
589
this.domNode.setAttribute('monaco-visible-content-widget', 'true');
590
this._isVisible = true;
591
}
592
593
if (typeof this._actual.afterRender === 'function') {
594
safeInvoke(this._actual.afterRender, this._actual, this._renderData.position, this._renderData.coordinate);
595
}
596
}
597
}
598
599
class PositionPair {
600
constructor(
601
public readonly modelPosition: IPosition | null,
602
public readonly viewPosition: Position | null
603
) { }
604
}
605
606
class Coordinate implements IContentWidgetRenderedCoordinate {
607
_coordinateBrand: void = undefined;
608
609
constructor(
610
public readonly top: number,
611
public readonly left: number
612
) { }
613
}
614
615
class AnchorCoordinate {
616
_anchorCoordinateBrand: void = undefined;
617
618
constructor(
619
public readonly top: number,
620
public readonly left: number,
621
public readonly height: number
622
) { }
623
}
624
625
// eslint-disable-next-line @typescript-eslint/no-explicit-any
626
function safeInvoke<T extends (...args: any[]) => any>(fn: T, thisArg: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T> | null {
627
try {
628
return fn.call(thisArg, ...args);
629
} catch {
630
// ignore
631
return null;
632
}
633
}
634
635