Skip to content

An Emacs development environment for Julia

License

Notifications You must be signed in to change notification settings

esc-emacs/julia-snail

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Julia Snail

snail.png

Snail is a development environment and REPL interaction package for Julia in the spirit of Common Lisp’s SLIME and Clojure’s CIDER. It enables convenient and dynamic REPL-driven development.

Features

REPL display
Snail uses libvterm with Emacs bindings to display Julia’s native REPL in a good terminal emulator. As a result, the REPL has good performance and far fewer display glitches than attempting to run the REPL in an Emacs-native term.el buffer.
REPL interaction
Snail provides a bridge between Julia code and a Julia process running in a REPL. The bridge allows Emacs to interact with and introspect the Julia image. Among other things, this allows loading entire files and individual functions into running Julia processes.
Cross-referencing
Snail is integrated with the built-in Emacs xref system. When a Snail session is active, it supports jumping to definitions of functions and macros loaded in the session.
Completion
Snail is also integrated with the built-in Emacs completion-at-point facility. Provided it is configured with the company-capf backend, company-mode completion will also work (this should be the case by default).
Parser
Snail contains a limited but serviceable Julia parser, used to infer the structure of source files and to enable features which require an understanding of code context, especially the module in which a particular piece of code lives. This enables awareness of the current module for completion and cross-referencing purposes.

Installation

Julia versions 1.0–1.3 work. No packages need to be installed on the Julia side (other than Julia itself).

On the Emacs side:

  1. Make sure you have Emacs 26.2 or later, compiled with module support (--with-modules). Check the value of module-file-suffix: it should be non-nil. (This is currently a default compile-time option Emacs distributed with Homebrew.)
  2. Install libvterm. It is available in Homebrew and Ubuntu 19.10, and in source form on other systems.
  3. Install emacs-libvterm using your Emacs package manager. It is available from MELPA. It is important to do this step separately from the julia-snail installation, as you may run into compilation problems.
  4. Install julia-snail using your Emacs package manager (see below for a sample use-package invocation).

Configuration

use-package setup

(use-package julia-snail
  :hook (julia-mode . julia-snail-mode)
  :config (progn
            (add-to-list 'display-buffer-alist
              '("\\*julia" (display-buffer-reuse-window
                            display-buffer-same-window)))
            ))

Manual setup

(add-to-list 'load-path "/path/to/julia-snail")
(require 'julia-snail)
(add-hook 'julia-mode-hook #'julia-snail-mode)
  • Configure display-buffer-alist to make REPL window switching smoother.
  • Configure key bindings in julia-snail-mode-map as desired.

Usage

Basics

Once Snail is properly installed, open a Julia source file. If julia-mode-hook has been correctly configured, julia-snail-mode should be enabled in the buffer (look for the Snail lighter in the modeline).

Start a Julia REPL using M-x julia-snail or C-c C-z. This will load all the Julia-side supporting code Snail requires, and start a server. The server runs on a TCP port (10011 by default) on localhost. You will see JuliaSnail.start(<port>) execute on the REPL.

The REPL buffer uses libvterm mode, and libvterm configuration and key bindings will affect it.

If the Julia program uses Pkg, then run M-x julia-snail-package-activate or C-c C-a to enable it. (Doing this using REPL commands like ] also works as normal.)

Load the current Julia source file using M-x julia-snail-send-buffer-file or C-c C-k. Notice that the REPL does not show an include() call, because the command executed across the Snail network connection. Among other advantages, this minimizes REPL history clutter.

Once some Julia code has been loaded into the running image, Snail can begin introspecting it for purposes of cross-references and identifier completion.

The julia-snail-mode minor mode provides a key binding map (julia-snail-mode-map) with the following commands:

keycommanddescription
C-c C-zjulia-snailstart a REPL; flip between REPL and source
C-c C-ajulia-snail-package-activateactivate the project using Project.toml
C-c C-djulia-snail-doc-lookupdisplay the docstring of the identifier at point
C-c C-cjulia-snail-send-top-level-formevaluate function around the point in the current module
C-M-xjulia-snail-send-top-level-formditto
C-c C-rjulia-snail-send-regionevaluate active region in the current module
C-c C-ljulia-snail-send-linecopy current line directly to REPL
C-c C-kjulia-snail-send-buffer-fileinclude() the current buffer’s file

Several commands include the note “in the current module”. This means the Snail parser will determine the enclosing module...end statements, and run the relevant code in that module. If the module has already been loaded, this means its global variables and functions will be available.

In addition, most xref commands are available (except xref-find-references). xref-find-definitions, by default bound to M-., does a decent job of jumping to function and macro definitions. Cross-reference commands are current-module aware.

Completion also works. Emacs built-in completion features, as well as company-complete, will do a reasonable job of finding the right completions in the context of the current module (though will not pick up local variables). Completion is current-module aware.

Multiple REPLs

To use multiple REPLs, set the local variables julia-snail-repl-buffer and julia-snail-port. They must be distinct per-project. They can be set at the file level, or at the directory level. The latter approach is recommended, using a .dir-locals.el file at the root of a project directory.

For example, consider two projects: Mars and Venus, both of which you wish to work on at the same time. They live in different directories.

The Mars project directory contains the following .dir-locals.el file:

((julia-mode . ((julia-snail-port . 10050)
                (julia-snail-repl-buffer . "*julia Mars*"))))

The Venus project directory contains the following .dir-locals.el file:

((julia-mode . ((julia-snail-port . 10060)
                (julia-snail-repl-buffer . "*julia Venus*"))))

(Be sure to refresh any buffers currently visiting files in Mars and Venus using find-alternate-file or similar after changing these variables.)

Now, source files in Mars will interact with the REPL running in the *julia Mars* buffer, and source files in Venus will interact with the REPL running in the *julia Venus* buffer.

Multiple Julia versions

The julia-snail-executable variable can be set at the file level or at the directory level and point to different versions of Julia for different projects. It should be a string referencing the executable binary path.

NB: On a Mac, the Julia binary is typically Contents/Resources/julia/bin/julia inside the distribution app bundle. You must either make sure julia-snail-executable is set to an absolute path, or configure your Emacs exec-path to correctly find the julia binary.

Future improvements

Foundational

  • The Julia interaction side of the Snail server is single-threaded (using @async). This means the interaction locks up while the REPL is working or running code. Unfortunately, Julia as of version 1.3 does not have user-accessible low-level multithreading primitives necessary to implement a truly multi-threaded Snail server.

Structural

  • The libvterm dependency forces the use of very recent Emacs releases, forces Emacs to be build with module support, complicates support for Windows, and is generally quite gnarly. It would be much better to re-implement the REPL in Elisp.
  • The current parser leaves much to be desired. It is woefully incomplete: among many other things, it cannot detect one-line top-level definitions (such as f(x) = 10x). In addition: it is slow, and not particularly straightforward in implementation. A rewrite would work better and enable more features. Unfortunately, parsers are hard. :)

Functional

  • The completion search does not include anything pulled into a module with using. Julia does not seem to have a built-in incantation for doing this (names with imported=true only includes explicitly imported parameters). This can be worked around on the Snail side, by extending the parser to recognize using statements.
  • Completion does not pick up local variables. This is yet another weakness of the parser.
  • A real eldoc implementation would be great, but difficult to do with Julia’s generic functions. The parser would also have to improve (notice a theme here?).
  • A debugger would be great.

About

An Emacs development environment for Julia

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Emacs Lisp 86.2%
  • Julia 13.8%