diff --git a/antidote_plugins_darwin.txt b/antidote_plugins_darwin.txt index ba9bd87..ff8bf43 100644 --- a/antidote_plugins_darwin.txt +++ b/antidote_plugins_darwin.txt @@ -11,12 +11,11 @@ ohmyzsh/ohmyzsh path:plugins/gitfast # Faster Git completio # ohmyzsh/ohmyzsh path:plugins/golang # Go-specific aliases and setup # ohmyzsh/ohmyzsh path:plugins/sudo # Sudo re-run shortcut with Esc-Esc -zsh-users/zsh-syntax-highlighting # Syntax highlights typed commands zsh-users/zsh-completions # Extra completions for many tools zsh-users/zsh-autosuggestions # History-based command suggestions # supercrabtree/k # Colorful `ls` + tree alternative -agkozak/zsh-z # Jump to frequently used directories +# agkozak/zsh-z removed: zoxide (late_50_tools.zsh) overwrote its `z` anyway romkatv/powerlevel10k # Fast, feature-rich Zsh prompt # romkatv/zsh-prompt-benchmark # Measure prompt rendering time # jeffreytse/zsh-vi-mode # Vi-mode keybindings with indicators @@ -24,4 +23,7 @@ romkatv/powerlevel10k # Fast, feature-rich Z junegunn/fzf path:shell/key-bindings.zsh # fzf keybindings (Ctrl-R, etc) junegunn/fzf path:shell/completion.zsh # fzf-enhanced completions + +# Keep syntax highlighting last: it wraps ZLE widgets defined by other plugins +zsh-users/zsh-syntax-highlighting # Syntax highlights typed commands # ohmyzsh/ohmyzsh path:plugins/fzf # fzf env setup (disabled: runs fzf --version, FZF_DEFAULT_COMMAND now set in early_20_environment.zsh) diff --git a/includes/early_20_environment.zsh b/includes/early_20_environment.zsh index 4bbe1d7..3c73e05 100644 --- a/includes/early_20_environment.zsh +++ b/includes/early_20_environment.zsh @@ -14,8 +14,8 @@ export EDITOR=$(command -v nvim || echo vi) export LESS='-i -N -w -z-4 -g -e -M -X -F -R -P%t?f%f \ :stdin .?pb%pb\%:?lbLine %lb:?bbByte %bb:-...' -export LESSCHARSET='latin1' -export LESSOPEN='|/usr/bin/lesspipe.sh %s 2>&-' +# LESSCHARSET removed: forcing latin1 garbled UTF-8 (nerd fonts, box drawing) +[ -x /usr/bin/lesspipe.sh ] && export LESSOPEN='|/usr/bin/lesspipe.sh %s 2>&-' export PAGER=less export KEYTIMEOUT=1 # Set the key timeout to 1/100th of a second @@ -59,8 +59,7 @@ case $OSTYPE in ;; esac -# z/zoxide paths -_Z_DATA=~/.zsh_dir_history +# zoxide paths (_Z_DATA removed along with the zsh-z plugin; zoxide owns `z` now) _ZO_DATA=~/.zo # control history diff --git a/includes/early_50_powerlevel10k.zsh b/includes/early_50_powerlevel10k.zsh index dea7fb1..14cbb58 100644 --- a/includes/early_50_powerlevel10k.zsh +++ b/includes/early_50_powerlevel10k.zsh @@ -69,21 +69,35 @@ function prompt_goenv_version() { # powerlevel 10 custom kube_context segment prompt_kube_context() { - # powerlevel10 has a builtin context, but want some extra features - CLUSTER_FILE=${ZSH_DIR}/k8s-clusters - local context - context=$(test -f ~/.kube/config && grep current-context ~/.kube/config | cut -d\ -f2) - if [[ -z $context ]]; then - context='unknown' + # powerlevel10 has a builtin context, but want some extra features. + # Context AND namespace are parsed straight from the kubeconfig file in one + # awk pass; the previous `kubectl config get-contexts` call cost ~110ms on + # every prompt render. + local CLUSTER_FILE="${ZSH}/k8s-clusters" + local kubeconfig="${KUBECONFIG%%:*}" + [[ -z "$kubeconfig" ]] && kubeconfig="${HOME}/.kube/config" + + local context namespace + if [[ -r "$kubeconfig" ]]; then + local parsed + parsed=$(awk ' + function dq(s) { gsub(/"/, "", s); return s } + /^current-context:/ { ctx = dq($2) } + /^- context:/ { ns = "" } + /^[[:space:]]+namespace:/ { ns = dq($2) } + /^[[:space:]]+name:/ { map[dq($2)] = ns } + END { print ctx; print map[ctx] } + ' "$kubeconfig") + local -a lines=("${(f)parsed}") + context="${lines[1]}" + namespace="${lines[2]}" fi + [[ -z "$context" ]] && context='unknown' + [[ -z "$namespace" ]] && namespace='default' if [[ "$context" =~ '^arn:aws' ]]; then context=${context#*/} fi - local namespace - namespace=$(kubectl config get-contexts --no-headers | grep '^\*' | awk '{ print $5 }') - if [ "${namespace}" = "" ]; then - namespace='default' - fi + local env env=$(test -f "${CLUSTER_FILE}" && grep "${context}" "${CLUSTER_FILE}" | cut -d\; -f1) if [ -z "${env}" ]; then @@ -92,6 +106,10 @@ prompt_kube_context() { p10k segment -s "${env}" -i $'\uE7B2' -t "${context}/${namespace}" } +# Optional: render the kube segment only while typing kube-related commands +# (hides it the rest of the time). Uncomment to enable. +# typeset -g POWERLEVEL9K_KUBE_CONTEXT_SHOW_ON_COMMAND='kubectl|kubecolor|helm|k9s|stern' + # Easily switch primary foreground/background colors typeset -g DEFAULT_BACKGROUND=237 typeset -g ALT_BACKGROUND=6 diff --git a/includes/init.zsh b/includes/init.zsh index 828eef0..12f98c1 100644 --- a/includes/init.zsh +++ b/includes/init.zsh @@ -69,6 +69,7 @@ esac all_paths=( "${HOME}/bin" + "${HOME}/.local/bin" "/usr/local/bin" "/usr/local/sbin" "/usr/bin" @@ -83,6 +84,7 @@ all_paths=( "/mnt/c/Program Files/Kubernetes/Minikube" "/var/lib/snapd/snap/bin" "/opt/homebrew/opt/mysql/bin" + "/Applications/Visual Studio Code.app/Contents/Resources/app/bin" ) for dir in "${all_paths[@]}"; do diff --git a/includes/late_50_tools.zsh b/includes/late_50_tools.zsh index 21fec18..f964547 100644 --- a/includes/late_50_tools.zsh +++ b/includes/late_50_tools.zsh @@ -143,7 +143,7 @@ fi if which op >/dev/null 2>&1; then op_completions="${ZSH_CACHE_DIR}/op_completions.zsh" if is_stale_file "${op_completions}"; then - op completion zsh > ${op_completions} + op completion zsh > ${op_completions} 2>/dev/null fi source_compiled "${op_completions}" fi @@ -160,6 +160,35 @@ else echo "zoxide not found, consider installing it!" fi +# Configure uv (fast Python package manager) +if (( $+commands[uv] )); then + if [[ -z "${UV_CACHE_DIR}" ]]; then + if [[ -n "${XDG_CACHE_HOME}" ]]; then + export UV_CACHE_DIR="${XDG_CACHE_HOME}/uv" + elif (( IS_MACOS )); then + export UV_CACHE_DIR="${HOME}/Library/Caches/uv" + else + export UV_CACHE_DIR="${HOME}/.cache/uv" + fi + fi + + if [[ -z "${UV_PYTHON_INSTALL_DIR}" ]]; then + if [[ -n "${XDG_DATA_HOME}" ]]; then + export UV_PYTHON_INSTALL_DIR="${XDG_DATA_HOME}/uv/python" + elif (( IS_MACOS )); then + export UV_PYTHON_INSTALL_DIR="${HOME}/Library/Application Support/uv/python" + else + export UV_PYTHON_INSTALL_DIR="${HOME}/.local/share/uv/python" + fi + fi + + uv_completion_cache="${ZSH_CACHE_DIR}/uv_completion.zsh" + if is_stale_file "${uv_completion_cache}"; then + uv generate-shell-completion zsh > "${uv_completion_cache}" + fi + source_compiled "${uv_completion_cache}" +fi + # if PHP composer is installed, add it to the path [ -d "${HOME}/.composer/vendor/bin" ] && export PATH="${HOME}/.composer/vendor/bin:${PATH}" diff --git a/tools/benchzshy.sh b/tools/benchzshy.sh index 81e0f9b..0a077b6 100755 --- a/tools/benchzshy.sh +++ b/tools/benchzshy.sh @@ -1,24 +1,45 @@ #!/bin/zsh +# Benchmark zsh startup time. +# Uses GNU time (gtime, from `brew install gnu-time`) when available, +# otherwise falls back to BSD /usr/bin/time -p (macOS built-in) — both +# produce parseable real/user/sys lines. + +runs=${1:-100} total_real=0.0 total_user=0.0 total_sys=0.0 -runs=100 +min_real= +max_real= + +if command -v gtime >/dev/null 2>&1; then + time_cmd=(gtime -f $'real %e\nuser %U\nsys %S') +else + time_cmd=(/usr/bin/time -p) +fi -echo "Benchmarking zsh startup over $runs runs..." +echo "Benchmarking zsh startup over $runs runs (using ${time_cmd[1]})..." -for i in {1..100}; do - output=$( gtime -f "real %e\nuser %U\nsys %S" zsh -i --login -c echo 2>&1 ) +for i in {1..$runs}; do + output=$( "${time_cmd[@]}" zsh -i --login -c echo 2>&1 ) real_time=$(echo "$output" | grep '^real' | awk '{print $2}') user_time=$(echo "$output" | grep '^user' | awk '{print $2}') sys_time=$(echo "$output" | grep '^sys' | awk '{print $2}') + if [[ -z "$real_time" ]]; then + echo "WARNING: could not parse timing output on run $i:" >&2 + echo "$output" | head -5 >&2 + exit 1 + fi + total_real=$(awk -v t=$total_real -v r=$real_time 'BEGIN{printf "%.6f", t + r}') total_user=$(awk -v t=$total_user -v r=$user_time 'BEGIN{printf "%.6f", t + r}') total_sys=$(awk -v t=$total_sys -v r=$sys_time 'BEGIN{printf "%.6f", t + r}') + [[ -z "$min_real" ]] || awk -v a=$real_time -v b=$min_real 'BEGIN{exit !(ab)}' && max_real=$real_time done echo "\n== Average Time Over $runs Runs ==" -echo "Real: $(awk -v t=$total_real -v r=$runs 'BEGIN{printf "%.3f", t/r}') s" +echo "Real: $(awk -v t=$total_real -v r=$runs 'BEGIN{printf "%.3f", t/r}') s (min $min_real, max $max_real)" echo "User: $(awk -v t=$total_user -v r=$runs 'BEGIN{printf "%.3f", t/r}') s" echo "Sys : $(awk -v t=$total_sys -v r=$runs 'BEGIN{printf "%.3f", t/r}') s" diff --git a/zshrc b/zshrc index 826fb4e..25eda9a 100644 --- a/zshrc +++ b/zshrc @@ -5,6 +5,13 @@ if [[ "$PROFILE_STARTUP" == true || "$PROFILE_ALL" == true ]]; then setopt xtrace fi +# Powerlevel10k instant prompt: paints a cached prompt immediately while the +# rest of init runs behind it. Keep near the top; anything that prints to the +# console during startup must run before this block (or be silenced). +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 + # Bootstrap core paths export ZSH="${HOME}/.zsh" export ZSH_CACHE_DIR="${ZSH}/cache"