Using Elixir 1.5's open command with terminal Emacs
Elixir 1.5 includes
some excellent new features,
especially around debugging. One of the subtle features is a new
open function inside
iex which opens your configured editor to the
file/line of the provided module and function. For example, if you run
the following in iex:
iex> open URI.decode_query
Elixir will open the URI module source code in your editor, at the
decode_query is defined. This works for both your library
code as well as the standard library source. It’s incredibly useful to
jump to code as you’re debugging inside
The open command works by looking for the
environment variables. This works great for GUI editors like sublime,
where you can simply set
export ELIXIR_EDITOR="subl" and be on your
way. For terminal based editors like Emacs, some hacking was involved
to make it work.
First things first, I wanted
open to align with my workflow. It
wasn’t enough for
open to launch the buffer inside a single Emacs
instance, since I often have half a dozen tmux sessions, each with
their own Emacs instance for the project and iex sessions running. I
needed a solution that allowed
open to target
ELIXIR_EDITOR at my
current project’s Emacs process. And down the rabbit hole I went.
To make it happen, I made use of Emacs’ built in client/server feature
where Emacs can start a “server” and
emacsclient can attach or
interact with it from elsewhere. For the “current project” target, I
first check for an active Git repo, and fallback to the current
directory basename. Additionally, when Emacs launches, I start an
Emacs server using this current project name. Lastly, I target
ELIXIR_EDITOR at a custom bash script which checks the current
project and calls
emacsclient with the appropriate server name.
Let’s break it down.
Note: we must save a couple bash scripts inside
instead of defining them somewhere in user-land. We have to do this
because Elixir and Emacs load our shell environment differently from
user-land, so things like our
.bash_profile won’t be loaded.
First, create a new file named
/usr/local/bin/current_project_name, with these contents:
#!/usr/bin/env sh if git rev-parse --git-dir > /dev/null 2>&1; then echo `basename $(git rev-parse --show-toplevel)` else echo `basename $(pwd)` fi
Next, you need to make the file executable with:
$ chmod +x /usr/local/bin/current_project_name
git rev-parse to get the current Git repo directory, so your
“current project” name will be correct, even if you are inside a child
directory of the project. If no Git repo is found, it falls back to
the basename of the current working directory. Now, you can run
current_project_name in your shell to test it out.
Next, we need to define a command to launch our
emacsclient based on
the current project. Define a new
emacsclient-elixir file at
/usr/local/bin/emacsclient-elixir with the following contents:
#!/usr/bin/env sh emacsclient -s $(current_project_name) $@
Next, make sure it’s executable:
$ chmod +x /usr/local/bin/emacsclient-elixir
We simply call
emacsclient with the
-s option, which uses our
current_project_name script to target the correct Emacs server.
Next, let’s make Elixir aware of our new editor command. Add the
following export to your environment in one of
.zshrc, or similar:
export ELIXIR_EDITOR="emacsclient-elixir +__LINE__ __FILE__"
The last step is to configure Emacs to start a server with the current
project name when it launches. Add the following to your
~/.emacs.d/init.el or the location of your Emacs init script:
(setq server-name (replace-regexp-in-string "\n$" "" (shell-command-to-string "current_project_name"))) (unless (server-running-p (symbol-value 'server-name)) (server-start) )
We have Emacs shell out to our
current_project_name script, then set
server-name based on that value. Lastly, we call
to boot the server.
Now we can try it out, but be sure to reload any terminal shell to grab the new commands and environment variables. Here it is in action inside the Phoenix project:
That’s it! Happy hacking!