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 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, 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.
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
:
As you can see, the total rendering time is less than 150ms. And my configuration (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:
# 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. 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. 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. 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:
This also works for arguments:
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:
_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:
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:
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:
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, 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.
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:
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
:
_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
:
#!/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
:
# 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.
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 bettercd
experience (aliased) -
eza
for a betterls
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:
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
Top comments (5)
eza
isn't a drop-in forls
, it fails with some command optionsls
uses. For example, you can't dols -larth
witheza
. The most common ofeza
's options, except the git ones, are part ofls
already, e.g. file colours and symbols, etc.I really could not cope with that amount of prompt output; 150ms to render? And that's with a really short branch name - what if you're on branch
feature/a-very-long-branch-name-like-this
? Also, I normally know what branch I'm on in any given directory, and can usegit status
if I get lost.zimfw.sh/ Is a great more modern and performant alternative to oh-my-zsh .
Else your setup is nearly identical to mine.
Check out atuin.sh from my sis Ellie. Great util for shell history. Apart from that I use all the tools you do, great collection and thanks for sharing.
broot is also good tool to search files and directories and to jump in it