Commit 079fba12ed49
Changed files (30)
dots
config
zsh
core
tools
home
common
dots/config/zsh/core/05-helpers.zsh
@@ -0,0 +1,29 @@
+# Shared helpers — loaded first, used everywhere
+
+# Bytecode compilation: compile .zsh → .zwc if source is newer
+_zsh_compile_if_needed() {
+ local src=$1 dst="${1}.zwc"
+ [[ -n $src && -r $src ]] || return 1
+ if [[ ! -f $dst || $src -nt $dst ]]; then
+ zcompile "$src" 2>/dev/null
+ fi
+}
+
+# Check if commands exist (hash lookup, no fork)
+has() {
+ local cmd
+ for cmd in "$@"; do
+ (( $+commands[$cmd] )) || return 1
+ done
+}
+
+# Source files from a directory, with bytecode compilation
+load_zsh_dir() {
+ local dir=$1 file
+ [[ -d $dir && -r $dir ]] || return 0
+ for file in "$dir"/*.zsh(N); do
+ [[ -r $file ]] || continue
+ _zsh_compile_if_needed "$file"
+ source "$file"
+ done
+}
dots/config/zsh/core/10-options.zsh
@@ -0,0 +1,28 @@
+# Shell options and history
+
+setopt HIST_FCNTL_LOCK
+setopt EXTENDED_HISTORY
+setopt HIST_EXPIRE_DUPS_FIRST
+setopt HIST_IGNORE_DUPS
+setopt HIST_IGNORE_SPACE
+setopt SHARE_HISTORY
+setopt autocd
+
+autoload -Uz select-word-style; select-word-style bash
+
+# Dumb terminal fallback (Emacs TRAMP, etc.)
+if [[ "$TERM" == "dumb" || "$TERM" == "emacs" ]]; then
+ TERM=eterm-color
+ unsetopt zle
+ unsetopt prompt_cr
+ unsetopt prompt_subst
+ unfunction precmd 2>/dev/null
+ unfunction preexec 2>/dev/null
+ PS1='$ '
+ return
+fi
+
+# Profiling support: DEBUG_ZSH_PERF=1 zsh -i
+if (( ${+DEBUG_ZSH_PERF} )); then
+ zmodload zsh/zprof
+fi
dots/config/zsh/core/20-completion.zsh
@@ -0,0 +1,109 @@
+# Deferred completion system — runs on first prompt, not at startup
+# Saves ~53ms by deferring compinit to precmd
+
+autoload -Uz compinit
+
+__deferred_compinit() {
+ local dump=${XDG_CACHE_HOME:-$HOME/.cache}/zsh/.zcompdump
+ mkdir -p "${dump:h}" 2>/dev/null
+
+ if [[ ! -f $dump ]]; then
+ compinit -d "$dump"
+ else
+ compinit -C -d "$dump" # -C skips security check (single-user machine)
+ fi
+ _zsh_compile_if_needed "$dump"
+
+ # Wire up alias expansion (needs compinit)
+ zle -C alias-expansion complete-word _generic
+ bindkey '^a' alias-expansion
+ zstyle ':completion:alias-expansion:*' completer _expand_alias
+
+ add-zsh-hook -d precmd __deferred_compinit
+ unfunction __deferred_compinit
+}
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd __deferred_compinit
+
+# Completion styles (zstyle declarations are cheap — no need to defer)
+zstyle ':completion:*' menu select
+zstyle ':completion::complete:*' use-cache on
+zstyle ':completion::complete:*' cache-path "${XDG_CACHE_HOME:-$HOME/.cache}/zsh/zcompcache"
+
+# Case-insensitive, partial-word, substring completion
+zstyle ':completion:*' matcher-list '' \
+ 'm:{a-z\-}={A-Z\_}' \
+ 'r:[^[:alpha:]]||[[:alpha:]]=** r:|=* m:{a-z\-}={A-Z\_}' \
+ 'r:[[:ascii:]]||[[:ascii:]]=** r:|=* m:{a-z\-}={A-Z\_}'
+
+# Group matches and describe
+zstyle ':completion:*:*:*:*:*' menu select
+zstyle ':completion:*:matches' group 'yes'
+zstyle ':completion:*:options' description 'yes'
+zstyle ':completion:*:options' auto-description '%d'
+zstyle ':completion:*:corrections' format ' %F{green}-- %d (errors: %e) --%f'
+zstyle ':completion:*:descriptions' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*:messages' format ' %F{purple} -- %d --%f'
+zstyle ':completion:*:warnings' format ' %F{red}-- no matches found --%f'
+zstyle ':completion:*:default' list-prompt '%S%M matches%s'
+zstyle ':completion:*' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*' verbose yes
+
+# Fuzzy match mistyped completions
+zstyle ':completion:*' completer _complete _list _match _approximate
+zstyle ':completion:*:match:*' original only
+zstyle ':completion:*:approximate:*' max-errors 1 numeric
+zstyle -e ':completion:*:approximate:*' max-errors 'reply=($((($#PREFIX+$#SUFFIX)/3))numeric)'
+
+# Don't complete unavailable commands
+zstyle ':completion:*:functions' ignored-patterns '(_*|pre(cmd|exec))'
+
+# Array completion element sorting
+zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters
+
+# Directories
+zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
+zstyle ':completion:*:*:cd:*' ignore-parents parent pwd
+zstyle ':completion:*:*:cd:*' tag-order local-directories directory-stack path-directories
+zstyle ':completion:*:*:cd:*:directory-stack' menu yes select
+zstyle ':completion:*:-tilde-:*' group-order 'named-directories' 'path-directories' 'users' 'expand'
+zstyle ':completion:*' squeeze-slashes true
+zstyle ':completion:*' special-dirs true
+
+# Environmental variables
+zstyle ':completion::*:(-command-|export):*' fake-parameters ${${${_comps[(I)-value-*]#*,}%%,*}:#-*-}
+
+# Populate hostname completion
+zstyle -e ':completion:*:hosts' hosts 'reply=(
+ ${=${=${=${${(f)"$(cat {/etc/ssh_,~/.ssh/known_}hosts(|2)(N) 2>/dev/null)"}%%[#| ]*}//\]:[0-9]*/ }//,/ }//\[/ }
+ ${=${${${${(@M)${(f)"$(cat ~/.ssh/config 2>/dev/null)"}:#Host *}#Host }:#*\**}:#*\?*}}
+)'
+
+# Don't complete uninteresting users
+zstyle ':completion:*:*:*:users' ignored-patterns \
+ adm amanda apache avahi beaglidx bin cacti canna clamav daemon \
+ dbus distcache dovecot fax ftp games gdm gkrellmd gopher \
+ hacluster haldaemon halt hsqldb ident junkbust ldap lp mail \
+ mailman mailnull mldonkey mysql nagios \
+ named netdump news nfsnobody nobody nscd ntp nut nx openvpn \
+ operator pcap postfix postgres privoxy pulse pvm quagga radvd \
+ rpc rpcuser rpm shutdown squid sshd sync uucp vcsa xfs '_*'
+zstyle '*' single-ignored show
+
+# Ignore multiple entries
+zstyle ':completion:*:(rm|kill|diff):*' ignore-line other
+zstyle ':completion:*:rm:*' file-patterns '*:all-files'
+
+# Man
+zstyle ':completion:*:manuals' separate-sections true
+zstyle ':completion:*:manuals.(^1*)' insert-sections true
+
+# SSH/SCP/RSYNC
+zstyle ':completion:*:(scp|rsync):*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
+zstyle ':completion:*:(scp|rsync):*' group-order users files all-files hosts-domain hosts-host hosts-ipaddr
+zstyle ':completion:*:ssh:*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
+zstyle ':completion:*:ssh:*' group-order users hosts-domain hosts-host users hosts-ipaddr
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-host' ignored-patterns '*(.|:)*' loopback ip6-loopback localhost ip6-localhost broadcasthost
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-domain' ignored-patterns '<->.<->.<->.<->' '^[-[:alnum:]]##(.[-[:alnum:]]##)##' '*@*'
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-ipaddr' ignored-patterns '^(<->.<->.<->.<->|(|::)([[:xdigit:].]##:(#c,2))##(|%*))' '127.0.0.<->' '255.255.255.255' '::1' 'fe80::*'
dots/config/zsh/core/30-prompt.zsh
@@ -0,0 +1,65 @@
+# Prompt — computed in precmd, no subshell forks
+# Variables are set in the hook, PROMPT just references them
+
+__prompt_precmd() {
+ local last_status=$?
+
+ __ps_err='' __ps_dir='' __ps_git='' __ps_nix='' __ps_kube=''
+
+ # Exit status
+ (( last_status )) && __ps_err="%F{red}?${last_status} "
+
+ # Directory with smart shortening (inline, no fork)
+ local dir="${PWD/#$HOME/~}"
+ local prefix=""
+ [[ "$dir" == */.local/share/worktrees/* ]] && prefix="🌿 "
+ [[ "$dir" == ~/desktop* ]] && prefix="🖥️ "
+
+ if (( ${#dir} > 50 )); then
+ local parts=("${(@s:/:)dir}")
+ local i
+ for (( i=1; i <= ${#parts[@]} - 2; i++ )); do
+ (( ${#dir} <= 50 )) && break
+ (( ${#parts[$i]} <= 2 )) && continue
+ if [[ "${parts[$i]}" == .* ]]; then
+ parts[$i]=".${parts[$i]:1:1}"
+ else
+ parts[$i]="${parts[$i]:0:1}"
+ fi
+ dir="${(j:/:)parts}"
+ done
+ fi
+ __ps_dir="${prefix}${dir}"
+
+ # Git: single call for branch + dirty state
+ local gstatus branch dirty
+ if gstatus=$(GIT_OPTIONAL_LOCKS=0 git status --porcelain=v2 -b --no-ahead-behind 2>/dev/null); then
+ branch=${gstatus#*branch.head }
+ branch=${branch%%$'\n'*}
+ if [[ -n $branch && $branch != "# "* ]]; then
+ [[ $gstatus == *$'\n'[^#]* ]] && dirty='*'
+ __ps_git=" %F{76}${branch}${dirty}%f"
+ fi
+ fi
+
+ # Nix shell indicator
+ if (( ${+IN_NIX_SHELL} )); then
+ if [[ -n $NIX_SHELL_PACKAGES ]]; then
+ __ps_nix=" %F{yellow}❄{$NIX_SHELL_PACKAGES}%f"
+ else
+ __ps_nix=" %F{yellow}❄%f"
+ fi
+ fi
+
+ # Kubeconfig indicator
+ if [[ -n $KUBECONFIG ]]; then
+ __ps_kube=" %F{134}k8s:${KUBECONFIG:t:r}%f"
+ fi
+}
+
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd __prompt_precmd
+
+setopt PROMPT_SUBST
+PROMPT='${__ps_err}%F{111}%m%f:%F{2}${__ps_dir}%f${__ps_git}${__ps_nix}${__ps_kube} %(!.#.$) '
+RPROMPT=""
dots/config/zsh/core/40-keybindings.zsh
@@ -0,0 +1,13 @@
+# Key bindings
+
+# Edit command line in $EDITOR
+autoload -U edit-command-line
+zle -N edit-command-line
+bindkey '^e' edit-command-line
+
+# Rationalise dots: ... → ../.. .... → ../../..
+__rationalise-dot() {
+ [[ $LBUFFER = *.. ]] && LBUFFER+=/.. || LBUFFER+=.
+}
+zle -N __rationalise-dot
+bindkey "." __rationalise-dot
dots/config/zsh/core/45-auto-expand.zsh
@@ -0,0 +1,102 @@
+# Auto-expanding aliases — expands abbreviations on space
+typeset -ga _vbe_abbrevations
+abbrev-alias() {
+ alias $1
+ _vbe_abbrevations+=(${1%%\=*})
+}
+_vbe_zle-autoexpand() {
+ local -a words; words=(${(z)LBUFFER})
+ if (( ${#_vbe_abbrevations[(r)${words[-1]}]} )); then
+ zle _expand_alias
+ fi
+ zle magic-space
+}
+zle -N _vbe_zle-autoexpand
+bindkey -M emacs " " _vbe_zle-autoexpand
+bindkey -M emacs "^ " magic-space
+bindkey -M isearch " " magic-space
+
+# Correct common typos
+has git && abbrev-alias gti=git
+has grep && abbrev-alias grpe=grep
+has sudo && abbrev-alias suod=sudo
+has ssh && abbrev-alias shs=ssh
+
+# Save keystrokes
+has git && abbrev-alias gls="git ls-files"
+has ip && {
+ abbrev-alias ip6='ip -6'
+ abbrev-alias ipb='ip -brief'
+}
+abbrev-alias tailf="tail -F"
+has mpv && abbrev-alias mpva="mpv --no-video"
+
+# Systemd aliases
+() {
+ local cmd
+ local -a cmds
+ cmds=(start stop reload restart status)
+
+ if [[ -d /run/systemd/system ]]; then
+ for cmd ($cmds) {
+ abbrev-alias $cmd="${(%):-%(#..sudo )}systemctl $cmd"
+ abbrev-alias u$cmd="systemctl --user $cmd"
+ }
+ else
+ for cmd ($cmds) {
+ function $cmd() {
+ name=$1 ; shift
+ ${(%):-%(#..sudo)} service $name $0 "$@"
+ }
+ (( $+functions[compdef] )) && compdef _services $cmd
+ }
+ fi
+}
+
+# grep aliases
+() {
+ local grep=grep
+ (( $+commands[ggrep] )) && grep=ggrep
+ local colors="--color=auto"
+ $grep -q $colors . <<< yes 2>/dev/null || colors=""
+ alias grep="command ${grep} ${colors}"
+ abbrev-alias rgrep="grep -r"
+ abbrev-alias egrep="grep -E"
+ abbrev-alias fgrep="grep -F"
+ (( $+commands[zgrep] )) && alias zgrep="GREP=${grep} command zgrep ${colors}"
+}
+
+# nixpkgs runner
+(( $+commands[nix] )) && nixpkgs() {
+ cmd=$1; shift
+ nix run nixpkgs\#${cmd} -- "$@"
+}
+
+# File viewer
+v() {
+ case $(file --brief --mime-type $1 2>/dev/null) in
+ image/svg+xml) ;;
+ image/*) (( $+commands[nsxiv] )) && ${I3SOCK+i3-tabbed} nsxiv $1; return ;;
+ video/*) (( $+commands[mpv] )) && ${I3SOCK+i3-tabbed} mpv --no-fs $1; return ;;
+ esac
+ if (( $+commands[bat] )); then
+ if (( ! $# )); then
+ gzip -cdfq | bat
+ else
+ for f in "$@"; do gzip -cdfq -- $f | bat --file-name ${f%.gz}; done
+ fi
+ elif (( $+commands[less] )); then
+ gzip -cdfq -- "$@" | less -FX
+ elif (( $+commands[zmore] )); then
+ zmore "$@"
+ elif (( $+commands[more] )); then
+ gzip -cdfq -- "$@" | more
+ else
+ gzip -cdfq -- "$@"
+ fi
+}
+
+# jayrah
+if [[ -d ${HOME}/src/github.com/chmouel/jayrah ]]; then
+ alias jayrah="uv --directory=${HOME}/src/github.com/chmouel/jayrah run jayrah"
+fi
dots/config/zsh/core/50-aliases.zsh
@@ -0,0 +1,45 @@
+# Aliases
+
+# Paste commands from docs: $ git status → just works
+alias \$=" "
+alias %=" "
+
+# Safe file operations
+alias mkdir="mkdir --parents --verbose"
+alias rm="rm --interactive"
+alias cp="cp --interactive"
+alias mv="mv --interactive"
+
+# ls → eza (base aliases come from home-manager eza module)
+has eza && {
+ alias l="ls -lah"
+ alias lt="eza --tree"
+}
+
+# Common shortcuts
+alias map="xargs -n1"
+alias gcd='cd $(git rev-parse --show-toplevel)'
+has nvim && alias vim=nvim
+alias wget="wget -c --hsts-file=${XDG_DATA_HOME:-$HOME/.local/share}/wget-hsts"
+has kubectl && alias k=kubectl
+
+# Finance aliases come from home/common/desktop/finance.nix
+
+# Global aliases (expand anywhere in a command)
+alias -g L="|less"
+alias -g EEL=' 2>&1 | less'
+alias -g GB='`git rev-parse --abbrev-ref HEAD`'
+alias -g GR='`git rev-parse --show-toplevel`'
+(( $+commands[jq] )) && alias -g MJ="| jq -C '.'" || alias -g MJ="| python -mjson.tool"
+
+# Suffix aliases (open by extension)
+alias -s {ape,avi,flv,m4a,mkv,mov,mp3,mp4,mpeg,mpg,ogg,ogm,wav,webm}=mpv
+alias -s org=emacs
+
+# Emacs integration
+[[ -n $INSIDE_EMACS ]] && \
+function ff () { print "\e]51;Efind-file $(readlink -f $1)\e\\"; }
+
+# Eat shell integration
+[ -n "$EAT_SHELL_INTEGRATION_DIR" ] && \
+ source "$EAT_SHELL_INTEGRATION_DIR/zsh"
dots/config/zsh/core/60-syntax-hl.zsh
@@ -0,0 +1,71 @@
+# Minimal syntax highlighting using ZSH's built-in region_highlight
+# Replaces zsh-syntax-highlighting plugin (~60 lines vs ~4000 lines)
+# Highlights: unknown commands (red), rm (dim bold), sudo (purple), quoted strings (yellow)
+
+__syntax_hl() {
+ emulate -L zsh
+
+ # Skip if buffer unchanged (redraws without edits)
+ [[ $BUFFER == "${__syntax_hl_prev-}" ]] && return
+ __syntax_hl_prev=$BUFFER
+
+ region_highlight=()
+ (( $#BUFFER )) || return
+
+ # First word (skip leading whitespace and VAR=val prefixes)
+ local cmd=${BUFFER##[[:space:]]#}
+ local -i offset=$(( $#BUFFER - $#cmd ))
+ cmd=${cmd%%[[:space:]]*}
+ [[ -n $cmd ]] || return
+
+ while [[ $cmd == *=* ]]; do
+ local rest=${BUFFER:$(( offset + $#cmd ))}
+ rest=${rest##[[:space:]]#}
+ offset=$(( $#BUFFER - $#rest ))
+ cmd=${rest%%[[:space:]]*}
+ [[ -n $cmd ]] || return
+ done
+
+ local -i cmd_end=$(( offset + $#cmd ))
+
+ # rm: dim bold on the whole line (visual warning)
+ if [[ $cmd == rm ]]; then
+ region_highlight+=("0 $#BUFFER fg=90,bold")
+ return
+ fi
+
+ # sudo: purple bold
+ if [[ $cmd == sudo ]]; then
+ region_highlight+=("$offset $cmd_end fg=164,bold")
+ return
+ fi
+
+ # unknown command: red bold
+ if ! whence -- "$cmd" >/dev/null 2>&1; then
+ region_highlight+=("$offset $cmd_end fg=red,bold")
+ fi
+
+ # Quoted strings: yellow
+ local QS="'" QD='"'
+ local -i pos=1 sq dq next close
+ while (( pos <= $#BUFFER )); do
+ sq=${BUFFER[(ib:pos:)$QS]}
+ dq=${BUFFER[(ib:pos:)$QD]}
+ if (( sq < dq )); then
+ next=$sq
+ close=${BUFFER[(ib:next+1:)$QS]}
+ elif (( dq <= $#BUFFER )); then
+ next=$dq
+ close=${BUFFER[(ib:next+1:)$QD]}
+ else
+ break
+ fi
+ if (( close <= $#BUFFER )); then
+ region_highlight+=("$(( next - 1 )) $close fg=yellow")
+ pos=$(( close + 1 ))
+ else
+ break
+ fi
+ done
+}
+zle -N zle-line-pre-redraw __syntax_hl
dots/config/zsh/functions/j
@@ -0,0 +1,30 @@
+local root=~/src/
+local res results args
+
+while getopts "np" opt; do
+ if [[ $opt = "?" ]]; then
+ print -r -- "$myname: unrecognized option: -$OPTARG" >&2
+ return 1
+ fi
+ eval "opt_$opt=\${OPTARG:--\$opt}"
+done
+(( OPTIND > 1 )) && shift $(( OPTIND - 1 ))
+
+local fnd=$1
+type -f zshz 2>/dev/null >/dev/null || opt_n=true
+
+if [[ -n ${fnd} ]];then
+
+ [[ -z ${opt_n} ]] && {
+ local zz=$(zshz -e ${fnd})
+ [[ -n ${zz} ]] && { echo "ZSHZ: ${zz}"; [[ -z ${opt_p} ]] && cd ${zz}; return;}
+ }
+
+ local results=($(fd -d 3 -t d . ${root}|egrep -i "${fnd}"))
+ [[ ${#results} == 1 ]] && { echo ${results}; [[ -z ${opt_p} ]] && cd ${results} ; return;}
+ [[ ${#results} == 0 ]] && { echo "No results found for ${fnd}"; return 1;}
+ args="-q ${fnd}"
+fi
+
+res=$(fd -d 3 -t d . ${root}|sed "s,${root},,"|fzf --height 50% --border ${args})
+[[ -n ${res} ]] && {echo ${root}${res}; [[ -z ${opt_p} ]] && cd ${root}${res} ; }
dots/config/zsh/functions/timezsh
@@ -0,0 +1,12 @@
+zmodload zsh/datetime
+runs=${1:-10}
+total_ms=0
+for i in {1..$runs}; do
+ start=$EPOCHREALTIME
+ zsh -i -c exit >/dev/null 2>&1
+ end=$EPOCHREALTIME
+ delta_ms=$(( (end - start) * 1000 ))
+ total_ms=$(( total_ms + delta_ms ))
+ printf "run %2d: %3.0f ms\n" "$i" "$delta_ms"
+done
+printf "\nAverage: %.0f ms\n" "$(( total_ms / runs ))"
dots/config/zsh/tools/autosuggestions.zsh
@@ -0,0 +1,12 @@
+# Zsh autosuggestions
+local plugin
+for plugin in \
+ /etc/profiles/per-user/$USER/share/zsh-autosuggestions/zsh-autosuggestions.zsh \
+ /run/current-system/sw/share/zsh-autosuggestions/zsh-autosuggestions.zsh \
+ /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh; do
+ if [[ -f "$plugin" ]]; then
+ source "$plugin"
+ ZSH_AUTOSUGGEST_STRATEGY=(history)
+ break
+ fi
+done
dots/config/zsh/tools/broot.zsh
@@ -0,0 +1,12 @@
+# Broot shell integration
+has broot || return
+
+local broot_init
+for broot_init in \
+ "${XDG_CONFIG_HOME:-$HOME/.config}/broot/launcher/bash/br" \
+ "$HOME/.config/broot/launcher/bash/br"; do
+ if [[ -f "$broot_init" ]]; then
+ source "$broot_init"
+ break
+ fi
+done
dots/config/zsh/tools/direnv.zsh
@@ -0,0 +1,12 @@
+# Direnv — cached hook to avoid fork on every startup
+has direnv || return
+
+local cache=${XDG_CACHE_HOME:-$HOME/.cache}/zsh/direnv-init.zsh
+local bin=${commands[direnv]}
+
+if [[ ! -f $cache || $bin -nt $cache ]]; then
+ mkdir -p ${cache:h}
+ direnv hook zsh > "$cache"
+ _zsh_compile_if_needed "$cache"
+fi
+source "$cache"
dots/config/zsh/tools/fzf.zsh
@@ -0,0 +1,13 @@
+# FZF — cached init to avoid fork on every startup
+has fzf || return
+[[ $options[zle] = on ]] || return
+
+local cache=${XDG_CACHE_HOME:-$HOME/.cache}/zsh/fzf-init.zsh
+local bin=${commands[fzf]}
+
+if [[ ! -f $cache || $bin -nt $cache ]]; then
+ mkdir -p ${cache:h}
+ fzf --zsh > "$cache"
+ _zsh_compile_if_needed "$cache"
+fi
+source "$cache"
dots/config/zsh/tools/kitty.zsh
@@ -0,0 +1,17 @@
+# Kitty terminal integration
+[[ -n "$KITTY_INSTALLATION_DIR" ]] || return
+
+export KITTY_SHELL_INTEGRATION="no-rc"
+autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration
+kitty-integration
+unfunction kitty-integration
+
+# SSH wrapper: use raw ssh for shpool sessions (host/session pattern)
+# Kitty SSH kitten interferes with RemoteCommand
+ssh() {
+ if [[ "$1" =~ / ]]; then
+ command ssh "$@"
+ else
+ kitty +kitten ssh "$@"
+ fi
+}
dots/config/zsh/tools/kubectl.zsh
@@ -0,0 +1,6 @@
+# Kubectl config switcher
+has kubectl || return
+
+local plugin_dir="${ZDOTDIR:-$HOME/.config/zsh}/plugins/kubectl-config-switcher"
+[[ -f "$plugin_dir/kubectl-config-switcher.plugin.zsh" ]] && \
+ source "$plugin_dir/kubectl-config-switcher.plugin.zsh"
dots/config/zsh/tools/lazyworktree.zsh
@@ -0,0 +1,86 @@
+# Lazyworktree integration
+has lazyworktree || return
+
+# Source shell functions (fast: ~0.4ms)
+local lw_functions="${commands[lazyworktree]:h:h}/share/lazyworktree/functions.zsh"
+[[ -f "$lw_functions" ]] && source "$lw_functions"
+
+# Named repo shortcuts
+wh() { worktree_jump "$HOME/src/home" "$@"; }
+wnix() { worktree_jump "$HOME/src/nixpkgs" "$@"; }
+_wh() { _worktree_jump "$HOME/src/home"; }
+_wnix() { _worktree_jump "$HOME/src/nixpkgs"; }
+(( $+functions[compdef] )) && { compdef _wh wh; compdef _wnix wnix; }
+
+# Auto-discover worktree aliases from source directories
+_lazyworktree_discover_aliases() {
+ setopt local_options nullglob
+ local -A repo_dirs=()
+ local base_dirs=(
+ "$HOME/src/gitlab.com:wgb"
+ "$HOME/src/github.com:wgh"
+ "$HOME/src/osp:wo"
+ "$HOME/src:ws"
+ "$HOME/src/tektoncd:wt"
+ )
+ local entry base_dir prefix repo_dir repo_name target_dir alias_name org_name
+
+ for entry in "${base_dirs[@]}"; do
+ base_dir="${entry%%:*}"
+ prefix="${entry##*:}"
+ [[ -d "$base_dir" ]] || continue
+
+ if [[ "$prefix" == "wgh" ]] || [[ "$prefix" == "wgb" ]]; then
+ eval "${prefix}_() {
+ local selection org_dir
+ selection=\$(find \"$base_dir\" -maxdepth 2 -name .git -type d 2>/dev/null | sed 's|/.git$||' | sed \"s|^$base_dir/||\" | fzf --prompt=\"$base_dir: \")
+ [[ -n \"\$selection\" ]] || return 1
+ worktree_jump \"$base_dir/\$selection\" \"\$@\"
+ }"
+ for org_dir in "$base_dir"/*/; do
+ [[ -d "$org_dir" ]] || continue
+ org_name="$(basename "$org_dir")"
+ for repo_dir in "$org_dir"/*/; do
+ [[ -d "$repo_dir" ]] || continue
+ [[ -d "$repo_dir/.git" ]] || continue
+ repo_name="$(basename "$repo_dir")"
+ target_dir="$repo_dir"
+ alias_name="${prefix}_${org_name}_${repo_name}"
+ repo_dirs[$alias_name]="$target_dir"
+ eval "${alias_name}() { worktree_jump \"$target_dir\" \"\$@\"; }"
+ done
+ done
+ else
+ eval "${prefix}_() {
+ local selection
+ selection=\$(ls -1 \"$base_dir\" 2>/dev/null | fzf --prompt=\"$base_dir: \")
+ [[ -n \"\$selection\" ]] || return 1
+ if [[ -d \"$base_dir/\$selection/.git\" ]]; then
+ worktree_jump \"$base_dir/\$selection\" \"\$@\"
+ else
+ echo \"Not a git repo: $base_dir/\$selection\" >&2
+ return 1
+ fi
+ }"
+ for repo_dir in "$base_dir"/*/; do
+ [[ -d "$repo_dir" ]] || continue
+ repo_name="$(basename "$repo_dir")"
+ [[ -d "$repo_dir/.git" ]] || continue
+ target_dir="$repo_dir"
+ alias_name="${prefix}_${repo_name}"
+ if [[ -n "${repo_dirs[$alias_name]}" ]] && [[ "${repo_dirs[$alias_name]}" != "$target_dir" ]]; then
+ alias_name="${prefix}_${repo_name}"
+ fi
+ repo_dirs[$alias_name]="$target_dir"
+ eval "${alias_name}() { worktree_jump \"$target_dir\" \"\$@\"; }"
+ done
+ fi
+ done
+}
+# Defer discovery to first prompt (saves ~210ms)
+__deferred_lazyworktree() {
+ _lazyworktree_discover_aliases >/dev/null 2>&1
+ add-zsh-hook -d precmd __deferred_lazyworktree
+ unfunction __deferred_lazyworktree
+}
+add-zsh-hook precmd __deferred_lazyworktree
dots/config/zsh/tools/nix-shell.zsh
@@ -0,0 +1,3 @@
+# Nix shell plugin
+local plugin_dir="${ZDOTDIR:-$HOME/.config/zsh}/plugins/zsh-nix-shell"
+[[ -f "$plugin_dir/nix-shell.plugin.zsh" ]] && source "$plugin_dir/nix-shell.plugin.zsh"
dots/config/zsh/tools/walk.zsh
@@ -0,0 +1,8 @@
+# Walk — terminal navigator
+has walk || return
+
+export WALK_REMOVE_CMD=trash-put
+export WALK_OPEN_WITH="nix:vim;go:vim;rs:vim;py:vim;sh:vim;bash:vim;zsh:vim;md:glow -p;txt:less -N"
+function lk {
+ cd "$(walk --icons "$@")"
+}
dots/config/zsh/tools/zsh-z.zsh
@@ -0,0 +1,6 @@
+# zsh-z (directory jumping)
+local plugin_dir="${ZDOTDIR:-$HOME/.config/zsh}/plugins/zsh-z"
+[[ -f "$plugin_dir/zsh-z.plugin.zsh" ]] || return
+source "$plugin_dir/zsh-z.plugin.zsh"
+export _Z_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/z"
+(( $+functions[zshz] )) && (( $+functions[compdef] )) && compdef _zshz j
dots/config/zsh/.gitignore
@@ -0,0 +1,1 @@
+*.zwc
dots/config/zsh/init.zsh
@@ -0,0 +1,27 @@
+# ZSH modular config entrypoint
+# Sourced from home-manager's .zshrc after its fpath/plugin setup
+#
+# Structure:
+# core/ — numbered files loaded in order (helpers, options, completion, prompt, etc.)
+# tools/ — per-tool configs (each guards with `has <tool> || return`)
+# functions/ — autoloaded functions (zero startup cost)
+
+local zsh_dir="${ZDOTDIR:-$HOME/.config/zsh}"
+
+# Register autoloaded functions
+if [[ -d "$zsh_dir/functions" ]]; then
+ fpath=("$zsh_dir/functions" $fpath)
+ autoload -U "$zsh_dir"/functions/*(x:tN)
+fi
+
+# Load core config (numbered for ordering)
+source "$zsh_dir/core/05-helpers.zsh"
+load_zsh_dir "$zsh_dir/core"
+
+# Load tool configs
+load_zsh_dir "$zsh_dir/tools"
+
+# Profiling output (if enabled)
+if (( ${+DEBUG_ZSH_PERF} )); then
+ zprof
+fi
dots/Makefile
@@ -86,6 +86,11 @@ agent-skill-manager-bin : ~/bin/agent-skill-manager
@mkdir -p ~/bin
@ln -snf $< $@
+##@ Shell
+
+all += zsh
+zsh : ~/.config/zsh/init.zsh ~/.config/zsh/core ~/.config/zsh/tools ~/.config/zsh/functions
+
##@ Dev Tools
all += git-template copilot-hooks opencode-plugin lazygit lazyworktree lazypr
home/common/desktop/kitty.nix
@@ -2,7 +2,7 @@
{
programs.kitty = {
enable = true;
- shellIntegration.enableZshIntegration = true;
+ shellIntegration.enableZshIntegration = false; # handled in dots/config/zsh/tools/kitty.zsh
settings = {
close_on_child_death = "yes";
font_family = "JetBrains Mono";
@@ -396,16 +396,5 @@
'';
};
- programs.zsh.initContent = ''
- # SSH wrapper - use raw ssh for shpool sessions (host/session pattern)
- # Kitty SSH kitten interferes with RemoteCommand
- ssh() {
- # Check if first argument contains / (shpool session pattern)
- if [[ "$1" =~ / ]]; then
- command ssh "$@"
- else
- kitty +kitten ssh "$@"
- fi
- }
- '';
+ # SSH wrapper moved to dots/config/zsh/tools/kitty.zsh
}
home/common/dev/lazyworktree.nix
@@ -5,7 +5,7 @@
# Config is managed via dots/.config/lazyworktree/config.yaml
programs.lazyworktree = {
enable = true;
- enableZshIntegration = true;
+ enableZshIntegration = false; # handled in dots/config/zsh/tools/lazyworktree.zsh
package = pkgs.lazyworktree;
# Explicit aliases for commonly used repositories
home/common/shell/atuin.nix
@@ -1,7 +1,7 @@
_: {
programs.atuin = {
enable = true;
- enableZshIntegration = true;
+ enableZshIntegration = false; # TODO: remove atuin entirely (see TODO)
flags = [ "--disable-up-arrow" ];
settings = {
auto_sync = true;
home/common/shell/default.nix
@@ -15,7 +15,7 @@
programs = {
broot = {
enable = true;
- enableZshIntegration = true;
+ enableZshIntegration = false; # handled in dots/config/zsh/tools/broot.zsh
};
eza.enable = true;
fd.enable = true;
home/common/shell/direnv.nix
@@ -3,7 +3,7 @@
{
programs.direnv = {
enable = true;
- enableZshIntegration = true;
+ enableZshIntegration = false; # handled in dots/config/zsh/tools/direnv.zsh
stdlib = ''
mkdir -p $HOME/.cache/direnv/layouts
pwd_hash=$(echo -n $PWD | shasum | cut -d ' ' -f 1)
home/common/shell/fzf.nix
@@ -1,7 +1,7 @@
_: {
programs.fzf = {
enable = true;
- enableZshIntegration = true;
+ enableZshIntegration = false; # handled in dots/config/zsh/tools/fzf.zsh
defaultOptions = [ "--bind=ctrl-j:accept" ];
changeDirWidgetOptions = [ "--preview 'tree -C {} | head -200'" ];
fileWidgetCommand = "rg --files";
home/common/shell/zsh.nix
@@ -1,28 +1,39 @@
{ config, pkgs, ... }:
{
- home.file."${config.programs.zsh.dotDir}/completion.zsh".source = ./zsh/completion.zsh;
- home.file."${config.programs.zsh.dotDir}/prompt.zsh".source = ./zsh/prompt.zsh;
- home.file."${config.programs.zsh.dotDir}/functions/j".source = ./zsh/j;
- home.file."${config.programs.zsh.dotDir}/auto-expanding-aliases.zsh".source =
- ./zsh/auto-expanding-aliases.zsh;
+ # Plugins: fetched by Nix, placed in ZDOTDIR/plugins, sourced by dots/config/zsh/tools/
+ home.file."${config.programs.zsh.dotDir}/plugins/kubectl-config-switcher".source =
+ pkgs.fetchFromGitHub {
+ owner = "chmouel";
+ repo = "kubectl-config-switcher";
+ rev = "5679aa70383cee93fc15351dd4895c29c90b78a5";
+ sha256 = "sha256-Aifa5ms2p/l0FkZE8Tep8QiDWUdfFfdKrTIbJNurxw4=";
+ };
+ home.file."${config.programs.zsh.dotDir}/plugins/zsh-z".source =
+ pkgs.fetchFromGitHub {
+ owner = "agkozak";
+ repo = "zsh-z";
+ rev = "aaafebcd97424c570ee247e2aeb3da30444299cd";
+ sha256 = "sha256-9Wr4uZLk2CvINJilg4o72x0NEAl043lP30D3YnHk+ZA=";
+ };
+ home.file."${config.programs.zsh.dotDir}/plugins/zsh-nix-shell".source =
+ pkgs.fetchFromGitHub {
+ owner = "chisui";
+ repo = "zsh-nix-shell";
+ rev = "v0.8.0";
+ sha256 = "sha256-Z6EYQdasvpl1P78poj9efnnLj7QQg13Me8x1Ryyw+dM=";
+ };
home.packages = with pkgs; [
nix-zsh-completions
+ zsh-autosuggestions
];
programs.zsh = {
enable = true;
- # zprof.enable = true;
- # See https://gist.github.com/ctechols/ca1035271ad134841284
- completionInit = ''
- autoload -Uz compinit
- for dump in ${config.programs.zsh.dotDir}/.zcompdump(N.mh+24); do
- compinit
- done
- compinit -C
- '';
- enableCompletion = true;
- autosuggestion.enable = true;
+ # Completion is deferred in our own init.zsh (precmd hook)
+ completionInit = "";
+ enableCompletion = false;
+ autosuggestion.enable = false; # handled in dots/config/zsh/tools/autosuggestions.zsh
autocd = true;
dotDir = "${config.xdg.configHome}/zsh";
defaultKeymap = "emacs";
@@ -41,130 +52,18 @@
if [ -d $HOME/.krew/bin ]; then
export PATH=$HOME/.krew/bin:$PATH
fi
- # TODO Move somewhere else
- export TLDR_CACHE_DIR="$XDG_CACHE_HOME"/tldr
+ export TLDR_CACHE_DIR="$XDG_CACHE_HOME"/tldr
'';
- # TODO Extract this to files.
+ # Thin wrapper — all runtime logic lives in dots/config/zsh/
initContent = ''
- # c.f. https://wiki.gnupg.org/AgentForwarding
- # gpgconf --create-socketdir &!
- path+="${config.programs.zsh.dotDir}/functions"
fpath+="$HOME/.local/state/nix/profile/share/zsh/site-functions"
- fpath+="${config.programs.zsh.dotDir}/functions"
- for func (${config.programs.zsh.dotDir}/functions) autoload -U $func/*(x:t)
- autoload -Uz select-word-style; select-word-style bash
- #if [ -n "$INSIDE_EMACS" ]; then
- # chpwd() { print -P "\033AnSiTc %d" }
- # print -P "\033AnSiTu %n"
- # print -P "\033AnSiTc %d"
- #fi
- if [[ "$TERM" == "dumb" || "$TERM" == "emacs" ]]
- then
- TERM=eterm-color
- unsetopt zle
- unsetopt prompt_cr
- unsetopt prompt_subst
- unfunction precmd
- unfunction preexec
- PS1='$ '
- return
- fi
- # eval "$(${config.programs.atuin.package}/bin/atuin init zsh)"
- # make sure navigation using emacs keybindings works on all non-alphanumerics
- # syntax highlighting
- source ${config.programs.zsh.dotDir}/plugins/zsh-nix-shell/nix-shell.plugin.zsh
- source ${pkgs.zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
- ZSH_HIGHLIGHT_PATTERNS+=('rm -rf *' 'fg=white,bold,bg=red')
- ZSH_HIGHLIGHT_PATTERNS+=('rm -fR *' 'fg=white,bold,bg=red')
- ZSH_HIGHLIGHT_PATTERNS+=('rm -fr *' 'fg=white,bold,bg=red')
- source ${config.programs.zsh.dotDir}/completion.zsh
- source ${config.programs.zsh.dotDir}/prompt.zsh
- source ${config.programs.zsh.dotDir}/plugins/kubectl-config-switcher/kubectl-config-switcher.plugin.zsh
- source ${config.programs.zsh.dotDir}/auto-expanding-aliases.zsh
- setopt HIST_IGNORE_SPACE
- alias -g L="|less"
- alias -g EEL=' 2>&1 | less'
- alias -g GB='`git rev-parse --abbrev-ref HEAD`'
- alias -g GR='`git rev-parse --show-toplevel`'
- alias -s {ape,avi,flv,m4a,mkv,mov,mp3,mp4,mpeg,mpg,ogg,ogm,wav,webm}=mpv
- alias -s org=emacs
- (( $+commands[jq] )) && alias -g MJ="| jq -C '.'" || alias -g MJ="| ${pkgs.python3}/bin/python -mjson.tool"
- (( $+functions[zshz] )) && compdef _zshz j
- [[ -n $INSIDE_EMACS ]] && \
- function ff () {
- print "\e]51;Efind-file $(readlink -f $1)\e\\"
- }
-
- export _Z_DATA="${config.xdg.dataHome}/z"
-
-
- [ -n "$EAT_SHELL_INTEGRATION_DIR" ] && \
- source "$EAT_SHELL_INTEGRATION_DIR/zsh"
-
- # walk - terminal navigator (only available on desktop systems)
- if (( $+commands[walk] )); then
- export WALK_REMOVE_CMD=trash-put
- export WALK_OPEN_WITH="nix:vim;go:vim;rs:vim;py:vim;sh:vim;bash:vim;zsh:vim;md:glow -p;txt:less -N"
- function lk {
- cd "$(walk --icons "$@")"
- }
- fi
- '';
- loginExtra = ''
- # if [[ -z $DISPLAY && $TTY = /dev/tty1 ]]; then
- # # exec dbus-run-session sway
- # exec dbus-run-session niri-session
- # fi
+ [[ -f "${config.programs.zsh.dotDir}/init.zsh" ]] && \
+ source "${config.programs.zsh.dotDir}/init.zsh"
'';
sessionVariables = {
RPROMPT = "";
};
-
- shellAliases = {
- mkdir = "mkdir --parents --verbose";
- rm = "rm --interactive";
- cp = "cp --interactive";
- mv = "mv --interactive";
- gcd = "cd (git root)";
- # ls = ''exa'';
- ll = "ls -l";
- la = "ls -a";
- l = "ls -lah";
- # t = ''exa --tree --level=2'';
- map = "xargs -n1";
- k = "kubectl";
- wget = "wget -c --hsts-file=${config.xdg.dataHome}/wget-hsts";
- # cr, crl, crf are now scripts in pkgs/my/scripts/bin/
- };
-
- plugins = [
- {
- name = "kubectl-config-switcher";
- src = pkgs.fetchFromGitHub {
- owner = "chmouel";
- repo = "kubectl-config-switcher";
- rev = "5679aa70383cee93fc15351dd4895c29c90b78a5";
- sha256 = "sha256-Aifa5ms2p/l0FkZE8Tep8QiDWUdfFfdKrTIbJNurxw4=";
- };
- }
- {
- name = "zsh-z";
- src = pkgs.fetchFromGitHub {
- owner = "agkozak";
- repo = "zsh-z";
- rev = "aaafebcd97424c570ee247e2aeb3da30444299cd";
- sha256 = "sha256-9Wr4uZLk2CvINJilg4o72x0NEAl043lP30D3YnHk+ZA=";
- };
- }
- {
- name = "zsh-nix-shell";
- src = pkgs.fetchFromGitHub {
- owner = "chisui";
- repo = "zsh-nix-shell";
- rev = "v0.8.0";
- sha256 = "sha256-Z6EYQdasvpl1P78poj9efnnLj7QQg13Me8x1Ryyw+dM=";
- };
- }
- ];
+ # No plugins here — we use home.file to place them without auto-sourcing.
+ # Sourcing is handled by dots/config/zsh/tools/*.zsh
};
}