2019-08-15
Getting LSP-based editing working for Go in GNU Emacs
When I set up autocompletion for Go in GNU Emacs years ago, I used
gocode
and eventually company-mode, because that was what was your best
option in 2015 and 2016. Things have happened since then; for a
summary, I'll refer you to "Go, pls stop breaking my editor"
from GopherCon 2019. The short version is that the future is on the
Language Server protocol, using lsp-mode in Emacs with the Go LSP
server. However, how to
set up that future in GNU Emacs today is not entirely obvious, and
it's taken me some time and puzzlement to get my
Emacs environment working in this modern (or future) world. Here is
what I've done so far and what I understand about Emacs' lsp-mode.
First, there's several parts of LSP in GNU Emacs. Lsp-mode is apparently the core, and then there's lsp-ui, which provides a bunch of the visible interface that you'll normally use. If you use company-mode, company-lsp hooks into the LSP stuff to make company talk to it; for Go, this replaces company-go. I get all of these through MELPA. Flycheck seems to automatically integrate with lsp-ui without any extra package.
To initially start all of this, what I did in my .emacs
was just:
(require 'lsp-mode) (add-hook 'go-mode-hook #'lsp) (require 'company-lsp) (push 'company-lsp company-backends)
(If you're coming from an existing gocode-based setup that uses company-mode, you'll want to stop using company-go. However, you can keep all of your existing company-mode keybindings and as far as I can tell, they just carry on working.)
This gives you a very busy interface by default (which is apparently deliberate, and I can't entirely blame them for the decision). This is where the split between lsp-mode and lsp-ui becomes important, because most of the busy things are done by lsp-ui and so must be configured or turned off through its settings. In general, the lsp-ui git repo README has nice animated GIFs that will generally illustrate what the various sub-components are (although their examples aren't using Go). This helps when you're trying to figure out what to change.
Before I discuss them, here are my current LSP customizations are
(all from custom-set-variables
):
'(lsp-enable-snippet nil) '(lsp-ui-doc-delay 1) '(lsp-ui-doc-max-height 8) '(lsp-ui-sideline-delay 2) '(lsp-ui-sideline-show-code-actions nil) '(lsp-ui-sideline-show-hover nil)
I also set a custom colour, although it's now surplus:
'(lsp-ui-sideline-code-action ((t (:foreground "dim gray"))))
I disable snippets because otherwise lsp-mode tells me that it requires yasnippet, and I don't want to install a package that I don't have a clear use for. Apparently some languages have LSP servers that provide snippets, but Go's currently doesn't.
I turn off code actions completely for two reasons. First, Go
currently only provides one code action, 'Organize imports', which
basically runs goimports
, and I can do
that myself without a button. Second, unfortunately right now code
actions appear even in non-graphical GNU Emacs sessions (such as
when you're ssh'd in to a machine), and as far as I know you can't
even select them in this case, so all they do is take up space. If
I used GNU Emacs for more and was better at configuring it, I would
arrange to turn off code actions only in Go mode, and perhaps only
when not running within a window system.
(I currently only use lsp-mode with Go, partly because Go is pretty much the only language I write with GNU Emacs right now. At some point I should investigate getting it working with Python too.)
In my 80 colum wide default GNU Emacs windows, sideline hover clutters up multiple lines with its information and obscures the actual code. If I had ultra-wide Emacs windows, the right-aligned sideline hover information might be usefully separated from the code on the left, but as it is it isn't. Much of the same information shows up in the lsp-ui-doc information, which appears up at the top right.
I set delays for both the sideline and the documentation because otherwise I find that things are far too frantic; if I'm moving the cursor around, there will be a constant flurry of things appearing and disappearing and changing. The current larger delays mean that things only start appearing once I've settled down.
The default maximum height for the LSP documentation area is frankly absurd, at least at the vertical size I tend to run my GNU Emacs windows at; with the defaults, it's entirely possible to have the documentation area for a structure or interface almost entirely take over your screen. You'll probably want to tune this for how big your Emacs windows tend to be. Unfortunately, as far as I know there's no way to dismiss the documentation area until you move to a new spot; I think the best you can do is write a function to toggle documentation on or off.
(One such Emacs Lisp function is available here. I may have to adopt it. An alternate would be to toggle the maximum height of the area back and forth, leaving it normally small but then allowing it to jump large if I wanted to pause to read all of something.)
PS: If you make a mistake in setting the LSP project root for
something, these are all saved in ~/.emacs.d/.lsp-session-v1
, or
you can use lsp-workspace-folder-remove
in GNU Emacs. One such
potential mistake is using the default project root for something
inside $GOPATH/src
, which will be its specific bit of go/src
instead of the whole thing. I'm not sure what the difference is,
but at least with a 'project root' of ~/go/src
you don't get
asked this question all the time in new areas.
PPS: My current lsp-mode setup is very likely to not be ideal and I'm pretty sure that I'm running into the limitations of my current Emacs knowledge. People who actually understand what they're doing can probably come up with a better setup, and I hope someone writes one up sometime.
(For instance, lsp-ui has a 'peek' feature to look at definitions or references of things, but I don't know how to easily navigate back from peeking at something. I assume that there is a trick to it that people who actively know Emacs already know.)