代码编辑器语言服务器协议完全指南

编辑器功能语言服务器

语言服务器协议(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 的标准化使得编辑器可以为各种编程语言提供一致的智能功能,极大地提升了开发体验和效率。