POST
My Elvish Configuration With Commentary
Last update: October 5, 2021
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
module contains path manipulation functions.
use path
The bundled str
and math
modules for string manipulation and math operations.
use str
use math
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
E:GOPATH = ~/Dropbox/Personal/devel/go
paths = [
~/bin
~/.emacs.d/bin
$E:GOPATH/bin
/usr/local/opt/coreutils/libexec/gnubin
/usr/local/opt/texinfo/bin
/usr/local/opt/python/libexec/bin
/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.
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-matcher
module 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/zzamboni/elvish-themes ^
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
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-backspace
to delete small-wordedit:insert:binding[Alt-Backspace] = $edit:kill-small-word-left~
-
Alt-d
to delete the small-word under the cursoredit: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.
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.
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
I use this together with my lazy-vars
module to read the GitHub token to use with the brew
command.
use github.com/zzamboni/elvish-modules/lazy-vars
lazy-vars:add-var HOMEBREW_GITHUB_API_TOKEN { 1pass:get-password "github api token for homebrew" }
lazy-vars:add-alias brew [ HOMEBREW_GITHUB_API_TOKEN ]
Also for my 750words command-line client.
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:
alias:new dfc e:dfc -p -/dev/disk1s4,devfs,map,com.apple.TimeMachine
alias:new cat bat
alias:new more bat --paging always
alias:new v vagrant
alias:new git hub
bat
man
(using bat
as the pager for man
pages).
E:MANPAGER = "sh -c 'col -bx | bat -l man -p'"
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 bat
integration above.
fn manpdf [@cmds]{
each [c]{
man -t $c | open -f -a /System/Applications/Preview.app
} $cmds
}
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 also configure Tab to trigger completion mode, but also to automatically enter “filter mode”, so I can keep typing the filename I want, without having to use the arrow keys. Disabled as this is the default behavior starting with commit b24e4a7, but you may need it if you are running an older version for any reason and want this behavior.
# edit:insert:binding[Tab] = {
# edit:completion:smart-start
# edit:completion:trigger-filter
# }
I load some command-specific completions from the elvish-completions package:
use github.com/zzamboni/elvish-completions/cd
use github.com/zzamboni/elvish-completions/ssh
use github.com/zzamboni/elvish-completions/builtins
I configure the git completer to use hub
instead of git
(if you use plain git, you don’t need to call git:init
)
use github.com/zzamboni/elvish-completions/git git-completions
git-completions:git-command = hub
git-completions:init
This is not usually necessary, but I load the comp
library specifically since I do a lot of tests and development of completions.
use github.com/zzamboni/elvish-completions/comp
Prompt theme
Starship
Testing Starship for my prompt.
eval (starship init elvish)
You can find my current Starship config file at https://gitlab.com/zzamboni/mac-setup/-/blob/master/files/homefiles/.config/starship.toml.
Chain
I use the chain prompt theme, ported from the fish theme at https://github.com/oh-my-fish/theme-chain (disabled for now while I test Starship).
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):
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.
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
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
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).
edit:insert:binding[Alt-i] = $dir:history-chooser~
I bind Alt-b/f
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. In my Terminal.app setup, Alt-left/right
also produce Alt-b/f
, so these bindings work for those keys as well.
edit:insert:binding[Alt-b] = $dir:left-small-word-or-prev-dir~
edit:insert:binding[Alt-f] = $dir:right-small-word-or-next-dir~
The following makes the location and history modes be case-insensitive by default:
edit:insert:binding[Ctrl-R] = {
edit:histlist:start
edit:histlist:toggle-case-sensitivity
}
I use exa as a replacement for the ls
command, so I alias ls
to it. Unfortunately, exa
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 exa
. All other options are passed as-is.
fn ls [@_args]{
use github.com/zzamboni/elvish-modules/util
e:exa --color-scale --git --group-directories-first (each [o]{
util:cond [
{ eq $o "-lrt" } "-lsnew"
{ eq $o "-lrta" } "-alsnew"
:else $o
]
} $_args)
}
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.
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
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.
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
eval (~/Dropbox/Personal/devel/conda/devenv/bin/conda "shell.elvish" "hook" | slurp)
# <<< conda initialize <<<
Environment variables
Default options to less
.
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).
E:EDITOR = "vim"
Locale setting.
E:LC_ALL = "en_US.UTF-8"
PKG_CONFIG configuration
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.
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
.
git-summary-repos-to-exclude = ['.emacs.d*' .cargo Library/Caches Dropbox/Personal/devel/go/src]
git-summary-fd-exclude-opts = [(each [d]{ put -E $d } $git-summary-repos-to-exclude)]
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
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
- Related:
- How to easily create and use human-readable IDs in Org mode and Doom Emacs
- My Doom Emacs configuration, with commentary
- How to insert screenshots in Org documents on macOS
- Using and writing completions in Elvish
- Beautifying Org Mode in Emacs
- My Hammerspoon Configuration, With Commentary
- My Emacs Configuration, With Commentary
- Bang-Bang (!!, !$) Shell Shortcuts in Elvish