From 74d1d608450c14a4e7f8e29e4c4935798db72518 Mon Sep 17 00:00:00 2001 From: Fabian Ising Date: Wed, 14 Oct 2026 10:53:36 +0200 Subject: [PATCH] [ZSH] Faster startup: instant prompt, single compinit, cached probes Re-enable p10k instant prompt (sudo-key ssh-add moved above it). Drop oh-my-zsh.sh (second compinit, update check); set up what OMZ plugins need ourselves. Cache java_home, go env, fzf --zsh output. Disable the kubecontext segment (ran kubectl per shell). Log starts over 1 s to ~/.cache/zsh/startup-slow.log to catch cold-start stalls. Co-Authored-By: Claude Opus 5.5 --- zsh/.mac_config.zsh_mac.example | 4 +- zsh/.p10k.zsh_mac.example | 2 +- zsh/.zsh_plugins.txt | 3 +- zsh/.zshrc | 129 ++++++++++++++++++-------------- zsh/lib/antidote.zsh | 18 +++++ zsh/lib/startup.zsh | 53 +++++++++++++ 6 files changed, 150 insertions(+), 59 deletions(-) create mode 100644 zsh/lib/startup.zsh diff --git a/zsh/.mac_config.zsh_mac.example b/zsh/.mac_config.zsh_mac.example index 42d2567..1daa6a6 100644 --- a/zsh/.mac_config.zsh_mac.example +++ b/zsh/.mac_config.zsh_mac.example @@ -1,6 +1,8 @@ export LC_ALL=en_US.UTF-8 export LANG=en_US.UTF-8 -export JAVA_HOME=`/usr/libexec/java_home` +# deps: system/user JDK dirs, which change when a JDK is added or removed +_zsh_cached java_home /Library/Java/JavaVirtualMachines ~/Library/Java/JavaVirtualMachines \ + -- /usr/libexec/java_home && read -r JAVA_HOME < $REPLY && export JAVA_HOME # macOS resets this on updates (and at random); background it, it's slow defaults write .GlobalPreferences AppleLocale en_DE &! diff --git a/zsh/.p10k.zsh_mac.example b/zsh/.p10k.zsh_mac.example index b13ee91..c547703 100644 --- a/zsh/.p10k.zsh_mac.example +++ b/zsh/.p10k.zsh_mac.example @@ -71,7 +71,7 @@ phpenv # php version from phpenv (https://github.com/phpenv/phpenv) scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv) haskell_stack # haskell version from stack (https://haskellstack.org/) - kubecontext # current kubernetes context (https://kubernetes.io/) + # kubecontext # off: runs kubectl on each shell's first prompt, even when hidden terraform # terraform workspace (https://www.terraform.io) # terraform_version # terraform version (https://www.terraform.io) aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) diff --git a/zsh/.zsh_plugins.txt b/zsh/.zsh_plugins.txt index 071549f..3cac4d9 100644 --- a/zsh/.zsh_plugins.txt +++ b/zsh/.zsh_plugins.txt @@ -1,5 +1,4 @@ -# Load OMZ plugins -ohmyzsh/ohmyzsh +# Load OMZ lib and plugins; not oh-my-zsh.sh itself (own compinit, theme, update check) ohmyzsh/ohmyzsh path:lib ohmyzsh/ohmyzsh path:plugins/git ohmyzsh/ohmyzsh path:plugins/golang diff --git a/zsh/.zshrc b/zsh/.zshrc index 3b46aaf..1a88625 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -1,47 +1,11 @@ -# Powerlevel10k instant prompt and machine-local layers must remain first. -[[ -f ~/.zsh/.powerline_config ]] && source ~/.zsh/.powerline_config -[[ -f ~/.zsh/.user_config ]] && source ~/.zsh/.user_config -[[ -f ~/.zsh/.virtual_env_config.zsh ]] && source ~/.zsh/.virtual_env_config.zsh -[[ -f ~/.zsh/.local_config ]] && source ~/.zsh/.local_config -[[ -f ~/.zsh/.env.local ]] && source ~/.zsh/.env.local - -source "${ZDOTDIR:-$HOME}/.zsh/lib/updates.zsh" -export LC_OSC52=1 - -# Completions for tools installed under ~/.local (e.g. tools-repo's safe* -# wrappers). Must precede antidote, whose completion plugin runs compinit. -fpath=("$HOME/.local/share/zsh/site-functions" $fpath) - -plugins_txt=${ZDOTDIR:-$HOME}/.zsh/.zsh_plugins.txt -static_file=${ZDOTDIR:-$HOME}/.cache/zsh/.zsh_plugins.zsh -source "${ZDOTDIR:-$HOME}/.zsh/lib/antidote.zsh" -source "${ZDOTDIR:-$HOME}/.zsh/lib/interactive.zsh" - -bindkey '^w' kill-region -bindkey -v -VI_MODE_SET_CURSOR=true -# fzf keys (Ctrl-R/Ctrl-T/Alt-C, **); after bindkey -v so vi keymaps get them -(( $+commands[fzf] )) && source <(fzf --zsh) - -[[ -f ~/.zsh/.mac_config.zsh ]] && source ~/.zsh/.mac_config.zsh -if (( $+commands[go] )); then - export GOPATH="$(go env GOPATH 2>/dev/null)" - [[ -n "$GOPATH" ]] && path+=("$GOPATH/bin") -fi - -# Use safe wrappers after ~/.local/bin has been added to PATH. -# Agent shells: /dev/null SSH_AUTH_SOCK=/tmp/sudo-agent.sock ssh-add -l | grep -q "$_sudo_fp" || SSH_AUTH_SOCK=/tmp/sudo-agent.sock ssh-add ~/.ssh/sudo_key @@ -54,8 +18,68 @@ if [[ -f ~/.ssh/sudo_key && -z $_in_coding_agent ]]; then SSH_AUTH_SOCK=/tmp/sudo-agent-confirm.sock ssh-add -c ~/.ssh/sudo_key unset _sudo_fp fi +_zst_mark sudo-agent + +# Powerlevel10k instant prompt; nothing below may need console input. +if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then + source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" +fi +_zst_mark instant-prompt + +# Machine-local layers. +[[ -f ~/.zsh/.powerline_config ]] && source ~/.zsh/.powerline_config +[[ -f ~/.zsh/.user_config ]] && source ~/.zsh/.user_config +[[ -f ~/.zsh/.virtual_env_config.zsh ]] && source ~/.zsh/.virtual_env_config.zsh +[[ -f ~/.zsh/.local_config ]] && source ~/.zsh/.local_config +[[ -f ~/.zsh/.env.local ]] && source ~/.zsh/.env.local +_zst_mark layers + +source "${ZDOTDIR:-$HOME}/.zsh/lib/updates.zsh" +export LC_OSC52=1 +_zst_mark updates + +# Completions for tools installed under ~/.local (e.g. tools-repo's safe* +# wrappers). Must precede antidote, whose completion plugin runs compinit. +fpath=("$HOME/.local/share/zsh/site-functions" $fpath) + +plugins_txt=${ZDOTDIR:-$HOME}/.zsh/.zsh_plugins.txt +static_file=${ZDOTDIR:-$HOME}/.cache/zsh/.zsh_plugins.zsh +source "${ZDOTDIR:-$HOME}/.zsh/lib/antidote.zsh" +_zst_mark antidote +source "${ZDOTDIR:-$HOME}/.zsh/lib/interactive.zsh" + +bindkey '^w' kill-region +bindkey -v +VI_MODE_SET_CURSOR=true +# fzf keys (Ctrl-R/Ctrl-T/Alt-C, **); after bindkey -v so vi keymaps get them +(( $+commands[fzf] )) && _zsh_cached fzf.zsh ${commands[fzf]:A} -- fzf --zsh && + source $REPLY +_zst_mark fzf + +[[ -f ~/.zsh/.mac_config.zsh ]] && source ~/.zsh/.mac_config.zsh +_zst_mark mac-config +# deps: go binary and `go env -w` files (macOS, Linux) +if (( $+commands[go] )) && _zsh_cached gopath ${commands[go]:A} \ + "$HOME/Library/Application Support/go/env" "${XDG_CONFIG_HOME:-$HOME/.config}/go/env" \ + -- env -u GOPATH go env GOPATH; then + [[ -n $GOPATH ]] || read -r GOPATH < $REPLY + export GOPATH + path+=("$GOPATH/bin") +fi +_zst_mark go + +# Use safe wrappers after ~/.local/bin has been added to PATH. +# Agent shells: [dep...] -- ; sets REPLY to the cache file. +_zsh_cached() { + local file=${ZDOTDIR:-$HOME}/.cache/zsh/$1 stamp old + local -a mt + shift + while [[ $# -gt 0 && $1 != -- ]]; do + mt=(); zstat -A mt +mtime -- $1 2>/dev/null + stamp+="${1:A}@${mt[1]:-none};" + shift + done + shift + [[ -r $file.stamp ]] && read -r old < $file.stamp + if [[ ! -s $file || $old != $stamp ]]; then + local tmp=$file.$$.tmp + if "$@" >| $tmp 2>/dev/null && [[ -s $tmp ]]; then + command mv -f $tmp $file + print -r -- $stamp >| $file.stamp + else + command rm -f $tmp + [[ -s $file ]] || return 1 + fi + fi + REPLY=$file +} + +# Slow-start log: per-step ms, written only if startup (until the first prompt) +# exceeds ZSH_STARTUP_LOG_MS. Diagnostic for cold starts; remove once resolved. +typeset -gF _zst_t0=$EPOCHREALTIME _zst_last=$EPOCHREALTIME +typeset -ga _zst_steps +_zst_mark() { + local ms + printf -v ms '%s=%.0f' $1 $(( (EPOCHREALTIME - _zst_last) * 1000 )) + _zst_steps+=($ms) + _zst_last=$EPOCHREALTIME +} +_zst_report() { + add-zsh-hook -d precmd _zst_report + _zst_mark precmd + local total=$(( (EPOCHREALTIME - _zst_t0) * 1000 )) + (( total >= ${ZSH_STARTUP_LOG_MS:-1000} )) || return 0 + local ts login=0 + strftime -s ts '%Y-%m-%dT%H:%M:%S' $EPOCHSECONDS + [[ -o login ]] && login=1 + printf '%s total=%.0f tmux=%d login=%d | %s\n' $ts $total ${+TMUX} $login "$_zst_steps" \ + >> ${ZDOTDIR:-$HOME}/.cache/zsh/startup-slow.log +}