From 55693172e2f68b0fe66a209bafa613692fa24951 Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Sat, 22 Nov 2025 21:40:27 +0000 Subject: [PATCH] feat(vscode): add quick actions to status bar tooltip (#15962) > This PR enhances the VSCode extension's status bar functionality by adding quick action commands to the status bar tooltip. grafik grafik --- editors/vscode/client/StatusBarItemHandler.ts | 50 +++++++++++++++ editors/vscode/client/extension.ts | 10 ++- editors/vscode/client/linter.ts | 64 +++++++++---------- 3 files changed, 88 insertions(+), 36 deletions(-) create mode 100644 editors/vscode/client/StatusBarItemHandler.ts diff --git a/editors/vscode/client/StatusBarItemHandler.ts b/editors/vscode/client/StatusBarItemHandler.ts new file mode 100644 index 0000000000000..a78ff9088a3dd --- /dev/null +++ b/editors/vscode/client/StatusBarItemHandler.ts @@ -0,0 +1,50 @@ +import { MarkdownString, StatusBarAlignment, StatusBarItem, ThemeColor, window } from 'vscode'; + +type StatusBarTool = 'linter' | 'formatter'; + +export default class StatusBarItemHandler { + private tooltipSections: Map = new Map(); + + private statusBarItem: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 100); + + private extensionVersion: string = ''; + + constructor(extensionVersion?: string) { + if (extensionVersion) { + this.extensionVersion = extensionVersion; + } + } + + public show(): void { + this.statusBarItem.show(); + } + + public setColorAndIcon(bgColor: string, icon: string): void { + this.statusBarItem.backgroundColor = new ThemeColor(bgColor); + this.statusBarItem.text = `$(${icon}) oxc`; + } + + /** + * Updates the tooltip text for a specific tool section. + * The tooltip can use markdown syntax and VSCode icons. + */ + public updateToolTooltip(toolId: StatusBarTool, text: string): void { + this.tooltipSections.set(toolId, text); + this.updateFullTooltip(); + } + + private updateFullTooltip(): void { + const text = Array.from(this.tooltipSections.values()).join('\n\n'); + + if (!(this.statusBarItem.tooltip instanceof MarkdownString)) { + this.statusBarItem.tooltip = new MarkdownString('', true); + this.statusBarItem.tooltip.isTrusted = true; + } + + this.statusBarItem.tooltip.value = `VSCode Extension v${this.extensionVersion}\n\n---\n\n${text}`; + } + + public dispose(): void { + this.statusBarItem.dispose(); + } +} diff --git a/editors/vscode/client/extension.ts b/editors/vscode/client/extension.ts index 3158df3053fd8..3b42ba2b3e7c0 100644 --- a/editors/vscode/client/extension.ts +++ b/editors/vscode/client/extension.ts @@ -9,6 +9,7 @@ import { restartClient, toggleClient, } from './linter'; +import StatusBarItemHandler from './StatusBarItemHandler'; const outputChannelName = 'Oxc'; @@ -42,6 +43,8 @@ export async function activate(context: ExtensionContext) { } }); + const statusBarItemHandler = new StatusBarItemHandler(context.extension.packageJSON?.version); + context.subscriptions.push( restartCommand, showOutputCommand, @@ -49,13 +52,16 @@ export async function activate(context: ExtensionContext) { configService, outputChannel, onDidChangeWorkspaceFoldersDispose, + statusBarItemHandler, ); configService.onConfigChange = async function onConfigChange(event) { - await onConfigChangeLinter(context, event, configService); + await onConfigChangeLinter(event, configService, statusBarItemHandler); }; - await activateLinter(context, outputChannel, configService); + await activateLinter(context, outputChannel, configService, statusBarItemHandler); + // Show status bar item after activation + statusBarItemHandler.show(); } export async function deactivate(): Promise { diff --git a/editors/vscode/client/linter.ts b/editors/vscode/client/linter.ts index c804aa66e4dd1..57b22f85781d1 100644 --- a/editors/vscode/client/linter.ts +++ b/editors/vscode/client/linter.ts @@ -1,27 +1,17 @@ import { promises as fsPromises } from 'node:fs'; -import { - commands, - ConfigurationChangeEvent, - ExtensionContext, - LogOutputChannel, - StatusBarAlignment, - StatusBarItem, - ThemeColor, - Uri, - window, - workspace, -} from 'vscode'; +import { commands, ConfigurationChangeEvent, ExtensionContext, LogOutputChannel, Uri, window, workspace } from 'vscode'; import { ConfigurationParams, ExecuteCommandRequest, ShowMessageNotification } from 'vscode-languageclient'; import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; import { join } from 'node:path'; -import { ConfigService } from './ConfigService'; -import { VSCodeConfig } from './VSCodeConfig'; import { OxcCommands } from './commands'; +import { ConfigService } from './ConfigService'; import { onClientNotification, runExecutable } from './lsp_helper'; +import StatusBarItemHandler from './StatusBarItemHandler'; +import { VSCodeConfig } from './VSCodeConfig'; const languageClientName = 'oxc'; @@ -31,8 +21,6 @@ const enum LspCommands { let client: LanguageClient | undefined; -let oxcStatusBarItem: StatusBarItem; - // Global flag to check if the user allows us to start the server. // When `oxc.requireConfig` is `true`, make sure one `.oxlintrc.json` file is present. let allowedToStartServer: boolean; @@ -41,6 +29,7 @@ export async function activate( context: ExtensionContext, outputChannel: LogOutputChannel, configService: ConfigService, + statusBarItemHandler: StatusBarItemHandler, ) { allowedToStartServer = configService.vsCodeConfig.requireConfig ? (await workspace.findFiles(`**/.oxlintrc.json`, '**/node_modules/**', 1)).length > 0 @@ -157,13 +146,13 @@ export async function activate( context.subscriptions.push(onDeleteFilesDispose); - updateStatusBar(context, configService.vsCodeConfig.enable); + updateStatusBar(statusBarItemHandler, configService.vsCodeConfig.enable); if (allowedToStartServer) { if (configService.vsCodeConfig.enable) { await client.start(); } } else { - generateActivatorByConfig(configService.vsCodeConfig, context); + generateActivatorByConfig(configService.vsCodeConfig, context, statusBarItemHandler); } } @@ -192,26 +181,33 @@ function getStatusBarState(enable: boolean): { bgColor: string; icon: string; to } } -function updateStatusBar(context: ExtensionContext, enable: boolean) { - if (!oxcStatusBarItem) { - oxcStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 100); - oxcStatusBarItem.command = OxcCommands.ToggleEnable; - context.subscriptions.push(oxcStatusBarItem); - oxcStatusBarItem.show(); - } - +function updateStatusBar(statusBarItemHandler: StatusBarItemHandler, enable: boolean) { const { bgColor, icon, tooltipText } = getStatusBarState(enable); - oxcStatusBarItem.text = `$(${icon}) oxc`; - oxcStatusBarItem.backgroundColor = new ThemeColor(bgColor); - oxcStatusBarItem.tooltip = tooltipText; + let text = + `**${tooltipText}**\n\n` + + `[$(terminal) Open Output](command:${OxcCommands.ShowOutputChannel})\n\n` + + `[$(refresh) Restart Server](command:${OxcCommands.RestartServer})\n\n`; + + if (enable) { + text += `[$(stop) Stop Server](command:${OxcCommands.ToggleEnable})\n\n`; + } else { + text += `[$(play) Start Server](command:${OxcCommands.ToggleEnable})\n\n`; + } + + statusBarItemHandler.setColorAndIcon(bgColor, icon); + statusBarItemHandler.updateToolTooltip('linter', text); } -function generateActivatorByConfig(config: VSCodeConfig, context: ExtensionContext): void { +function generateActivatorByConfig( + config: VSCodeConfig, + context: ExtensionContext, + statusBarItemHandler: StatusBarItemHandler, +): void { const watcher = workspace.createFileSystemWatcher('**/.oxlintrc.json', false, true, !config.requireConfig); watcher.onDidCreate(async () => { allowedToStartServer = true; - updateStatusBar(context, config.enable); + updateStatusBar(statusBarItemHandler, config.enable); if (client && !client.isRunning() && config.enable) { await client.start(); } @@ -221,7 +217,7 @@ function generateActivatorByConfig(config: VSCodeConfig, context: ExtensionConte // only can be called when config.requireConfig allowedToStartServer = (await workspace.findFiles(`**/.oxlintrc.json`, '**/node_modules/**', 1)).length > 0; if (!allowedToStartServer) { - updateStatusBar(context, false); + updateStatusBar(statusBarItemHandler, false); if (client && client.isRunning()) { await client.stop(); } @@ -266,11 +262,11 @@ export async function toggleClient(configService: ConfigService): Promise } export async function onConfigChange( - context: ExtensionContext, event: ConfigurationChangeEvent, configService: ConfigService, + statusBarItemHandler: StatusBarItemHandler, ): Promise { - updateStatusBar(context, configService.vsCodeConfig.enable); + updateStatusBar(statusBarItemHandler, configService.vsCodeConfig.enable); if (client === undefined) { return;