过去,许多代码编辑器都是为特定语言构建的,为了提供丰富而智能的代码编辑功能,编辑器和语言工具之间的紧密集成必不可少。
另一方面,虽然有(现在仍然有)更多通用编辑器,但它们在更高级的语言特定功能(如代码完成、“转到定义”等)方面功能不足(例如,使用正则表达式进行代码突出显示)。
随着代码编辑器和编程语言的不断增长,这成为了经典的M*N复杂性问题。
但随后微软推出了语言服务器协议(LSP)作为上述问题的解决方案,它优雅地将这种M*N复杂性转变为更易于管理的M+N情况。
LSP 最初是由VS Code 的需求驱动的:
LSP 将语言服务器与代码编辑器(语言客户端)分开。通过使语言服务器成为专用于语言理解的独立进程,LSP 使任何编辑器都可以使用标准语言服务器。这意味着所有编辑器都可以使用单个标准语言服务器。
这种互操作性是通过一组定义的标准消息和程序实现的,这些消息和程序控制着语言服务器和编辑器之间的通信。LSP 定义了开发工具和语言服务器之间使用 JSON-RPC 发送的消息的格式。
语言服务器功能每个语言服务器的功能列表可能有所不同,但通常它们提供以下功能:
自动完成
转到定义/声明
查找参考资料
代码格式
诊断
文档
ETC。
例如
这里您可以看到gopls (Go 语言服务器)提供的编辑器功能列表。
在这里您可以看到可用功能的完整 LSP 规范。
LSP 如何工作?语言服务器协议建立在JSON-RPC之上。它具体使用 JSON RPC 2.0。您可以将 JSON-RPC 视为使用 JSON 进行数据编码的远程过程调用协议。
简而言之,它的工作原理如下。首先,编辑器与语言服务器建立连接,然后随着开发人员输入代码,编辑器将增量更改发送到语言服务器。然后它发回以下见解:代码完成建议、诊断。
让我们看一个自动完成的真实示例。此案例中来自语言客户端(编辑器)的请求将是:
{ “jsonrpc” : “2.0” , “id” : 1 , “method” : “textDocument/completion” , “params” : { “textDocument” : { “uri” : “file:///home/alex/code/test/main.go” } , “position” : { “line” : 35 , “character” : 21 } } }
它发送了有关当前光标位置和缓冲区文件的信息。让我们分解一下:
ID:客户端设置此字段来唯一标识请求。请求处理后,将返回具有相同请求 ID 的响应,以便客户端可以匹配哪个响应针对哪个请求。
method:包含要调用的方法的名称的字符串。
Params:要传递给方法的参数。这可以是数组或对象。
语言服务器可以访问此文件,对其进行分析并提供建议:
{ "jsonrpc" : "2.0" , "id" : 1 , "result" : { "isIncomplete" : false , "items" : [ { "label" : "Println" , "kind" : 3 , "insertText" : "Println(${1:format}, ${2:a ...interface{}})$0" , "insertTextFormat" : 2 , "detail" : "func Println(a ...interface{}) (n int, err error)" , "documentation" : "Println 格式 ..." } , // ... 其他项目 ] } }
Go 语言服务器最流行和最常用的 Go 语言服务器是gopls。许多编辑器都在使用它,例如Visual Studio Code Go 扩展。
之前,Sourcegraph 团队还开发了另一个流行的 Go 语言服务器,名为go-langserver,但该服务器已不再处于积极维护中。
如果主机上没有 gopls 语言服务器,许多编辑器会自动安装它,但您也可以手动安装它:
go install golang.org/x/tools/gopls@latest
结论 多亏了语言服务器协议,各种编程语言和编码环境都能普遍使用高级编码功能。 了解代码编辑器的工作原理是件好事,因此了解这项被广泛使用的技术(LSP)也大有益处。 LSP 对语言提供商和工具供应商来说都是双赢之举!