My ZSH Prompt

Posted

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

(16:16:49)(0)(~/p/config)
% true
(16:16:49)(0)(~/p/config)
% false
(16:16:59)(1)(~/p/config)
%

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 kevincox.ca
~/p/kevincox.ca
0 ~/p/kevincox.ca#master
16:36:10% false
1 ~/p/kevincox.ca#master
16:46:36% env=prod
0 ~/p/kevincox.ca#master env=prod
16:46:46% deploy . --env=$env
Deploying site...
Deployed.
0 ~/p/kevincox.ca#master env=prod
16:46:59%

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/kevincox.ca#master
17:15:06% env=default
0 ~/p/kevincox.ca#master env=default
17:15:15% stack=core/dns
0 ~/p/kevincox.ca#master 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/kevincox.ca#master env=default stack=core/dns
17:17:37%

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.

Implementation

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.

setopt PROMPT_SUBST

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

prompt_vars_formatted=''
existing_vars=(
	vcs_info_msg_0_
	vcs_info_msg_1_
)
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' ' ')"
	else
		prompt_vars_formatted=''
	fi
}
precmd_functions+=(prompt_vars)

PROMPT=$'%{\e[40m%}%(?_%F{green}_%F{red})%?%f '
if [[ $USER != 'kevincox' ]]; then
	PROMPT+=$'%n%{\e[2m%}@%{\e[22m%}'
fi
if [[ "$SSH_CLIENT" ]]; then
	PROMPT+=$'%M%{\e[2m%}:%{\e[22m%}'
fi
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.
TMOUT=1
TRAPALRM() {
	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.