Gopls: Implementation

Last major update: Jan 16 2024

This doc presents a high-level overview of the structure of gopls to help new contributors find their way. It is not intended to be a complete description of the implementation, nor even of any key components; for that, the package documentation (linked below) and other comments within the code are a better guide.

The diagram below shows selected components of the gopls module and their relationship to each other according to the Go import graph. Tests and test infrastructure are not shown, nor are utility packages, nor packages from the x/tools module. For brevity, packages are referred to by their last segment, which is usually unambiguous.

The height of each blob corresponds loosely to its technical depth. Some blocks are wide and shallow, such as protocol, which declares Go types for the entire LSP protocol. Others are deep, such as cache and golang, as they contain a lot of dense logic and algorithms.

Gopls architecture

Starting from the bottom, we’ll describe the various components.

The lowest layer defines the request and response types of the Language Server Protocol:

The next layer defines a number of important and very widely used data structures:

The settings layer defines the data structure (effectively a large tree) for gopls configuration options, along with its JSON encoding.

The cache layer is the largest and most complex component of gopls. It is concerned with state management, dependency analysis, and invalidation: the Session of communication with the client; the Folders that the client has opened; the View of a particular workspace tree with particular build options; the Snapshot of the state of all files in the workspace after a particular edit operation; the contents of all files, whether saved to disk (DiskFile) or edited and unsaved (Overlay); the Cache of in-memory memoized computations, such as parsing go.mod files or build the symbol index; and the Package, which holds the results of type checking a package from Go syntax.

The cache layer depends on various auxiliary packages, including:

The cache also defines gopls’s go/analysis driver, which runs modular analysis (similar to go vet) across the workspace. Gopls also includes a number of analysis passes that are not part of vet.

The next layer defines four packages, each for handling files in a particular language: mod for go.mod files; work for go.work files; template for files in text/template syntax; and golang, for files in Go itself. This package, by far the largest, provides the main features of gopls: navigation, analysis, and refactoring of Go code. As most users imagine it, this package is gopls.

The server package defines the LSP service implementation, with one handler method per LSP request type. Each handler switches on the type of the file and dispatches to one of the four language-specific packages.

The lsprpc package connects the service interface to our jsonrpc2 server.

Bear in mind that the diagram is a dependency graph, a “static” viewpoint of the program’s structure. A more dynamic viewpoint would order the packages based on the sequence in which they are encountered during processing of a particular request; in such a view, the bottom layer would represent the “wire” (protocol and command), the next layer up would hold the RPC-related packages (lsprpc and server), and features (e.g. golang, mod, work, template) would be at the top.

The cmd package defines the command-line interface of the gopls command, around which gopls’s main package is just a trivial wrapper. It is usually run without arguments, causing it to start a server and listen indefinitely. It also provides a number of subcommands that start a server, make a single request to it, and exit, providing traditional batch-command access to server functionality. These subcommands are primarily provided as a debugging aid; but see https://go.dev/issue/63693.


The source files for this documentation can be found beneath golang.org/x/tools/gopls/doc.