All Articles

Creating a custom ZSH prompt

Intro

The goal of this post is to end up with a prompt like this:

Your first custom prompt

But first, there are a number of great terminal prompts already available over at the oh-my-zsh repository on Github.

However, maybe you want to make one of your own and you’re not sure where to start.. I was in this position today, so I decided to figure it out.

Firstly, I took a bunch of inspiration from the themes that are already in the repository, and their corresponding source code.

I started with a theme called steeef and modified from there.

Colors

A natural first step was to figure out how to color things, which looked fairly simple…

orange="%F{166}"

Should give me the color orange, and it did. You can test these by simply typing in a terminal that is running ZSH:

print -P "%{%F{166}%} Hello World ---> $"

This will generate you a prompt that looks something like:

Your first custom prompt

The next thing to figure out is why does 166 correspond to orange? These are a form of ANSI escape codes which are ways of signaling to the underlying terminal emulator things like color, cursor location, text decoration etc.

You can find a list of them in various places online.

Composing colors

To compose colors together, ensure that the color you want a specific word or symbol to be is placed just before the color, e.g. to alter the above prompt and have the word “World” appear in blue, it would be:

print -P "%{%F{166}%} Hello %{%F{4}%}World ---> $"

Which changes the above prompt to:

Second custom prompt

However, as you may have already noticed - this changes all the words in the prompt to the same color - which is probably not what you wanted…

In order to fix this issue, the concept of a reset is introduced:

reset="%f"

This can then be used in-between words, or at the end of your prompt to ensure that the commands entered after the prompt are also not colorized, e.g.

reset="%f"
print -P \
"%{%F{166}%} Hello %{%F{4}%}World%{$reset%} ---> $"

Precmd

precmd is a “hook” that is executed before each prompt. This is useful for things like figuring out whether we should display git information, e.g. the hook will be executed once you cd to a new directory - giving us a chance to figure out whether this directory is under a vcs.

This can be used alongside a ZSH plugin called vcs_info in the following way:

precmd() {
    zstyle ':vcs_info:*' formats '%F{15}on %F{61}%b'
    vcs_info
}

The vcs_info function above handles the heavy lifting for us, such as determening whether to show vcs information. All we need to do is define a format for the prompt, which you can see above we do with zstyle.

Putting everything together

It’s possible to do anything you can normally do in a bash script in these theme files too, e.g. an if statement to determine whether we are a specific user or not, see below for more. The full theme presented at the start of this post can be achieved with the following:

autoload -Uz vcs_info
zstyle ':vcs_info:*' enable git
zstyle ':vcs_info:*:prompt:*' check-for-changes true

# Colors
blue="%F{33}"
cyan="%F{37}"
green="%F{64}"
orange="%F{166}"
red="%F{124}"
reset="%f"
violet="%F{61}"
white="%F{15}"

# User specific options
user="%{$blue%}"
if [[ "$USER" == "root" ]]; then
    user="%{$red%}"
fi

# Highlight that we're connected via SSH.
host="%{$cyan%}";
cloud=""
if [[ "$SSH_TTY" ]]; then
	host="%{$terminfo[bold]$cyan%}";
    cloud=☁️
fi

precmd() {
    zstyle ':vcs_info:*' formats '%F{15}on %F{61}%b'
    vcs_info
}

PROMPT=$'
%{$user%}%n %{$white%}at %{$host%}%m%{$cloud%} %{$terminfo[sgr0]%}%{$white%}in %{$green%}%~%{$reset%} ${vcs_info_msg_0_}%{$reset%}
$ '

There’s nothing really magical going on here, we just use variables to define colors instead of repeating the ANSI escape codes over and over, and there are some if statements depending on which user we are, whether we’re connected via SSH etc.

There’s a lot more you can do like defining an RPROMPT etc. which will show on the right side of the terminal emulator rather than the left - these are left as an exercise for the reader.