语言服务器协议(LSP,Language Server Protocol)是现代代码编辑器的核心技术之一,它解耦了编辑器与编程语言工具,使得编辑能够为各种编程语言提供智能功能。本文详细介绍 LSP 的架构设计、消息协议、实现方法与最佳实践。
LSP 架构概述
协议架构设计
/* LSP 架构 */
class LanguageServer {
constructor(connection, workspace) {
this.connection = connection;
this.workspace = workspace;
this.documents = new Map();
this.languageClients = new Map();
// 注册请求处理器
this.registerHandlers();
}
registerHandlers() {
// 初始化
this.connection.onInitialize((params) => this.handleInitialize(params));
this.connection.onInitialized((params) => this.handleInitialized(params));
// 文档同步
this.connection.onDidOpenTextDocument((params) => this.handleDocOpen(params));
this.connection.onDidChangeTextDocument((params) => this.handleDocChange(params));
this.connection.onDidCloseTextDocument((params) => this.handleDocClose(params));
// 语义功能
this.connection.onDefinition((params) => this.handleGoToDefinition(params));
this.connection.onReferences((params) => this.handleFindReferences(params));
this.connection.onHover((params) => this.handleHover(params));
this.connection.onCompletion((params) => this.handleCompletion(params));
}
start() {
this.connection.listen();
}
}
LSP 客户端实现
// LSP 客户端
class LSPLanguageClient {
constructor(serverOptions, clientOptions) {
this.serverOptions = serverOptions;
this.clientOptions = clientOptions;
this.connection = null;
this.initializeResult = null;
}
async start() {
// 创建 JSON-RPC 连接
this.connection = createJSONRPCConnection({
send: (message) => this.sendMessage(message),
listen: (callback) => this.setMessageCallback(callback)
});
// 发送初始化请求
this.initializeResult = await this.connection.sendRequest('initialize', {
processId: process.pid,
rootUri: this.clientOptions.rootUri,
capabilities: this.clientOptions.capabilities,
workspaceFolders: this.clientOptions.workspaceFolders
});
// 通知服务器初始化完成
await this.connection.sendNotification('initialized', {});
return this.initializeResult;
}
sendMessage(message) {
// 通过stdio、WebSocket或其他传输层发送
this.transport.write(message);
}
setMessageCallback(callback) {
this.transport.onData((data) => callback(data));
}
}
消息协议详解
JSON-RPC 消息格式
/* JSON-RPC 消息格式 */
// 请求消息
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/completion",
"params": {
"textDocument": { "uri": "file:///src/main.ts" },
"position": { "line": 10, "character": 5 }
}
}
// 响应消息
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"isIncomplete": false,
"items": [
{ "label": "console.log", "kind": 1, "insertText": "console.log($0)" },
{ "label": "console.error", "kind": 1, "insertText": "console.error($0)" }
]
}
}
// 错误响应
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32600,
"message": "Invalid Request",
"data": "The method is not supported"
}
}
方法能力协商
// 客户端能力
const clientCapabilities = {
textDocument: {
synchronization: {
willSave: false,
willSaveWaitUntil: false,
didSave: true,
didOpen: true,
didClose: true
},
completion: {
dynamicRegistration: true,
completionItem: {
snippetSupport: true,
commitCharactersSupport: true,
documentationFormat: ["markdown", "plaintext"]
}
},
definition: { dynamicRegistration: true },
references: { dynamicRegistration: true },
hover: { dynamicRegistration: true },
signatureHelp: { dynamicRegistration: true }
},
workspace: {
applyEdit: true,
workspaceFolders: true,
symbol: { dynamicRegistration: true }
}
};
核心功能实现
代码补全
// 代码补全实现
class CompletionProvider {
constructor(languageService) {
this.languageService = languageService;
}
provideCompletions(params) {
const document = this.getDocument(params.textDocument.uri);
const position = this.toPosition(params.position);
// 获取补全项
const completions = this.languageService.getCompletions(document, position);
return {
isIncomplete: false,
items: completions.map(item => (completionItem: this.toCompletionItem(item))
};
}
toCompletionItem(item) {
return {
label: item.label,
kind: item.kind || CompletionItemKind.Variable,
detail: item.detail,
documentation: item.documentation,
insertText: item.insertText,
insertTextFormat: item.isSnippet ? InsertTextFormat.Snippet : InsertTextFormat.PlainText,
range: item.range,
commitCharacters: item.commitCharacters
};
}
resolveCompletionItem(item) {
// 补全项解析 - 延迟加载详细信息
const details = this.languageService.getCompletionDetails(item.label);
if (details) {
item.detail = details.detail;
item.documentation = details.documentation;
}
return item;
}
}
定义跳转
// 定义跳转实现
class DefinitionProvider {
constructor(languageService) {
this.languageService = languageService;
}
provideDefinition(params) {
const document = this.getDocument(params.textDocument.uri);
const position = this.toPosition(params.position);
// 查找定义位置
const definition = this.languageService.getDefinition(document, position);
if (!definition) return null;
// 转换为 LSP 格式
if (definition.isLocal) {
return [Location.create(definition.uri, Range.create(
definition.startLine, definition.startColumn,
definition.endLine, definition.endColumn
))];
} else {
return [LocationLink.create(
definition.originSelectionRange,
definition.targetUri,
definition.targetRange,
definition.targetSelectionRange
)];
}
}
toPosition(position) {
return Position.create(position.line, position.character);
}
}
悬停提示
// 悬停提示实现
class HoverProvider {
constructor(languageService) {
this.languageService = languageService;
}
provideHover(params) {
const document = this.getDocument(params.textDocument.uri);
const position = this.toPosition(params.position);
// 获取符号信息
const symbol = this.languageService.getSymbolAtPosition(document, position);
if (!symbol) return null;
// 构建 Markdown 内容
const contents = MarkupContent.create('markdown', `**${symbol.name}`**\n\n${symbol.signature || ''}\n\n${symbol.documentation || ''}`);
return {
contents,
range: Range.create(
symbol.startLine, symbol.startColumn,
symbol.endLine, symbol.endColumn
)
};
}
}
文档同步
增量文档同步
// 文档管理
class DocumentManager {
constructor() {
this.documents = new Map();
this.version = 0;
}
openDocument(params) {
const doc = {
uri: params.textDocument.uri,
text: params.textDocument.text,
version: params.textDocument.version,
languageId: params.textDocument.languageId
};
this.documents.set(doc.uri, doc);
}
changeDocument(params) {
const doc = this.documents.get(params.textDocument.uri);
if (!doc) return;
// 检查版本一致性
if (params.textDocument.version !== doc.version + 1) {
console.warn(`Version mismatch: expected ${doc.version + 1}, got ${params.textDocument.version}`);
}
// 应用增量变更
const changes = params.contentChanges;
for (const change of changes) {
if (change.range) {
const range = toRange(change.range);
doc.text = applyTextEdit(doc.text, range, change.text);
} else {
// 全量替换
doc.text = change.text;
}
}
doc.version = params.textDocument.version;
}
closeDocument(uri) {
this.documents.delete(uri);
}
getDocument(uri) {
return this.documents.get(uri);
}
}
语言特性
符号查找
// 符号查找实现
class SymbolProvider {
constructor(languageService) {
this.languageService = languageService;
}
provideDocumentSymbols(params) {
const document = this.getDocument(params.textDocument.uri);
// 解析文档中的符号
const symbols = this.languageService.findSymbols(document);
return symbols.map(sym => (DocumentSymbol.create(
sym.name,
sym.detail,
sym.kind,
sym.range,
sym.selectionRange,
sym.children
)));
}
provideWorkspaceSymbols(params) {
const query = params.query;
const results = [];
// 搜索工作区中的符号
for (const [uri, doc] of this.documents) {
const symbols = this.languageService.findSymbols(doc);
const matches = symbols.filter(s =>
s.name.toLowerCase().includes(query.toLowerCase())
);
for (const match of matches) {
results.push(SymbolInformation.create(
match.name,
match.kind,
match.location,
match.containerName
));
}
}
return results;
}
}
代码操作
// 代码操作(Code Actions)
class CodeActionProvider {
constructor(languageService) {
this.languageService = languageService;
}
provideCodeActions(params) {
const document = this.getDocument(params.textDocument.uri);
const range = toRange(params.range);
const context = params.context;
// 获取光标位置的诊断信息
const diagnostics = context.diagnostics;
// 获取可用的代码操作
const actions = [];
// 1. 基于诊断的操作
for (const diag of diagnostics) {
const fixes = this.languageService.getFixes(document, diag);
actions.push(...fixes);
}
// 2. 通用代码操作
const quickFixes = this.languageService.getQuickFixes(document, range);
actions.push(...quickFixes);
return actions.map(action => (CodeAction.create(
action.title,
{ changes: action.changes },
action.kind,
action.isPreferred
)));
}
}
进度报告
长时间操作
// 进度报告实现
class ProgressReporter {
constructor(connection) {
this.connection = connection;
this.tokenCounter = 0;
}
createProgress(title) {
const token = `progress-${++this.tokenCounter}`;
// 启动进度通知
this.connection.sendNotification('$/progress', {
token: token,
value: {
kind: "begin",
title: title,
cancellable: false
}
});
return {
report: (message, percentage) => {
this.connection.sendNotification('$/progress', {
token: token,
value: {
kind: "report",
message: message,
percentage: percentage
}
});
},
done: (message) => {
this.connection.sendNotification('$/progress', {
token: token,
value: {
kind: "end",
message: message || ""
}
});
}
};
}
async withProgress(title, task) {
const progress = this.createProgress(title);
try {
await task(progress);
} finally {
progress.done();
}
}
}
总结
- LSP 架构 - 语言服务器与客户端的解耦设计,支持多种编程语言
- 消息协议 - JSON-RPC 2.0 消息格式,方法能力协商机制
- 核心功能 - 代码补全、定义跳转、悬停提示、符号查找
- 文档同步 - 增量变更同步,版本管理
- 代码操作 - 基于诊断的修复,通用快速操作
- 进度报告 - 长时间操作的进度反馈机制
LSP 的标准化使得编辑器可以为各种编程语言提供一致的智能功能,极大地提升了开发体验和效率。