Last update: November 22, 2025
In this blog post I will walk you through my current Elvish configuration file, with running commentary about the different sections.
This is also my first blog post written using org-mode, which I have started using for writing and documenting my code, using literate programming. The content below is included unmodified from my rc.org file (as of the date shown above), from which the rc.elv file is directly generated.
If you are interested in writing your own Literate Config files, check out my new book Literate Config on Leanpub!
Without further ado…
Module loading#
Load a number of commonly-used modules so that they are available in my interactive session.
Load the bundled re module to have access to regular expression functions.
use re
The bundled readline-binding module associates some Emacs-like keybindings for manipulation of the command line.
use readline-binding
The bundled path and os modules contains path manipulation/checking and other functions.
use path
use os
The bundled str and math modules for string manipulation and math operations.
use str
use math
Some code runs only if certain external binary exists, I define a couple of functions to help with this.
fn only-when-external { |prog lambda|
if (has-external $prog) { $lambda }
}
# Convert POSIX env assignments to Elvish
fn read-posix-envvars {
each {|l|
var _ key val = (re:split &max=3 '[ =]' $l)
set val = (re:replace '^"' '' (re:replace '"$' '' $val))
set-env $key $val
}
}
Paths#
First we set up the executable paths. We set the GOPATH environment variable while we are at it, since we need to use it as part of the path.
# Where all the Go stuff is
#if (path:is-dir ~/Dropbox/Personal/devel/go) {
# set E:GOPATH = ~/Dropbox/Personal/devel/go
#} else {
set E:GOPATH = ~/go
#}
var brew-paths = []
only-when-external /home/linuxbrew/.linuxbrew/bin/brew {
/home/linuxbrew/.linuxbrew/bin/brew shellenv | grep -v PATH | sed 's/;$//;' | read-posix-envvars
set brew-paths = [$E:HOMEBREW_PREFIX/bin $E:HOMEBREW_PREFIX/sbin]
}
# Optional paths, add only those that exist
var optpaths = [
$E:GOPATH/bin
$@brew-paths
~/.local/bin
~/bin/(uname -s | tr '[:upper:]' '[:lower:]')-(uname -m)
~/.emacs.d/bin
~/.local/share/flatpak/exports/bin
/var/lib/flatpak/exports/bin
/usr/local/opt/coreutils/libexec/gnubin
/usr/local/opt/texinfo/bin
/usr/local/opt/python/libexec/bin
/usr/local/go/bin
/snap/bin
~/Work/automated-security-helper
~/.toolbox/bin
~/.local/bin
~/.local/share/omakub/bin
]
var optpaths-filtered = [(each {|p|
if (path:is-dir $p) { put $p }
} $optpaths)]
set paths = [
~/bin
$@optpaths-filtered
/usr/local/bin
/usr/local/sbin
/usr/sbin
/sbin
/usr/bin
/bin
]
My work machine setup blocks proxy.golang.org, so I configure for all modules to be downloaded directly from their source.
set E:GONOPROXY = "*"
I have a quick sanity check because sometimes certain paths disappear depending on new versions, etc. This prints a warning when opening a new shell, if there are any non-existing directories in $paths. We need some wrapping around path:eval-symlinks to avoid seeing warnings when the directory does not exist.
each {|p|
if (not (path:is-dir &follow-symlink $p)) {
echo (styled "Warning: directory "$p" in $paths no longer exists." red)
}
} $paths
Package installation#
The bundled epm module allows us to install and manage Elvish packages.
use epm
For now I use these packages:
- github.com/zzamboni/elvish-modules contains all my modules except completions and themes. Maybe these should be separated eventually, but for now this works fine.
- github.com/zzamboni/elvish-themes contains my prompt themes (only chain for now).
- github.com/zzamboni/elvish-completions contains my completer definitions.
- github.com/xiaq/edit.elv, which includes the
smart-matchermodule used below. - github.com/muesli/elvish-libs for the git utilities module.
- github.com/iwoloschin/elvish-packages for the update.elv package.
epm:install &silent-if-installed ^
github.com/zzamboni/elvish-modules ^
github.com/zzamboni/elvish-completions ^
github.com/xiaq/edit.elv ^
github.com/muesli/elvish-libs
# github.com/iwoloschin/elvish-packages
The modules within each package get loaded individually below.
Automatic proxy settings#
When I am in the office, I need to use a proxy to access the Internet. For macOS applications, the proxy is set automatically using a company-provided PAC file. For the environment variables http_proxy and https_proxy, commonly used by command-line programs, the proxy module allows me to define a test which determines when the proxy should be used, so that the change is done automatically. We load this early on so that other modules which need to access the network get the correct settings already.
First, we load the module and set the proxy host.
use github.com/zzamboni/elvish-modules/proxy
set proxy:host = "http://aproxy.corproot.net:8080"
Next, we set the test function to enable proxy auto-setting. In my case, the /etc/resolv.conf file contains the corproot.net domain (set through DHCP) when I’m in the corporate network, so I can check for that.
proxy:test = {
and ?(test -f /etc/resolv.conf) ^
?(egrep -q '^(search|domain).*(corproot.net|company.com)' /etc/resolv.conf)
}
We run an initial check so that other commands in rc.org get the correctd settings already, even before the first prompt.
proxy:autoset
General modules and settings#
I add a couple of keybindings which are missing from the default readline-binding module:
Alt-backspaceto delete small-wordset edit:insert:binding[Alt-Backspace] = $edit:kill-small-word-left~Alt-dto delete the small-word under the cursorset edit:insert:binding[Alt-d] = $edit:kill-small-word-right~I also bind “instant preview mode” to Alt-m. This is useful to see the results of a command while you are typing it.
set edit:insert:binding[Alt-m] = $edit:-instant:start~Limit the height of location and history mode so that they don’t cover the whole screen.
set edit:max-height = 20
1Password#
My 1pass module provides some wrappers for interacting with the 1Password command line utility.
use github.com/zzamboni/elvish-modules/1pass
Read aliases defined by the op plugin command. See https://blog.1password.com/shell-plugins/ for more details about op shell plugins.
1pass:read-aliases
I haven’t gotten around to write an op plugin for this, so I still use my lazy-vars module to read the credentials for my 750words command-line client.
use github.com/zzamboni/elvish-modules/lazy-vars
set E:USER_750WORDS = diego@zzamboni.org
lazy-vars:add-var PASS_750WORDS { 1pass:get-password "750words.com" }
lazy-vars:add-alias 750words-client.py [ PASS_750WORDS ]
Aliases and miscellaneous functions#
Elvish does not have built-in alias functionality, but this is implemented easily using the alias module, which stores the alias definitions as functions under ~/.elvish/aliases/ and loads them automatically.
use github.com/zzamboni/elvish-modules/alias
For reference, I define here a few of my commonly-used aliases.
only-when-external dfc {
alias:new dfc e:dfc -p -/dev/disk1s4,devfs,map,com.apple.TimeMachine
}
only-when-external vagrant {
alias:new v vagrant
}
only-when-external hub {
alias:new git hub
}
Use bat as my default pager, if installed.
only-when-external bat {
alias:new cat bat --paging never --plain
alias:new more bat --paging always --plain
#set E:MANPAGER = "sh -c 'col -bx | bat -l man -p'"
}
only-when-external batcat {
alias:new cat batcat --paging never --plain
alias:new more batcat --paging always --plain
#set E:MANPAGER = "sh -c 'col -bx | batcat -l man -p'"
}
If available, use the scripts from bat-extras to replace some other commands, like man.
only-when-external batman {
alias:new man batman
}
Open man pages as PDF, I gathered this tip from https://twitter.com/MrAhmadAwais/status/1279066968981635075. Neat but not very useful for daily use, particularly with the batman integration above.
fn manpdf {|@cmds|
each {|c|
man -t $c | open -f -a /System/Applications/Preview.app
} $cmds
}
only-when-external fdfind {
alias:new fd fdfind
}
Completions#
The smart-matcher module tries prefix match, smart-case prefix match, substring match, smart-case substring match, subsequence match and smart-case subsequence match automatically.
use github.com/xiaq/edit.elv/smart-matcher
smart-matcher:apply
Other possible values for edit:completion:matcher are [p]{ edit:match-prefix &smart-case $p } for smart-case completion (if your pattern is entirely lower case it ignores case, otherwise it’s case sensitive). &smart-case can be replaced with &ignore-case to make it always case-insensitive.
I now use the universal Carapace completer module for most commends instead of custom-built Elvish completions:
# Enable the universal command completer if available.
# See https://github.com/rsteube/carapace-bin
only-when-external carapace {
eval (carapace _carapace | slurp)
}
One exception is the ssh completer, I like my custom version from the elvish-completions package better, because it completes hostnames from ~/.ssh/config instead of from ~/.ssh/known_hosts.
use github.com/zzamboni/elvish-completions/ssh
Prompt theme#
Starship#
I now use Starship for my prompt.
only-when-external starship {
eval (starship init elvish --print-full-init | slurp)
}
You can find my current Starship config file at https://gitlab.com/zzamboni/mac-setup/-/blob/master/files/homefiles/.config/starship.toml.
Chain#
I developed the chain prompt theme, ported from the fish theme at https://github.com/oh-my-fish/theme-chain. This whole section is disabled since I switched to Starship, but left here for reference.
epm:install &silent-if-installed github.com/zzamboni/elvish-themes
use github.com/zzamboni/elvish-themes/chain
chain:bold-prompt = $false
I set the color of the directory segment, the prompt chains and the prompt arrow in my prompt to a session-identifying color (a different color for each session).
chain:segment-style = [
&dir= session
&chain= session
&arrow= session
&git-combined= session
&git-repo= bright-blue
]
Customize some of the glyphs for the font I use in my terminal. I use the Fira Code font which includes ligatures, so I disable the last chain, and set the arrow segment to a combination of characters which shows up as a nice arrow.
chain:glyph[arrow] = "|>"
chain:show-last-chain = $false
Other prompt settings#
Elvish has a comprehensive mechanism for displaying prompts with useful information while avoiding getting blocked by prompt functions which take too long to finish. For the most part the defaults work well. One change I like to make is to change the stale prompt transformer function to make the prompt dim when stale (the default is to show the prompt in inverse video):
set edit:prompt-stale-transform = {|x| styled $x "bright-black" }
Another possibility is to make the prompt stay the same when stale - useful to avoid distractions (disabled for now):
# edit:prompt-stale-transform = $all~
I also like the continuous update of the prompt as I type (by default it only updates on Enter and on $pwd changes, but I like also git status changes to be updated automatically), so I increase its eagerness.
set edit:-prompt-eagerness = 10
iTerm2 shell integration support#
The iterm2 module provides support for iTerm2’s Shell Integration features. Note that iterm2:init must be called after setting up the prompt, hence this is done after loading the chain module above.
use github.com/zzamboni/elvish-modules/iterm2
iterm2:init
set edit:insert:binding[Ctrl-L] = $iterm2:clear-screen~
Long-running-command notifications#
The long-running-notifications module allows for producing a notification when a command takes longer than a certain time to finish (by default the period is 10 seconds). The module automatically detects when terminal-notifier is available on macOS and uses it to produce Mac-style notifications, otherwise it prints a notification on the terminal.
use github.com/zzamboni/elvish-modules/long-running-notifications
For Linux, modify the notification function to use an icon (default doesn’t include one).
set long-running-notifications:notification-fns[libnotify][notify] = {|cmd duration start|
notify-send --icon terminal "Finished: "$cmd "Running time: "$duration"s"
}
Directory and command navigation and history#
Elvish comes with built-in location and command history modes, and these are the main mechanism for accessing prior directories and commands. The weight-keeping in location mode makes the most-used directories automatically raise to the top of the list over time.
I have decades of muscle memory using !! and !$ to insert the last command and its last argument, respectively. The bang-bang module allows me to keep using them.
use github.com/zzamboni/elvish-modules/bang-bang
The dir module implements a directory history and some related functions. I alias the cd command to dir:cd so that any directory changes are kept in the history. I also alias cdb to dir:cdb function, which allows changing to the base directory of the argument.
use github.com/zzamboni/elvish-modules/dir
alias:new cd &use=[github.com/zzamboni/elvish-modules/dir] dir:cd
alias:new cdb &use=[github.com/zzamboni/elvish-modules/dir] dir:cdb
dir also implements a custom directory history chooser, which I bind to Alt-i (I have found I don’t use this as much as I thought I would - the built-in location mode works nicely).
set edit:insert:binding[Alt-i] = $dir:history-chooser~
I bind Alt-b/f and Alt-Left/Right to dir:left-small-word-or-prev-dir and dir:right-small-word-or-next-dir respectively, which “do the right thing” depending on the current content of the command prompt: if it’s empty, they move back/forward in the directory history, otherwise they move through the words of the current command.
set edit:insert:binding[Alt-b] = $dir:left-small-word-or-prev-dir~
set edit:insert:binding[Alt-f] = $dir:right-small-word-or-next-dir~
set edit:insert:binding[Alt-Left] = $dir:left-small-word-or-prev-dir~
set edit:insert:binding[Alt-Right] = $dir:right-small-word-or-next-dir~
If fzf is installed, then we can have a fancier history search instead of the default bound to Ctrl-R.
# Filter the command history through the fzf program. This is normally bound
# to Ctrl-R.
fn fzf_history {
var new-cmd = (
edit:command-history &dedup &newest-first &cmd-only |
to-terminated "\x00" |
try {
fzf --no-sort --read0 --info=hidden --exact ^
--query=$edit:current-command
} catch {
# If the user presses [Escape] to cancel the fzf operation it will exit
# with a non-zero status. Ignore that we ran this function in that case.
return
}
)
set edit:current-command = $new-cmd
}
only-when-external fzf {
set edit:insert:binding[Ctrl-R] = { fzf_history >/dev/tty 2>&1 }
}
I use eza as a replacement for the ls command, so I alias ls to it. Unfortunately, eza does not understand the -t option to sort files by modification time, so I explicitly look for the -lrt and -lrta option combinations (which I use very often, and always trip me off) and replace them with the correct options for eza. All other options are passed as-is.
only-when-external eza {
var eza-ls~ = { |@_args|
use github.com/zzamboni/elvish-modules/util
e:eza --color-scale --git --group-directories-first --header --group-directories-first --icons=auto (each {|o|
util:cond [
{ eq $o "-lrt" } "-lsnew"
{ eq $o "-lrta" } "-alsnew"
:else $o
]
} $_args)
}
edit:add-var ls~ $eza-ls~
alias:new lt eza --tree --level=2 --long --icons --git
}
Dynamic terminal title#
The terminal-title module handles setting the terminal title dynamically according to the current directory or the current command being executed.
use github.com/zzamboni/elvish-modules/terminal-title
Loading private settings#
The private module sets up some private settings such as authentication tokens. This is not on github :) The $private-loaded variable gets set to $ok if the module was loaded correctly.
var private-loaded = ?(use private)
O’Reilly Atlas#
I sometimes use the O’Reilly Atlas publishing platform. The atlas module contains some useful functions for triggering and accessing document builds.
use github.com/zzamboni/elvish-modules/atlas
OpsGenie#
I used OpsGenie at work for a while, so I put together the opsgenie library to make API operations easier. I don’t actively use or maintain this anymore.
use github.com/zzamboni/elvish-modules/opsgenie
LeanPub#
I use LeanPub for publishing my books, so I have written a few utility functions. I don’t use this regularly, I have much better integration using Hammerspoon and CircleCI, I wrote about it in my blog: Automating Leanpub book publishing with Hammerspoon and CircleCI. The Leanpub API key is fetched from 1Password when needed.
use github.com/zzamboni/elvish-modules/leanpub
set leanpub:api-key-fn = { 1pass:get-item leanpub &fields=["API key"] }
TinyTeX#
Tiny module with some utility functions for using TinyTeX.
use github.com/zzamboni/elvish-modules/tinytex
Conda integration#
Conda integration for Elvish. This is not yet in the main Conda distribution, but in a PR: https://github.com/conda/conda/pull/10731
The following block will get added to rc.elv by conda init elvish. Having it tangled out allows me to control where in the file it appears, since Conda only replaces/updates it instead of adding it again.
if (path:is-dir ~/Dropbox/Personal/devel/conda/devenv/bin) {
set @paths = ~/Dropbox/Personal/devel/conda/devenv/bin $@paths
}
only-when-external conda {
conda config --set auto_activate_base false
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
eval (~/Dropbox/Personal/devel/conda/devenv/bin/conda "shell.elvish" "hook" | upgrade-scripts-for-0.17 -lambda | slurp)"; conda activate aws"
# <<< conda initialize <<<
}
I can configure Conda to deactivate itself, or to set a default environment, through some files in my home directory.
conda-deactivate = ~/.conda-deactivate
conda-default-env = ~/.conda-default-env
if (path:is-regular $conda-deactivate) {
conda deactivate
} else {
if (path:is-regular $conda-default-env) {
conda activate (cat $conda-default-env)
}
}
Flatpak aliases#
The following code creates/updates short aliases for Flatpak-installed applications. I create them in my ~/bin/ directory, but could be anything else. It’s very simplistic: it only creates a symlink that points to the full binary, the symlink is named after the last dot-separated component of the full name (for example, /var/lib/flatpak/exports/bin/app.zen_browser.zen gets a symlink named zen).
each {|dir|
if (os:is-dir $dir) {
each {|f|
var short = (str:to-lower (echo $f | re:awk &sep='\.' {|@f| print $f[-1]}))
ln -sf $f ~/bin/$short
} [$dir/*]
}
} [/var/lib/flatpak/exports/bin ~/.local/share/flatpak/exports/bin]
Mise#
If mise is installed, configure it.
only-when-external mise {
var mise: = (ns [&])
eval (mise activate elvish | slurp) &ns=$mise: &on-end={|ns| set mise: = $ns }
mise:activate
edit:add-var mise~ {|@args| mise:mise $@args }
}
PyEnv#
I do some manual setup for PyEnv, since it does not yet have built-in support for Elvish.
only-when-external pyenv {
set paths = [ ~/.pyenv/shims $@paths ]
set-env PYENV_SHELL elvish
}
Environment variables#
Default options to less.
set E:LESS = "-i -R"
Use vim as the editor from the command line (although I am an Emacs fan, I still sometimes use vim for quick editing).
set E:EDITOR = "vim"
Locale setting.
set E:LC_ALL = "en_US.UTF-8"
PKG_CONFIG configuration.
set E:PKG_CONFIG_PATH = "/usr/local/opt/icu4c/lib/pkgconfig"
Git repository summary#
The git-summary module allows displaying the git status of multiple repositories in a single list. I use it to keep track of the status of my commonly-used repos. I load the module as gs to make it easier to call its functions.
use github.com/zzamboni/elvish-modules/git-summary gs
Stop gitstatusd from staying in the background, since it’s only used for this purpose.
set gs:stop-gitstatusd-after-use = $true
Customize the command used for finding git repos for git-summary:summary-status &all, to ignore some uninteresting repos. List of directories to exclude is defined in $git-summary-repos-to-exclude.
var git-summary-repos-to-exclude = ['.emacs.d*' .cargo Library/Caches Dropbox/Personal/devel/go/src]
var git-summary-fd-exclude-opts = [(each {|d| put -E $d } $git-summary-repos-to-exclude)]
set gs:find-all-user-repos-fn = {
fd -H -I -t d $@git-summary-fd-exclude-opts '^.git$' ~ | each $path:dir~
}
Utility functions#
The util module includes various utility functions.
use github.com/zzamboni/elvish-modules/util
I use muesli’s git utilities module.
use github.com/muesli/elvish-libs/git
The update.elv package prints a message if there are new commits in Elvish after the running version.
use github.com/iwoloschin/elvish-packages/update
set update:curl-timeout = 3
update:check-commit &verbose
Set up electric delimiters in the command line.
use github.com/zzamboni/elvish-modules/util-edit
util-edit:electric-delimiters
ASCII spinners and TTY escape code generation.
use github.com/zzamboni/elvish-modules/spinners
use github.com/zzamboni/elvish-modules/tty
Work-specific stuff#
I have a private library which contains some work-specific functions.
use work