My ZSH Prompt


I just updated my shell prompt and figured I would share. It used to look like this:

% true
% false

At one point the little “bubble” with the status code was only visible if it was non-zero but I decided that I liked it being there, reassuringly, all the time.

Something not visible in the static representation is that the clock in the active prompt actually ticks every second. This means that the times seen in the scrollback are a good reflection of when I ran the command. At first I was worried that this would be distracting but I rarely notice it at all. The only downside I have found is that it can be hard to select text for copying at the last line of the terminal. However hitting enter to draw a new prompt fixes that as a fairly easy workaround for an issue that affects me rarely.

Overall I liked my prompt but wanted to make some tweaks.

And now it looks like this:

0 ~
16:35:55% cd p
0 ~/p
16:36:01% cd gridfinder
0 ~/p/gridfinder#v1
16:36:07% cd
0 ~/p/
16:36:10% false
1 ~/p/
16:46:36% env=prod
0 ~/p/ env=prod
16:46:46% deploy . --env=$env
Deploying site...
0 ~/p/ env=prod

It is definitely a bit more minimal in style, although the addition of VCS branch and variables adds a lot of utility to me. I don’t use variables for computation often but I fairly regularly use them to select a service or environment that I am working with and being able to see what I have “selected” is really useful. Then I can pull commands (or small inline scripts) from my history to perform common actions. For example at work I would often do something like:

0 ~/p/
17:15:06% env=default
0 ~/p/ env=default
17:15:15% stack=core/dns
0 ~/p/ env=default stack=core/dns
17:17:26% (cd ${stack:?} && terraform init && {terraform workspace new ${env:?}; terraform workspace select $env} && terraform apply)
... terraform output ...
0 ~/p/ env=default stack=core/dns

Having these variables right in the prompt to indicate what I am currently working on is basically as important to me as working directory or VCS branch.


The main problem with including variables is that there are a ton defined in the shell and I want to keep the list short by including only the relevant ones. The main approach was removing variables that existed at shell start. That is very good and the remaining corner cases can be handled by only considering variables that start with a lower case letter (avoids _ and uppercase shell variables) as well as a small ignore list.


autoload -Uz vcs_info
zstyle ':vcs_info:*' enable git hg
zstyle ':vcs_info:*' formats $'%{\e[2m%}#%{\e[0m%}%K%b'

prompt_vars() {
	local new=(${$(set +):|existing_vars})
	if [[ $new ]]; then
		prompt_vars_formatted=" $(builtin typeset -m $new | sed -e '/^[^a-z]/d' -e $'s/=/%{\e[2m%}=%{\e[22m%}/' | tr '\n' ' ')"

PROMPT=$'%{\e[40m%}%(?_%F{green}_%F{red})%?%f '
if [[ $USER != 'kevincox' ]]; then
if [[ "$SSH_CLIENT" ]]; then
PROMPT+=$'%-2<...<%~%<<$vcs_info_msg_0_$prompt_vars_formatted%E\n%{\e[2m%}%*%#%{\e[22;1m%} '
preexec() {
	printf '\e[0m'

# Redraw the time every second.
	zle reset-prompt

# Make sure this is after all of the variables created during shell init.
existing_vars+=($(set +))

I’m not going to explain all of the escape sequences but the docs are easy to understand.