基于vscode-languageserver的LSP实现:客户端向服务器发自定义请求方法
在vscode-languageserver中实现客户端到服务器的自定义请求/命令
我来帮你一步步搞定在vscode-languageserver里实现客户端到服务器的自定义请求,其实流程很清晰,分服务器端注册处理逻辑和客户端发送请求两部分,还能兼顾类型安全,下面是具体步骤:
第一步:定义共享类型(可选但推荐)
为了避免客户端和服务器端参数/返回值类型不匹配,建议把自定义请求的方法名、参数结构、返回结果结构抽成一个共享的TypeScript文件,两端都能引用:
// shared.ts // 自定义请求的唯一方法名,确保两端完全一致 export const CustomCommandMethod = 'myExtension/doCustomAction'; // 客户端传给服务器的参数结构 export interface CustomCommandParams { targetFile: string; operation: string; extraData?: Record<string, any>; } // 服务器返回给客户端的结果结构 export interface CustomCommandResult { success: boolean; detail?: string; data?: any; }
第二步:服务器端注册自定义请求处理器
在你的语言服务器代码里,用connection.onRequest方法注册对应方法名的处理逻辑,收到客户端请求后就会执行这里的代码:
// server.ts import { createConnection, TextDocuments } from 'vscode-languageserver/node'; import { CustomCommandMethod, CustomCommandParams, CustomCommandResult } from './shared'; // 创建LSP连接 const connection = createConnection(); const documents = new TextDocuments(); // 注册自定义请求的处理函数 connection.onRequest( CustomCommandMethod, async (params: CustomCommandParams): Promise<CustomCommandResult> => { try { // 这里写你的自定义业务逻辑,比如解析文件、执行操作等 connection.console.log(`收到自定义请求:处理文件 ${params.targetFile},执行操作 ${params.operation}`); // 模拟处理结果 return { success: true, detail: `已完成 ${params.operation} 操作`, data: { processedFile: params.targetFile } }; } catch (error) { // 处理错误并返回失败结果 return { success: false, detail: error instanceof Error ? error.message : '处理请求时发生未知错误' }; } } ); // 启动文档监听和服务器 documents.listen(connection); connection.listen();
第三步:客户端(VS Code插件)发送自定义请求
在你的VS Code插件代码里,获取语言客户端实例,用sendRequest方法发送请求,还能等待服务器返回的结果:
// extension.ts import { ExtensionContext, window, commands } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; import { CustomCommandMethod, CustomCommandParams, CustomCommandResult } from './shared'; let client: LanguageClient; export function activate(context: ExtensionContext) { // 配置服务器启动选项(根据你的服务器类型调整,这里是Node.js服务器) const serverOptions: ServerOptions = { run: { module: context.asAbsolutePath('./out/server.js') }, debug: { module: context.asAbsolutePath('./out/server.js'), options: { execArgv: ['--inspect=6009'] } } }; // 配置客户端选项 const clientOptions: LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'your-language' }], // 替换成你的语言标识符 synchronize: { fileEvents: window.createFileSystemWatcher('**/*.your-ext') // 替换成你的文件扩展名 } }; // 创建并启动语言客户端 client = new LanguageClient( 'yourLanguageServer', '你的语言服务器', serverOptions, clientOptions ); client.start(); // 示例:注册一个VS Code命令,触发时发送自定义请求 context.subscriptions.push( commands.registerCommand('myExtension.triggerCustomCommand', async () => { const activeEditor = window.activeTextEditor; if (!activeEditor) { window.showErrorMessage('请先打开一个文件'); return; } // 构造请求参数 const params: CustomCommandParams = { targetFile: activeEditor.document.uri.fsPath, operation: 'analyze', extraData: { timestamp: Date.now() } }; try { // 发送请求并等待结果 const result = await client.sendRequest<CustomCommandResult>(CustomCommandMethod, params); if (result.success) { window.showInformationMessage(result.detail || '操作成功'); } else { window.showErrorMessage(result.detail || '操作失败'); } } catch (error) { window.showErrorMessage(`发送请求失败:${error instanceof Error ? error.message : '未知错误'}`); } }) ); } export function deactivate(): Thenable<void> | undefined { return client?.stop(); }
额外提示
- 如果你的自定义操作不需要返回结果(只需要通知服务器执行),可以用通知代替请求:服务器端用
connection.onNotification注册处理器,客户端用client.sendNotification发送,用法和请求类似,只是没有返回值。 - 一定要保证自定义方法名在客户端和服务器端完全一致,否则服务器会找不到对应的处理器,请求会失败。
- 共享类型文件能帮你在开发阶段就发现类型不匹配的问题,大幅减少调试成本,非常推荐使用。
内容的提问来源于stack exchange,提问作者Alexey Nikiforov
相关产品推荐
相关产品推荐

