LSP Client 端搭建
客户端(Client)运行在编辑器插件中,负责启动 Server 进程、转发文档事件、展示语言能力结果。本章搭建完整的 Client 端。
安装依赖
package.json:
json
{
"dependencies": {
"vscode-languageclient": "^9.0.0"
},
"devDependencies": {
"@types/vscode": "^1.85.0",
"typescript": "^5.0.0"
},
"engines": {
"vscode": "^1.85.0"
}
}LanguageClient 构造
创建 Client 实例:
typescript
import * as vscode from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions
} from 'vscode-languageclient/node';
let client: LanguageClient;
export function activate(context: vscode.ExtensionContext) {
// Server 启动配置
const serverOptions: ServerOptions = {
run: {
module: context.asAbsolutePath('server/out/server.js'),
transport: TransportKind.ipc
},
debug: {
module: context.asAbsolutePath('server/out/server.js'),
transport: TransportKind.ipc,
options: {
execArgv: ['--inspect=6009'] // 调试端口
}
}
};
// 客户端选项
const clientOptions: LanguageClientOptions = {
documentSelector: [
{ scheme: 'file', language: 'plaintext' }
],
synchronize: {
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.txt')
}
};
// 创建 Client
client = new LanguageClient(
'myLsp', // 唯一 ID
'My Language Server', // 显示名称
serverOptions,
clientOptions
);
// 启动
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}ServerOptions 配置
run / debug 双模式
typescript
const serverOptions: ServerOptions = {
// 生产模式
run: {
module: path.join(__dirname, 'server.js'),
transport: TransportKind.ipc
},
// 调试模式(F5 附加调试器)
debug: {
module: path.join(__dirname, 'server.js'),
transport: TransportKind.ipc,
options: { execArgv: ['--inspect=6009'] }
}
};调试模式选择
typescript
// 调试时使用 debug 配置
const serverOptions: ServerOptions = {
run: { module, transport },
debug: { module, transport, options: { execArgv: ['--inspect=6009'] } }
};
// 开发时强制调试模式
if (process.env.VSCODE_DEBUG === 'true') {
serverOptions.run = serverOptions.debug;
}serverOptions.module 与 transport
module 指定
| 形式 | 说明 |
|---|---|
| 文件路径 | 直接执行 JS 文件 |
| 函数 | 自定义启动逻辑 |
typescript
// 函数形式:自定义启动
const serverOptions: ServerOptions = () => {
const childProcess = require('child_process');
return childProcess.spawn('node', ['server.js'], {
cwd: __dirname
});
};transport 传输
| 传输 | 场景 |
|---|---|
TransportKind.ipc | Node IPC(默认推荐) |
TransportKind.stdio | 标准 IO |
TransportKind.pipe | 命名管道 |
LanguageClientOptions 详解
documentSelector
声明 Server 服务的文档类型:
typescript
const clientOptions: LanguageClientOptions = {
documentSelector: [
// 指定语言
{ scheme: 'file', language: 'plaintext' },
// 或文件模式
{ scheme: 'file', pattern: '**/*.dsl' }
]
};synchronize 同步
typescript
synchronize: {
// 文件系统事件通知 Server
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.dsl'),
// 配置区段(变更时通知 Server)
configurationSection: 'myLsp'
}initializationOptions
初始化时传给 Server 的配置:
typescript
initializationOptions: {
maxDiagnostics: 50,
enableHints: true
}客户端生命周期管理
启动与停止
typescript
export function activate(context: vscode.ExtensionContext) {
// 启动
client.start();
context.subscriptions.push(client);
// 命令:重启 Server
context.subscriptions.push(
vscode.commands.registerCommand('myLsp.restart', async () => {
await client.stop();
client.start();
})
);
}
export function deactivate(): Thenable<void> | undefined {
// 插件停用时停止 Client
return client?.stop();
}状态事件
typescript
// 状态变化监听
client.onDidChangeState((event) => {
switch (event.newState) {
case State.Running:
console.log('Server 运行中');
break;
case State.Starting:
console.log('Server 启动中');
break;
case State.Stopped:
console.log('Server 已停止');
break;
}
});输出日志
typescript
// 输出到 Output 面板
client.outputChannel.show();
client.outputChannel.appendLine('Client 日志');完整示例
src/extension.ts:
typescript
import * as vscode from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient/node';
let client: LanguageClient;
export function activate(context: vscode.ExtensionContext) {
// Server 配置
const serverModule = context.asAbsolutePath(
'server/out/server.js'
);
const serverOptions: ServerOptions = {
run: {
module: serverModule,
transport: TransportKind.ipc
},
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: { execArgv: ['--inspect=6009'] }
}
};
// 客户端选项
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'plaintext' }],
synchronize: {
configurationSection: 'myLsp',
fileEvents: vscode.workspace.createFileSystemWatcher('**/*')
},
initializationOptions: {
maxDiagnostics: 100
},
outputChannelName: 'My Language Server'
};
// 创建并启动
client = new LanguageClient(
'myLsp',
'My Language Server',
serverOptions,
clientOptions
);
client.start();
context.subscriptions.push(client);
// 状态栏显示 Server 状态
const statusBar = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100
);
statusBar.text = '$(server) LSP';
statusBar.show();
client.onDidChangeState((event) => {
if (event.newState === State.Running) {
statusBar.text = '$(check) LSP 运行中';
} else if (event.newState === State.Stopped) {
statusBar.text = '$(error) LSP 已停止';
}
});
context.subscriptions.push(statusBar);
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
}调试配置
launch.json:
json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/client/out/**/*.js"],
"preLaunchTask": "npm: watch"
},
{
"name": "Attach to Server",
"type": "node",
"request": "attach",
"port": 6009,
"restart": true
}
]
}常见问题
| 问题 | 处理 |
|---|---|
| Server 不启动 | 检查 module 路径与编译产物 |
| 无语言能力 | 确认 documentSelector 匹配 |
| 连接失败 | 检查 transport 一致 |
| 崩溃循环 | 检查 Server 错误日志 |
Client 端搭建完成,Server 与 Client 已连通。下一章实现具体的语言特性(诊断、补全等)。