Set up Emacs for Golang from scratch - December 2016

This is a summary of how I set up Emacs for Go development from scratch on macOS. It is based on the refs & inspiration mentioned below, and brought up to date for the start of 2017.

TLDR: The complete list of go get and M-x package-install commands, and the Emacs init.el file are at the top of this document, followed by a step-by-step explaination.

References and inspiration for this:

Summary: All Commands Without Blah Blah

Bash Config

# file: ~/.bash_profile
export GOPATH=~/Development/gocode
export PATH=$PATH:$GOPATH/bin

Go Get

$ go get -u golang.org/x/tools/cmd/...
$ go get -u github.com/rogpeppe/godef/...
$ go get -u github.com/nsf/gocode
$ go get -u golang.org/x/tools/cmd/goimports
$ go get -u golang.org/x/tools/cmd/guru
$ go get -u github.com/dougm/goflymake

M-x package-install

go-mode
exec-path-from-shell
auto-complete
go-autocomplete
flymake-go
neotree
atom-one-dark-theme

Reminder

Remember to copy go-guru.el to your load path.

Emacs Config (without auto-generated Custom stuff)

; file: ~/.emacs.d/init.el
;; High level aesthetic stuff
(tool-bar-mode -1)                  ; Disable the button bar atop screen
(scroll-bar-mode -1)                ; Disable scroll bar
(setq inhibit-startup-screen t)     ; Disable startup screen with graphics
(set-default-font "Monaco 12")      ; Set font and size
(setq-default indent-tabs-mode nil) ; Use spaces instead of tabs
(setq tab-width 2)                  ; Four spaces is a tab
(setq visible-bell nil)             ; Disable annoying visual bell graphic
(setq ring-bell-function 'ignore)   ; Disable super annoying audio bell

;; Make keyboard bindings not suck
(setq mac-option-modifier 'super)
(setq mac-command-modifier 'meta)
(global-set-key "\M-c" 'copy-region-as-kill)
(global-set-key "\M-v" 'yank)
(global-set-key "\M-g" 'goto-line)

;; Set up package repositories so M-x package-install works.
(require 'package) 
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))
(package-initialize)

(load-theme 'atom-one-dark t)       ; Color theme installed via melpa

;; Add a directory to the load path so we can put extra files there
(add-to-list 'load-path "~/.emacs.d/lisp/")

;; Snag the user's PATH and GOPATH
(when (memq window-system '(mac ns))
  (exec-path-from-shell-initialize)
  (exec-path-from-shell-copy-env "GOPATH"))

;; Define function to call when go-mode loads
(defun my-go-mode-hook ()
  (add-hook 'before-save-hook 'gofmt-before-save) ; gofmt before every save
  (setq gofmt-command "goimports")                ; gofmt uses invokes goimports
  (if (not (string-match "go" compile-command))   ; set compile command default
      (set (make-local-variable 'compile-command)
           "go build -v && go test -v && go vet"))

  ;; guru settings
  (go-guru-hl-identifier-mode)                    ; highlight identifiers
  
  ;; Key bindings specific to go-mode
  (local-set-key (kbd "M-.") 'godef-jump)         ; Go to definition
  (local-set-key (kbd "M-*") 'pop-tag-mark)       ; Return from whence you came
  (local-set-key (kbd "M-p") 'compile)            ; Invoke compiler
  (local-set-key (kbd "M-P") 'recompile)          ; Redo most recent compile cmd
  (local-set-key (kbd "M-]") 'next-error)         ; Go to next error (or msg)
  (local-set-key (kbd "M-[") 'previous-error)     ; Go to previous error or msg

  ;; Misc go stuff
  (auto-complete-mode 1))                         ; Enable auto-complete mode

;; Connect go-mode-hook with the function we just defined
(add-hook 'go-mode-hook 'my-go-mode-hook)

;; Ensure the go specific autocomplete is active in go-mode.
(with-eval-after-load 'go-mode
   (require 'go-autocomplete))

;; If the go-guru.el file is in the load path, this will load it.
(require 'go-guru)

Step-by-step explainations

Now that I’ve summarized everything above, if you really want to understand each step, or if you want to be picky about what you include, read on!

Environment Vars

Edit ~/.bash_profile so it exports the GOROOT, GOPATH, and has appropriate entries to the executable PATH:

# file: ~/.bash_profile
export GOROOT=/usr/local/go            
export GOPATH=~/Development/gocode
export PATH=$PATH:$GOROOT/bin
export PATH=$PATH:$GOPATH/bin

Make sure this takes effect. You can use source ~/.bash_profile; I just start a new terminal.

Get go tools

Get the associated development tools like godoc. Since I’ve installed these things before, I’ll use the -u flag to update what I already have.

$ go get -u golang.org/x/tools/cmd/...

Set up Emacs config file

Create a new ~/.emacs.d/init.el file, or edit an existing one.

Use the Melpa emacs package repo

; file: ~/.emacs.d/init.el
(require 'package) ;; You might already have this line
(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))
(package-initialize) ;; You might already have this line

Now if you restart emacs and do M-x package-list-packages it will contact the melpa.org server and you’ll see a bunch of packages in the list; many from gnu, some others from melpa.

Install go-mode

Run M-x package-install and install go-mode. In recent emacs it loads this automatically, so if you start foo.go it puts you into Go mode.

Install + use path helper

M-x package-install and install exec-path-from-shell.

Now add the following to your emacs config file. It will invoke a shell and have it print out PATH and GOPATH values. It adds a couple seconds to your emacs startup time, unfortunately.

; file: .emacs.d/init.el
(when (memq window-system '(mac ns))
  (exec-path-from-shell-initialize)
  (exec-path-from-shell-copy-env "GOPATH"))

Install godef

Install the godef tool from the cmd line, again using -u to update an existing version:

$ go get -u github.com/rogpeppe/godef/...

Note for gb users

godef will not know how to find your project’s files until you run a special function to figure out a proper GOPATH. You can do it manually or add a hook to run it as needed: M-x go-set-project. I ended up using projectile (a project-centric emacs plugin) and issue the go-set-project in the project switching hook.

Projectile

I used package-install to install projectile and flx-ido to get around the gb/godef/GOPATH problem that was just mentioned. This is totally optional, but instructions are here for posterity. Once installed, edit your config file to contain:

(projectile-mode)
(defun my-switch-project-hook ()
  (go-set-project))
(add-hook 'projectile-after-switch-project-hook #'my-switch-project-hook)

Now, you’ll have projectile mode begun automatically when emacs starts, and it runs go-set-project every time you switch projects with C-c p p (even if the target project is in Javascript or Ook).

Create Go mode load hook

Emacs will load go-mode when you open a go source file. You can customize how your go-mode behaves by adding stuff to a hook. This is a good place to add other customizations for go-mode as well.

; file: .emacs.d/init.el
(defun my-go-mode-hook ()
  (add-hook 'before-save-hook 'gofmt-before-save) ; gofmt before every save
  ; Godef jump key binding                                                      
  (local-set-key (kbd "M-.") 'godef-jump)
  (local-set-key (kbd "M-*") 'pop-tag-mark)
  )
(add-hook 'go-mode-hook 'my-go-mode-hook)

Other go mode customizations

These statements appear within the my-go-mode-hook definition. This way the effects are localized to go mode.

(local-set-key (kbd "M-p") 'compile)            ; Invoke compiler
(local-set-key (kbd "M-P") 'recompile)          ; Redo most recent compile cmd
(local-set-key (kbd "M-]") 'next-error)         ; Go to next error (or msg)
(local-set-key (kbd "M-[") 'previous-error)     ; Go to previous error or msg

Autocomplete and go-autocomplete

In Emacs, M-x package-install auto-complete.

Add this statement to the go mode hook’s function definition:

; file: ~/.emacs.d/init.el
(auto-complete-mode 1)

Now when you load a go source file, emacs will say you’re in mode Go AC.

Next install go-autocomplete:

$ go get -u github.com/nsf/gocode

And M-x package-install go-autocomplete.

Special note for gb users

The gocode binary will not be able to autocomplete symbols unless it is configured to use gb semantics. To manually switch between go and gb project structures, do this in a terminal:

$ gocode set package-lookup-mode gb

It should output package-lookup-mode "gb". If you need to work on something in a traditional go project structure, run the same command but with go instead of gb.

If anyone knows how to make go-autocomplete detect the project environment, let me know.

goimports

Install goimports, which organizes the imports at the top of each file. It also adds/removes things intelligently. The go get isn’t necessary if you installed all the tools above with golang.org/x/tools/cmd/....

$ go get -u golang.org/x/tools/cmd/goimports

And somewhere in your my-go-mode-hook():

; file: ~/.emacs.d/init.el
(setq gofmt-command "goimports")

guru (replaces the deprecated oracle tool)

guru is a source code analysis tool that works with any editor to give you industrial-strength IDE stuff like call graphs (what calls X, find the definition of X, what are the aliases for X, etc).

You’ve already installed guru when you installed the mess of go tools earlier. guru is replaces the oracle tool, which you might have read about.

Get go-guru.el and put it somewhere in your load path. I put it in ~/.emacs.d/lisp. I’m not sure why this should be necessary, given that go-mode is a thing, but you have to load it yourself.

; file: ~/.emacs.d/init.el - top level, not in your go hook
(add-to-list 'load-path "~/.emacs.d/lisp/")
(require 'go-guru)

And to configure emacs to do nice things with guru, consider adding stuff to your go mode load hook.

; file: ~/.emacs.d/init.el
(go-guru-hl-identifier-mode)

By default all of the commands are invoked via C-c C-o ? where ? is a single letter (so, lift up on the Control key). They designed the keybindings pretty well so they are fairly mnemonic:

For some of these you have to set a scope. I’m fuzzy on exactly what this means. I tried main (the name of the package I was working in) and it did not work. I then tried . and it worked. The documentation says “The scope is typically specified as a comma-separated set of packages, or wildcarded subtrees like github.com/my/dir/…”.

flymake

Flymake will build your source file as you edit it, using a shadow copy of in-memory file, rather than the one on disk. It will show syntax errors as you make them.

M-x package-install flymake-go

$ go get -u github.com/dougm/goflymake

Unrelated to Golang

Neotree

Neotree is a popular sidebar thing to show file structure.

M-x package-install neotree

Now navigate to a source directory (or whatever) and do M-x neotree-dir and you can tell it which directory you want to open as a sidebar.

Color Themes

The default look is pretty standard but you can personalize it with color themes. First check out this compendium of emacs themes and find some you like. I installed one via M-x package-install atom-one-dark-theme, and added a this line below the (package-initialize) call in my emacs init:

; file: ~/.emacs.d/init.el
(load-theme 'atom-one-dark t)