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.
---
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;