From 5b087830dbbe3bfe9a19eada9f8d41495ef95c24 Mon Sep 17 00:00:00 2001 From: Sysix Date: Sun, 19 Jan 2025 18:43:38 +0100 Subject: [PATCH] refactor(vscode): move commands and findBinary to own files --- editors/vscode/client/ConfigService.ts | 2 +- editors/vscode/client/commands.ts | 112 +++++++++++++++ editors/vscode/client/extension.ts | 182 +++---------------------- editors/vscode/client/findBinary.ts | 44 ++++++ editors/vscode/package.json | 2 +- 5 files changed, 178 insertions(+), 164 deletions(-) create mode 100644 editors/vscode/client/commands.ts create mode 100644 editors/vscode/client/findBinary.ts diff --git a/editors/vscode/client/ConfigService.ts b/editors/vscode/client/ConfigService.ts index 7d84f09708085..f59d5fda22a1a 100644 --- a/editors/vscode/client/ConfigService.ts +++ b/editors/vscode/client/ConfigService.ts @@ -6,7 +6,7 @@ export class ConfigService implements IDisposable { private static readonly _namespace = 'oxc'; private readonly _disposables: IDisposable[] = []; - public config: Config; + public readonly config: Config; public onConfigChange: | ((this: ConfigService, config: ConfigurationChangeEvent) => void) diff --git a/editors/vscode/client/commands.ts b/editors/vscode/client/commands.ts new file mode 100644 index 0000000000000..3769f4200420b --- /dev/null +++ b/editors/vscode/client/commands.ts @@ -0,0 +1,112 @@ +import { CodeAction, Command, commands, Disposable, window, workspace } from 'vscode'; + +import { CodeActionRequest, CodeActionTriggerKind, LanguageClient, Position, Range } from 'vscode-languageclient/node'; +import { Config } from './Config'; + +const commandPrefix = 'oxc'; + +export const enum OxcCommands { + RestartServer = `${commandPrefix}.restartServer`, + ApplyAllFixesFile = `${commandPrefix}.applyAllFixesFile`, + ShowOutputChannel = `${commandPrefix}.showOutputChannel`, + ToggleEnable = `${commandPrefix}.toggleEnable`, +} + +export const restartServerCommand = (client: LanguageClient): Disposable => { + return commands.registerCommand( + OxcCommands.RestartServer, + async () => { + if (!client) { + window.showErrorMessage('oxc client not found'); + return; + } + + try { + if (client.isRunning()) { + await client.restart(); + + window.showInformationMessage('oxc server restarted.'); + } else { + await client.start(); + } + } catch (err) { + client.error('Restarting client failed', err, 'force'); + } + }, + ); +}; + +export const showOutputChannelCommand = (client: LanguageClient): Disposable => { + return commands.registerCommand( + OxcCommands.ShowOutputChannel, + () => { + client.outputChannel.show(); + }, + ); +}; + +export const toggleEnabledCommand = (config: Config): Disposable => { + return commands.registerCommand( + OxcCommands.ToggleEnable, + () => { + config.updateEnable(!config.enable); + }, + ); +}; + +export const applyAllFixesFileCommand = (client: LanguageClient): Disposable => { + return commands.registerCommand( + OxcCommands.ApplyAllFixesFile, + async () => { + if (!client) { + window.showErrorMessage('oxc client not found'); + return; + } + const textEditor = window.activeTextEditor; + if (!textEditor) { + window.showErrorMessage('active text editor not found'); + return; + } + + const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1); + const codeActionResult = await client.sendRequest(CodeActionRequest.type, { + textDocument: { + uri: textEditor.document.uri.toString(), + }, + range: Range.create(Position.create(0, 0), lastLine.range.end), + context: { + diagnostics: [], + only: [], + triggerKind: CodeActionTriggerKind.Invoked, + }, + }); + const commandsOrCodeActions = await client.protocol2CodeConverter.asCodeActionResult(codeActionResult || []); + + await Promise.all( + commandsOrCodeActions + .map(async (codeActionOrCommand) => { + // Commands are always applied. Regardless of whether it's a Command or CodeAction#command. + if (isCommand(codeActionOrCommand)) { + await commands.executeCommand(codeActionOrCommand.command, codeActionOrCommand.arguments); + } else { + // Only preferred edits are applied + // LSP states edits must be run first, then commands + if (codeActionOrCommand.edit && codeActionOrCommand.isPreferred) { + await workspace.applyEdit(codeActionOrCommand.edit); + } + if (codeActionOrCommand.command) { + await commands.executeCommand( + codeActionOrCommand.command.command, + codeActionOrCommand.command.arguments, + ); + } + } + }), + ); + + function isCommand(codeActionOrCommand: CodeAction | Command): codeActionOrCommand is Command { + return typeof codeActionOrCommand.command === 'string'; + } + }, + ); +}; diff --git a/editors/vscode/client/extension.ts b/editors/vscode/client/extension.ts index 896f2107b5e2d..d1f93f59ddfd8 100644 --- a/editors/vscode/client/extension.ts +++ b/editors/vscode/client/extension.ts @@ -1,41 +1,26 @@ -import { promises as fsPromises } from 'node:fs'; +import { ExtensionContext, StatusBarAlignment, StatusBarItem, ThemeColor, window, workspace } from 'vscode'; import { - CodeAction, - Command, - commands, - ExtensionContext, - StatusBarAlignment, - StatusBarItem, - ThemeColor, - window, - workspace, -} from 'vscode'; - -import { - CodeActionRequest, - CodeActionTriggerKind, + Executable, + LanguageClient, + LanguageClientOptions, MessageType, - Position, - Range, + ServerOptions, ShowMessageNotification, -} from 'vscode-languageclient'; - -import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; +} from 'vscode-languageclient/node'; -import { join } from 'node:path'; +import { + applyAllFixesFileCommand, + OxcCommands, + restartServerCommand, + showOutputChannelCommand, + toggleEnabledCommand, +} from './commands'; import { ConfigService } from './ConfigService'; +import findBinary from './findBinary'; const languageClientName = 'oxc'; const outputChannelName = 'Oxc'; -const commandPrefix = 'oxc'; - -const enum OxcCommands { - RestartServer = `${commandPrefix}.restartServer`, - ApplyAllFixesFile = `${commandPrefix}.applyAllFixesFile`, - ShowOutputChannel = `${commandPrefix}.showOutputChannel`, - ToggleEnable = `${commandPrefix}.toggleEnable`, -} let client: LanguageClient; @@ -43,146 +28,18 @@ let myStatusBarItem: StatusBarItem; export async function activate(context: ExtensionContext) { const configService = new ConfigService(); - const restartCommand = commands.registerCommand( - OxcCommands.RestartServer, - async () => { - if (!client) { - window.showErrorMessage('oxc client not found'); - return; - } - - try { - if (client.isRunning()) { - await client.restart(); - - window.showInformationMessage('oxc server restarted.'); - } else { - await client.start(); - } - } catch (err) { - client.error('Restarting client failed', err, 'force'); - } - }, - ); - - const showOutputCommand = commands.registerCommand( - OxcCommands.ShowOutputChannel, - () => { - client?.outputChannel?.show(); - }, - ); - - const toggleEnable = commands.registerCommand( - OxcCommands.ToggleEnable, - () => { - configService.config.updateEnable(!configService.config.enable); - }, - ); - - const applyAllFixesFile = commands.registerCommand( - OxcCommands.ApplyAllFixesFile, - async () => { - if (!client) { - window.showErrorMessage('oxc client not found'); - return; - } - const textEditor = window.activeTextEditor; - if (!textEditor) { - window.showErrorMessage('active text editor not found'); - return; - } - - const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1); - const codeActionResult = await client.sendRequest(CodeActionRequest.type, { - textDocument: { - uri: textEditor.document.uri.toString(), - }, - range: Range.create(Position.create(0, 0), lastLine.range.end), - context: { - diagnostics: [], - only: [], - triggerKind: CodeActionTriggerKind.Invoked, - }, - }); - const commandsOrCodeActions = await client.protocol2CodeConverter.asCodeActionResult(codeActionResult || []); - - await Promise.all( - commandsOrCodeActions - .map(async (codeActionOrCommand) => { - // Commands are always applied. Regardless of whether it's a Command or CodeAction#command. - if (isCommand(codeActionOrCommand)) { - await commands.executeCommand(codeActionOrCommand.command, codeActionOrCommand.arguments); - } else { - // Only preferred edits are applied - // LSP states edits must be run first, then commands - if (codeActionOrCommand.edit && codeActionOrCommand.isPreferred) { - await workspace.applyEdit(codeActionOrCommand.edit); - } - if (codeActionOrCommand.command) { - await commands.executeCommand( - codeActionOrCommand.command.command, - codeActionOrCommand.command.arguments, - ); - } - } - }), - ); - - function isCommand(codeActionOrCommand: CodeAction | Command): codeActionOrCommand is Command { - return typeof codeActionOrCommand.command === 'string'; - } - }, - ); context.subscriptions.push( - applyAllFixesFile, - restartCommand, - showOutputCommand, - toggleEnable, + applyAllFixesFileCommand(client), + restartServerCommand(client), + showOutputChannelCommand(client), + toggleEnabledCommand(configService.config), configService, ); const outputChannel = window.createOutputChannel(outputChannelName, { log: true }); - async function findBinary(): Promise { - let bin = configService.config.binPath; - if (bin) { - try { - await fsPromises.access(bin); - return bin; - } catch {} - } - - const workspaceFolders = workspace.workspaceFolders; - const isWindows = process.platform === 'win32'; - - if (workspaceFolders?.length && !isWindows) { - try { - return await Promise.any( - workspaceFolders.map(async (folder) => { - const binPath = join( - folder.uri.fsPath, - 'node_modules', - '.bin', - 'oxc_language_server', - ); - - await fsPromises.access(binPath); - return binPath; - }), - ); - } catch {} - } - - const ext = isWindows ? '.exe' : ''; - // NOTE: The `./target/release` path is aligned with the path defined in .github/workflows/release_vscode.yml - return ( - process.env.SERVER_PATH_DEV ?? - join(context.extensionPath, `./target/release/oxc_language_server${ext}`) - ); - } - - const command = await findBinary(); + const command = await findBinary(context, configService.config); const run: Executable = { command: command!, options: { @@ -232,6 +89,7 @@ export async function activate(context: ExtensionContext) { serverOptions, clientOptions, ); + client.onNotification(ShowMessageNotification.type, (params) => { switch (params.type) { case MessageType.Debug: diff --git a/editors/vscode/client/findBinary.ts b/editors/vscode/client/findBinary.ts new file mode 100644 index 0000000000000..8e5d2323f05a6 --- /dev/null +++ b/editors/vscode/client/findBinary.ts @@ -0,0 +1,44 @@ +import { promises as fsPromises } from 'node:fs'; +import { join } from 'node:path'; + +import { ExtensionContext, workspace } from 'vscode'; + +import { Config } from './Config'; + +export default async function findBinary(context: ExtensionContext, config: Config): Promise { + let bin = config.binPath; + if (bin) { + try { + await fsPromises.access(bin); + return bin; + } catch {} + } + + const workspaceFolders = workspace.workspaceFolders; + const isWindows = process.platform === 'win32'; + + if (workspaceFolders?.length && !isWindows) { + try { + return await Promise.any( + workspaceFolders.map(async (folder) => { + const binPath = join( + folder.uri.fsPath, + 'node_modules', + '.bin', + 'oxc_language_server', + ); + + await fsPromises.access(binPath); + return binPath; + }), + ); + } catch {} + } + + const ext = isWindows ? '.exe' : ''; + // NOTE: The `./target/release` path is aligned with the path defined in .github/workflows/release_vscode.yml + return ( + process.env.SERVER_PATH_DEV ?? + join(context.extensionPath, `./target/release/oxc_language_server${ext}`) + ); +} diff --git a/editors/vscode/package.json b/editors/vscode/package.json index ade39d397de92..5ec99ee8c26b0 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -140,7 +140,7 @@ "server:build:debug": "cargo build -p oxc_language_server", "server:build:release": "cross-env CARGO_TARGET_DIR=./target cargo build -p oxc_language_server --release", "lint": "npx oxlint --config=oxlint.json --tsconfig=tsconfig.json", - "test": "esbuild client/config.spec.ts --bundle --outfile=out/config.spec.js --external:vscode --format=cjs --platform=node --target=node16 --minify --sourcemap && vscode-test", + "test": "esbuild \"client/*.spec.ts\" --bundle --outdir=out --external:vscode --format=cjs --platform=node --target=node16 --minify --sourcemap && vscode-test", "type-check": "tsc --noEmit" }, "devDependencies": {