Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StatusBarItem - proposed API for async hover #238297

Merged
merged 14 commits into from
Jan 21, 2025
3 changes: 3 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
Expand Down
27 changes: 19 additions & 8 deletions src/vs/workbench/api/browser/mainThreadStatusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -12,30 +12,32 @@ 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[] = [];
for (const [entryId, item] of statusbarService.getEntries()) {
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 {
Expand All @@ -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)));
}
Expand Down
10 changes: 6 additions & 4 deletions src/vs/workbench/api/browser/statusBarExtensionPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -49,7 +50,7 @@ export interface IExtensionStatusBarItemService {

onDidChange: Event<IExtensionStatusBarItemChangeEvent>;

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;

Expand All @@ -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 {
Expand All @@ -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}`;
}
Expand Down
5 changes: 3 additions & 2 deletions src/vs/workbench/api/common/extHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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<string | IMarkdownString | undefined>;
}

export interface MainThreadStorageShape extends IDisposable {
Expand Down
58 changes: 54 additions & 4 deletions src/vs/workbench/api/common/extHostStatusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<string | vscode.MarkdownString | undefined>);
private _name?: string;
private _color?: string | ThemeColor;
private _backgroundColor?: ThemeColor;
Expand Down Expand Up @@ -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;
}
Expand All @@ -133,6 +140,14 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
return this._tooltip;
}

public get tooltip2(): vscode.MarkdownString | string | undefined | ((token: vscode.CancellationToken) => Promise<vscode.MarkdownString | string | undefined>) {
if (this._extension) {
checkProposedApiEnabled(this._extension, 'statusBarItemTooltip');
}

return this._tooltip2;
lszomoru marked this conversation as resolved.
Show resolved Hide resolved
}

public get color(): string | ThemeColor | undefined {
return this._color;
}
Expand Down Expand Up @@ -164,6 +179,15 @@ export class ExtHostStatusBarEntry implements vscode.StatusBarItem {
this.update();
}

lszomoru marked this conversation as resolved.
Show resolved Hide resolved
public set tooltip2(tooltip: vscode.MarkdownString | string | undefined | ((token: vscode.CancellationToken) => Promise<vscode.MarkdownString | string | undefined>)) {
if (this._extension) {
checkProposedApiEnabled(this._extension, 'statusBarItemTooltip');
}

this._tooltip2 = tooltip;
lszomoru marked this conversation as resolved.
Show resolved Hide resolved
this.update();
}

public set color(color: string | ThemeColor | undefined) {
this._color = color;
this.update();
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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<string, ExtHostStatusBarEntry>();
private readonly _existingItems = new Map<string, StatusBarItemDto>();

constructor(mainContext: IMainContext, commands: CommandsConverter) {
Expand All @@ -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<string | htmlContent.IMarkdownString | undefined> {
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);
lszomoru marked this conversation as resolved.
Show resolved Hide resolved

return entry;
}

setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable<any>): Disposable {
Expand Down
19 changes: 19 additions & 0 deletions src/vscode-dts/vscode.proposed.statusBarItemTooltip.d.ts
Original file line number Diff line number Diff line change
@@ -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<string | MarkdownString | undefined>);
}
}
Loading