AB
AiBoss
Wiki

什么是语言服务器协议(Language Server Protocol)?

语言服务器协议(LSP)是一套把开发工具与语言服务器进程之间交换的消息标准化的协议。它让语言功能的实现与编辑器解耦:语言提供方只需写一个语言服务器,编辑器只需实现一次客户端,就能复用同一套语言能力。

语言服务器协议(Language Server Protocol,LSP)是一套把开发工具与语言服务器进程之间来回传递的消息加以标准化的协议。它要解决的问题是:过去每为一个编辑器或集成开发环境(IDE)增加一门语言的富编辑功能,都要在该编辑器所用的语言里重写一遍这门语言的领域模型,代价高、重复多;LSP 把这些能力抽到一个独立进程里,用统一的消息格式对外提供。

为什么重要

按照 Microsoft Learn 官方文档的描述,在编辑器或 IDE 中支持源代码自动补全、转到定义这类富编辑功能,传统上非常困难且耗时。通常的做法是在编辑器或 IDE 所用的编程语言里写一套领域模型,包括扫描器、解析器、类型检查器、构建器等。文档举的例子是:Eclipse CDT 插件为 Eclipse IDE 提供 C/C++ 支持,由于 Eclipse IDE 本身用 Java 编写,该插件也用 Java 编写。若沿用这一思路,就意味着要为 Visual Studio Code 用 TypeScript 实现一套 C/C++ 领域模型,再为 Visual Studio 用 C# 实现另一套。

文档同时指出,如果开发工具能复用已有的语言专属库,构建领域模型会容易得多;但这些库通常就是用该语言本身实现的,例如好的 C/C++ 领域模型用 C/C++ 写成。把一个 C/C++ 库集成进用 TypeScript 编写的编辑器,技术上可行,但做起来很难。于是有了另一种思路:让库跑在自己的进程里,通过进程间通信与之对话,来回发送的消息就构成一个协议。LSP 正是把这些消息标准化之后的产物。

文档也说明,使用语言服务器或守护进程并非新想法,Vim 和 Emacs 等编辑器早已用这种方式提供语义自动补全。LSP 的目标是简化这类集成,为把语言功能暴露给多种工具提供一个有用的框架。有了共同协议,开发工具只要复用某门语言领域模型的现有实现,就能以很小的代价把该语言的功能集成进来:语言服务器后端可以用 PHP、Python 或 Java 编写,再通过 LSP 接入各种工具。协议工作在共同的抽象层次上,因此工具无需完全理解底层领域模型的细节,也能提供丰富的语言服务。

工作机制

根据该官方文档,LSP 的核心做法可以拆成以下几点:

  • 进程分离:语言服务器作为独立进程运行,开发工具通过进程间通信与之交换消息,而不是把语言实现链接进编辑器本体。
  • 消息标准化:来回发送的消息被统一成一套协议,即 LSP。这样同一门语言只需一个服务器,就能被不同 IDE 使用;而工具方只需实现一次协议的客户端侧。
  • 双端分工:语言提供方负责创建语言服务器,语言消费方负责实现客户端。文档称这对语言提供方和语言消费方是双赢。
  • 抽象层次:协议工作在共同抽象层,工具不必理解底层领域模型的细微差别,就能提供富语言服务。
  • 远程调用基础:文档提到,该协议以 JSON-RPC 作为远程调用的支撑,原因是其简单且已有现成库。

文档还交代了协议的演进脉络:LSP 随时间发展,目前处于 3.0 版本。它起步于 OmniSharp 采用语言服务器概念为 C# 提供富编辑功能,最初 OmniSharp 使用 HTTP 协议加 JSON 载荷,并被集成进包括 Visual Studio Code 在内的多个编辑器。差不多同一时期,Microsoft 开始做 TypeScript 语言服务器,目标是在 Emacs、Sublime Text 等编辑器中支持 TypeScript;该实现中编辑器通过标准输入输出(stdin/stdout)与 TypeScript 服务器进程通信,请求与响应用的是受 V8 调试器协议启发的 JSON 载荷。TypeScript 服务器被集成进 TypeScript Sublime 插件和 VS Code。在集成两个不同的语言服务器之后,VS Code 团队开始探索面向编辑器与 IDE 的通用语言服务器协议。LSP 以 TypeScript 服务器所用的协议为起点,并借鉴 VS Code 语言 API 扩充了更多语言功能。

典型例子

文档中出现的真实系统与用法包括:

  • Eclipse CDT:为 Eclipse IDE 提供 C/C++ 支持,因 IDE 本身用 Java 编写而用 Java 实现,是「在编辑器语言里重写领域模型」这一传统做法的代表。
  • OmniSharp:最早把语言服务器概念用于 C# 富编辑功能的项目之一,初期采用 HTTP 加 JSON 载荷,并被集成进多个编辑器。
  • TypeScript 语言服务器:通过 stdin/stdout 与编辑器通信,使用受 V8 调试器协议启发的 JSON 载荷,被集成进 TypeScript Sublime 插件与 VS Code。
  • Visual Studio Code:既是上述语言服务器的集成方,其团队也推动了通用协议的探索,协议扩充部分借鉴了 VS Code 语言 API。
  • Vim 与 Emacs:文档提到它们此前已通过语言服务器或守护进程方式提供语义自动补全,说明这一模式有先例。

边界与常见误解

第一,LSP 不是语言本身,也不是某个编辑器的插件规范。按文档的定位,它标准化的是开发工具与语言服务器进程之间交换的消息;语言服务器后端可以用 PHP、Python、Java 等不同语言编写,协议只规定通信方式。

第二,LSP 不等于「语言服务器」这一想法本身。文档明确指出,使用语言服务器或守护进程并不新鲜,Vim、Emacs 早有类似做法;LSP 的贡献在于把这些消息标准化、提供一个通用框架,从而简化集成。

第三,它并不要求工具理解语言内部实现。文档强调协议工作在共同抽象层次上,工具无需完全理解底层领域模型的细节;反过来说,这也意味着工具获得的是协议所暴露的那一层能力,而非语言实现的全部内部信息。

第四,关于代价与限制,文档提到的是传统做法的代价:为每个编辑器重写领域模型费时费力,把用某语言写的库集成进另一种语言写的编辑器技术上可行但很难。文档并未给出 LSP 自身的性能数据或适用性边界,因此不宜替它下结论。此外,文档所述版本为 3.0,具体实现支持哪些语言功能,需以各实现为准。

最后需要说明:以上内容均来自 Microsoft Learn 的官方文档表述,属于该文档对 LSP 的定位与历史描述,不代表其他组织或整个行业的共识。

参考资料