Another neat finding in Go’s language server. Basically, I wanted to
include some sort of way to run some static analyser with my language
server. I remember golanci-lint
was long the “de facto” tool for
this, but seems that staticcheck has grown a
lot in popularity. So I wanted to integrate that with my gopls
.
Naturally, the first step was installing the tool itself. Fortunately,
that can be done super easily with just:
$ go install honnef.co/go/tools/cmd/staticcheck@latest
Next I needed to enable this somehow with gopls
. Again fortunately,
all the possible settings for gopls
can be found
here.
Including the simple variable for staticcheck
. To pass in this
setting to eglot
, I need to configure variable
eglot-workspace-configuration
, which basically allows you to
configure LSP servers specifically for a given project and given LSP.
To pass in the setting to the gopls
, we need to pass in a plist
with the configurations we want:
(use-package eglot
:custom
(eglot-workspace-configuration '((:gopls . ((staticcheck . t))))))
;; OR
(setq-default eglot-workspace-configuration '((:gopls . ((staticcheck . t)))))
Naturally, you can pass any setting you desire that is available for
the language server this way.
I needed to write some funky Go code using relatively new Go
generics in it, just to quickly notice that my LSP client in Emacs
didn’t recognise those like you would expect. Fixing gopls
was
relatively straight-forward, since my issue seemed to be just an old
version. So I just installed gopls
while having Go 1.18+ installed. In
my case, I had already generics-aware Go version installed but seemed
like that I had installed my gopls
version before the generics
update, so I had to just reinstall it with:
$ go install golang.org/x/tools/gopls@latest
After that gopls
was pleased, or at least my eglot
didn’t complain
about syntactical errors in my code. Pretty much immediately I
realised that now my goimports
was broken, so it didn’t organise my
imports accordingly. I knew that gopls
was able to do this stuff
instead of using goimports
, but I just never was eager to fix
something that was already working. But since now it was broken, I
decided to find a way to fix it.
What I did was just uninstall goimports
from my machine and started
relying on gopls
for organising my imports. Previously, I had set my
Emacs so that when I saved my files, it always ran goimports
(when
working on Go files, of course). Setting my eglot
to do that was
relatively simple, but then I noticed that it only formats my code, it
doesn’t automatically import the libraries like how goimports
does.
Like I mentioned earlier, gopls
should be able to work exactly like
goimports
in this case, so I had to start digging on how I can make
my eglot
to do this. Basically, how gopls
does this, is it uses
source.organizeImports
action for it. So I needed to run that
somehow on save.
Fortunately, eglot
exports all these code actions that the LSP can
do with a neat function called eglot-code-actions
. After some
tinkering, I was able to call that before the save:
(use-package go-mode
:ensure t
:preface
(defun tok/gofmt-before-save ()
(interactive)
(gofmt-before-save)
;; Run `eglot-code-actions' only in buffers where `eglot' is active.
(when (functionp 'eglot-code-actions)
(eglot-code-actions nil nil "source.organizeImports" t))
:hook (go-mode . (lambda ()
;; Using depth -10 will put this before eglot's
;; willSave notification so that the notification
;; reports the actual contents that will be
;; saved.
(add-hook 'before-save-hook 'tok/gofmt-before-save -10 t))))
I decided to use gofmt-before-save
, which comes from go-mode
here since I noticed that if you would just run
eglot-format-buffer
formatting doesn’t open a new buffer where it
lists all the errors and instead prints them in the LSPs messages.
You can probably dig them somehow from there and print in a new
buffer, but I liked already the existing behaviour of running
gofmt
with go-mode
so I decided to use that one.
Small fix, but a really good one. Happy generics-aware hacking.