Setting up a Julia workflow in Emacs

Oct 17, 2021 · 799 words · 4 minute read

Introduction

Recently, I have been playing with Julia and I must say I am really enyoing it. But to really start having fun with it, I needed to set up a workflow in Emacs that was similar to my Python workflow.

There are three main ingredients to my workflow. When I open up a Julia buffer in emacs, I would like to open a Julia REPL inside emacs in a vertical split. Then I would like to be able to send chunks of code from the code buffer to the REPL. Finally, we need code completion and other nice features of an IDE. So the three things we need are

I like to have two windows open side-by-side in Emacs. The left one contains the code that I am working on, and the one on the right contain a live REPL. I like to use a Jupyter notebook style of working with “cells” that I can execute with C-RET and see the results immediately in the REPL. I find this style gives me the best of both worlds – I get this notebook style cell-based workflow with all the editing powers of emacs.

Preliminaries

My emacs configuration extensively uses use-package. I also use the awesome evil-mode, so my configuration may use evil-specific commands to set up keybindings. For LSP, I am also assuming that you have already installed and enabled the lsp-mode package.

Julia REPL

vterm

Using vterm istead of the regular emacs terminal gives us all the power of Julia repl, including colors, completion, etc.

(use-package vterm
    :ensure t)

Julia REPL configuration

(use-package julia-mode
  :ensure t)

(use-package julia-repl
  :ensure t
  :hook (julia-mode . julia-repl-mode)

  :init
  (setenv "JULIA_NUM_THREADS" "8")

  :config
  ;; Set the terminal backend
  (julia-repl-set-terminal-backend 'vterm)
  
  ;; Keybindings for quickly sending code to the REPL
  (define-key julia-repl-mode-map (kbd "<C-RET>") 'my/julia-repl-send-cell)
  (define-key julia-repl-mode-map (kbd "<M-RET>") 'julia-repl-send-line)
  (define-key julia-repl-mode-map (kbd "<S-return>") 'julia-repl-send-buffer))

Now a Julia buffer can be started using C-c C-z. You get a fully featured Julia REPL, thanks to the vterm backend. julia-repl already provides the nice functions to send individual lines or the entire buffer to the REPL, so I bind them to M-RET and S-RET, respectively. However, usually I need to send a larger chunk of code. That’s where the function my/julia-repl-send-cell comes in. I explain it later.

IDE-like features using Language Server Protocol

The package lsp-julia worked out of the box for me without much fuss, which is always a nice feeling. Assuming that you already have LSP set up on your emacs, then I just needed to instal lsp-julia as mentioned on their README and add the following my emacs config.

(quelpa '(lsp-julia :fetcher github
                    :repo "non-Jedi/lsp-julia"
                    :files (:defaults "languageserver")))

(use-package lsp-julia
  :config
  (setq lsp-julia-default-environment "~/.julia/environments/v1.6"))

(add-hook 'julia-mode-hook #'lsp-mode)

Jupyter-notebook like code cells

This is my favorite part of the workflow. Julia code between ### is treated as a “cell”. If you press C-Ret, the current cell is sent to the julia shell and executed. This extremely uesful when writing scripts. Almost all of my code is written in this way. You can basically a jupyter-notebook style convenience of having cells while having the power of Emacs.

We define the function my/julia-repl-send-cell which sends the cell to the REPL. I like to bind this to C-RET, as shown above.

(defun my/julia-repl-send-cell() 
  ;; "Send the current julia cell (delimited by ###) to the julia shell"
  (interactive)
  (save-excursion (setq cell-begin (if (re-search-backward "^###" nil t) (point) (point-min))))
  (save-excursion (setq cell-end (if (re-search-forward "^###" nil t) (point) (point-max))))
  (set-mark cell-begin)
  (goto-char cell-end)
  (julia-repl-send-region-or-line)
  (next-line))

Returning to the original location after executing a cell

The above command will execute the current cell and take you to the next cell, such that hitting C-RET repeatedly executes succesive cells, which is very convenient. However, sometimes I want to execute the cell and remain at the same line because I am in middle of testing that cell. I often use evil-mode’s jump list to go back to previous cursor locations, with keybindings C-o and C-i (for going backward and forward in the jump list, respectively). So we can just hit C-o to go back to where we were before executing the cell, but we need to tell evil to save the location to jump list. This can be done by simply adding an evil commmnd property.

;; Allow the use of evil jump list C-o to jump back to where we were before executing the cell
(evil-add-command-properties #'my/julia-repl-send-cell :jump t)

Now, I can use C-RET C-o to execute the call and go back to where I was.