I’ve been Emacs user since around ~2014, I used to maintain my own Emacs config and publish it (I also would flash custom roms on my android phones). With time, I yearned more for stability. So I switched to an Emacs framework: Centaur Emacs. This allowed me to continue using Emacs without having to worry about a broken config, as it was well maintained.

With time, I realized I was adding a lot more on top of Centaur Emacs as my own config. So again, I switched to Doom Emacs, which supported a lot more modules than Centaur and also saw more community contributions (chicken-egg problem). Today let’s go (pun! hehe) over the additional config I have written over the defaults provided by Doom Emacs for Go development.

Do you see the pattern here? While one perspective is that Emacs always requires tinkering. The more realistic perspective is that Emacs is all about customization, even when using a framework like Doom Emacs, users can always modify it to their requirements and preferences.

Go in Doom Emacs Link to heading

Doom Emacs comes with pre-built modules for Go, to enable it all we need to do is add the following to your doom/init.el file:

(go +lsp)

The README in the Go modules folder, lists the pre-requisites to be installed. It also recommends using gopls. gopls is the official Go language server developed by the Go team.

LSP is an editor agnostic protocol, which allows editors to be linked to language servers and utilize them for features such as syntax checks, code-navigation, linting and so on. Before editors used to implement each of these functionalities from scratch for different languages/frameworks, with LSP, they could simply make RPC calls to the language server installed on the users system.

To install gopls, we can simply use go install:

go install golang.org/x/tools/gopls@latest

This is already a fully usable setup. This will provide all the functionality you expect from a modern editor (code completion, code navigation, syntax checking, linting). But we can fine tune some things to make it better for Go development.

Format Buffer Link to heading

Go projects follow gofmt for formatting. So it’d be nice if we could get Emacs to auto-format before saving. Both gopls and Emacs’s lsp-mode support buffer formatting via the lsp-format-buffer command. Doom Emacs abstracts this for us behind the +format/buffer-or-region command.

Either of these commands can be used or we could also add the +format-buffer-h command to the before-save-hook hook, so it automatically does this before saving. The simplest option is to add to the following to ~/.doom.d/init.el:

(format +onsave)

This automatically formats buffers before saving. _Note: This applies for all major modes and not just Go.

Organize Imports Link to heading

To organize imports one could manually run lsp-organize-imports. I prefer running this automatically as a hook just before saving a file. To do this, add the following to your ~/.doom.d/config.el:

(add-hook 'go-mode-hook #'lsp-deferred)
;; Make sure you don't have other goimports hooks enabled.
(defun lsp-go-install-save-hooks ()
    (add-hook 'before-save-hook #'lsp-organize-imports t t))
(add-hook 'go-mode-hook #'lsp-go-install-save-hooks)

Gofumpt Link to heading

I prefer using gofumpt over the standard gofmt, if you do so too, then you can simply ask lsp to use gofumpt by adding this to your ~/.doom.d/config.el:

(after! lsp-mode
  (setq  lsp-go-use-gofumpt t)

Analyzers Link to heading

By default, gopls doesn’t enable all analyzers. You can find the list of analyzers and their default values here. Some of the analyzers I manually enable are:

  • fieldalignment: find structs that would use less memory if their fields were sorted
  • nilness: check for redundant or impossible nil comparisons
  • shadow: check for possible unintended shadowing of variables
  • unusedparams: check for unused parameters of functions
  • unusedwrite: checks for unused writes
  • useany: check for constraints that could be simplified to “any”
  • unusedvariable: check for unused variables

To enable these, we add the following to ~/.doom.d/config.el:

(after! lsp-mode
  (setq  lsp-go-analyses '((fieldalignment . t)
                           (nilness . t)
                           (shadow . t)
                           (unusedparams . t)
                           (unusedwrite . t)
                           (useany . t)
                           (unusedvariable . t)))

Tree-sitter Link to heading

Emacs generally uses regular expressions for parsing languages, this is slow and inaccurate at times. Tree-sitter is a parser generator tool and an incremental parsing library. It was also merged into Emacs 29. To use tree-sitter in Emacs, first add the following to ~/.doom.d/config.el file’s :tools section:


Also modify Go to use tree-sitter by modifying ~/.doom.d/config.el:

(go +lsp +tree-sitter)

Summary Link to heading

That’s it! Not much configuration over the default doom setup. Certain changes I would be looking forward to:

  1. Some of the analyzer being set to default true, so we can skip them.
  2. Since tree-sitter is merged into Emacs, we shouldn’t be required to enable it anymore.
  3. elgot, which is an alternative for lsp-mode also got merged into Emacs, curious how this will all play out.

This is post 003 of #100DaysToOffload.