Path: blob/main/src/vs/editor/browser/services/hoverService/updatableHoverWidget.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { isHTMLElement } from '../../../../base/browser/dom.js';6import { isManagedHoverTooltipMarkdownString, type IHoverWidget, type IManagedHoverContent, type IManagedHoverOptions } from '../../../../base/browser/ui/hover/hover.js';7import type { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from '../../../../base/browser/ui/hover/hoverDelegate.js';8import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';9import { CancellationTokenSource } from '../../../../base/common/cancellation.js';10import { isMarkdownString, type IMarkdownString } from '../../../../base/common/htmlContent.js';11import { IDisposable } from '../../../../base/common/lifecycle.js';12import { isFunction, isString } from '../../../../base/common/types.js';13import { localize } from '../../../../nls.js';1415type IManagedHoverResolvedContent = IMarkdownString | string | HTMLElement | undefined;1617export class ManagedHoverWidget implements IDisposable {1819private _hoverWidget: IHoverWidget | undefined;20private _cancellationTokenSource: CancellationTokenSource | undefined;2122constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) { }2324onDidHide() {25if (this._cancellationTokenSource) {26// there's an computation ongoing, cancel it27this._cancellationTokenSource.dispose(true);28this._cancellationTokenSource = undefined;29}30}3132async update(content: IManagedHoverContent, focus?: boolean, options?: IManagedHoverOptions): Promise<void> {33if (this._cancellationTokenSource) {34// there's an computation ongoing, cancel it35this._cancellationTokenSource.dispose(true);36this._cancellationTokenSource = undefined;37}38if (this.isDisposed) {39return;40}4142let resolvedContent: string | HTMLElement | IMarkdownString | undefined;43if (isString(content) || isHTMLElement(content) || content === undefined) {44resolvedContent = content;45} else {46// compute the content, potentially long-running4748this._cancellationTokenSource = new CancellationTokenSource();49const token = this._cancellationTokenSource.token;5051let managedContent;52if (isManagedHoverTooltipMarkdownString(content)) {53if (isFunction(content.markdown)) {54managedContent = content.markdown(token).then(resolvedContent => resolvedContent ?? content.markdownNotSupportedFallback);55} else {56managedContent = content.markdown ?? content.markdownNotSupportedFallback;57}58} else {59managedContent = content.element(token);60}6162// compute the content63if (managedContent instanceof Promise) {6465// show 'Loading' if no hover is up yet66if (!this._hoverWidget) {67this.show(localize('iconLabel.loading', "Loading..."), focus, options);68}6970resolvedContent = await managedContent;71} else {72resolvedContent = managedContent;73}7475if (this.isDisposed || token.isCancellationRequested) {76// either the widget has been closed in the meantime77// or there has been a new call to `update`78return;79}80}8182this.show(resolvedContent, focus, options);83}8485private show(content: IManagedHoverResolvedContent, focus?: boolean, options?: IManagedHoverOptions): void {86const oldHoverWidget = this._hoverWidget;8788if (this.hasContent(content)) {89const hoverOptions: IHoverDelegateOptions = {90content,91target: this.target,92actions: options?.actions,93linkHandler: options?.linkHandler,94trapFocus: options?.trapFocus,95appearance: {96showPointer: this.hoverDelegate.placement === 'element',97skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing98showHoverHint: options?.appearance?.showHoverHint,99},100position: {101hoverPosition: HoverPosition.BELOW,102},103};104105this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);106}107oldHoverWidget?.dispose();108}109110private hasContent(content: IManagedHoverResolvedContent): content is NonNullable<IManagedHoverResolvedContent> {111if (!content) {112return false;113}114115if (isMarkdownString(content)) {116return !!content.value;117}118119return true;120}121122get isDisposed() {123return this._hoverWidget?.isDisposed;124}125126dispose(): void {127this._hoverWidget?.dispose();128this._cancellationTokenSource?.dispose(true);129this._cancellationTokenSource = undefined;130}131}132133134