Emacs for Go06 May 2014
In this post I'm going to explore customizing Emacs to make hacking on Go a little more fluid. This is a living document and will probably be updated as I discover new modes and tools.
The Emacs modes I currently use are: go-mode, oracle, goflymake, and gocode.
I break out my Emacs configuration into language or package specific configuration files. This keeps ~/emacs.d/init.el pretty lean as all it is doing is loading in other configuration files with calls to load like this: (load "~/.emacs.d/go.el").
go-mode is the first mode we'll look at. It provides syntax highlighting, default indentation matching the golang standards and gofmt, the ability to add and remove imports, the ability to browse documentation and support for integrating godoc and godef.
To enable go-mode add the following go.el:
(require 'go-mode) (add-hook 'before-save-hook 'gofmt-before-save)
Let's start by looking at arguably the most fundamental capability added by go-mode - managing imports. go-mode adds three functions of note: go-import-add, go-remove-unused-imports and go-goto-imports.
go-import-add is bound to C-c C-a by default. If you call go-import-add with a prefix it will prompt you to provive an alternate name for the import. To do this type C-u C-c C-a. If no list of imports exists go-import-add will create one. If the import you are adding is currently in the list of imports but commented out it will be uncommented. Pretty good stuff for a very common pattern. One point to keep in mind is that go-import-add will only work once you have a package declaration in place.
go-remove-unused-imports, as the name implies, removes all unused imports. As with go-import-add calling go-removed-unused-imports with a prefix will change the default behavior - commenting out unused imports instead of removing them. By default isn't bound to anything by default. So let's start by setting up a keybinding specific to go-mode in ~/.emacs.d/go.el
(add-hook 'go-mode-hook '(lambda () (local-set-key (kbd "C-c C-r") 'go-remove-unused-imports)))
Now we can use C-c C-r to remove all unused imports, or C-u C-c C-r to comment out unused imports.
go-goto-imports is a pretty nifty helper function that will move the point to the imports block. By default it isn't bound to any keys by default so let's hop back into ~/.emacs.d/go.el and add a keybinding for it. I set it to C-c C-g as a mnemonic of "goto"
(add-hook 'go-mode-hook '(lambda () (local-set-key (kbd "C-c C-g") 'go-goto-imports)))
gofmt formats the current buffer according to the gofmt rules. By default it isn't bound to anything. I bind gofmt in two ways: C-c C-f as a mnemonic for "format" allowing me to format the buffer without saving and also with a "before-save-hook" so that the buffer is always correctly formatted when I save.
(add-hook 'go-mode-hook '(lambda () (local-set-key (kbd "C-c C-f") 'gofmt))) (add-hook 'before-save-hook 'gofmt-before-save)
godoc shows the go documentation for a given package. Note, godoc depends on the godoc utility. It must be installed and on your $PATH. To install it run: go get code.google.com/p/go.tools/cmd/godoc. By default godoc isn't bound to anything. I map this to C-c C-k as a mnemonic for man -k
(add-hook 'go-mode-hook '(lambda () (local-set-key (kbd "C-c C-k") 'godoc)))
godef is a really nifty tool that parses go code and enables you to quickly jump the definition of any symbol or read its description. As with godoc, godef must be installed and on your $PATH. To install godef run: go get code.google.com/p/rog-go/exp/cmd/godef. go-mode provides two functions for interacting with godef: godef-describe and godef-jump.
godef-describe is insanely useful and will give you information about the symbol you're looking at. By default is is bound to C-c C-d. If the point is on a function call godef-describewill show its full definition (parameter name and type, return type). If the point is on a struct godef-describe will show all members of the struct.
godef-jump will jump the defintion of the expression at the point. It is bound to C-c C-j. Remember that C-x <LEFT> will take you back to the previous buffer once you're done reading the definition of whatever you're looking at.
go oracle is an external source analysis tool that let's you answer questions like: "what interfaces does this type implement?", "which types implement this interface?", "who is listening for events on this channel?", "what are the possible callers of this function?"...etc. I highly recommend reading the go oracle documentation in depth - it is an advanced tool and I'm only scratching the surface here. As the documentation states, this tool is a work in progress. Rough edges are expected but the benefits are well worth it.
To install go-oracle run: go get code.google.com/p/go.tools/cmd/oracle which will install the tool in your $GOPATH/bin directory. However, the emacs bindings expect to find go-oracle in $GOROOT/bin and not $GOPATH/bin so you'll need to manually move the binary to the right place: mv $GOPATH/bin/oracle $GOROOT/bin/. Next we'll need to configure emacs to load the bindings. Add something like the following to go.el -- obviously with $GOPATH matching your own $GOPATH. The second line ensures go-oracle-mode is enabled whenever editing go code.
(load "$GOPATH/src/code.google.com/p/go.tools/cmd/oracle/oracle.el") (add-hook 'go-mode-hook 'go-oracle-mode)
Before using go-oracle you must set the 'scope of analysis'. This is done by calling the go-oracle-set-scope function M-x go-oracle-set-scope and listing one or more packages or import paths of packages. In the simplest case this is the 'main' package of your go application. As the tool states, bigger scopes are better since the tool will analyze more code - but it might also slow down the oracle a bit. If you need to specify multiple packages simply separate them with a space. An example, from the documentation, of an analysis scope is: fmt code.google.com/p/go.tools/cmd/oracle which includes the fmt package and also the go-oracle package for analysis.
To start getting a feel for go-oracle move the point to a function and enter C-c C-o < to see what calls this function. To see the possible sends/receives on a channel move the point to the channel and run C-c C-o c.By default go-oracle-mode sets up the following keybinds. Note: keybindings with '<' and '>' such as go-oracle-callers may not work on Mac OS X so you might need to rebind them.
C-c C-o < go-oracle-callers C-c C-o > go-oracle-callees C-c C-o c go-oracle-peers C-c C-o d go-oracle-definition C-c C-o f go-oracle-freevars C-c C-o g go-oracle-callgraph C-c C-o i go-oracle-implements C-c C-o p go-oracle-pointsto C-c C-o r go-oracle-referrers C-c C-o s go-oracle-callstack C-c C-o t go-oracle-describe
goflymake is a wrapper around the go tool providing flymake compatible syntax checking for go source. Since goflymake is actually compiling the source we get full compile and link checking as well.
To install the goflymake wrapper run go get -u github.com/dougm/goflymake. Next we'll need to configure emacs. Add the following to go.el obviously replacing $GOPATH with the location of your go workspace.
(add-to-list 'load-path "$GOPATH/src/github.com/dougm/goflymake") (require 'go-flymake')
The error checking provided by goflymake looks like the following. Note: unused imports are highlighted as an error, as is the incorrect call to http.Get()
gocode provides context-sensitive autocompletion. To install gocode run: go get -u github.com/nsf/gocode. gocode alsp depends on either auto-complete-mode or company-mode. I went with company-mode and company-mode-go. Both packages are available in ELPA so simply run: M-x package-install RET company-go
Next we need to automatically load company-mode whenever go-mode is loaded and also configure company-go as its backend. Add the following to go.el.
(add-hook 'go-mode-hook 'company-mode) (add-hook 'go-mode-hook (lambda () (set (make-local-variable 'company-backends) '(company-go)) (company-mode)))
The auto-completion provided by gocode and company-go looks like the following (after searching for "Get"). Notice the function definition in the minibuffer.
That's it for now. Happy hacking.