Minimal Setup for Elixir development in Emacs

I've been working professionally with Elixir for 7 months, obviously using emacs as my daily editor. In this post we'll see some packages that could be useful for Elixir development.

I'm not using LSP (yet), so the goal here is just to have a minimal setup for Elixir development.

We're going to use use-package to install all the needed packages.

Syntax highlighting

We'll use Elixir-mode. This package give us syntax highlighting support and some useful features like mix-format which let us format our code using mix format task, which is available since Elixir 1.6


  (use-package elixir-mode
    :ensure t
    :bind (:map elixir-mode-map
                ("C-c C-f" . elixir-format)))

Go to definition

A LSP backed package like elixir-lsp could be way more accurate for this functionality but now we're going to use dumb-jump.

This package give us "go to definition" functionality just using regex. It has support for Elixir out the box and it works pretty well.

We'll use helm to show the different options when dumb-jump finds more than one definition for the same term.


  (use-package dumb-jump
    :ensure t
    :init
    (setq dumb-jump-selector 'helm))

Snippets

Using snippets can improve the speed of writing code. I'm using yasnippet, a snippets template system, and also a set of snippets(for many languages including Elixir) called yasnippet-snippets.

We can install these packages with the following code. Notice that we're enabling yasnippet only for a few modes. If you want to you can enable it globally using (yas-global-mode 1).


  (use-package yasnippet
    :ensure t
    :hook ((prog-mode . yas-minor-mode)
           (conf-mode . yas-minor-mode)
           (text-mode . yas-minor-mode)
           (snippet-mode . yas-minor-mode)))

  (use-package yasnippet-snippets
    :ensure t
    :after (yasnippet))

Running tests

Running tests without leaving our editor is a nice feature to have in any editor/IDE. Unfortunately I haven't found any package that does this, so I wrote some elisp code to accomplish it.

To make this happen we're going to use feature called compile. Compile allows us to execute a shell command and print the resulting output in a "compilation" buffer so we can see the results.

We can run tests using mix test in different ways

  • mix test: run all the tests within our project.

  • mix test path_to_test_file.exs: run the given test file.

  • mix test path_to_test_file.exs:line_number: run the test defined around the given line number.

There are some other ways but these are the ones we're going to use.


  (defun my/mix-run-test (&optional at-point)
    "If AT-POINT is true it will pass the line number to mix test."
    (interactive)
    (let* ((current-file (buffer-file-name))
           (current-line (line-number-at-pos))
           (mix-file (concat (projectile-project-root) "mix.exs"))
           (default-directory (file-name-directory mix-file))
           (mix-env (concat "MIX_ENV=test ")))

      (if at-point
          (compile (format "%s mix test %s:%s" mix-env current-file current-line))
        (compile (format "%s mix test %s" mix-env current-file)))))

  (defun my/mix-run-test-file ()
    "Run mix test over the current file."
    (interactive)
    (my/mix-run-test nil))

  (defun my/mix-run-test-at-point ()
    "Run mix test at point."
    (interactive)
    (my/mix-run-test t))

We have three functions, a "private" function called my/mix-run-test which will do all the "magic", this function will get some data about the context where it was called. We got the current-file and current-line from where the function was called, and now we'll use this to build our shell command and then use this shell command to call compile.

Now we can create two more functions to expose two different behaviors, running an entire test file or just the test our cursor is placed on. We're going to use the latest to have a keybinding a run the test quickest.

Now we can add this to our previous code:


  (use-package elixir-mode
    :ensure t
    :bind (:map elixir-mode-map
                ("C-c C-f" . elixir-format)
                ("C-c C-t" . my/mix-run-test-at-point)))

Now if we press C-c C-t in a file(my_test.exs) where our cursor is on line 10, emacs will build the command mix test my_test.exs:10 and run it in a compilation buffer.

Others useful packages

  • Projectile: It's a package to handle many projects. It allows us to switch between projects easily.

  • Magit: The best interface so far for use git. If you haven't use it you definitively should give it a try.

  • direnv-mode: It's a package to load environment variables using a .envrc file. It's useful to load all the environment variables you need for a project.

Conclusion

I use this setup with a few tweaks in my daily work and it works pretty well for my needs.