Finding Terminal Utopia
Tags: linux, macos, terminal, zsh
Published: 2024-05-22 10:35:55

---
title: Finding Terminal Utopia
published: true
date: 2024-05-22 09:57:11 UTC
tags: linux,macOS,terminal,zsh
canonical_url: https://www.daveyshafik.com/archives/70863-finding-terminal-utopia.html
---

For the last few weeks I’ve been setting up a new laptop from scratch for the first time in a… very long time, taking the time especially to re-evaluate my terminal setup and integrating some new tools, and re-integrating some old tools in a new way.

> **Note:** Almost all of the tools and scripts mentioned below can be easily installed using homebrew. If you want to use [my .zshrc config](https://gist.github.com/dshafik/67fe3e0ba5096a00c91cccb0792a884b#file-zshrc) as-is, you’ll want to use homebrew.
> 
> _If you want to skip ahead and see the final outcome, checkout the screencast at the end of this post._

## Building a Better Prompt

Everything in the terminal starts with your prompt. Before I moved to zsh, I used `bash-it` with `bash` to create a fancy prompt that gave me things like current directory, git status and branch, and tool versions. Moving to zsh, I installed `oh-my-zsh` and my prompt took a giant leap forward with display of command exit status, command timing, icons, and more tool versions. Unfortunately, my prompt got so bloated that I started to dread opening a new terminal.

Thankfully, I found [Starship](https://starship.rs), a super fast, super configurable prompt written in Rust. It works with most shells, on most operating systems.

Even with additional prompt information like Kubernetes context and namespace, Google Cloud CLI version, and a prettier (to me!) more usable prompt, it’s blazing fast.

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-2.30.00%E2%80%AFAM-1024x79.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-2.30.00%E2%80%AFAM.png)

Here you can see that I’m in the `bag` directory, which is on the main branch with some untracked files, using NodeJS v22.2.0, and PHP 8.3.7. You can also see that the previous command took five seconds to run, and the current (24 hours) time, and you can see how quickly it renders using `starship explain`:

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-2.32.32%E2%80%AFAM-1024x158.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-2.32.32%E2%80%AFAM.png)

As you can see, the total rendering time is less than 150ms. And [my configuration](https://gist.github.com/dshafik/67fe3e0ba5096a00c91cccb0792a884b#file-starship-toml) (which is minimally updated from the default) includes support for dozens of other languages and useful information.

## After the Prompt

Before we add any other applications, let’s add some zsh scripts to make using the parts after the prompt nicer.

First, we’re going to configure how zsh stores your history. The history is a log of every command you’ve entered into the shell. We are going to set the following options:

```sh
# Save the history in your home directory as .zsh_history
export HISTFILE=$HOME/.zsh_history 

# Set the history size to 2000 commands
export HISTSIZE=2000               

# Store the same number to disk
export SAVEHIST=$HISTSIZE          

# Share history between sessions
setopt share_history               

# Remove duplicates first when HISTSIZE is met
setopt hist_expire_dups_first      

# If a command is issued multiple times in a row, ignore dupes
setopt hist_ignore_dups  

# Allow editing the command before executing again
setopt hist_verify                 

# Do not add commands prefixed with a space to the history
setopt hist_ignore_space           
```

Next, let’s change how we interact with the history. By default, you use `Ctrl+r` or `Ctrl+Shift+R` which will bring up the bck-i-search UI, that will match history commands as you type, to navigate forward or backwards from the current match you need to then press `Ctrl+r` or `Ctrl+Shift+R` again multiple times until you find the item you want.

This is not intuitive, or efficient, so our first task is fix that using [zsh-autosuggestions](https://github.com/zsh-users/zsh-autosuggestions). zsh-autosuggestions will provide autocomplete suggestions from your history (and other locations) as you type, simply press the right arrow key, or use the end-of-line keyboard shortcut (e.g. `ctrl+e`) to accept the suggestion. If you only want part of a suggestion, you can use the forward-word keyboard shortcut, which is `option+right arrow` or `alt+right arrow` which will complete only up to the end of the next word and continue providing suggestions from there.

This works great if you can remember the exact start of the previous command you want to use, but what if you only remember part of it? Enter [zsh-history-substring-search](https://github.com/zsh-users/zsh-history-substring-search). This script allows you to navigate your history using substring matches, and you can bind it to your up and down arrow keys so that you can simply type the substring and then hit the up key to start finding matches.

Lastly, let’s make _what_ commands we’re running easier to read, using [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting). This script (which must be loaded _before_ the zsh-history-substring-search above) will provide syntax highlighting of commands as you write them. This will help you avoid typos for incorrect commands, and make complex commands easier to read.

## Changing Directories For The Better

One of the most — if not _the most_ — common commands that I use is `cd`. A very simple commands, it changes your current working directory to the one specified.

One simple trick to improve your use of `cd` is knowing that `cd -` will take you back to whatever directory you were previously in. This alone had a massive impact on my ability to navigate around my directory structure.

There is, however, an even better solution: `zoxide`, a `cd` alternate, and can be used as a drop-in replacement.

`zoxide` uses fuzzy matching and a frequency of usage ranking to intelligently let you change directories based on substrings. For example, to reach the `bag` repo that lives at `~/src/bag`, I can use `cd bag` from anywhere, if however I want to enter the `bag-benchmarks` repo, I can use `cd bag-` and it will take me there instead.

Under the hood, `zoxide` uses `fzf` for completions and interactive selection, but `fzf`‘s utility doesn’t end there.

## Finding Clarity Through Fuzziness

`fzf` is really the hero of this story, as it can integrate into many different aspects of your terminal to bring improvements.

For example, it can be used as part of tab suggestions. When you hit `<tab>` to get suggestions, it will display a UI with a keyboard navigable, searchable, list of possibilities:

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-8.24.49%E2%80%AFPM-1024x634.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-8.24.49%E2%80%AFPM.png)

This also works for arguments:

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-8.25.54%E2%80%AFPM-1024x262.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-8.25.54%E2%80%AFPM.png)

But fzf can do **so much** more. To expand on it’s capabilities, let’s add two helpers: `fd` and `eza`.

### Find Everything

`fd` is a simpler, faster alternative to `find`, which is valuable in and of itself, but paired with `fzf`, it can enable faster/better suggestions. Adding the following will use `fd` for both file and folder completion:

```sh
_fzf_compgen_path() {
    fd --hidden --exclude .git . "$1"
}

_fzf_compgen_dir() {
    fd --type=d --hidden --exclude .git . "$1"
}
```

With this in place, `fzf` will suggest both directories and files (except `.git`) when trying to auto-complete paths (see above screenshot with `ls`), but will only show directories (except `.git`) when using commands like `cd` that only accept a directory:

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-11.37.30%E2%80%AFPM.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-11.37.30%E2%80%AFPM.png)

### Show Everything

`eza` is a drop-in replacement for `ls`, and provides a much richer file list experience, from adding rich colors, to showing icons and git status.

I alias `eza` to `ls` with the following options:

```sh
alias ls="eza --icons=always --color=always --git"
```

This allows me to continue to use `ls` the way I always have (compact format by default, adding `-al` to show all files in list format) but will add `eza` colors, icons, and show git information is list view.

For example, calling `ls -al` will show the following:

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-8.39.44%E2%80%AFPM-1010x1024.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-21-at-8.39.44%E2%80%AFPM.png)

Here you can see the standard `ls -al` style output but with excellent highlighting, as well as the introduction of git status (between the date time and the icon) and the icons.

**WARNING:** `eza` expects you to be using a [nerdfont](https://www.nerdfonts.com), which you will install as part of installing Starship, but if you chose not to do so, you will want to do that separately. I use [SauceCodePro](https://www.programmingfonts.org/#source-code-pro).

### See Everything

Now that you have `eza` installed, you can setup `fzf` previews, which can be context aware, similar to the file and directory lists shown when tab completing those.

For example, with the integration of `eza` and `fzf`, that list of directories for `cd` can include a tree-based preview of the selected directory. To invoke `fzf` you use the placeholder ` **` and then hit `<tab>`, e.g. `cd** ` could show the following:

[![](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-22-at-12.43.38%E2%80%AFAM-1024x457.png)](https://www.daveyshafik.com/wp-content/uploads/2024/05/Screenshot-2024-05-22-at-12.43.38%E2%80%AFAM.png)

You can then make your selection using the up/down arrow keys and hit `<enter>` to replace the `**` with the selected directory.

With some additional configuration, and a simple script, we can show either a directory tree preview, or use `bat` to show a syntax highlighted preview of the file contents.

Add the following to your `.zshrc`:

```sh
 _fzf_comprun() {
   local command=$1
   shift

   case "$command" in
     cd) fzf --preview 'eza --tree --color=always {} | head -200' "$@" ;;
     export|unset) fzf --preview "eval 'echo \$'{}" "$@" ;;
     ssh) fzf --preview 'dig {}' "$@" ;;
     cat|bat) fzf --preview 'bat -n --color=always {}' "$@" ;;
     *) fzf --preview '$HOME/bin/fzf-preview.sh {}' "$@" ;;
   esac
 }
```

Add the following as an executable script at `$HOME/bin/fzf-preview.sh`:

```sh
#!/bin/bash
if test -d "$@"; then
    eza --tree --color=always --icons=always --git "$@" | head -200
else
    bat -n --color=always "$@"
fi
```

Lastly, we can override the default `<tab>` completion behavior to use `fzf` with preview by adding the following configuration (and using the same `fzf-preview.sh` script above) by adding the following to your `.zshrc`:

```sh
 # Enable using fzf preview with eza when using tab completion with `cd`
 zstyle ':completion:*' menu no
 zstyle ':fzf-tab:complete:*' fzf-preview '$HOME/bin/fzf-preview.sh $realpath'
 zstyle ':fzf-tab:complete:cd:*' fzf-preview 'eza --tree --color=always --icons=always --git $realpath | head -200'
 zstyle ':fzf-tab:*' switch-group '<' '>'
```

## Reaching Nerdvana

Now that you know all the pieces, you need to put them together, which is why I have made my entire `.zshrc` available [here](https://gist.github.com/dshafik/67fe3e0ba5096a00c91cccb0792a884b#file-zshrc).

With this `.zshrc` you will have the following features:

- Starship prompt
- command syntax highlighting
- automatic suggestions for completions as you type
- substring search using the up/down keys
- `zoxide` for a better `cd` experience (aliased)
- `eza` for a better `ls` experience (aliased)
- `fd` for better file search
- `fzf` for fuzzy finding and previews with `<tab>` activation for directories and files

You can see a demo of all of these features below:

[![asciicast](https://asciinema.org/a/xGT6D1sI6cpsv8lBaOeRrfcXt.svg)](https://asciinema.org/a/xGT6D1sI6cpsv8lBaOeRrfcXt)

While you don’t need to adopt all of these tools and features, together they create a powerful terminal experience.

Is there something missing? Let me know in the comments below!

If you want to learn more about `bat`, used to show syntax highlighted file previews, and other developer-focused tools, check out my earlier post: [Five Must-Have Command Line Tools for Developers](https://www.daveyshafik.com/archives/70845-five-command-line-tools-for-developers.html)