Notes

The ultimate guide to creating your own Zsh (oh-my-zsh) theme

Edit on GitHub

System Administration
6 minutes

Now that Zsh is now the default shell on macOS, i figured it was a good time to install oh-my-zsh and give it another go. And obviously, i immediately wanted to customize my prompt. So i ended up going through the process of creating a custom Zsh theme..

I need a minimalist prompt with

  • timestamp
  • git branch name and status
  • no username and hosts on local machine
  • a line break at the beginning of prompt to give visual separation between commands
  • no need for username and host. It’s my laptop, i know what my name and host is.

Something like this:

--------------------------------------------------------------------------------
$ mysite.com (master)                                               Sat 2, 15:39

and i ended up with this

amnastic.zsh-theme.png

code is here on Github

Game plan

set the theme to random

1# ~/.zshrc
2ZSH_THEME="random"

every time you like a prompt, run echo $PROMPT to get the prompt string value for the current theme and copy it. If you want to check the name of the theme, run echo -e ${ZSH_THEME}

I immediately liked af-magic when i came across it, it was similar to what i wanted so i based my custom theme on it

Where do i put my theme?

Themes are located in a themes folder and must end with .zsh-theme. The basename of the file is the name of the theme.

zsh_custom
└── themes
    └── amnastic.zsh-theme

Then edit your .zshrc to use that theme.

1ZSH_THEME="amnastic"

Prompt and such

You can find all of these under the Login information heading when you man zshmisc. Here’s a quick cheatsheet

%n - username
%m - short name of curent host
%M - name of curent host
%# - a `%` or a `#`, depending on whether the shell is running as root or not
%~ - relative path
%/ or %d - absolute path
%c or %C - Trailing  component of the current working directory.
%t - time 12hr am/pm format
%T - time 24hr format
%w - day and date (day-dd)
%D - Date (default: yy-mm-dd)
%D{%f} - day of the month
%l or %y - The  line  (tty)  the  user is logged in on, without `/dev/' prefix.

You can check the value of any of these arguments with -P to the print command

 1print -P %n # aamnah
 2print -P %m # Serenity
 3print -P %M # Serenity.local
 4
 5print -P %# # %
 6print -P '%(!.#.$)' # $
 7
 8print -P %~ # ~/Documents
 9print -P %c # Documents
10print -P %/ # /Users/aamnah/Documents
11
12print -P %t # 8:44PM
13print -P %T # 20:44
14print -P %w # Sat 2
15print -P %D # 20-05-02 yy-mm-dd
16print -P %W # 05/02/20 mm/dd/yy
17print -P %D{%f} # 2
18
19print -P %l # s001

You can have a prompt to the right with RPROMPT or RPS1

1RPROMPT=' %{$gray%}%T%{$reset_color%}'

A splash of colors

You can use 256-colors if supported, which they are on macOS Cataline (what i am on right now). You can create an if block to set fallbacks. Use 256 color if available, or use these..

 1#use extended color palette if available
 2if [[ $terminfo[colors] -ge 256 ]]; then
 3    turquoise="%F{81}"
 4    orange="%F{166}"
 5    purple="%F{135}"
 6    hotpink="%F{161}"
 7    limegreen="%F{118}"
 8else
 9    turquoise="%F{cyan}"
10    orange="%F{yellow}"
11    purple="%F{magenta}"
12    hotpink="%F{red}"
13    limegreen="%F{green}"
14fi
  • Colors are represented as %F{237} or %F{red} or $FG[237] or $fg[red]. All are valid formats. The reset for the color value can change based on the format you use

    • %F{237} 256 color number
    • %F{red} 8 color name (black, red, green, yellow, blue, magenta, cyan, white)
    • $FG[237] (notice the $ sign instead of %) 256 color number
    • $fg[red] (notice the $ and lower case fg) 8 color name (black, red, green, yellow, blue, magenta, cyan, white)
    • %{$fg_bold[blue]%} bold variants
  • %F is Foreground color, $f for resetting foreground color

  • %K is bacKground color, %k for resetting background color

  • $reset_color is a Zsh variable that resets the color of output

  • You can use unicode for symbols

  • %E Clear to end of line.

  • %U (%u) to Start (stop) underline mode.

You can check out how a color looks with:

1# change the value of 237 below to any one of 256-color
2print -P '%F{237} %m %f'

Git

You can use $(git_prompt_info) to show git branch and whether it is dirty or not. It’s a function that comes buit in with oh-my-zsh.

You can find all git functions in ~/.oh-my-zsh/lib/git.zsh

 1$(git_prompt_info) # Outputs current branch info in prompt format
 2$(parse_git_dirty) # Checks if working tree is dirty
 3$(git_remote_status) # Gets the difference between the local and remote branches
 4$(git_current_branch) # Outputs the name of the current branch
 5$(git_commits_ahead) # Gets the number of commits ahead from remote
 6$(git_commits_behind) # Gets the number of commits behind remote
 7$(git_prompt_ahead) # Outputs if current branch is ahead of remote
 8$(git_prompt_behind) # Outputs if current branch is behind remote
 9$(git_prompt_remote) # Outputs if current branch exists on remote or not
10$(git_prompt_short_sha) # Formats prompt string for current git commit short SHA
11$(git_prompt_long_sha) # Formats prompt string for current git commit long SHA
12$(git_prompt_status) # Get the status of the working tree
13$(git_current_user_name) # Outputs the name of the current user
14$(git_current_user_email) # Outputs the email of the current user
15$(git_repo_name) # Output the name of the root directory of the git repository

git_prompt_info can further be customized with plenty of ZSH_THEME_GIT_PROMPT_ variables that let you customize things like prefix and suffix and much more

 1ZSH_THEME_GIT_PROMPT_PREFIX="$FG[075]($FG[078]"
 2ZSH_THEME_GIT_PROMPT_CLEAN=""
 3ZSH_THEME_GIT_PROMPT_DIRTY="$my_orange*%{$reset_color%}"
 4ZSH_THEME_GIT_PROMPT_SUFFIX="$FG[075])%{$reset_color%}"
 5
 6ZSH_THEME_GIT_PROMPT_ADDED="%{$fg[cyan]%} ✈"
 7ZSH_THEME_GIT_PROMPT_MODIFIED="%{$fg[yellow]%} ✭"
 8ZSH_THEME_GIT_PROMPT_DELETED="%{$fg[red]%} ✗"
 9ZSH_THEME_GIT_PROMPT_RENAMED="%{$fg[blue]%} ➦"
10ZSH_THEME_GIT_PROMPT_UNMERGED="%{$fg[magenta]%} ✂"
11ZSH_THEME_GIT_PROMPT_UNTRACKED="%{$fg[grey]%} ✱"

Here is a list of all the variables that you can use to customize $(git_prompt_info)

 1ZSH_THEME_GIT_COMMITS_AHEAD_PREFIX
 2ZSH_THEME_GIT_COMMITS_AHEAD_SUFFIX
 3ZSH_THEME_GIT_COMMITS_BEHIND_PREFIX
 4ZSH_THEME_GIT_COMMITS_BEHIND_SUFFIX
 5ZSH_THEME_GIT_PROMPT_ADDED
 6ZSH_THEME_GIT_PROMPT_AHEAD
 7ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE
 8ZSH_THEME_GIT_PROMPT_AHEAD_REMOTE_COLOR
 9ZSH_THEME_GIT_PROMPT_BEHIND
10ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE
11ZSH_THEME_GIT_PROMPT_BEHIND_REMOTE_COLOR
12ZSH_THEME_GIT_PROMPT_CLEAN
13ZSH_THEME_GIT_PROMPT_DELETED
14ZSH_THEME_GIT_PROMPT_DIRTY
15ZSH_THEME_GIT_PROMPT_DIVERGED
16ZSH_THEME_GIT_PROMPT_DIVERGED_REMOTE
17ZSH_THEME_GIT_PROMPT_EQUAL_REMOTE
18ZSH_THEME_GIT_PROMPT_MODIFIED
19ZSH_THEME_GIT_PROMPT_PREFIX
20ZSH_THEME_GIT_PROMPT_REMOTE_EXISTS
21ZSH_THEME_GIT_PROMPT_REMOTE_MISSING
22ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_DETAILED
23ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_PREFIX
24ZSH_THEME_GIT_PROMPT_REMOTE_STATUS_SUFFIX
25ZSH_THEME_GIT_PROMPT_RENAMED
26ZSH_THEME_GIT_PROMPT_SHA_AFTER
27ZSH_THEME_GIT_PROMPT_SHA_BEFORE
28ZSH_THEME_GIT_PROMPT_STASHED
29ZSH_THEME_GIT_PROMPT_SUFFIX
30ZSH_THEME_GIT_PROMPT_UNMERGED
31ZSH_THEME_GIT_PROMPT_UNTRACKED

Single vs. Double Quotes

You need to set the prompt string with single quotes ' '

1PS1='%{$blue%}%c %{$purple%}%(!.#.») %{$reset_color%}'

above code will work just fine, whereas

1PS1="%{$blue%}%c %{$purple%}%(!.#.») %{$reset_color%}"

will show proper syntax highlighting in code editor, but will not update git when directory is changed. The double quotes make the variables in PROMPT to get evaluated when sourcing .zshrc and that’s an issue.

That’s a big issue to be honest, because it applies for not just the theme, it applies for all files in the custom/ folder. This gave me a hard time after having copied over code from my .bash_aliases. Some aliases there defined with double quotes made my git prompt stopped working again.

The following will cause issues

1## Recursively delete `.DS_Store` files
2alias cleanup="find . -type f -name '*.DS_Store' -ls -delete"
3
4## Kill all the tabs in Chrome to free up memory
5# [C] explained: http://www.commandlinefu.com/commands/view/402/exclude-grep-from-your-grepped-output-of-ps-alias-included-in-description
6alias chromekill="ps ux | grep '[C]hrome Helper --type=renderer' | grep -v extension-process | tr -s ' ' | cut -d ' ' -f2 | xargs kill"

Had to flip the single and double arrangement: single quote outside, double quote inside

1## Recursively delete `.DS_Store` files
2alias cleanup='find . -type f -name "*.DS_Store" -ls -delete'
3
4## Kill all the tabs in Chrome to free up memory
5# [C] explained: http://www.commandlinefu.com/commands/view/402/exclude-grep-from-your-grepped-output-of-ps-alias-included-in-description
6alias chromekill='ps ux | grep "[C]hrome Helper --type=renderer" | grep -v extension-process | tr -s " " | cut -d " " -f2 | xargs kill'