My Ultimate Developer Setup: A Deep Dive into My ZSH Configuration
The Terminal Is My Home
I spend 80% of my coding time in the terminal. Over the years, I’ve crafted a ZSH configuration that transforms the command line from a basic tool into a productivity powerhouse. Today, I’m sharing my entire setup - from the prompt that greets me every morning to the 40+ tools and custom functions that make my development workflow smooth as butter.
The Foundation: Why ZSH + Zinit?
I chose ZSH over Bash for its superior completion system, better scripting capabilities, and amazing plugin ecosystem. But the real game-changer is Zinit - a blazing-fast plugin manager that loads my entire configuration in milliseconds, not seconds.
# Initialize zinit with automatic installation
ZINIT_HOME="${HOME}/.local/share/zinit/zinit.git"
if [[ ! -f $ZINIT_HOME/zinit.zsh ]]; then
print -P "%F{33} %F{220}Installing zinit...%f"
mkdir -p "$(dirname $ZINIT_HOME)"
git clone https://github.com/zdharma-continuum/zinit "$ZINIT_HOME"
fi
source "${ZINIT_HOME}/zinit.zsh"
autoload -Uz _zinit
(( ${+_comps} )) && _comps[zinit]=_zinit
The beauty of Zinit? Async loading. While other plugin managers freeze your terminal on startup, Zinit loads plugins in the background. I get a responsive prompt immediately, and features appear as they’re ready.
Zinit Annexes - Supercharging the Plugin Manager
I use four powerful Zinit annexes that extend its capabilities:
zinit light-mode for \
zdharma-continuum/zinit-annex-as-monitor \
zdharma-continuum/zinit-annex-bin-gem-node \
zdharma-continuum/zinit-annex-patch-dl \
zdharma-continuum/zinit-annex-rust
These enable:
- as-monitor: Monitor and report plugin loading
- bin-gem-node: Manage binary programs from gems and npm
- patch-dl: Download and apply patches to plugins
- rust: Handle Rust packages seamlessly
Oh-My-Zsh Libraries: The Best Parts Without the Bloat
Instead of loading the entire Oh-My-Zsh framework, I cherry-pick only the essential libraries:
zinit snippet OMZL::git.zsh # Git aliases and functions
zinit snippet OMZL::history.zsh # Better history management
zinit snippet OMZL::key-bindings.zsh # Standard key bindings
zinit snippet OMZL::theme-and-appearance.zsh # Terminal colors
zinit snippet OMZL::completion.zsh # Completion tweaks
zinit snippet OMZL::directories.zsh # Directory navigation helpers
This gives me Oh-My-Zsh’s best features without the 150ms startup penalty.
The Prompt: Starship + Powerlevel10k Configuration
I use Starship as my primary prompt:
eval "$(starship init zsh)"
But I also have Powerlevel10k configuration ready:
# Powerlevel10k - A fast and customizable prompt
zinit ice depth=1
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh
[[ -f ~/.p10k.zsh ]] && source ~/.p10k.zsh
Core Configuration: The Settings That Matter
Essential Environment Variables
# Default applications
export EDITOR='nvim' # Neovim for everything
export VISUAL='nvim' # GUI editors? No thanks
export PAGER='less' # For viewing long outputs
# Language settings
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
History That Actually Works
My history configuration preserves 1 million commands (yes, million). I can find that obscure command I ran 6 months ago:
HISTFILE="${HOME}/.zsh_history"
HISTSIZE=1000000
SAVEHIST=1000000
# Smart history options
setopt EXTENDED_HISTORY # Save timestamp and duration
setopt SHARE_HISTORY # Share between sessions
setopt HIST_IGNORE_ALL_DUPS # No duplicates
setopt HIST_REDUCE_BLANKS # Clean up commands
The Plugin Arsenal
Here’s where the magic happens. My carefully curated plugins:
VI Mode Configuration (Loads First)
# Configure zsh-vi-mode before loading (keep block cursor always)
function zvm_config() {
ZVM_CURSOR_STYLE_ENABLED=false
}
zinit light jeffreytse/zsh-vi-mode
Core Plugins (Async Loading)
# Fast syntax highlighting and suggestions
zinit wait lucid for \
atinit"ZINIT[COMPINIT_OPTS]=-C; zicompinit; zicdreplay" \
zdharma-continuum/fast-syntax-highlighting \
atload"!_zsh_autosuggest_start" \
zsh-users/zsh-autosuggestions
- fast-syntax-highlighting: Real-time syntax checking. Invalid commands show in red BEFORE you press enter.
- zsh-autosuggestions: Fish-like autosuggestions based on command history.
Navigation and Productivity Tools
zinit wait lucid for \
agkozak/zsh-z \
MichaelAquilina/zsh-you-should-use \
zdharma-continuum/history-search-multi-word \
paulirish/git-open \
Aloxaf/fzf-tab
- zsh-z: Smart directory jumping.
z blogtakes me to my blog directory from anywhere. - zsh-you-should-use: Reminds me of aliases I should be using.
- history-search-multi-word: Multi-word history searching with syntax highlighting.
- git-open: Type
git opento open the repo in your browser. - fzf-tab: Replaces default tab completion with FZF. Revolutionary for file navigation.
Completions and Binary Tools
# Enhanced completions
zinit wait lucid for \
blockf atpull'zinit creinstall -q .' \
zsh-users/zsh-completions
# Binary tools from GitHub releases
zinit wait lucid from"gh-r" as"program" for \
sbin"bat/bat" @sharkdp/bat \
sbin"fzf" junegunn/fzf \
sbin"ripgrep/rg" BurntSushi/ripgrep
Zinit automatically downloads and manages:
- bat: A cat clone with syntax highlighting
- fzf: Fuzzy finder for everything
- ripgrep: Blazing fast grep alternative
Advanced Completion System
My completion configuration makes tab completion incredibly powerful:
# Initialize completion
autoload -Uz compinit && compinit
# Completion styling
zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}' 'r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
zstyle ':completion:*' verbose yes
zstyle ':completion:*:*:*:*:descriptions' format '%F{green}-- %d --%f'
zstyle ':completion:*:*:*:*:corrections' format '%F{yellow}!- %d (errors: %e) -!%f'
zstyle ':completion:*:messages' format '%F{purple}-- %d --%f'
zstyle ':completion:*:warnings' format '%F{red}-- no matches found --%f'
zstyle ':completion:*' group-name ''
zstyle ':completion:*:default' list-prompt '%S%M matches%s'
zstyle ':completion:*' accept-exact '*(N)'
zstyle ':completion:*' use-cache on
zstyle ':completion:*' cache-path ~/.zsh/cache
zstyle ':fzf-tab:complete:*' fzf-preview 'bat --color=always --line-range :50 {}'
This gives me:
- Case-insensitive matching
- Fuzzy matching (type
cd dowโ matchesDownloads) - Colored completions grouped by type
- File previews with bat when using fzf-tab
- Cached completions for speed
Custom Functions: Work Smarter, Not Harder
The Universal Extractor
Handles 12 different archive formats with one command:
function extract() {
if [ -f $1 ]; then
case $1 in
*.tar.bz2) tar xjf $1 ;;
*.tar.gz) tar xzf $1 ;;
*.tar.xz) tar xJf $1 ;;
*.bz2) bunzip2 $1 ;;
*.rar) unrar e $1 ;;
*.gz) gunzip $1 ;;
*.tar) tar xf $1 ;;
*.tbz2) tar xjf $1 ;;
*.tgz) tar xzf $1 ;;
*.zip) unzip $1 ;;
*.Z) uncompress $1 ;;
*.7z) 7z x $1 ;;
*) echo "'$1' cannot be extracted via extract" ;;
esac
else
echo "'$1' is not a valid file"
fi
}
Fuzzy History Search That Actually Works
My enhanced Ctrl+R with preview:
function fzf_history_search() {
local selected
selected=$(fc -rl 1 | awk '{$1=""; print substr($0,2)}' |
fzf --height 40% --layout=reverse --border --color=border:blue \
--preview='echo {}' --preview-window=down:3:wrap)
LBUFFER=$selected
zle reset-prompt
}
zle -N fzf_history_search
bindkey '^R' fzf_history_search
Directory Navigation with Preview
Jump to any directory with fuzzy search and tree preview:
function fcd() {
local dir
dir=$(fd --type d --hidden --follow --exclude .git . "${1:-.}" |
fzf --preview 'eza --tree --level=1 --color=always {}' --preview-window=right:50%) &&
cd "$dir"
}
The “Find and Edit” Power Combo
Search file contents and jump to exact line in Neovim:
function ff() {
local file line
read -r file line < <(rg --line-number --no-heading --color=always --smart-case "${*:-}" |
fzf --ansi --delimiter : \
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \
--preview-window 'up,60%,border-bottom,+{2}+3/3,~3' |
awk -F: '{print $1, $2}')
if [[ -n "$file" ]]; then
${EDITOR:-vim} "$file" +$line
fi
}
Smart Package Installation for Arch
Intelligently routes packages to correct repository:
function in() {
local -a inPkg=("$@")
local -a arch=()
local -a aur=()
# Detect the AUR helper
if pacman -Qi paru &>/dev/null ; then
aurhelper="paru"
elif pacman -Qi yay &>/dev/null ; then
aurhelper="yay"
else
echo "No AUR helper found. Install paru or yay first."
return 1
fi
# Sort packages by repo
for pkg in "${inPkg[@]}"; do
if pacman -Si "${pkg}" &>/dev/null ; then
arch+=("${pkg}")
else
aur+=("${pkg}")
fi
done
# Install packages
if [[ ${#arch[@]} -gt 0 ]]; then
echo "Installing from official repositories: ${arch[@]}"
sudo pacman -S --needed "${arch[@]}"
fi
if [[ ${#aur[@]} -gt 0 ]]; then
echo "Installing from AUR: ${aur[@]}"
${aurhelper} -S --needed "${aur[@]}"
fi
}
Command Not Found Handler
Suggests packages when commands aren’t found:
function command_not_found_handler() {
local purple='\e[1;35m' bright='\e[0;1m' green='\e[1;32m' reset='\e[0m'
printf 'zsh: command not found: %s\n' "$1"
local entries=( ${(f)"$(/usr/bin/pacman -F --machinereadable -- "/usr/bin/$1")"} )
if (( ${#entries[@]} )) ; then
printf "${bright}$1${reset} may be found in the following packages:\n"
local pkg
for entry in "${entries[@]}" ; do
local fields=( ${(0)entry} )
if [[ "$pkg" != "${fields[2]}" ]] ; then
printf "${purple}%s/${bright}%s ${green}%s${reset}\n" "${fields[1]}" "${fields[2]}" "${fields[3]}"
fi
printf ' /%s\n' "${fields[4]}"
pkg="${fields[2]}"
done
fi
return 127
}
Universal System Update
One command to rule them all:
function update() {
echo "๐ฆ Starting system update..."
# Determine package manager and update system packages
if command -v paru &>/dev/null; then
echo "โ๏ธ Updating with paru..."
paru -Syu --noconfirm
elif command -v yay &>/dev/null; then
echo "โ๏ธ Updating with yay..."
yay -Syu --noconfirm
else
echo "โ๏ธ Updating with pacman..."
sudo pacman -Syu
fi
# Update zinit and plugins
echo "๐ Updating zinit plugins..."
zinit self-update
zinit update --parallel
# Update other package managers if installed
if command -v flatpak &>/dev/null; then
echo "๐ Updating flatpak packages..."
flatpak update -y
fi
if command -v snap &>/dev/null; then
echo "๐ฑ Updating snap packages..."
sudo snap refresh
fi
# Clean up orphaned packages
if command -v paru &>/dev/null || command -v yay &>/dev/null; then
echo "๐งน Cleaning up orphaned packages..."
pacman -Qtdq | sudo pacman -Rns - 2>/dev/null || echo "No orphaned packages to remove"
fi
# Clean package cache
echo "๐งผ Cleaning package cache..."
if command -v paru &>/dev/null; then
paru -Sc --noconfirm
elif command -v yay &>/dev/null; then
yay -Sc --noconfirm
else
sudo pacman -Sc --noconfirm
fi
echo "โ
System update complete!"
}
Simple but Essential: mkcd
Create and enter directory in one go:
function mkcd() {
mkdir -p "$1" && cd "$1"
}
The Alias Game: Shortcuts Everywhere
Smart AUR Helper Detection
# Detect AUR helper for package management
if pacman -Qi paru &>/dev/null; then
aurhelper="paru"
elif pacman -Qi yay &>/dev/null; then
aurhelper="yay"
else
aurhelper="sudo pacman"
fi
Complete Alias Collection
System and Navigation
alias c='clear'
alias reload='source ~/.zshrc'
alias ..='cd ..'
alias ...='cd ../..'
alias .3='cd ../../..'
alias .4='cd ../../../..'
alias mkdir='mkdir -p' # Always create parent directories
File Operations (with safety)
alias cp='cp -i' # Confirm before overwrite
alias mv='mv -i' # Confirm before overwrite
alias rm='rm -i' # Confirm before delete
Enhanced Directory Listing
# Prefer eza over ls for better visuals
if command -v eza &>/dev/null; then
alias ls='eza --icons=auto --group-directories-first'
alias l='eza -l --icons=auto --group-directories-first'
alias ll='eza -la --icons=auto --sort=name --group-directories-first'
alias lt='eza --tree --level=2 --icons=auto'
alias ld='eza -lD --icons=auto'
else
alias ls='ls --color=auto'
alias l='ls -lh'
alias ll='ls -lha'
alias ld='ls -lhd */'
fi
Package Management (Arch-specific)
alias un='$aurhelper -Rns' # Uninstall with dependencies
alias up='$aurhelper -Syu' # Full system update
alias pl='$aurhelper -Qs' # List installed packages
alias pa='$aurhelper -Ss' # Search available packages
alias pc='$aurhelper -Sc' # Clean package cache
alias po='$aurhelper -Qtdq | $aurhelper -Rns -' # Remove orphans
Git Shortcuts
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gl='git log --graph --oneline --decorate'
alias gd='git diff'
alias gp='git push'
alias gpl='git pull'
Development
alias vc='code' # VS Code
alias py='python3'
alias serve='python3 -m http.server 8000'
System Information
alias meminfo='free -h'
alias cpuinfo='lscpu'
alias diskinfo='df -h'
alias sysinfo='neofetch'
Network
alias myip='curl -s https://ifconfig.me'
alias ports='sudo netstat -tulanp'
Docker
alias d='docker'
alias dc='docker-compose'
alias dps='docker ps'
alias dimg='docker images'
Comprehensive Keybindings
My keybindings combine the best of Vim and Emacs:
# Navigation
bindkey '^A' beginning-of-line # Ctrl+A
bindkey '^E' end-of-line # Ctrl+E
bindkey '^[[1;5C' forward-word # Ctrl+Right
bindkey '^[[1;5D' backward-word # Ctrl+Left
bindkey '^[[H' beginning-of-line # Home
bindkey '^[[F' end-of-line # End
bindkey '^[[3~' delete-char # Delete
bindkey '^K' kill-line # Kill to end of line
bindkey '^U' kill-whole-line # Kill entire line
# History navigation
bindkey '^P' up-line-or-history # Ctrl+P (like Vim)
bindkey '^N' down-line-or-history # Ctrl+N (like Vim)
bindkey '^S' history-incremental-search-forward # Ctrl+S
# Custom: FZF history search on Ctrl+R
bindkey '^R' fzf_history_search
Note: Most VI-mode keybindings are handled by the zsh-vi-mode plugin.
Tool Integrations: The Power User’s Toolkit
FZF - The Command Line Game Changer
My FZF configuration for maximum productivity:
if command -v fzf &>/dev/null; then
export FZF_DEFAULT_COMMAND='fd --type f --hidden --exclude .git'
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border --preview-window=right:60%'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_ALT_C_COMMAND='fd --type d --hidden --exclude .git'
export FZF_ALT_C_OPTS="--preview 'eza --tree {} | head -200'"
fi
This gives me:
- Ctrl+T: Fuzzy file search with preview
- Alt+C: Fuzzy directory change with tree preview
- Ctrl+R: Enhanced history search (custom function)
Development Environments
Node.js with NVM
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
Rust with Cargo
. "$HOME/.cargo/env"
Android Development
export ANDROID_HOME=$HOME/Android/Sdk
export ANDROID_SDK_ROOT=$ANDROID_HOME
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/cmdline-tools/latest/bin
# Android Studio if installed in /opt
export PATH=$PATH:/opt/android-studio/bin
# Android NDK
export NDK_HOME=$ANDROID_HOME/ndk/25.2.9519653
export PATH=$PATH:$NDK_HOME
Google Cloud SDK
# Multiple possible locations
if [ -f "${HOME}/Downloads/google-cloud-sdk/path.zsh.inc" ]; then
source "${HOME}/Downloads/google-cloud-sdk/path.zsh.inc"
fi
if [ -f '/home/kn/google-cloud-sdk/path.zsh.inc' ]; then
. '/home/kn/google-cloud-sdk/path.zsh.inc'
fi
Command Enhancement Tools
The Fuck - Fix Commands with Style
if command -v thefuck &>/dev/null; then
eval $(thefuck --alias)
fi
GitHub Copilot CLI
if command -v github-copilot-cli &>/dev/null; then
eval "$(github-copilot-cli alias -- "$0")"
fi
Tools Managed by Zinit
- Bat: Syntax-highlighted cat replacement
- Ripgrep (rg): Ultra-fast grep alternative
- FZF: Fuzzy finder for everything
- Eza: Modern ls with icons and git integration
- Fd: User-friendly find alternative
Performance Optimizations
Startup Speed Tricks
- Async Plugin Loading: Using
zinit wait lucidfor non-critical plugins - Lazy NVM Loading: NVM only loads when needed (saves 400ms!)
- Compilation Cache: ZSH compilations cached in
~/.zsh/cache - Minimal Synchronous Loading: Only vi-mode loads synchronously
History Performance
With 1 million lines of history, these settings keep it fast:
HIST_IGNORE_ALL_DUPS: Prevents history bloatINC_APPEND_HISTORY: Writes immediately (no data loss)SHARE_HISTORY: All terminals share the same history
Security and Safety Features
Built-in Protections
- Confirmation prompts:
cp -i,mv -i,rm -iprevent accidents - Space-prefix privacy: Commands starting with space aren’t saved to history
- Local config isolation:
.zshrc.localfor sensitive/machine-specific settings
Smart Error Handling
The command_not_found_handler doesn’t just complain - it helps:
$ htop
zsh: command not found: htop
htop may be found in the following packages:
extra/htop 3.3.0-1
/usr/bin/htop
Additional Power Tools Not Mentioned
Profiling Support
# Enable profiling (uncomment to use)
# zmodload zsh/zprof
# ... your config ...
# zprof # Shows what's slowing down startup
Local Customizations
# Source local customizations if they exist
[[ -f ~/.zshrc.local ]] && source ~/.zshrc.local
This lets me have machine-specific settings without cluttering the main config.
The Complete Tool List
Here’s everything my setup includes:
Core Tools
- ZSH: The shell itself
- Zinit: Plugin manager
- Starship: Rust-powered prompt
- Neovim: Default editor
ZSH Plugins (via Zinit)
- zsh-vi-mode: Complete VI keybindings
- fast-syntax-highlighting: Real-time syntax checking
- zsh-autosuggestions: Command suggestions
- zsh-z: Directory jumping
- zsh-you-should-use: Alias reminders
- history-search-multi-word: Enhanced history search
- git-open: Open repos in browser
- fzf-tab: FZF-powered tab completion
- zsh-completions: Additional completions
Command Line Tools
- bat: Better cat with highlighting
- eza: Better ls with icons
- ripgrep: Better grep
- fd: Better find
- fzf: Fuzzy finder
- thefuck: Command correction
- github-copilot-cli: AI assistance
Development Environments
- NVM: Node.js versions
- Cargo/Rust: Rust toolchain
- Android SDK: Full Android dev setup
- Google Cloud SDK: GCP tools
Package Managers
- pacman: Arch base packages
- paru/yay: AUR helpers
- flatpak: Flatpak apps
- snap: Snap packages
Oh-My-Zsh Libraries (cherry-picked)
- git.zsh: Git aliases and functions
- history.zsh: History management
- key-bindings.zsh: Standard bindings
- theme-and-appearance.zsh: Colors
- completion.zsh: Completion system
- directories.zsh: Directory helpers
The Numbers
- 482 lines of configuration
- 40+ tools integrated
- 9 ZSH plugins loaded asynchronously
- 30+ aliases for common tasks
- 8 custom functions for complex operations
- < 200ms startup time
- 1,000,000 commands in history
Tips for Adopting This Setup
- Start Small: Don’t copy everything at once
- Test Incrementally: Add one feature, test, repeat
- Customize Paths: Many paths are specific to my system
- Check Dependencies: Some tools need manual installation
- Read the Comments: The config is fully documented
Common Issues and Solutions
“Command not found” after copying config
- Install the missing tools first (bat, eza, ripgrep, etc.)
- Or comment out sections until you install them
Slow startup
- Comment out NVM section (biggest culprit)
- Use
zprofto identify slow parts
Keybindings not working
- Check if your terminal emulator supports the key codes
- Some bindings might conflict with terminal settings
The Philosophy
This setup embodies three principles:
- Speed: Everything should be instant
- Discovery: Tools should help you find what you need
- Safety: Mistakes should be hard to make
Every line in my config serves these goals.
Conclusion: Your Terminal, Your Rules
My 482-line ZSH configuration transforms the terminal from a tool into a superpower. But remember - the best config is the one that fits YOUR workflow.
Start with the basics:
- Get a good prompt (Starship)
- Add syntax highlighting
- Set up fuzzy finding
- Create aliases for YOUR common tasks
Then gradually add more as you discover what you need. The terminal is a personal space - make it yours.
The terminal renaissance is real. Join us on the command line side - we have syntax highlighting! ๐