LSP Server 端搭建
语言服务器(Server)负责语言分析。本章从零搭建一个可运行的 LSP Server:创建连接、管理文档、声明能力。
项目结构
server/
├── package.json
├── tsconfig.json
└── src/
└── server.ts # Server 入口server/package.json:
json
{
"name": "my-lsp-server",
"version": "0.1.0",
"main": "./out/server.js",
"dependencies": {
"vscode-languageserver": "^9.0.0",
"vscode-languageserver-textdocument": "^1.0.0"
},
"scripts": {
"compile": "tsc -p ."
}
}createConnection 创建连接
src/server.ts:
typescript
import {
createConnection,
TextDocuments,
ProposedFeatures,
InitializeParams,
InitializeResult,
TextDocumentSyncKind
} from 'vscode-languageserver/node';
import {
TextDocument
} from 'vscode-languageserver-textdocument';
// 创建连接(stdio 通信)
const connection = createConnection(ProposedFeatures.all);
// 文档管理器
const documents: TextDocuments<TextDocument> =
new TextDocuments(TextDocument);
// 文档打开/变更/保存监听
documents.onDidOpen((event) => {
console.log(`文档打开: ${event.document.uri}`);
});
documents.onDidChangeContent((event) => {
console.log(`文档变更: ${event.document.uri}`);
});
documents.onDidSave((event) => {
console.log(`文档保存: ${event.document.uri}`);
});
documents.onDidClose((event) => {
console.log(`文档关闭: ${event.document.uri}`);
});
// 文档变化时同步
documents.listen(connection);
// 启动连接
connection.listen();onInitialize 初始化
初始化时接收客户端信息并声明能力:
typescript
connection.onInitialize((params: InitializeParams): InitializeResult => {
// 客户端信息
console.log(`客户端: ${params.clientInfo?.name} ${params.clientInfo?.version}`);
// 工作区信息
console.log(`根目录: ${params.rootUri}`);
// 能力声明
return {
capabilities: {
// 文档同步方式
textDocumentSync: {
openClose: true, // 打开/关闭通知
change: TextDocumentSyncKind.Incremental // 增量同步
},
// 支持的特性
completionProvider: {
triggerCharacters: ['.', ':'],
resolveProvider: false
},
hoverProvider: true,
definitionProvider: true
},
serverInfo: {
name: 'my-lsp-server',
version: '0.1.0'
}
};
});ServerCapabilities 配置
文档同步
typescript
textDocumentSync: {
openClose: true,
change: TextDocumentSyncKind.Full // 全量同步
// 或 TextDocumentSyncKind.Incremental 增量
}| 同步模式 | 说明 |
|---|---|
None | 不同步 |
Full | 每次全量发送 |
Incremental | 只发送变更部分(推荐) |
能力开关表
typescript
capabilities: {
// 编辑能力
completionProvider: { triggerCharacters: ['.'] },
formattingProvider: true,
documentFormattingProvider: true,
codeActionProvider: true,
// 导航能力
definitionProvider: true,
referencesProvider: true,
documentSymbolProvider: true,
workspaceSymbolProvider: true,
// 信息能力
hoverProvider: true,
signatureHelpProvider: { triggerCharacters: ['('] },
// 高级能力
documentHighlightProvider: true,
foldingRangeProvider: true,
semanticTokensProvider: {
legend: { tokenTypes: ['keyword'], tokenModifiers: [] },
full: true
}
}connection.onDidChangeConfiguration 配置监听
监听客户端配置变化:
typescript
import {
DidChangeConfigurationParams
} from 'vscode-languageserver';
// 全局配置缓存
let config = {
maxDiagnostics: 100,
enableHints: true
};
// 初始化时读取配置
connection.onInitialize((params) => {
// 读取配置(客户端随初始化发送)
const settings = params.initializationOptions as any;
if (settings) {
config = { ...config, ...settings };
}
return { capabilities };
});
// 配置变更监听
connection.onDidChangeConfiguration((params) => {
const settings = params.settings;
if (settings && settings.languageServerExample) {
config = { ...config, ...settings.languageServerExample };
}
console.log('配置已更新');
});完整 Server 骨架
typescript
import {
createConnection,
TextDocuments,
ProposedFeatures,
InitializeParams,
InitializeResult,
TextDocumentSyncKind
} from 'vscode-languageserver/node';
import {
TextDocument
} from 'vscode-languageserver-textdocument';
// 1. 创建连接
const connection = createConnection(ProposedFeatures.all);
// 2. 文档管理器
const documents: TextDocuments<TextDocument> =
new TextDocuments(TextDocument);
// 3. 初始化
connection.onInitialize((params: InitializeParams): InitializeResult => {
return {
capabilities: {
textDocumentSync: {
openClose: true,
change: TextDocumentSyncKind.Incremental
},
completionProvider: { triggerCharacters: ['.'] },
hoverProvider: true,
definitionProvider: true
},
serverInfo: {
name: 'my-lsp-server',
version: '0.1.0'
}
};
});
// 4. 文档监听
documents.onDidChangeContent((event) => {
// 文档变化后触发诊断等
const doc = event.document;
console.log(`内容长度: ${doc.getText().length}`);
});
// 5. 生命周期
connection.onInitialized(() => {
console.log('Server 初始化完成');
});
connection.onExit(() => {
console.log('Server 退出');
});
// 6. 启动
documents.listen(connection);
connection.listen();Server 调试
调试模式
launch.json:
json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to Server",
"port": 6009,
"restart": true
}
]
}日志输出
typescript
// 输出到 Server 日志
console.log('Server 日志');
// 输出到 Output 面板
connection.console.log('连接日志');常见问题
| 问题 | 处理 |
|---|---|
| 连接不建立 | 检查 transport 与进程启动 |
| 文档不同步 | 确认 documents.listen 调用 |
| 能力不生效 | 检查 capabilities 声明 |
| 配置读不到 | 检查初始化与变更监听 |
Server 骨架已就绪,下一章搭建 Client 端实现进程管理与通信。