diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 102841305a432..79fc43225b960 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -322,6 +322,9 @@ const _allApiProposals = { speech: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.speech.d.ts', }, + statusBarItemTooltip: { + proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.statusBarItemTooltip.d.ts', + }, tabInputMultiDiff: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputMultiDiff.d.ts', }, diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 4988e519f39d0..92d66d7a2f084 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainThreadStatusBarShape, MainContext, ExtHostContext, StatusBarItemDto } from '../common/extHost.protocol.js'; +import { MainThreadStatusBarShape, MainContext, ExtHostContext, StatusBarItemDto, ExtHostStatusBarShape } from '../common/extHost.protocol.js'; import { ThemeColor } from '../../../base/common/themables.js'; import { extHostNamedCustomer, IExtHostContext } from '../../services/extensions/common/extHostCustomers.js'; import { DisposableStore, toDisposable } from '../../../base/common/lifecycle.js'; @@ -12,17 +12,20 @@ import { IAccessibilityInformation } from '../../../platform/accessibility/commo import { IMarkdownString } from '../../../base/common/htmlContent.js'; import { IExtensionStatusBarItemService, StatusBarUpdateKind } from './statusBarExtensionPoint.js'; import { IStatusbarEntry, StatusbarAlignment } from '../../services/statusbar/browser/statusbar.js'; +import { IManagedHoverTooltipMarkdownString } from '../../../base/browser/ui/hover/hover.js'; +import { CancellationToken } from '../../../base/common/cancellation.js'; @extHostNamedCustomer(MainContext.MainThreadStatusBar) export class MainThreadStatusBar implements MainThreadStatusBarShape { + private readonly _proxy: ExtHostStatusBarShape; private readonly _store = new DisposableStore(); constructor( extHostContext: IExtHostContext, @IExtensionStatusBarItemService private readonly statusbarService: IExtensionStatusBarItemService ) { - const proxy = extHostContext.getProxy(ExtHostContext.ExtHostStatusBar); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostStatusBar); // once, at startup read existing items and send them over const entries: StatusBarItemDto[] = []; @@ -30,12 +33,11 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { entries.push(asDto(entryId, item)); } - proxy.$acceptStaticEntries(entries); + this._proxy.$acceptStaticEntries(entries); this._store.add(statusbarService.onDidChange(e => { - if (e.added) { - proxy.$acceptStaticEntries([asDto(e.added[0], e.added[1])]); - } + const added = e.added ? [asDto(e.added[0], e.added[1])] : []; + this._proxy.$acceptStaticEntries(added, e.removed); })); function asDto(entryId: string, item: { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number }): StatusBarItemDto { @@ -56,8 +58,17 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this._store.dispose(); } - $setEntry(entryId: string, id: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void { - const kind = this.statusbarService.setOrUpdateEntry(entryId, id, extensionId, name, text, tooltip, command, color, backgroundColor, alignLeft, priority, accessibilityInformation); + $setEntry(entryId: string, id: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, hasTooltipProvider: boolean, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void { + const tooltipOrTooltipProvider = hasTooltipProvider + ? { + markdown: (cancellation: CancellationToken) => { + return this._proxy.$provideTooltip(entryId, cancellation); + }, + markdownNotSupportedFallback: undefined + } satisfies IManagedHoverTooltipMarkdownString + : tooltip; + + const kind = this.statusbarService.setOrUpdateEntry(entryId, id, extensionId, name, text, tooltipOrTooltipProvider, command, color, backgroundColor, alignLeft, priority, accessibilityInformation); if (kind === StatusBarUpdateKind.DidDefine) { this._store.add(toDisposable(() => this.statusbarService.unsetEntry(entryId))); } diff --git a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts index 8b2c415ab0b45..fa0d7fd2307c2 100644 --- a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts +++ b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts @@ -13,7 +13,7 @@ import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment, import { ThemeColor } from '../../../base/common/themables.js'; import { Command } from '../../../editor/common/languages.js'; import { IAccessibilityInformation, isAccessibilityInformation } from '../../../platform/accessibility/common/accessibility.js'; -import { IMarkdownString } from '../../../base/common/htmlContent.js'; +import { IMarkdownString, isMarkdownString } from '../../../base/common/htmlContent.js'; import { getCodiconAriaLabel } from '../../../base/common/iconLabels.js'; import { hash } from '../../../base/common/hash.js'; import { Event, Emitter } from '../../../base/common/event.js'; @@ -22,6 +22,7 @@ import { Iterable } from '../../../base/common/iterator.js'; import { ExtensionIdentifier } from '../../../platform/extensions/common/extensions.js'; import { asStatusBarItemIdentifier } from '../common/extHostTypes.js'; import { STATUS_BAR_ERROR_ITEM_BACKGROUND, STATUS_BAR_WARNING_ITEM_BACKGROUND } from '../../common/theme.js'; +import { IManagedHoverTooltipMarkdownString } from '../../../base/browser/ui/hover/hover.js'; // --- service @@ -49,7 +50,7 @@ export interface IExtensionStatusBarItemService { onDidChange: Event; - setOrUpdateEntry(id: string, statusId: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): StatusBarUpdateKind; + setOrUpdateEntry(id: string, statusId: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined | IManagedHoverTooltipMarkdownString, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): StatusBarUpdateKind; unsetEntry(id: string): void; @@ -75,7 +76,8 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService { } setOrUpdateEntry(entryId: string, - id: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, + id: string, extensionId: string | undefined, name: string, text: string, + tooltip: IMarkdownString | string | undefined | IManagedHoverTooltipMarkdownString, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined ): StatusBarUpdateKind { @@ -87,7 +89,7 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService { role = accessibilityInformation.role; } else { ariaLabel = getCodiconAriaLabel(text); - if (tooltip) { + if (typeof tooltip === 'string' || isMarkdownString(tooltip)) { const tooltipString = typeof tooltip === 'string' ? tooltip : tooltip.value; ariaLabel += `, ${tooltipString}`; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8eee9d953f980..3f31329b105a5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -676,7 +676,7 @@ export interface MainThreadQuickOpenShape extends IDisposable { } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: string, statusId: string, extensionId: string | undefined, statusName: string, text: string, tooltip: IMarkdownString | string | undefined, command: ICommandDto | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void; + $setEntry(id: string, statusId: string, extensionId: string | undefined, statusName: string, text: string, tooltip: IMarkdownString | string | undefined, hasTooltipProvider: boolean, command: ICommandDto | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): void; $disposeEntry(id: string): void; } @@ -692,7 +692,8 @@ export type StatusBarItemDto = { }; export interface ExtHostStatusBarShape { - $acceptStaticEntries(added?: StatusBarItemDto[]): void; + $acceptStaticEntries(added?: StatusBarItemDto[], removed?: string): void; + $provideTooltip(entryId: string, cancellation: CancellationToken): Promise; } export interface MainThreadStorageShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostStatusBar.ts b/src/vs/workbench/api/common/extHostStatusBar.ts index 327d53c5c925a..b05a1879f104a 100644 --- a/src/vs/workbench/api/common/extHostStatusBar.ts +++ b/src/vs/workbench/api/common/extHostStatusBar.ts @@ -14,6 +14,8 @@ import { DisposableStore } from '../../../base/common/lifecycle.js'; import { IExtensionDescription } from '../../../platform/extensions/common/extensions.js'; import { MarkdownString } from './extHostTypeConverters.js'; import { isNumber } from '../../../base/common/types.js'; +import * as htmlContent from '../../../base/common/htmlContent.js'; +import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js'; export class ExtHostStatusBarEntry implements vscode.StatusBarItem { @@ -43,6 +45,7 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { private _text: string = ''; private _tooltip?: string | vscode.MarkdownString; + private _tooltip2?: string | vscode.MarkdownString | undefined | ((token: vscode.CancellationToken) => Promise); private _name?: string; private _color?: string | ThemeColor; private _backgroundColor?: ThemeColor; @@ -113,6 +116,10 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { return this._id ?? this._extension!.identifier.value; } + public get entryId(): string { + return this._entryId; + } + public get alignment(): vscode.StatusBarAlignment { return this._alignment; } @@ -133,6 +140,14 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { return this._tooltip; } + public get tooltip2(): vscode.MarkdownString | string | undefined | ((token: vscode.CancellationToken) => Promise) { + if (this._extension) { + checkProposedApiEnabled(this._extension, 'statusBarItemTooltip'); + } + + return this._tooltip2; + } + public get color(): string | ThemeColor | undefined { return this._color; } @@ -164,6 +179,15 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { this.update(); } + public set tooltip2(tooltip: vscode.MarkdownString | string | undefined | ((token: vscode.CancellationToken) => Promise)) { + if (this._extension) { + checkProposedApiEnabled(this._extension, 'statusBarItemTooltip'); + } + + this._tooltip2 = tooltip; + this.update(); + } + public set color(color: string | ThemeColor | undefined) { this._color = color; this.update(); @@ -258,10 +282,18 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem { color = ExtHostStatusBarEntry.ALLOWED_BACKGROUND_COLORS.get(this._backgroundColor.id); } - const tooltip = MarkdownString.fromStrict(this._tooltip); + let tooltip: undefined | string | htmlContent.IMarkdownString; + let hasTooltipProvider: boolean; + if (typeof this._tooltip2 === 'function') { + tooltip = MarkdownString.fromStrict(this._tooltip); + hasTooltipProvider = true; + } else { + tooltip = MarkdownString.fromStrict(this._tooltip2 ?? this._tooltip); + hasTooltipProvider = false; + } // Set to status bar - this.#proxy.$setEntry(this._entryId, id, this._extension?.identifier.value, name, this._text, tooltip, this._command?.internal, color, + this.#proxy.$setEntry(this._entryId, id, this._extension?.identifier.value, name, this._text, tooltip, hasTooltipProvider, this._command?.internal, color, this._backgroundColor, this._alignment === ExtHostStatusBarAlignment.Left, this._priority, this._accessibilityInformation); @@ -320,6 +352,7 @@ export class ExtHostStatusBar implements ExtHostStatusBarShape { private readonly _proxy: MainThreadStatusBarShape; private readonly _commands: CommandsConverter; private readonly _statusMessage: StatusBarMessage; + private readonly _entries = new Map(); private readonly _existingItems = new Map(); constructor(mainContext: IMainContext, commands: CommandsConverter) { @@ -328,16 +361,33 @@ export class ExtHostStatusBar implements ExtHostStatusBarShape { this._statusMessage = new StatusBarMessage(this); } - $acceptStaticEntries(added: StatusBarItemDto[]): void { + $acceptStaticEntries(added: StatusBarItemDto[], removed?: string): void { for (const item of added) { this._existingItems.set(item.entryId, item); } + + if (removed) { + this._entries.delete(removed); + } + } + + async $provideTooltip(entryId: string, cancellation: vscode.CancellationToken): Promise { + const entry = this._entries.get(entryId); + if (!entry) { + return undefined; + } + + const tooltip = typeof entry.tooltip2 === 'function' ? await entry.tooltip2(cancellation) : entry.tooltip2; + return !cancellation.isCancellationRequested ? MarkdownString.fromStrict(tooltip) : undefined; } createStatusBarEntry(extension: IExtensionDescription | undefined, id: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem; createStatusBarEntry(extension: IExtensionDescription, id?: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem; createStatusBarEntry(extension: IExtensionDescription, id: string, alignment?: ExtHostStatusBarAlignment, priority?: number): vscode.StatusBarItem { - return new ExtHostStatusBarEntry(this._proxy, this._commands, this._existingItems, extension, id, alignment, priority); + const entry = new ExtHostStatusBarEntry(this._proxy, this._commands, this._existingItems, extension, id, alignment, priority); + this._entries.set(entry.entryId, entry); + + return entry; } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { diff --git a/src/vscode-dts/vscode.proposed.statusBarItemTooltip.d.ts b/src/vscode-dts/vscode.proposed.statusBarItemTooltip.d.ts new file mode 100644 index 0000000000000..5db0495b5a73b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.statusBarItemTooltip.d.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/234339 + + export interface StatusBarItem { + + /** + * The tooltip text when you hover over this entry. + * + * Can optionally return the tooltip in a thenable if the computation is expensive. + */ + tooltip2: string | MarkdownString | undefined | ((token: CancellationToken) => ProviderResult); + } +}