In the previous issue of learning by observing, I’ve described how one can debug the Go compiler. This time, I’d like to share with you how one can debug gopls using GoLand.

gopls is a language server protocol implementation for Go. Its source code is available on GitHub. While, typically, debugging a program in GoLand is as easy as a couple of clicks, the problem with gopls is that a) it’s launched by an editor b) at an arbitrary moment in time. This makes it harder to attach a debugger to it. Fortunately, there’s a better way called a daemon. It allows splitting gopls into a client and a server. The former works as a proxy while the latter does all the work. What is more important in our case, it’s trivial to launch the server right in the IDE.

Let’s start with checking out Go tools and opening gopls/main.go. Before launching it, we need to add an argument -listen=:37374 (you can use any other port or even a Unix socket) that tells gopls to act as a server. In GoLand, one does it by modifying a run configuration.

Modify Run Configuration…

It’s enough to update Program arguments. The rest is automatically generated by the IDE.

Run Configuration

After saving the run configuration with OK, we can launch gopls by choosing Debug ‘go build golang.org/…’ instead of Modify Run Configuration… Now, we need to teach an editor to start a client version of gopls. The process might look different for different editors. I use Visual Studio Code as the most popular one.

Actually, there’s not much to tell here. The Go plugin for VS Code has a setting go.languageServerFlags that allows passing arguments to gopls. It’s enough to add -remote=:37374 to the list of flags (don’t forget to update the port if you’ve decided to change it for -listen). In my case, .vscode/settings.json looks pretty much basic.

{
    "go.languageServerFlags": [
        "-remote=:37374"
    ]
}

Now, let’s add a breakpoint in GoLand. internal/lsp/server_gen.go is an LSP server entry point. For many commands, it’s easy enough to start here. The next logical step would be to call a command and see what happens.

Go to Definition

The official LSP documentation uses Go to Definition as an example. Let’s find out how it’s implemented for Go.

Breakpoint Hit

That’s it; we’re good to dive in. GoLand hits the breakpoint, so one can examine the program’s state and control its execution in an interactive manner. All variables have actual values, and we can even change them. The most curious can go further and evaluate expressions right inside the running application instance.

P.S. Special thanks go to Hana Kim, who gave me a hint about the gopls daemon on Gophers Slack. Thank you!