Creating a fish-like zsh prompt with abbreviated folder names

April 30, 2022

Matrix

ZSH is a great bash alternative, that expands your possibilities in terms of what you can do in the terminal. Most people add a sprinkle of flavour on top of the default zsh experience by installing oh-my-zsh.

The main feature that people use in oh-my-zsh is themes. There is a plethora of themes that are also listed and described inside of the oh-my-zsh repository on GitHub.

How to use themes after installing oh-my-zsh

If you've got oh-my-zsh installed, chances are that your .zshrc (inside your home directory) was prepopulated with certain variables that your system will use to modify your zsh prompt.

One of those variables is ZSH_THEME, which also lives inside of your .zshrc file. If you set that to some other theme, e.g. agnoster, you will, after restarting your shell, see a different theme.

Creating a custom theme

The themes, that you can set in your .zshrc live inside the directory ~/.oh-my-zsh/themes. Each theme has its filename in the format {theme_name}.zsh-theme. To create your custom theme, create a new file, e.g. abbreviated.zsh-theme.

In my terminal prompt, I enjoy having the current directory and my current git branch printed before the place for my input. It makes it easy for me to see where I am and what I'm doing.

A good simple theme that does both of those things is gallifrey. We can use the contents of the file gallifrey.zsh-theme to provide a basis for our improved theme.

The contents of the gallifrey.zsh-theme look like this:

# ZSH Theme - Preview: https://github.com/ohmyzsh/ohmyzsh/wiki/Themes#gallifrey
return_code=\"%(?..%{$fg[red]%}%? ↵%{$reset_color%})\"
host_color=\"%(!.%{$fg[red]%}.%{$fg[green]%})\"

PROMPT=\"${host_color}%m%{$reset_color%} %2~ \\$(git_prompt_info)%{$reset_color%}%B»%b \"
RPS1=\"${return_code}\"

ZSH_THEME_GIT_PROMPT_PREFIX=\"%{$fg[yellow]%}\"
ZSH_THEME_GIT_PROMPT_SUFFIX=\"› %{$reset_color%}\"

unset return_code host_color

One thing that we could improve in our prompt/theme (in my humble opinion) is that we could make the directory names a bit shorter, similar to what some flavours of the fish shell do. Instead of listing the current directory as /Users/jaka/code/node/some-project, we could first make that shorter. Our gallifrey theme already shortens that to the last two directories, like this: /node/some-project.

If we look at the contents of the file gallifrey.zsh-theme, we can see the prompt variable set to:

PROMPT=\"${host_color}%m%{$reset_color%} %2~ \\$(git_prompt_info)%{$reset_color%}%B»%b \"

What the %2 variable does, is list 2 of the parent directories in the current folder hierarchy. If we were to replace that with %3, we would list the 3 parent directories.

However, with that we get the full folder names. There is no simple way to say, that we only want the first letters of the folder names.

What we will have to do is create our custom function, that gets the current directory hierarchy using pwd and generates a properly formatted folder string.

We can, in our newly created abbreviated.zsh-theme file, define a dirprompt function like so:

function dirprompt {
 pwd | sed -E 's/(.)([^\\/]+)\\//\\1\\/\
/g' | tail -3 | tr -d '\
' | tail | tr -s \"//\" \"/\"
}

What this functions does is the following:

  • Takes in the current directory, gets all of the strings ending with a / character, e.g., from /Users/jaka/test it gets Users/ and jaka/.
  • Then, it extracts the match group 1 from the matches, where it matches only the first character in each match, e.g., from /Users it gets U and from jaka/ it gets j.
  • The match gets replaced in the output with the match group 1, meaning that each folder name gets substituted with its initial letter.
  • Since the last folder is not matched by the regex, its full name remains in the output of the prompt.
  • To extract only the last few folders, each regex match gets a newline character at the end of its substitution so that the tail command can then be used.
  • After using the tail command, we join the lines using tr and remove the weird first new line using another tail.
  • After that, we get rid of the weird output for the home directory, and replace // in e.g. //jaka to /, to get e.g. /jaka in the home directory.

The last thing we need to do is to use the function when outputing the folder and git info in our terminal prompt.

Since we've previously copied the gallifrey theme, the prompt of our abbreviated.zsh-theme now look like this:

PROMPT=\"${host_color}%m%{$reset_color%} %2~ \\$(git_prompt_info)%{$reset_color%}%B»%b \"

This will be evaluated when initializing the terminal session, where the %2 will be interpolated for each terminal command. Since we want to use a function, we'll have to call it after each terminal command that gets executed. To do that, we'll have to change the double-quotes (") to single quotes (') which means that the PROMPT will be evaluated every time a new line in the terminal gets printed.

To suit my needs, I've changed my PROMPT to be:

PROMPT='$(dirprompt)~ $(git_prompt_info)%{$reset_color%}%B»%b '

This now prints lines such as:

c/o/aliasr~ ‹master› » ls -la

where the current directory is /Users/jaka/code/other/aliasr.

Sources