Terminal & Shell Setup¶
Ghostty¶
I've started using Mitchell Hashimoto's Ghostty, it's fast, clean and runs natively on both macOS and Linux.
Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.
Configuration¶
theme = catppuccin-mocha
font-family = "MesloLGM Nerd Font"
keybind = global:cmd+grave_accent=toggle_quick_terminal
Ghostty on macOS has a Quick Terminal, I've set a global keybinding of Cmd+` so I can access it from anywhere.
Optional: VS Code Settings¶
VS Code users will need to add the following to their settings.json
:
"terminal.external.osxExec": "Ghostty.app",
"terminal.integrated.fontFamily": "MesloLGM Nerd Font",
Z shell (zsh)¶
Required software:
brew install bat eza fzf jandedobbeleer/oh-my-posh/oh-my-posh
For bat
, eza
, and fzf
most distros have recent enough versions, use your package manager.
You'll need to install oh-my-posh
using their script:
curl -s https://ohmyposh.dev/install.sh | bash -s
Note
Ghostty has Nerd Fonts built-in, so if you use another terminal emulator you will need to install the Meslo
font yourself.
Automatic Install¶
curl -s https://andrew.lecody.com/guides/terminal-and-shell-setup/install.sh | bash -s
Files¶
Here are the files, if you just want to look at them instead of running my install script.
install.sh
#!/bin/bash
set -e
REQUIRED_PROGRAMS=(
wget
curl
git
bat
fzf
eza
oh-my-posh
)
[[ "$OSTYPE" == darwin* ]] && REQUIRED_PROGRAMS+=(brew)
MISSING_PROGRAMS=()
# Check each command
for cmd in "${REQUIRED_PROGRAMS[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
MISSING_PROGRAMS+=("$cmd")
fi
done
# If any commands are missing, list them and exit
if [ ${#MISSING_PROGRAMS[@]} -ne 0 ]; then
echo "Error: Missing required programs:"
printf '%s\n' "${MISSING_PROGRAMS[@]}"
exit 1
fi
[ -f /etc/zsh/zshrc ] && grep -q "skip_global_compinit=1" /etc/zsh/zshrc && \
(grep -q "^skip_global_compinit=1$" ~/.zshenv 2>/dev/null || \
(echo "skip_global_compinit=1" >> ~/.zshenv && echo "Updated ~/.zshenv"))
echo "Installing plugins to ~/.local/share"
ZSH_PLUGINS=(
mattmc3/ez-compinit
lukechilds/zsh-nvm
)
if [[ "$OSTYPE" == darwin* ]]; then
# On macOS I prefer installing these using brew
brew install zsh-autosuggestions zsh-syntax-highlighting
else
ZSH_PLUGINS+=(zsh-users/zsh-syntax-highlighting zsh-users/zsh-autosuggestions)
fi
for REPO in "${ZSH_PLUGINS[@]}"; do
echo "$REPO"
git clone --depth 1 "https://github.com/$REPO" "$HOME/.local/share/$(basename "$REPO")" >/dev/null
done
echo "Installing themes to ~/.local/share/themes"
THEME_URLS=(
https://andrew.lecody.com/guides/terminal-and-shell-setup/acecat.omp.toml
https://github.com/catppuccin/zsh-syntax-highlighting/raw/main/themes/catppuccin_mocha-zsh-syntax-highlighting.zsh
https://github.com/catppuccin/delta/raw/main/catppuccin.gitconfig
)
mkdir -p ~/.local/share/themes
for THEME_URL in "${THEME_URLS[@]}"; do
THEME_FILENAME=$(basename "$THEME_URL")
echo "$THEME_FILENAME"
wget -q -c "$THEME_URL" -O ~/.local/share/themes/"$THEME_FILENAME"
done
BAT_THEMES_DIR="$(bat --config-dir)/themes"
echo "Installing Catppuccin Mocha theme for bat"
mkdir -p "$BAT_THEMES_DIR"
wget -q -c https://github.com/catppuccin/bat/raw/main/themes/Catppuccin%20Mocha.tmTheme -O "$BAT_THEMES_DIR/Catppuccin Mocha.tmTheme"
bat cache --build
[ -f ~/.zshrc ] && mv ~/.zshrc ~/.zshrc-"$(date +%s)"
curl -s https://andrew.lecody.com/guides/terminal-and-shell-setup/zshrc > ~/.zshrc
zshrc
# Set ZSH_DEBUGRC to profile startup times, e.g.
# time ZSH_DEBUGRC=1 zsh -i -c exit
# https://www.dotruby.com/articles/profiling-zsh-setup-with-zprof
if [[ -n "$ZSH_DEBUGRC" ]]; then
zmodload zsh/zprof
fi
# better command history
HISTSIZE=10000
SAVEHIST=$HISTSIZE
HISTFILE=~/.zsh_history
setopt histignoredups extendedhistory
export PATH="$HOME/.local/bin:$PATH"
# faster way to initialize completion system, and some nice styles
# https://github.com/mattmc3/ez-compinit
# Available completion styles: gremlin, ohmy, prez, zshzoo
# You can add your own too. To see all available completion styles
# run 'compstyle -l'
zstyle ':plugin:ez-compinit' 'compstyle' 'zshzoo'
source ~/.local/share/ez-compinit/ez-compinit.plugin.zsh
# automatically escapes characters in unquoted URLs
autoload -Uz url-quote-magic
zle -N self-insert url-quote-magic
# bracketed-paste-magic is required for url-quote-magic
autoload -Uz bracketed-paste-magic
zle -N bracketed-paste bracketed-paste-magic
# This speeds up pasting w/ autosuggest
# https://github.com/zsh-users/zsh-autosuggestions/issues/238
pasteinit() {
OLD_SELF_INSERT=${${(s.:.)widgets[self-insert]}[2,3]}
zle -N self-insert url-quote-magic
}
pastefinish() {
zle -N self-insert $OLD_SELF_INSERT
}
zstyle :bracketed-paste-magic paste-init pasteinit
zstyle :bracketed-paste-magic paste-finish pastefinish
# bat config
export BAT_THEME="Catppuccin Mocha"
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
alias bathelp="bat --language=help --style=plain"
alias -g -- --help="--help 2>&1 | bathelp"
help() {
"$@" --help 2>&1 | bathelp
}
# fzf keybindings and fuzzy search
source <(fzf --zsh)
# catppuccin mocha theme for fzf
export FZF_DEFAULT_OPTS=" \
--color=bg+:#313244,bg:#1e1e2e,spinner:#f5e0dc,hl:#f38ba8 \
--color=fg:#cdd6f4,header:#f38ba8,info:#cba6f7,pointer:#f5e0dc \
--color=marker:#b4befe,fg+:#cdd6f4,prompt:#cba6f7,hl+:#f38ba8 \
--color=selected-bg:#45475a \
--multi"
ZSH_AUTOSUGGEST_STRATEGY=(history completion)
ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE=20
ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(bracketed-paste)
source ~/.local/share/themes/catppuccin_mocha-zsh-syntax-highlighting.zsh
# Brew should be setting HOMEBREW_PREFIX automatically via ~/.zprofile
case "$OSTYPE" in
darwin*) SHARE_DIR="$HOMEBREW_PREFIX/share";;
*) SHARE_DIR="$HOME/.local/share";;
esac
source "$SHARE_DIR/zsh-autosuggestions/zsh-autosuggestions.zsh"
source "$SHARE_DIR/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
# NodeJS (nvm)
# Using zsh-nvm for lazy loading nvm
# https://github.com/lukechilds/zsh-nvm
export NVM_LAZY_LOAD=true
export NVM_COMPLETION=true
[ -f ~/.local/share/zsh-nvm/zsh-nvm.plugin.zsh ] && source ~/.local/share/zsh-nvm/zsh-nvm.plugin.zsh
# uv autocompletion
if command -v uv > /dev/null; then
eval "$(uv generate-shell-completion zsh)"
eval "$(uvx --generate-shell-completion zsh)"
fi
# aliases & functions
alias zls="zfs list -o type,name,available,used,logicalused,usedbysnapshots,compressratio,mountpoint"
alias zsl="zfs list -t snapshot"
alias s="ssh -l root"
# use eza instead of ls
alias ls="eza"
alias l="eza --long --binary --classify=auto" # list, binary size, type indicator (e.g. `/` for dirs)
alias ll="l --all" # list, all
alias llm="ll --sort=modified" # list, all, sort by modification date
alias lls="ll --sort=size" # list, all, sort by size
alias la="eza -lbhHigUmuSa" # list, all, verbose
alias lx="eza -lbhHigUmuSa@" # list, all, verbose, and xattrs
alias tree="eza --tree" # tree
alias lS="eza -1" # display one entry per line
# get all kubernetes resources for a namespace
kubectlgetall() {
if [ -z "$1" ]; then
echo "Usage: $0 <namespace>"
return 1
fi
for i in $(kubectl api-resources --verbs=list --namespaced -o name | grep -v "events.events.k8s.io" | grep -v "events" | sort | uniq); do
echo "Resource:" $i
kubectl -n ${1} get --ignore-not-found ${i}
done
}
# base64 decode that also works with base64url variant (no padding)
b64d() {
local data=""
while IFS= read -r line || [ -n "$line" ]; do
data+="$line"
done
echo "${data}==" | base64 --decode
}
ytd() {
yt-dlp --cookies-from-browser=firefox \
--replace-in-metadata "uploader_id" "^@" "" \
--output "$HOME/Downloads/yt-dlp/%(uploader_id)s/%(extractor)s_%(display_id)s.%(ext)s" \
--embed-metadata \
--sponsorblock-mark all \
"$@"
}
jitter() {
local secs=$(( ( RANDOM % 60 ) + 1 ))
echo "sleep for $secs seconds..."
sleep "$secs"
}
zshspeed() {
for i in $(seq 1 10); do
time zsh -i -c exit
done
}
# THESE LINES MUST ALWAYS BE AT THE BOTTOM OF THIS FILE!
eval "$(oh-my-posh init zsh --config ~/.local/share/themes/acecat.omp.toml)"
if [[ -n "$ZSH_DEBUGRC" ]]; then
zprof
fi
acecat Oh-My-Posh Theme
version = 3
[upgrade]
source = "cdn"
interval = "168h"
auto = false
notice = false
[palette]
base = "#1e1e2e"
text = "#cdd6f4"
pink = "#f5c2e7"
red = "#f38ba8"
yellow = "#f9e2af"
green = "#a6e3a1"
teal = "#94e2d5"
blue = "#89b4fa"
[[blocks]]
type = "prompt"
alignment = "left"
[[blocks.segments]]
template = "{{ if .WSL }}WSL at {{ end }}{{.Icon}}"
foreground = "p:text"
type = "os"
style = "plain"
[[blocks.segments]]
template = " \uf0e7"
foreground = "p:red"
type = "root"
style = "plain"
[[blocks.segments]]
template = " {{ if eq .PWD \"~\" }}\uf015{{ else }}\uf07c{{ end }} {{ .Path }}"
foreground = "p:blue"
type = "path"
style = "plain"
[blocks.segments.properties]
style = "full"
folder_format = "<d>%s</d>"
edge_format = "<b>%s</b>"
[[blocks.segments]]
template = " {{ .UpstreamIcon }} {{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if and (.Staging.Changed) (.Working.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }}"
foreground = "p:yellow"
type = "git"
style = "plain"
[blocks.segments.properties]
fetch_upstream_icon = true
fetch_status = true
[[blocks]]
type = "prompt"
alignment = "right"
[[blocks.segments]]
template = " <p:red>\uf071 {{ reason .Code }}({{ .Code }})</>"
type = "status"
style = "plain"
[[blocks.segments]]
template = " \uf252 {{ .FormattedMs }}"
type = "executiontime"
style = "plain"
[blocks.segments.properties]
style = "round"
threshold = 3000
[[blocks.segments]]
template = "{{ if .SSHSession }} \ueba9 {{ .UserName }}@{{ .HostName }}{{ end }}"
foreground = "p:yellow"
type = "session"
style = "plain"
[[blocks.segments]]
template = " \uf017 {{ .CurrentDate | date .Format }}"
foreground = "p:blue"
type = "time"
style = "plain"
[blocks.segments.properties]
time_format = "15:04:05"
[[blocks]]
type = "prompt"
alignment = "left"
newline = true
[[blocks.segments]]
template = "❯ "
foreground = "p:green"
type = "status"
style = "plain"
foreground_templates = ["{{ if gt .Code 0 }}p:red{{ end }}"]
[blocks.segments.properties]
always_enabled = true
[[tooltips]]
template = "\ue81d {{.Context}}{{if .Namespace}} :: {{.Namespace}}{{end}}"
type = "kubectl"
style = "plain"
tips = [ "kubectl" ]
Finishing Touches¶
To use delta with git, update your ~/.gitconfig
with the following:
[interactive]
diffFilter = delta --color-only
[include]
path = ~/.local/share/themes/catppuccin.gitconfig
[delta]
features = catppuccin-mocha
side-by-side = true
navigate = true # use n and N to move between diff sections
dark = true
[merge]
conflictstyle = zdiff3
[diff]
colorMoved = default