Gopls:实现
最后主要更新:2024 年 1 月 16 日
本文档提供 gopls 结构的高层概览,以帮助新贡献者了解其运作方式。本文档无意作为实现或任何关键组件的完整描述;关于这方面,包文档(下文链接)和代码中的其他注释是更好的指南。
下图显示了 gopls 模块的选定组件及其根据 Go 导入图的关系。未显示测试和测试基础设施,未显示实用程序包,也未显示来自 x/tools 模块的包。为简洁起见,包以其通常无歧义的最后一个段落来引用。
每个块的高度大致对应其技术深度。有些块宽而浅,例如 protocol,它为整个 LSP 协议声明了 Go 类型。其他块很深,例如 cache 和 golang,因为它们包含大量密集型逻辑和算法。
从底层开始,我们将描述各个组件。
最低层定义了语言服务器协议的请求和响应类型
-
protocol 包定义了标准协议;它主要根据 Microsoft 提供的架构定义机械生成。最重要的类型是 DocumentURI,它代表一个
file:
URL,该 URL 标识客户端编辑器文档。它还提供了Mapper
,用于在源代码位置使用的不同坐标系之间进行映射:UTF-8、UTF-16 和 token.Pos。 -
command 包定义了 Gopls 的非标准命令,这些命令全部通过
workspace/executeCommand
扩展机制调用。这些命令通常由服务器作为 Code Actions 或 Code Lenses 的后续操作返回;大多数客户端不会直接构造对它们的调用。
下一层定义了许多重要且使用非常广泛的数据结构
-
file 包定义了客户端文件的主要抽象:它的
Identity
(URI 和内容哈希),以及它的Handle
(它还提供了文件特定快照的版本和内容)。 -
parsego 包定义了
File
,即 Go 源文件的已解析形式,包括其内容、语法树和坐标映射(Mapper 和 token.File)。该包执行各种类型的树修复,以规避 Go 解析器在错误恢复方面的不足。 -
metadata 包定义了
Package
,这是 Go 包元数据的抽象,类似于go list -json
的输出。元数据由 go/packages 生成,该包负责调用go list
。(用户报告称,它在某种程度上可以与用于 Bazel 的 GOPACKAGESDRIVER 一起使用,但我们不维护针对此场景的测试。)该包还提供
Graph
,即工作区的完整导入图;每个图节点都是一个Package
。
settings 层定义了 gopls 配置选项的数据结构(实际上是一个大树),以及其 JSON 编码。
cache 层是 gopls 中最大、最复杂的组件。它涉及状态管理、依赖分析和失效:与客户端通信的 Session
;客户端打开的 Folder
s;特定构建选项下的特定工作区树的 View
;某个编辑操作后工作区所有文件状态的 Snapshot
;所有文件的内容,无论是已保存到磁盘的(DiskFile
)还是已编辑但未保存的(Overlay
);内存中已缓存的计算(如解析 go.mod 文件或构建符号索引)的 Cache
;以及 Package
,它保存了从 Go 语法类型检查包的结果。
缓存层依赖于各种辅助包,包括
-
filecache 包,它管理 gopls 的持久性、事务性、基于文件的键/值存储。
-
xrefs、methodsets 和 typerefs 包定义了用于构建从类型检查派生的信息索引的算法,以及在文件缓存中对这些可序列化索引进行编码和解码的算法。
这些包共同实现了 v0.12 重新设计所带来的快速重启、降低内存消耗和跨进程协同工作,这些内容在 “Scaling gopls for the growing Go ecosystem” 文章中有描述。
缓存还定义了 gopls 的 go/analysis 驱动程序,它在工作区中运行模块化分析(类似于 go vet
)。Gopls 还包含一些不属于 vet 的分析通道。
下一层定义了四个包,每个包用于处理特定语言的文件:mod 用于 go.mod 文件;work 用于 go.work 文件;template 用于 text/template
语法的askell;以及 golang,用于 Go 本身的文件。这个包是迄今为止最大的,提供了 gopls 的主要功能:Go 代码的导航、分析和重构。正如大多数用户所设想的那样,这个包就是 gopls。
server 包定义了 LSP 服务实现,每个 LSP 请求类型都有一个处理程序方法。每个处理程序根据文件类型进行切换,并将请求分派给四个特定语言的包之一。
lsprpc 包将服务接口连接到我们的 jsonrpc2 服务器。
请注意,该图是依赖图,是程序结构的“静态”视角。更动态的视角将根据处理特定请求时遇到的顺序来排列包;在这种视图中,底层代表“线路”(协议和命令),上一层将容纳 RPC 相关包(lsprpc 和 server),而功能(例如,golang、mod、work、template)将位于顶部。
cmd 包定义了 gopls
命令的命令行接口,gopls 的主包只是一个简单的包装器。它通常不带参数运行,导致它启动一个服务器并无限期监听。它还提供了一些子命令,这些子命令启动一个服务器,向其发送单个请求,然后退出,从而提供对服务器功能的传统批处理命令访问。这些子命令主要作为调试辅助工具提供;但请参阅 https://golang.ac.cn/issue/63693。
本文档的源代码可以在 golang.org/x/tools/gopls/doc 下找到。