Gopls:实现

最后主要更新:2024 年 1 月 16 日

本文档提供 gopls 结构的高层概览,以帮助新贡献者了解其运作方式。本文档无意作为实现或任何关键组件的完整描述;关于这方面,包文档(下文链接)和代码中的其他注释是更好的指南。

下图显示了 gopls 模块的选定组件及其根据 Go 导入图的关系。未显示测试和测试基础设施,未显示实用程序包,也未显示来自 x/tools 模块的包。为简洁起见,包以其通常无歧义的最后一个段落来引用。

每个块的高度大致对应其技术深度。有些块宽而浅,例如 protocol,它为整个 LSP 协议声明了 Go 类型。其他块很深,例如 cachegolang,因为它们包含大量密集型逻辑和算法。

Gopls architecture

从底层开始,我们将描述各个组件。

最低层定义了语言服务器协议的请求和响应类型

  • 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;客户端打开的 Folders;特定构建选项下的特定工作区树的 View;某个编辑操作后工作区所有文件状态的 Snapshot;所有文件的内容,无论是已保存到磁盘的(DiskFile)还是已编辑但未保存的(Overlay);内存中已缓存的计算(如解析 go.mod 文件或构建符号索引)的 Cache;以及 Package,它保存了从 Go 语法类型检查包的结果。

缓存层依赖于各种辅助包,包括

  • filecache 包,它管理 gopls 的持久性、事务性、基于文件的键/值存储。

  • xrefsmethodsetstyperefs 包定义了用于构建从类型检查派生的信息索引的算法,以及在文件缓存中对这些可序列化索引进行编码和解码的算法。

    这些包共同实现了 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 下找到。