Crafted Emacs – Example: Adding Go
Happy New Year
Hello Crafters, welcome to 2024! Hope everyone had a wonderful and safe holiday season, and now that is behind us time to get back to work, right!?!!
Crafted Emacs Update
Since the release of Crafted Emacs v2 last year, things have been a bit quite on the development front. Since the last time I gave an update there have been a few improvements:
- Usage of
eglot-ensure
was changed to an “opt-in” configuration. This change included checking to see if the mode was actually available on the system before addingeglot-ensure
to a hook. This makes a lot more sense now, not everyone will have all the programming modes installed, much less have the desire to turn oneglot
for all of them. - There was a bug in the docs after the release, I missed removing a reference to the development branch. Thanks to @bird-dancer for noticing and providing the PR for the fix!
- Another bug related to loading the
custom-file
was found by @jvalleroy, also thanks for the PR to fix it! - @mgmarlow has been fairly active, providing discussion, the patch for
eglot-ensure
mentioned above, and an example configuration for therust
programming language. Thank you!! - I changed our
tree-sitter
configuration, originally we tried to install everyts
grammar by default, but a change to thetree-sit-auto
broke it. Reviewing how we were using that package, it seemed we had been approaching the problem backwards all along, so it is also an “opt-in” rather than an “opt-out” configuration.
Example: Adding the Go language
There have been frequent questions around providing one language mode or another, or all of them, over the course of the project. With Crafted Emacs v2, we moved to more general modules, which a user could use as building blocks for extending Crafted Emacs as they see fit. You can read more of the language module discussion here if you wish.
Currently, we have the example of adding rust
as a language module, I'll walk through the code I used recently to extend my own configuration for the Go language.
The initial process
I took a little time to search the internet for others who had configured Emacs for Go development. I also took some time to review the existing go-mode.el
, which originally shipped with Go, but later was rewritten by Dominik Honnef, see the GitHub page for more information.
Updating my init.el
configuration
I need to make several changes in my base configuration file:
- I add to the list of packages which need to be installed
- Turn on the tree-sitter mode for Go
Adding the needed packages
I need a couple of packages installed as Emacs doesn't know about Go out of the box. I add these lines early in my init.el
file:
(add-to-list 'package-selected-packages 'go-eldoc)
(add-to-list 'package-selected-packages 'go-mode)
I evaluate these lines, then evaluate this (package-install-selected-packages :noconfirm)
to get the packages installed.
Update treesit-auto to install go-ts-mode
I like to use tree-sitter modes, so I add go
to the list I already have. Mine now looks like this (yours may be different):
(with-eval-after-load 'crafted-ide-config (crafted-ide-configure-tree-sitter '(go java javascript latex markdown python typescript)))
Putting together a custom module
I start by building on the work in crafted-ide-config
:
(require 'crafted-ide-config nil :noerror)
This way, I get to take advantage of the work already done to setup eglot, tree-sitter modes, editorconfig, etc.
Setup project.el
We need to tell project.el
about Go projects. project.el
does not know about GOPATH
or go modules, so we need to tell it how to find the go.mod
file. This also enables eglot
to work as it uses project.el
to find project assets.
(require 'project)
(defun project-find-go-module (dir)
(when-let ((root (locate-dominating-file dir "go.mod")))
(cons 'go-module root)))
(cl-defmethod project-root ((project (head go-module)))
(cdr project))
(add-hook 'project-find-functions #'project-find-go-module)
Setup hooks
Now, we need to setup some hooks to turn things on. I setup both go-mode
and go-ts-mode
here, but you may choose whichever. That said, go-mode
and go-ts-mode
are not equal. There are features provided in go-mode
that are not available in the ts
version. This lack of parity is a bit unfortunate, and exists for other language modes as well. Usually, the ts
modes is much less fully featured.
You can reasonably pick one of the modes in the example below and leave the other one out, or just include both if you aren't sure, no harm done.
On the hooks, you'll see I order them using numbers. This is because I want to make sure flymake
is loaded before the call to flymake-show-buffer-diagnostics
.
For the before-save-hook
I make sure it is first in the list (a negative number makes it earlier in the list), but I also make it buffer local, rather than a global setting. So the call to eglot-format-buffer
only gets called on go
buffers.
(require 'go-mode)
(require 'eglot)
;; these lines are only needed if you choose to use go-mode
(add-hook 'go-mode-hook 'flymake-mode 8)
(add-hook 'go-mode-hook 'flymake-show-buffer-diagnostics 9)
(add-hook 'go-mode-hook 'eglot-ensure 10)
;; these lines are only needed if you choose to use go-ts-mode.
(add-hook 'go-ts-mode-hook 'flymake-mode 8)
(add-hook 'go-ts-mode-hook 'flymake-show-buffer-diagnostics 9)
(add-hook 'go-ts-mode-hook 'eglot-ensure 10)
;; Optional: install eglot-format-buffer as a save hook.
;; The depth of -10 places this before eglot's willSave notification,
;; so that that notification reports the actual contents that will be saved.
(defun eglot-format-buffer-on-save ()
(add-hook 'before-save-hook #'eglot-format-buffer -10 t))
(add-hook 'go-mode-hook #'eglot-format-buffer-on-save)
(add-hook 'go-ts-mode-hook #'eglot-format-buffer-on-save)
Setup gopls
Configuring gopls
when using eglot
happens on the eglot-workspace-configuration
which can be set globally in your Emacs configuration or in a .dir-locals.el
file in your project. I choose to set it in my Emacs configuration. For more configuration options for gopls
see here
(setq-default eglot-workspace-configuration
'((:gopls .
((staticcheck . t)
(matcher . "CaseSensitive")))))
Setup flymake window height
And, finally, as I want the diagnostics window to popup when I open a Go file, I configure the window height to my preference. This sets the window height to 10 lines at the bottom of the frame so I can read the diagnostics as they popup. Recall, I add the function to open the flymake diagnostics window to the hook go-mode
and go-ts-mode
hooks.
(add-to-list 'display-buffer-alist
'("^\\*Flymake diagnostics"
(display-buffer-reuse-window display-buffer-pop-up-window)
(window-height . 10)))
Updating my configuration
I put all that code in a file located in the custom-modules
folder in my user-emacs-directory
, which in my case is: $HOME/.emacs.d/custom-modules/my-ide-go.el
. Crafted Emacs automatically puts the $HOME/.emacs.d/custom-modules
onto the Emacs load-path
list, so all that remains is to add the appropriate require
in my init.el
file:
(require 'my-ide-go)
Now, I can restart Emacs or just evaluate that one line and my Go configuration is installed!
Tags: #emacs