# ============================================================================= # ~/.zshrc # ============================================================================= # Interactive shell configuration. Environment and PATH live in ~/.zshenv. # # Order matters: # 1. Profiling switch # 2. tmux autostart (exec early, so the outer shell skips all init work) # 3. Cheap stuff: options, keybindings, history # 4. Expensive stuff: completion system, cached init output, prompt # 5. Aliases # 6. Functions # 7. tmux integration (~/.zsh/tmux.zsh) # 8. Finish: self-compile, profiling report # # Clear caches after a theme/tool update: zshrc-refresh # Measure startup time: ZSHRC_PROFILE=1 zsh -i -c exit # ============================================================================= # ----------------------------------------------------------------------------- # 1. Profiling (opt-in via environment variable) # ----------------------------------------------------------------------------- [[ -n "$ZSHRC_PROFILE" ]] && zmodload zsh/zprof # ----------------------------------------------------------------------------- # 2. tmux autostart # ----------------------------------------------------------------------------- # Must come first: the outer shell gets replaced by tmux, so there's no point # initializing it. The shell inside tmux runs this file again with $TMUX set. # # Terminals are detected via environment variables instead of ps//proc, which # saves a handful of forks per shell start. # # Note: none of these variables survive an SSH login, so SSH sessions don't # autostart tmux. Use ZSH_FORCE_TMUX=1 or uncomment the SSH line below. # # Overrides: # ZSH_FORCE_TMUX=1 always start tmux # ZSH_NO_TMUX=1 never start tmux if [[ -o interactive && -z "$TMUX" && -z "$ZSHRC_PROFILE" && $UID -ne 0 && -t 1 ]]; then # Leave IDEs and embedded terminals alone if [[ -z "$TERMINAL_EMULATOR" && -z "$INSIDE_EMACS" \ && -z "$VSCODE_INJECTION" && -z "$NVIM" ]]; then _zsh_want_tmux=0 [[ "$TERM_PROGRAM" == ghostty || -n "$GHOSTTY_RESOURCES_DIR" ]] && _zsh_want_tmux=1 # Ghostty [[ -n "$KITTY_WINDOW_ID" ]] && _zsh_want_tmux=1 # Kitty [[ -n "$VTE_VERSION" ]] && _zsh_want_tmux=1 # GNOME Terminal, xfce4-terminal, Tilix # [[ -n "$SSH_CONNECTION" ]] && _zsh_want_tmux=1 # SSH logins [[ -n "$ZSH_FORCE_TMUX" ]] && _zsh_want_tmux=1 [[ -n "$ZSH_NO_TMUX" ]] && _zsh_want_tmux=0 if (( _zsh_want_tmux )) && (( $+commands[tmux] )); then unset _zsh_want_tmux exec tmux new-session -A -s main fi unset _zsh_want_tmux fi fi # ----------------------------------------------------------------------------- # 3. Options, keybindings, history # ----------------------------------------------------------------------------- bindkey -e # Emacs keybindings, even if EDITOR=vi setopt interactive_comments # allow "# comments" on the command line HISTFILE=~/.zsh_history HISTSIZE=50000 # entries kept in memory SAVEHIST=50000 # entries written to $HISTFILE setopt hist_ignore_all_dups # drop older duplicates setopt hist_ignore_space # don't record lines starting with a space setopt hist_reduce_blanks # squeeze superfluous whitespace setopt hist_verify # show history expansion before running it setopt extended_history # store timestamps setopt share_history # share history between running shells # If prompts ever feel sluggish: share_history re-reads the history file on # every prompt. Alternative that only appends: # unsetopt share_history # setopt inc_append_history # ----------------------------------------------------------------------------- # 4a. Cache directory and prompt theme # ----------------------------------------------------------------------------- ZSH_CACHE_DIR=${XDG_CACHE_HOME:-$HOME/.cache}/zsh [[ -d $ZSH_CACHE_DIR ]] || mkdir -p $ZSH_CACHE_DIR OMP_CONFIG=$HOME/.zsh/atomic-one-line.omp.json # ----------------------------------------------------------------------------- # 4b. Completion system # ----------------------------------------------------------------------------- # A bare "compinit" runs compaudit and stats every $fpath directory on each # start - hundreds of stat() calls, noticeable with a cold page cache. # So: full check once a day, otherwise load straight from the dump. () { emulate -L zsh setopt extended_glob local zdump=$ZSH_CACHE_DIR/zcompdump autoload -Uz compinit if [[ -n $zdump(#qNmh-24) ]]; then compinit -C -d $zdump # dump younger than 24h -> skip the audit else compinit -d $zdump # full run, once a day touch $zdump fi # Compiled dump loads noticeably faster if [[ ! -s ${zdump}.zwc || $zdump -nt ${zdump}.zwc ]]; then zcompile $zdump 2>/dev/null fi } zstyle ':completion:*' auto-description 'specify: %d' zstyle ':completion:*' completer _expand _complete _correct _approximate zstyle ':completion:*' format 'Completing %d' zstyle ':completion:*' group-name '' zstyle ':completion:*' menu select=long zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*' zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s zstyle ':completion:*' use-compctl false zstyle ':completion:*' verbose true # Cache expensive completion results (e.g. apt package lists) zstyle ':completion:*' use-cache on zstyle ':completion:*' cache-path $ZSH_CACHE_DIR/compcache # kill: colored process list zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31' zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd' # ssh: hosts from ~/.ssh/config (+ config.d). "zstyle -e" evaluates this lazily # on , not on every shell start. Wildcard patterns are filtered out. zstyle -e ':completion:*:*:ssh:*' hosts \ 'reply=(${(f)"$(grep -h "^Host " ~/.ssh/config ~/.ssh/config.d/*(N) 2>/dev/null | sed "s/^Host //" | tr " " "\n" | grep -v "[*?]")"})' # (list-colors for files is set further down, once LS_COLORS exists) # ----------------------------------------------------------------------------- # 4c. Cached init output (television, dircolors) # ----------------------------------------------------------------------------- # Both tools print the same static shell code on every run. Instead of forking # them each time, their output is written to one file, compiled, and sourced. # The cache is rebuilt when ~/.dircolors or the tv binary is newer than it. # # Each block is followed by an explicit newline: some tools end their output # without one, which would glue the next block onto the last line. () { emulate -L zsh local cache=$ZSH_CACHE_DIR/init.zsh local stale=0 [[ -s $cache ]] || stale=1 [[ -f ~/.dircolors && ~/.dircolors -nt $cache ]] && stale=1 [[ -n $commands[tv] && $commands[tv] -nt $cache ]] && stale=1 if (( stale )); then { if (( $+commands[tv] )); then tv init zsh print fi if (( $+commands[dircolors] )); then if [[ -r ~/.dircolors ]]; then dircolors -b ~/.dircolors else dircolors -b fi print fi } > $cache.tmp.$$ 2>/dev/null && mv -f $cache.tmp.$$ $cache rm -f $cache.tmp.$$ zcompile $cache 2>/dev/null fi [[ -s $cache ]] && source $cache } # LS_COLORS is set now -> colorize file completion zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS} # ----------------------------------------------------------------------------- # 4d. Prompt (oh-my-posh) # ----------------------------------------------------------------------------- # Since v26, oh-my-posh caches its init code itself under ~/.cache/oh-my-posh/ # (file name contains version + config hash); "init zsh" only prints a small # stub that sources it. Caching that stub again would break on every update. # # If the prompt is suddenly plain ("host%"), oh-my-posh isn't in PATH - # check ~/.zshenv. if (( $+commands[oh-my-posh] )); then eval "$(oh-my-posh init zsh --config "$OMP_CONFIG")" fi # ----------------------------------------------------------------------------- # 5. Aliases # ----------------------------------------------------------------------------- alias ls='ls --color=auto' alias grep='grep --color=auto' alias fgrep='fgrep --color=auto' alias egrep='egrep --color=auto' [[ -f ~/.bash_aliases ]] && source ~/.bash_aliases [[ -f ~/.config/common_aliases ]] && source ~/.config/common_aliases # ----------------------------------------------------------------------------- # 6. Functions # ----------------------------------------------------------------------------- # zshrc-refresh: clear all zsh caches and restart the shell zshrc-refresh() { rm -f $ZSH_CACHE_DIR/init.zsh{,.zwc} \ $ZSH_CACHE_DIR/zcompdump{,.zwc} \ ~/.zshrc.zwc # oh-my-posh normally cleans up its own cache. If the prompt misbehaves # after an update, additionally run: # rm -rf ~/.cache/oh-my-posh echo "Cache removed, restart shell" exec zsh } # tm [name]: create or switch to a tmux session (default: main) tm() { local s=${1:-main} if [[ -n "$TMUX" ]]; then tmux has-session -t "=$s" 2>/dev/null || tmux new-session -d -s "$s" tmux switch-client -t "=$s" else tmux new-session -A -s "$s" fi } # y: run yazi and cd into the directory it was closed in y() { local tmp="$(mktemp -t "yazi-cwd.XXXXXX")" cwd yazi "$@" --cwd-file="$tmp" IFS= read -r -d '' cwd < "$tmp" [[ -n "$cwd" && "$cwd" != "$PWD" ]] && builtin cd -- "$cwd" rm -f -- "$tmp" } # Bandcamp Aliases [[ -f ~/.zsh/bandcamp.zsh ]] && source ~/.zsh/bandcamp.zsh # ----------------------------------------------------------------------------- # 7. tmux integration # ----------------------------------------------------------------------------- # Window names from the current directory plus active SSH hosts per pane. # Only does something inside tmux. [[ -f ~/.zsh/tmux.zsh ]] && source ~/.zsh/tmux.zsh # ----------------------------------------------------------------------------- # 8. Finish # ----------------------------------------------------------------------------- # Compile this file. zsh picks up ~/.zshrc.zwc automatically as long as it is # newer than ~/.zshrc, and it gets rebuilt here after every change. if [[ ! -s ~/.zshrc.zwc || ~/.zshrc -nt ~/.zshrc.zwc ]]; then zcompile ~/.zshrc 2>/dev/null fi [[ -n "$ZSHRC_PROFILE" ]] && zprof eval "$(direnv hook zsh)"