Changing the Erlang shell prompt
Did you know that you can change the Erlang shell prompt?
I previously wrote about colouring the ‘iex’ prompt. Can we do the same for the Erlang shell prompt?
We need a module containing our new prompt function; we’ll start with something basic, as follows:
-module(shell_prompt).
-export([prompt_func/1]).
prompt_func(_Opts) ->
"erl> ".
Compile it, and put the result in $HOME/ebin
:
mkdir -p "$HOME/ebin"
erlc -o "$HOME/ebin" shell_prompt.erl
We need a $HOME/.erlang
file:
code:load_abs(filename:join([os:getenv("HOME"), "ebin", "shell_prompt"])).
shell:prompt_func({shell_prompt, prompt_func}).
The first line loads $HOME/ebin/shell_prompt.beam
; the second line sets a function from that module as the prompt
function.
Alternatively, we could allow loading arbitrary modules from $HOME/ebin
. In that case, the first line would look like this instead:
code:add_patha(filename:join([os:getenv("HOME"), "ebin"])).
.erlang
file is loaded on every Erlang instantiation, including things like running Elixir’s mix
and so on.
Bear that in mind before you do anything too involved.
That results in a shell prompt that looks like this:
Erlang/OTP 25 [erts-13.0.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
Eshell V13.0.4 (abort with ^G)
erl> █
We can make our prompt look identical to the default Erlang prompt with the following:
prompt_func(Opts) ->
prompt_func(erlang:is_alive(), lists:keyfind(history, 1, Opts)).
prompt_func(false, {history, N}) ->
io_lib:format("~B> ", [N]);
prompt_func(true, {history, N}) ->
io_lib:format("(~s)~B> ", [node(), N]).
From the documentation:
The function is called as
Mod:Func(L)
, whereL
is a list of key-value pairs created by the shell. Currently there is only one pair:{history, N}
, whereN
is the current command number.
I decided to allow for the case where future Erlang adds more things to the list.
Aside: the default prompt implementation uses ~w
(format as an Erlang term) to display the history number. I’m not
sure why. I suspect it’s just in case it’s not a number.
I like to also display the shell process ID in the prompt:
prompt_func(Opts) ->
prompt_func(erlang:is_alive(), lists:keyfind(history, 1, Opts)).
prompt_func(false, {history, N}) ->
io_lib:format("~w ~B> ", [self(), N]);
prompt_func(true, {history, N}) ->
io_lib:format("~w (~s)~B> ", [self(), node(), N]).
That looks like this:
$ erl -sname foo
Erlang/OTP 25 [erts-13.0.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
Eshell V13.0.4 (abort with ^G)
<0.86.0> (foo@ROGER-SURFACEBOOK3)1> █
Let’s add some colour:
-define(Reset, "\e[0m").
-define(IRed, "\e[0;91m").
-define(IGreen, "\e[0;92m").
-define(IYellow, "\e[0;93m").
-define(IWhite, "\e[0;97m").
prompt_func(Opts) ->
prompt_func(erlang:is_alive(), get_colour(), lists:keyfind(history, 1, Opts)).
prompt_func(false, _Colour, {history, N}) ->
io_lib:format("~w ~B> ", [self(), N]);
prompt_func(true, Colour, {history, N}) ->
io_lib:format("~w (" ++ Colour ++ "~s" ?Reset ")~B> ", [self(), node(), N]).
get_colour() ->
get_colour(os:getenv("WHICH_ENVIRONMENT")).
get_colour(false) -> ?IWhite;
get_colour("dev") -> ?IGreen;
get_colour("test") -> ?IYellow;
get_colour(_) -> ?IRed.
That comes out looking like this:
$ WHICH_ENVIRONMENT=dev erl -sname foo
Erlang/OTP 25 [erts-13.0.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
Eshell V13.0.4 (abort with ^G)
<0.86.0> (foo@ROGER-SURFACEBOOK3)1> █
I had problems in the distant past (Erlang R16 or so) which made me go back to a monochrome prompt. Maybe I’ll have better luck this time.
Unlike Elixir and its .iex.exs
file, Erlang doesn’t look in the current directory for the .erlang
file; per the
documentation:
When Erlang/OTP is started, the system searches for a file named
.erlang
in the user’s home directory and thenfilename:basedir(user_config, "erlang")
.
On Linux, that usually resolves to $HOME/.config/erlang
.
It would occasionally be nice if it also looked in the current directory; I’ll try to write another blog post about that at some point.