每天都在使用编辑器,language-service-protocol的工作原理到底是什么?

807 阅读3分钟

language-service-protocol是什么

身为开发工程师,我们每天使用的最多的工具就是编辑器。在使用的过程中,有一些特定的功能,比如:为编程语言添加自动完成、转到定义、悬停文档等。传统上,必须为每个独立的开发工具重复这项工作,因为每个工具都提供不同的API来实现相同的功能。

language-service是为了提供特定语言智慧和通信协议,使进程间通信开发工具沟通。

language-service-protocol(后文都简称“LSP”)的目的就是标准化此类服务器和开发工具如何通信的协议。通过LSP,单个语音服务器可以在多个开发工具中重复使用,从而可以支持多种语言。

language-service-protocol是如何工作的

language-service作为单独的进程运行,开发工具使用JSON-RPC上的语言协议与服务器通信。

JSON-RPC,是一个无状态且轻量级的远程过程调用(RPC)传送协议,其传递内容透过 JSON 为主。

JSON-RPC的格式长这样:

Content-Length: ...\r\n
\r\n
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "textDocument/didOpen",
  "params": {
    ...
  }
} 

method是用户触发的一次事件,比如:

  • textDocument/completion 从用户光标处补全代码
  • textDocument/signatureHelp 从用户光标处获取函数签名信息
  • textDocument/definition 从用户点击位置转到定义
  • ...

下图是一个工具和language-service在编辑会话期间如何通信的示例: 在这里插入图片描述

  1. 用户在工具中打开文件:工具通知language-service文档已打开。从现在开始,文档内容的真实性不再存在于文件系统中,而是由工具保存在内存中。现在必须在工具和language-service之间同步内容。
  2. 用户进行编辑:工具将文档更改通知language-service,并且文档的语言表示由language-service更新。language-service会分析此信息,并将检测到的错误和警告返回给工具。
  3. 用户在打开文档的符号上执行“转到定义”:该工具发送带有两个参数的请求(文档URL、转到定义的文本位置)发送给服务器。服务器根据请求中的这两个参数进行响应。
  4. 用户关闭文档:工具发送关闭通知,通知language-service该文档现在不再内存中。当前内容现在在文件系统上是最新的。

“转到定义”的示例(c++)

请求:

{
    "jsonrpc": "2.0",
    "id" : 1,
    "method": "textDocument/definition",
    "params": {
        "textDocument": {
            "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/use.cpp"
        },
        "position": {
            "line": 3,
            "character": 12
        }
    }
}

回应:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "uri": "file:///p%3A/mseng/VSCode/Playgrounds/cpp/provide.cpp",
        "range": {
            "start": {
                "line": 0,
                "character": 4
            },
            "end": {
                "line": 0,
                "character": 11
            }
        }
    }
}

当用户使用不同的语言时,开发工具通常会为每种编程语言启动一个language-service。

⚠️注意:language-service与工具的实际集成不是由LSP定义的,而是又工具实现者决定的。

LSP提供者和消费者的库SDK

  1. 每个开发工具通常都提供一个用于集成language-service的库,例如对于JavaScript/Typescript,有语言客户端npm模块。
  2. 用于不同实现语言的language-service SDK,有一个SDK来实现特定语言的language-service。例如要使用Node.js实现language-service,有语言服务器npm模块。

LSP解决了什么问题

以前对于编辑器来说,一门相通的语言要单独实现一套自己的代码,那么有N中语言,M的IDE,意味着要写N*M套代码,现在有了LSP作为一层中间的抽象,只需要写N+M套套代码就够了。 在这里插入图片描述 还有一个好处就是language-service运行在一个单独的进程中:

  • language-service属于CPU密集型程序,为了正确的验证一个文件,需要解析大量文件(构建AST->静态程序分析),在VSCode插件中,每个窗口的所有插件共享一个Extension Host,如果语言服务卡住的话,所有的插件都会受到影响。在独有的进程中运行language-service可以避免这个性能问题。