perf(zsh): cache tool init, lazy-load, remove plugins

Startup: 150ms → 40ms (native), projected 10-30s → <1s (iSH)

- Cache mise/fzf/zoxide init output keyed on binary mtime
- zcompile all sourced files to bytecode at install time
- Lazy-load compinit (first Tab), fzf+widgets (first keystroke)
- Remove autosuggestions and syntax-highlighting plugins
- Switch mise to shims mode (no per-prompt hook)
- Conditional mkdir (skip if dirs exist)
- Remove TRAPINT/flash handler, cache session info
- Eliminate per-prompt subshell forks (REPLY pattern)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-24 18:08:40 +00:00
parent 53897ed9e9
commit ffd26e06d6
4 changed files with 98 additions and 114 deletions

View File

@@ -1,12 +1,11 @@
# Prompt # Prompt
(( ${+PROMPT_MIN_DURATION} )) || typeset -gi PROMPT_MIN_DURATION=2 # show duration after N seconds (( ${+PROMPT_MIN_DURATION} )) || typeset -gi PROMPT_MIN_DURATION=2 # show duration after N seconds
(( ${+PROMPT_FLASH_DELAY} )) || typeset -gi PROMPT_FLASH_DELAY=4 # flash prompt for N centiseconds
typeset -gi _dots_prompt_cmd_start=0 typeset -gi _dots_prompt_cmd_start=0
typeset -gi _dots_prompt_cmd_ran=0 typeset -gi _dots_prompt_cmd_ran=0
typeset -gi _dots_prompt_flashing=0
typeset -g _dots_prompt_symbol="λ" typeset -g _dots_prompt_symbol="λ"
typeset -g _dots_prompt_base="" typeset -g _dots_prompt_base=""
typeset -g _dots_session_cache=""
typeset -gA _dots_pc typeset -gA _dots_pc
_dots_init_colors() { _dots_init_colors() {
@@ -59,14 +58,17 @@ _dots_abbrev_path() {
local -a parts=( "${(@s:/:)dir}" ) local -a parts=( "${(@s:/:)dir}" )
local count=${#parts[@]} local count=${#parts[@]}
(( count <= 3 )) && { print -r -- "$dir"; return } if (( count <= 3 )); then
REPLY="$dir"
return
fi
local result="" local result=""
local i local i
for (( i=1; i <= count-3; i++ )); do for (( i=1; i <= count-3; i++ )); do
result+="${parts[i][1]}/" result+="${parts[i][1]}/"
done done
print -r -- "${result}${parts[-3]}/${parts[-2]}/${parts[-1]}" REPLY="${result}${parts[-3]}/${parts[-2]}/${parts[-1]}"
} }
_dots_session() { _dots_session() {
@@ -189,18 +191,15 @@ typeset -g _dots_git_async_fd=""
_dots_git_async_callback() { _dots_git_async_callback() {
local fd=$1 local fd=$1
local result="" local result=""
# Use sysread for efficient non-blocking read from fd
if [[ -n "$fd" ]] && sysread -i "$fd" result 2>/dev/null; then if [[ -n "$fd" ]] && sysread -i "$fd" result 2>/dev/null; then
result="${result%$'\n'}" # trim trailing newline result="${result%$'\n'}"
_dots_git_info_result="$result" _dots_git_info_result="$result"
_dots_build_dots_prompt_base _dots_build_dots_prompt_base
PROMPT="$_dots_prompt_base" PROMPT="$_dots_prompt_base"
# Only reset prompt if not in a special ZLE widget (e.g. fzf)
if zle && [[ "${WIDGET:-}" != _dots_* ]]; then if zle && [[ "${WIDGET:-}" != _dots_* ]]; then
zle reset-prompt 2>/dev/null zle reset-prompt 2>/dev/null
fi fi
fi fi
# Clean up
exec {fd}<&- exec {fd}<&-
zle -F "$fd" 2>/dev/null zle -F "$fd" 2>/dev/null
_dots_git_async_fd="" _dots_git_async_fd=""
@@ -226,7 +225,8 @@ _dots_git_async_start() {
} }
_dots_build_dots_prompt_base() { _dots_build_dots_prompt_base() {
local dir_path="$(_dots_abbrev_path)" _dots_abbrev_path
local dir_path="$REPLY"
local symbol="${_dots_pc[grey]}${_dots_prompt_symbol}${_dots_pc[reset]}" local symbol="${_dots_pc[grey]}${_dots_prompt_symbol}${_dots_pc[reset]}"
(( EUID == 0 )) && symbol="${_dots_pc[orange]}${_dots_pc[bold]}#${_dots_pc[reset]}" (( EUID == 0 )) && symbol="${_dots_pc[orange]}${_dots_pc[bold]}#${_dots_pc[reset]}"
@@ -264,7 +264,7 @@ _dots_precmd() {
(( e )) && rp_parts+=("${_dots_pc[orange]}[${e}]${_dots_pc[reset]}") (( e )) && rp_parts+=("${_dots_pc[orange]}[${e}]${_dots_pc[reset]}")
local session="$(_dots_session)" local session="$_dots_session_cache"
[[ -n "$session" ]] && rp_parts+=("${_dots_pc[dark_bg]}${_dots_pc[dark]}[${_dots_pc[orange]}${session}${_dots_pc[reset]}${_dots_pc[dark_bg]}${_dots_pc[dark]}]${_dots_pc[reset]}") [[ -n "$session" ]] && rp_parts+=("${_dots_pc[dark_bg]}${_dots_pc[dark]}[${_dots_pc[orange]}${session}${_dots_pc[reset]}${_dots_pc[dark_bg]}${_dots_pc[dark]}]${_dots_pc[reset]}")
RPROMPT="${(j: :)rp_parts}" RPROMPT="${(j: :)rp_parts}"
@@ -282,36 +282,11 @@ _dots_precmd() {
TRAPINT() {
# Only customize when ZLE is active (at prompt, not during command)
if [[ -o zle ]] && [[ -o interactive ]] && (( ${+WIDGET} )); then
if [[ -z "$BUFFER" ]] && (( ! _dots_prompt_flashing )); then
# Empty buffer: flash the prompt symbol
_dots_prompt_flashing=1
local git_part=""
[[ -n "$_dots_git_info_result" ]] && git_part=" ${_dots_git_info_result}"
local flash_prompt=$'\n'"${_dots_pc[dark_bg]}${_dots_pc[dark]}#${_dots_pc[teal]}$(_dots_abbrev_path)${_dots_pc[reset]}${_dots_pc[dark_bg]}${_dots_pc[dark]}#${_dots_pc[reset]}${git_part}"$'\n'$'%{\e[48;2;248;140;20m\e[30m%}'"${_dots_prompt_symbol}"$' %{\e[0m%}'
PROMPT="$flash_prompt"
zle reset-prompt
zselect -t $PROMPT_FLASH_DELAY
_dots_prompt_flashing=0
PROMPT="$_dots_prompt_base"
zle reset-prompt
return 0
elif [[ -n "$BUFFER" ]]; then
# Buffer has content: clear autosuggest, then default behavior
zle autosuggest-clear 2>/dev/null
fi
fi
# Propagate signal: use special return code -1 to let zsh handle normally
return $((128 + ${1:-2}))
}
_dots_prompt_init() { _dots_prompt_init() {
zmodload zsh/datetime 2>/dev/null zmodload zsh/datetime 2>/dev/null
zmodload zsh/zselect 2>/dev/null
zmodload zsh/system 2>/dev/null zmodload zsh/system 2>/dev/null
_dots_init_colors _dots_init_colors
_dots_session_cache="$(_dots_session)"
_dots_build_dots_prompt_base _dots_build_dots_prompt_base
setopt PROMPT_SUBST EXTENDED_HISTORY INC_APPEND_HISTORY_TIME setopt PROMPT_SUBST EXTENDED_HISTORY INC_APPEND_HISTORY_TIME

View File

@@ -1,19 +1,38 @@
# Profiling: ZSH_BENCH=1 zsh # Profiling: ZSH_BENCH=1 zsh
[[ -n "$ZSH_BENCH" ]] && zmodload zsh/zprof [[ -n "$ZSH_BENCH" ]] && zmodload zsh/zprof
# Upgrade xterm-color to xterm-256color (gh cs ssh sets the weaker value) # Terminal capabilities
[[ "$TERM" == "xterm-color" ]] && export TERM=xterm-256color [[ "$TERM" == "xterm-color" ]] && export TERM=xterm-256color
# Assume truecolor support if terminal advertises 256color (covers SSH, tmux)
[[ -z "$COLORTERM" && "$TERM" == *256color* ]] && export COLORTERM=truecolor [[ -z "$COLORTERM" && "$TERM" == *256color* ]] && export COLORTERM=truecolor
_dots_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/dots" _dots_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/dots"
# Cache eval output keyed on binary mtime — busts on brew upgrade / tool update
_dots_cached_eval() {
local name="$1" bin="$2"; shift 2
local cache="$_dots_cache_dir/${name}.zsh"
if [[ -f "$cache" && "$cache" -nt "$bin" ]]; then
source "$cache"
else
"$@" > "$cache" 2>/dev/null
if [[ -s "$cache" ]]; then
zcompile "$cache" 2>/dev/null
source "$cache"
else
rm -f "$cache"; return 1
fi
fi
}
# --- Environment ---
_dots_load_profile() { source "$HOME/.profile" } _dots_load_profile() { source "$HOME/.profile" }
_dots_load_profile _dots_load_profile
_dots_setup_dirs() { _dots_setup_dirs() {
mkdir -p "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$HOME/.local/bin" "$WORKSPACE" "$_dots_cache_dir" local d; for d in "$XDG_DATA_HOME" "$XDG_CONFIG_HOME" "$HOME/.local/bin" "$WORKSPACE" "$_dots_cache_dir"; do
[[ -d "$d" ]] || mkdir -p "$d"
done
} }
_dots_setup_dirs _dots_setup_dirs
@@ -37,12 +56,13 @@ _dots_cache_ls_colors
[[ -f ~/.aliases ]] && source ~/.aliases [[ -f ~/.aliases ]] && source ~/.aliases
# --- Completion (lazy — deferred until first Tab press) ---
_dots_init_completion() { _dots_init_completion() {
local comp_dir="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions" local comp_dir="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/completions"
[[ -d "$comp_dir" ]] && fpath=("$comp_dir" $fpath) [[ -d "$comp_dir" ]] && fpath=("$comp_dir" $fpath)
autoload -Uz compinit autoload -Uz compinit
# Daily cache invalidation
local dump="$HOME/.zcompdump" local dump="$HOME/.zcompdump"
if [[ -f "$dump" ]]; then if [[ -f "$dump" ]]; then
zmodload -F zsh/stat b:zstat 2>/dev/null zmodload -F zsh/stat b:zstat 2>/dev/null
@@ -57,7 +77,6 @@ _dots_init_completion() {
compinit compinit
fi fi
# Completion styling
zstyle ':completion:*' menu select zstyle ':completion:*' menu select
zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}" zstyle ':completion:*' list-colors "${(s.:.)LS_COLORS}"
zstyle ':completion:*' group-name '' zstyle ':completion:*' group-name ''
@@ -65,40 +84,22 @@ _dots_init_completion() {
zstyle ':completion:*:warnings' format $'\e[38;2;248;140;20m-- no matches --\e[0m' zstyle ':completion:*:warnings' format $'\e[38;2;248;140;20m-- no matches --\e[0m'
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'
} }
# Stub that loads real completion on first Tab, then replays the keypress
_dots_lazy_comp_widget() {
_dots_init_completion _dots_init_completion
zle -D _dots_lazy_comp_widget
_dots_load_plugins() { # If fzf-completion exists (loaded via zle-line-init), use it; otherwise default
local plugin_dir="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/plugins" if (( ${+widgets[fzf-completion]} )); then
zle fzf-completion "$@"
ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=#3C3C3C' else
zle expand-or-complete "$@"
local f="$plugin_dir/zsh-autosuggestions/zsh-autosuggestions.zsh" fi
[[ -f "$f" ]] && source "$f"
# syntax-highlighting must be sourced last
f="$plugin_dir/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
[[ -f "$f" ]] && source "$f"
# Syntax highlighting theme
typeset -gA ZSH_HIGHLIGHT_STYLES
ZSH_HIGHLIGHT_STYLES[command]='fg=#2CB494'
ZSH_HIGHLIGHT_STYLES[builtin]='fg=#2CB494'
ZSH_HIGHLIGHT_STYLES[alias]='fg=#2CB494'
ZSH_HIGHLIGHT_STYLES[function]='fg=#2CB494'
ZSH_HIGHLIGHT_STYLES[unknown-token]='fg=#F88C14'
ZSH_HIGHLIGHT_STYLES[path]='fg=#CCE0D0,underline'
ZSH_HIGHLIGHT_STYLES[globbing]='fg=#F88C14'
ZSH_HIGHLIGHT_STYLES[single-quoted-argument]='fg=#7290B8'
ZSH_HIGHLIGHT_STYLES[double-quoted-argument]='fg=#7290B8'
ZSH_HIGHLIGHT_STYLES[dollar-quoted-argument]='fg=#7290B8'
ZSH_HIGHLIGHT_STYLES[comment]='fg=#808080'
ZSH_HIGHLIGHT_STYLES[arg0]='fg=#2CB494'
ZSH_HIGHLIGHT_STYLES[default]='fg=#CCE0D0'
ZSH_HIGHLIGHT_STYLES[commandseparator]='fg=#808080'
ZSH_HIGHLIGHT_STYLES[redirection]='fg=#F88C14'
ZSH_HIGHLIGHT_STYLES[option]='fg=#7290B8'
} }
_dots_load_plugins zle -N _dots_lazy_comp_widget
bindkey '^I' _dots_lazy_comp_widget
# --- History & options ---
_dots_load_history() { _dots_load_history() {
HISTFILE="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/history" HISTFILE="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/history"
@@ -111,22 +112,31 @@ _dots_load_history
setopt IGNORE_EOF setopt IGNORE_EOF
source "$HOME/.zsh/widgets.zsh" # --- Tool init (cached) ---
_dots_load_fzf() { _dots_load_mise() {
command -v fzf &>/dev/null || return local bin="${commands[mise]:-}"
[[ -n "$bin" ]] || return
_dots_cached_eval mise "$bin" mise activate --shims zsh
}
# fzf env vars (needed by widgets and zoxide before fzf init loads)
if [[ -n "${commands[fzf]:-}" ]]; then
export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git"' export FZF_DEFAULT_COMMAND='rg --files --hidden --glob "!.git"'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
export FZF_DEFAULT_OPTS='--style=minimal --layout=reverse --height=40% --border=none --no-scrollbar --prompt="> " --info=inline-right --no-separator --margin=1,0,0,0 --color=fg:#808080,fg+:#CCE0D0,bg:-1,bg+:#1A1A1A --color=hl:#2CB494,hl+:#2CB494,info:#808080,marker:#2CB494 --color=prompt:#2CB494,spinner:#88409C,pointer:#2CB494,header:#808080 --color=border:#3C3C3C,preview-border:#3C3C3C,gutter:#1A1A1A,preview-fg:#CCE0D0' export FZF_DEFAULT_OPTS='--style=minimal --layout=reverse --height=40% --border=none --no-scrollbar --prompt="> " --info=inline-right --no-separator --margin=1,0,0,0 --color=fg:#808080,fg+:#CCE0D0,bg:-1,bg+:#1A1A1A --color=hl:#2CB494,hl+:#2CB494,info:#808080,marker:#2CB494 --color=prompt:#2CB494,spinner:#88409C,pointer:#2CB494,header:#808080 --color=border:#3C3C3C,preview-border:#3C3C3C,gutter:#1A1A1A,preview-fg:#CCE0D0'
# fzf --zsh requires v0.48+ fi
if fzf --zsh &>/dev/null; then
source <(fzf --zsh) _dots_load_fzf() {
else local bin="${commands[fzf]:-}"
[[ -n "$bin" ]] || return
if ! _dots_cached_eval fzf "$bin" fzf --zsh; then
local -a fzf_paths=( local -a fzf_paths=(
"${HOMEBREW_PREFIX:-/opt/homebrew}/opt/fzf/shell" "${HOMEBREW_PREFIX:-/opt/homebrew}/opt/fzf/shell"
"/usr/share/fzf" "/usr/share/fzf"
"${XDG_DATA_HOME:-$HOME/.local/share}/fzf/shell" "${XDG_DATA_HOME:-$HOME/.local/share}/fzf/shell"
) )
local dir
for dir in "${fzf_paths[@]}"; do for dir in "${fzf_paths[@]}"; do
[[ -f "$dir/key-bindings.zsh" ]] && source "$dir/key-bindings.zsh" && break [[ -f "$dir/key-bindings.zsh" ]] && source "$dir/key-bindings.zsh" && break
done done
@@ -136,21 +146,28 @@ _dots_load_fzf() {
fi fi
} }
_dots_load_zoxide() { _dots_load_zoxide() {
command -v zoxide &>/dev/null || return local bin="${commands[zoxide]:-}"
[[ -n "$bin" ]] || return
export _ZO_FZF_OPTS="$FZF_DEFAULT_OPTS" export _ZO_FZF_OPTS="$FZF_DEFAULT_OPTS"
export _ZO_ECHO=0 export _ZO_ECHO=0
eval "$(zoxide init zsh)" _dots_cached_eval zoxide "$bin" zoxide init zsh
} }
_dots_load_mise
_dots_load_zoxide
# --- Interactive shell ---
source "$HOME/.zsh/prompt.zsh" source "$HOME/.zsh/prompt.zsh"
_dots_load_mise() { # Load fzf + widgets after first prompt renders (zle-line-init fires before first keystroke)
command -v mise &>/dev/null && eval "$(mise activate zsh)" autoload -Uz add-zle-hook-widget
} _dots_lazy_widgets() {
_dots_load_mise
_dots_load_fzf _dots_load_fzf
_dots_load_zoxide source "$HOME/.zsh/widgets.zsh"
add-zle-hook-widget -d zle-line-init _dots_lazy_widgets
}
add-zle-hook-widget zle-line-init _dots_lazy_widgets
[[ -n "$ZSH_BENCH" ]] && zprof || true [[ -n "$ZSH_BENCH" ]] && zprof || true

View File

@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Description: # Description:
# Configure zsh shell with direct plugin management. # Configure zsh shell.
# #
# install zsh # install zsh
@@ -24,26 +24,6 @@ if ! command -v zsh &> /dev/null; then
esac esac
fi fi
zsh --version | log_quote
# plugin directory (XDG compliant)
PLUGIN_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zsh/plugins"
mkdir -p "$PLUGIN_DIR"
# install zsh-autosuggestions
if [ ! -d "$PLUGIN_DIR/zsh-autosuggestions" ]; then
git clone -q \
https://github.com/zsh-users/zsh-autosuggestions.git \
"$PLUGIN_DIR/zsh-autosuggestions"
fi
# install zsh-syntax-highlighting
if [ ! -d "$PLUGIN_DIR/zsh-syntax-highlighting" ]; then
git clone -q \
https://github.com/zsh-users/zsh-syntax-highlighting.git \
"$PLUGIN_DIR/zsh-syntax-highlighting"
fi
# change default shell to zsh # change default shell to zsh
if [[ "$SHELL" != *zsh ]]; then if [[ "$SHELL" != *zsh ]]; then
sudo chsh -s "$(command -v zsh)" "$(whoami)" sudo chsh -s "$(command -v zsh)" "$(whoami)"
@@ -51,4 +31,5 @@ if [[ "$SHELL" != *zsh ]]; then
fi fi
log_pass "zsh configured" log_pass "zsh configured"
zsh --version | log_quote

View File

@@ -23,8 +23,6 @@ if ! command -v stow &> /dev/null; then
esac esac
fi fi
stow --version | log_quote
root_dir=${DOTFILES:-$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")} root_dir=${DOTFILES:-$(dirname "$(dirname "$(dirname "$(realpath "$0")")")")}
rm -f "$HOME/.bash_profile" rm -f "$HOME/.bash_profile"
@@ -49,5 +47,18 @@ fi
# Bust PATH cache to force rebuild with new profile # Bust PATH cache to force rebuild with new profile
rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/dots/path" rm -f "${XDG_CACHE_HOME:-$HOME/.cache}/dots/path"
log_pass "stow linked" # Compile zsh dotfiles for faster shell startup
if command -v zsh &>/dev/null; then
zsh -c '
for f in ~/.zsh/*.zsh ~/.aliases ~/.profile(N); do
[[ $f.zwc -nt $f ]] || zcompile "$f" 2>/dev/null
done
'
fi
# Bust tool init caches so they regenerate with new PATH/tools
rm -f "${XDG_CACHE_HOME:-$HOME/.cache}"/dots/{fzf,mise,zoxide}.zsh{,.zwc}
log_pass "stow linked"
stow --version | log_quote