My ZSH Prompt
Published: 2021-04-17 01:13:31

---
title: My ZSH Prompt
date: 2021-03-19 21:54:00 UTC
published: true
tags: 
canonical_url: https://kevincox.ca/2021/03/19/my-zsh-prompt/
---

<main><p>I just updated my shell prompt and figured I would share. It used to look like this:</p>

<div><pre><code><font color="#8D9197">╭</font>(16:16:49)<font color="#8D9197">─</font>(<font color="#98FF36">0</font>)<font color="#8D9197">─</font>(~/p/config)
<font color="#8D9197">╰</font>% true
<font color="#8D9197">╭</font>(16:16:49)<font color="#8D9197">─</font>(<font color="#98FF36">0</font>)<font color="#8D9197">─</font>(~/p/config)
<font color="#8D9197">╰</font>% false
<font color="#8D9197">╭</font>(16:16:59)<font color="#8D9197">─</font>(<font color="#FF7474">1</font>)<font color="#8D9197">─</font>(~/p/config)
<font color="#8D9197">╰</font>%
</code></pre></div>

<p>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.</p>

<p>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.</p>

<p>Overall I liked my prompt but wanted to make some tweaks.</p>

<ul>
  <li>Waste less space with formatting (like the “skeuomorphic” bubbles).</li>
  <li>Add shell variables.</li>
</ul>

<p>And now it looks like this:</p>

<div><pre><code><span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~</span>
<font color="#8D9197">16:35:55%</font> cd p
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p</span>
<font color="#8D9197">16:36:01%</font> cd gridfinder
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/gridfinder<font color="#8D9197">#</font>v1</span>
<font color="#8D9197">16:36:07%</font> cd kevincox.ca
~/p/kevincox.ca
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca<font color="#8D9197">#</font>master</span>
<font color="#8D9197">16:36:10%</font> false
<span style="display:inline-block;width:100%;background-color:black"><font color="#FF7474">1</font> ~/p/kevincox.ca<font color="#8D9197">#</font>master</span>
<font color="#8D9197">16:46:36%</font> env=prod
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca<font color="#8D9197">#</font>master env<font color="#8D9197">=</font>prod</span>
<font color="#8D9197">16:46:46%</font> deploy . --env=$env
Deploying site...
Deployed.
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca<font color="#8D9197">#</font>master env<font color="#8D9197">=</font>prod</span>
<font color="#8D9197">16:46:59%</font>
</code></pre></div>

<p>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:</p>

<div><pre><code><span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca#master</span>
<font color="#8D9197">17:15:06%</font> env=default
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca#master env=default</span>
<font color="#8D9197">17:15:15%</font> stack=core/dns
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca#master env=default stack=core/dns</span>
<font color="#8D9197">17:17:26%</font> (cd ${stack:?} &amp;&amp; terraform init &amp;&amp; {terraform workspace new ${env:?}; terraform workspace select $env} &amp;&amp; terraform apply)
... terraform output ...
<span style="display:inline-block;width:100%;background-color:black"><font color="#98FF36">0</font> ~/p/kevincox.ca#master env=default stack=core/dns</span>
<font color="#8D9197">17:17:37%</font>
</code></pre></div>

<p>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.</p>

<h2 id="implementation"><a href="https://kevincox.ca/2021/03/19/my-zsh-prompt/#implementation">Implementation</a></h2>

<p>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 <code>_</code> and uppercase shell variables) as well as a small ignore list.</p>

<div><pre><code>setopt PROMPT_SUBST

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

<span>prompt_vars_formatted</span><span>=</span><span>''</span>
<span>existing_vars</span><span>=(</span>
	<span># List of variables to ignore.</span>
	vcs_info_msg_0_
	vcs_info_msg_1_
<span>)</span>
prompt_vars<span>()</span> <span>{</span>
	<span>local </span><span>new</span><span>=(</span><span>${</span><span>$(</span><span>set</span> +<span>)</span>:|existing_vars<span>}</span><span>)</span>
	<span>if</span> <span>[[</span> <span>$new</span> <span>]]</span><span>;</span> <span>then
		</span><span>prompt_vars_formatted</span><span>=</span><span>" </span><span>$(</span><span>builtin typeset</span> <span>-m</span> <span>$new</span> | <span>sed</span> <span>-e</span> <span>'/^[^a-z]/d'</span> <span>-e</span> <span>$'s/=/%{</span><span>\e</span><span>[2m%}=%{</span><span>\e</span><span>[0m%}%K/'</span> | <span>tr</span> <span>'\n'</span> <span>' '</span><span>)</span><span>"</span>
	<span>else
		</span><span>prompt_vars_formatted</span><span>=</span><span>''</span>
	<span>fi</span>
<span>}</span>
precmd_functions+<span>=(</span>prompt_vars<span>)</span>

<span>PROMPT</span><span>=</span><span>'%K%(?_%F{green}_%F{red})%?%f '</span>
<span>if</span> <span>[[</span> <span>$USER</span> <span>!=</span> <span>'kevincox'</span> <span>]]</span><span>;</span> <span>then</span>
	<span># Include the user if it isn't my regular user.</span>
	PROMPT+<span>=</span><span>$'%n%{</span><span>\e</span><span>[2m%}@%{</span><span>\e</span><span>[0m%}%K'</span>
<span>fi
if</span> <span>[[</span> <span>"</span><span>$SSH_CLIENT</span><span>"</span> <span>]]</span><span>;</span> <span>then</span>
	<span># Include the hostname if connecting over SSH.</span>
	PROMPT+<span>=</span><span>$'%M%{</span><span>\e</span><span>[2m%}:%{</span><span>\e</span><span>[0m%}%K'</span>
<span>fi
</span>PROMPT+<span>=</span><span>$'%-2&lt;...&lt;%~%&lt;&lt;$vcs_info_msg_0_$prompt_vars_formatted%E</span><span>\n</span><span>%k%{</span><span>\e</span><span>[2m%}%*%#%{</span><span>\e</span><span>[0m%} '</span>

<span># Redraw the time every second.</span>
<span>TMOUT</span><span>=</span>1
TRAPALRM<span>()</span> <span>{</span>
	zle reset-prompt
<span>}</span>

<span># Make sure this is after all of the variables created during shell init.</span>
existing_vars+<span>=(</span><span>$(</span><span>set</span> +<span>)</span><span>)</span>
</code></pre></div>
<p>I’m not going to explain all of the escape sequences but <a href="http://zsh.sourceforge.net/Doc/Release/Prompt-Expansion.html">the docs</a> are easy to understand.</p>
</style>