Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { isHTMLElement } from '../../../../base/browser/dom.js';
7
import { isManagedHoverTooltipMarkdownString, type IHoverWidget, type IManagedHoverContent, type IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js';
8
import type { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js';
9
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
10
import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
11
import { isMarkdownString, type IMarkdownString } from '../../../../base/common/htmlContent.js';
12
import { IDisposable } from '../../../../base/common/lifecycle.js';
13
import { isFunction, isString } from '../../../../base/common/types.js';
14
import { localize } from '../../../../nls.js';
15
16
type IManagedHoverResolvedContent = IMarkdownString | string | HTMLElement | undefined;
17
18
export class ManagedHoverWidget implements IDisposable {
19
20
private _hoverWidget: IHoverWidget | undefined;
21
private _cancellationTokenSource: CancellationTokenSource | undefined;
22
23
constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) { }
24
25
onDidHide() {
26
if (this._cancellationTokenSource) {
27
// there's an computation ongoing, cancel it
28
this._cancellationTokenSource.dispose(true);
29
this._cancellationTokenSource = undefined;
30
}
31
}
32
33
async update(content: IManagedHoverContent, focus?: boolean, options?: IManagedHoverOptions): Promise<void> {
34
if (this._cancellationTokenSource) {
35
// there's an computation ongoing, cancel it
36
this._cancellationTokenSource.dispose(true);
37
this._cancellationTokenSource = undefined;
38
}
39
if (this.isDisposed) {
40
return;
41
}
42
43
let resolvedContent: string | HTMLElement | IMarkdownString | undefined;
44
if (isString(content) || isHTMLElement(content) || content === undefined) {
45
resolvedContent = content;
46
} else {
47
// compute the content, potentially long-running
48
49
this._cancellationTokenSource = new CancellationTokenSource();
50
const token = this._cancellationTokenSource.token;
51
52
let managedContent;
53
if (isManagedHoverTooltipMarkdownString(content)) {
54
if (isFunction(content.markdown)) {
55
managedContent = content.markdown(token).then(resolvedContent => resolvedContent ?? content.markdownNotSupportedFallback);
56
} else {
57
managedContent = content.markdown ?? content.markdownNotSupportedFallback;
58
}
59
} else {
60
managedContent = content.element(token);
61
}
62
63
// compute the content
64
if (managedContent instanceof Promise) {
65
66
// show 'Loading' if no hover is up yet
67
if (!this._hoverWidget) {
68
this.show(localize('iconLabel.loading', "Loading..."), focus, options);
69
}
70
71
resolvedContent = await managedContent;
72
} else {
73
resolvedContent = managedContent;
74
}
75
76
if (this.isDisposed || token.isCancellationRequested) {
77
// either the widget has been closed in the meantime
78
// or there has been a new call to `update`
79
return;
80
}
81
}
82
83
this.show(resolvedContent, focus, options);
84
}
85
86
private show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): void {
87
const oldHoverWidget = this._hoverWidget;
88
89
if (this.hasContent(content)) {
90
const hoverOptions: IHoverDelegateOptions = {
91
content,
92
target: this.target,
93
actions: options?.actions,
94
linkHandler: options?.linkHandler,
95
trapFocus: options?.trapFocus,
96
appearance: {
97
showPointer: this.hoverDelegate.placement === 'element',
98
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing
99
showHoverHint: options?.appearance?.showHoverHint,
100
},
101
position: {
102
hoverPosition: HoverPosition.BELOW,
103
},
104
};
105
106
this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);
107
}
108
oldHoverWidget?.dispose();
109
}
110
111
private hasContent(content: IManagedHoverResolvedContent): content is NonNullable<IManagedHoverResolvedContent> {
112
if (!content) {
113
return false;
114
}
115
116
if (isMarkdownString(content)) {
117
return !!content.value;
118
}
119
120
return true;
121
}
122
123
get isDisposed() {
124
return this._hoverWidget?.isDisposed;
125
}
126
127
dispose(): void {
128
this._hoverWidget?.dispose();
129
this._cancellationTokenSource?.dispose(true);
130
this._cancellationTokenSource = undefined;
131
}
132
}
133
134