# ============================================================================= # ~/.zshrc # ============================================================================= # Reihenfolge ist hier Absicht: # 1. Profiling-Schalter # 2. tmux-exec (ganz oben, damit die aeussere Shell sich die Init spart) # 3. billige Optionen / Keybindings / History # 4. teure Sachen: compinit + gecachte eval-Ausgaben # 5. Aliases, Funktionen, PATH # # Cache leeren nach Theme-/Tool-Update: zshrc-refresh # Startzeit messen: ZSHRC_PROFILE=1 zsh -i -c exit # ============================================================================= # ----------------------------------------------------------------------------- # 1. Profiling (optional, per Env-Variable) # ----------------------------------------------------------------------------- if [[ -n "$ZSHRC_PROFILE" ]]; then zmodload zsh/zprof fi # ----------------------------------------------------------------------------- # 2. tmux-Autostart # ----------------------------------------------------------------------------- # Muss vor allem anderen stehen: die aeussere Shell wird durch tmux ersetzt und # muss deshalb gar nicht erst komplett initialisiert werden. Frueher lief das # zshrc pro Terminalstart zweimal komplett durch (einmal aussen, einmal im # tmux-Fenster) - das faellt damit weg. # # Terminalerkennung laeuft ueber Environment-Variablen statt ueber ps/proc, # das spart ~6 Forks pro Shellstart. if [[ -o interactive && -z "$TMUX" && -z "$ZSHRC_PROFILE" && $UID -ne 0 && -t 1 ]]; then # IDEs und eingebettete Terminals explizit ausnehmen 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 [[ -n "$KITTY_WINDOW_ID" ]] && _zsh_want_tmux=1 [[ -n "$VTE_VERSION" ]] && _zsh_want_tmux=1 # xfce4-terminal, gnome-terminal [[ -n "$ZSH_FORCE_TMUX" ]] && _zsh_want_tmux=1 # manueller Override [[ -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. Grundoptionen, Keybindings, History # ----------------------------------------------------------------------------- bindkey -e # Emacs-Keybindings, auch wenn EDITOR=vi setopt interactivecomments # "#" auf der Kommandozeile = Kommentar HISTFILE=~/.zsh_history HISTSIZE=50000 # Eintraege im RAM SAVEHIST=50000 # Eintraege in der Datei # (vorher stand SAVEHIST auf 1000 - HISTFILESIZE # ist eine Bash-Variable und hat nichts getan) setopt hist_ignore_all_dups # aeltere Duplikate rauswerfen setopt hist_ignore_space # Zeilen mit fuehrendem Leerzeichen nicht merken setopt hist_reduce_blanks setopt hist_verify # Expansion erst anzeigen, dann ausfuehren setopt extended_history # mit Zeitstempel (Ersatz fuer HISTTIMEFORMAT) setopt share_history # History zwischen laufenden Shells teilen # Falls die Shell sich beim Start traege anfuehlt, obwohl sonst alles schnell # ist: share_history liest bei jedem Prompt die History-Datei neu ein. Dann # stattdessen mal testen: # unsetopt share_history # setopt inc_append_history # ----------------------------------------------------------------------------- # 4a. Cache-Verzeichnis # ----------------------------------------------------------------------------- ZSH_CACHE_DIR=${XDG_CACHE_HOME:-$HOME/.cache}/zsh [[ -d $ZSH_CACHE_DIR ]] || mkdir -p $ZSH_CACHE_DIR # Pfad zum oh-my-posh-Theme OMP_CONFIG=$HOME/.zsh/rudolfs-dark.omp.json # ----------------------------------------------------------------------------- # 4b. Completion-System # ----------------------------------------------------------------------------- # Nacktes "compinit" laeuft bei jedem Start compaudit und statet alle # $fpath-Verzeichnisse ab - hunderte stat()-Calls. Bei warmem Page-Cache egal, # bei kaltem Cache (also genau nach Idle) der teuerste Einzelposten. # Deshalb: voller Audit nur einmal pro Tag, sonst direkt aus dem Dump. () { emulate -L zsh setopt local_options extended_glob local zdump=$ZSH_CACHE_DIR/zcompdump autoload -Uz compinit if [[ -n $zdump(#qNmh-24) ]]; then compinit -C -d $zdump # Dump juenger als 24h -> Check ueberspringen else compinit -d $zdump # einmal taeglich voll durchlaufen touch $zdump fi # Dump kompilieren: laedt deutlich schneller, gerade von kalter Platte 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 # Completion-Ergebnisse cachen (z.B. apt-Paketlisten) zstyle ':completion:*' use-cache on zstyle ':completion:*' cache-path $ZSH_CACHE_DIR/compcache # list-colors: die Zeile "zstyle ':completion:*' list-colors ''" im alten zshrc # hat die LS_COLORS-Zuweisung direkt darueber wieder plattgemacht - deshalb hier # nur noch einmal, weiter unten nach dircolors. 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: mit "zstyle -e" wird das erst beim Tab-Druck ausgewertet, # nicht mehr bei jedem Shellstart. 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 "[*?]")"})' # ----------------------------------------------------------------------------- # 4c. Gecachte Init-Ausgaben (television, dircolors) # ----------------------------------------------------------------------------- # Beide Tools geben bei jedem Start denselben statischen Shell-Code aus. Statt # sie jedes Mal zu starten, wird die Ausgabe einmal in eine Datei geschrieben, # kompiliert und danach nur noch gesourct. # # WICHTIG: die Bloecke brauchen eine Trennzeile. Manche Tools beenden ihre # Ausgabe ohne Newline, dann klebt die erste Zeile des naechsten Blocks hinten # an der letzten Zeile des vorigen - und man bekommt kaputte Pfade wie # ".../init.26.0.5.abc..zsh#compdef". () { 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 } # oh-my-posh cacht seinen Init-Code seit v26 selbst unter ~/.cache/oh-my-posh/ # (Dateiname enthaelt Version + Config-Hash). "oh-my-posh init zsh" gibt nur # noch einen kleinen Stub aus, der diese Datei sourct. Ein eigener Cache # darueber bringt deshalb nichts und bricht, sobald sich Version oder Theme # aendert - der Stub zeigt dann auf einen Pfad, den es nicht mehr gibt. if (( $+commands[oh-my-posh] )); then eval "$(oh-my-posh init zsh --config "$OMP_CONFIG")" fi # Jetzt steht LS_COLORS -> Completion einfaerben zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS} # Cache verwerfen und Shell neu starten zshrc-refresh() { rm -f $ZSH_CACHE_DIR/init.zsh{,.zwc} \ $ZSH_CACHE_DIR/zcompdump{,.zwc} \ ~/.zshrc.zwc # oh-my-posh raeumt seinen eigenen Cache normalerweise selbst auf. # Falls das Prompt nach einem Update zickt, zusaetzlich: # rm -rf ~/.cache/oh-my-posh echo "Cache geleert, starte Shell neu ..." exec zsh } # ----------------------------------------------------------------------------- # 5. PATH # ----------------------------------------------------------------------------- typeset -U path PATH # automatisch dedupliziert path=( $HOME/.local/bin # pip3 --user $path $HOME/go/bin $HOME/.cargo/bin ) path=($^path(N-/)) # nur existierende Verzeichnisse behalten # ----------------------------------------------------------------------------- # 6. 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 # ----------------------------------------------------------------------------- # 7. tmux-Fenstername = aktuelles Verzeichnis # ----------------------------------------------------------------------------- # Vorher via PROMPT_COMMAND - das ist eine Bash-Variable und hat in zsh nie # funktioniert. Jetzt korrekt als chpwd-Hook: laeuft nur beim Verzeichnis- # wechsel, nicht bei jedem Prompt. if [[ -n "$TMUX" ]]; then autoload -Uz add-zsh-hook _tmux_rename_window() { tmux rename-window "${PWD:t}" 2>/dev/null } add-zsh-hook chpwd _tmux_rename_window _tmux_rename_window fi # ----------------------------------------------------------------------------- # 8. Funktionen # ----------------------------------------------------------------------------- # yazi mit cwd-Uebernahme function 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 ----------------------------------------------------------------- function bandcampdl() { yt-dlp \ -x \ --audio-format mp3 \ --audio-quality 0 \ --embed-metadata \ --embed-thumbnail \ --convert-thumbnails jpg \ -o "%(album_artist)s/%(release_year)s - %(album)s/%(album_artist)s - %(album)s - %(track_number)02d - %(title)s.%(ext)s" \ "$1" } function bandcampbatch() { local file="$1" while IFS= read -r url; do [[ -z "$url" || "$url" == \#* ]] && continue bandcampdl "$url" done < "$file" } function bandcamplist() { local url outfile tmpfile band count command -v yt-dlp >/dev/null 2>&1 || { echo "Fehler: yt-dlp wurde nicht gefunden." return 1 } if (( $# < 1 || $# > 2 )); then echo "Usage:" echo " bandcamplist [output.txt]" return 1 fi url="$1" if [[ ! "$url" =~ ^https?://([^.]+)\.bandcamp\.com/music/?$ ]]; then echo "Fehler: Keine gueltige Bandcamp-/music-URL:" echo " $url" return 1 fi band="${match[1]}" if [[ $# -eq 2 ]]; then outfile="$2" else outfile="bandcamp-${band}.txt" fi tmpfile=$(mktemp) if ! yt-dlp \ --flat-playlist \ --print webpage_url \ "$url" \ | sort -u > "$tmpfile" then rm -f "$tmpfile" echo "Fehler beim Abrufen der Links." return 1 fi count=$(wc -l < "$tmpfile") if (( count == 0 )); then rm -f "$tmpfile" echo "Keine Album- oder Track-Links gefunden." return 1 fi mv "$tmpfile" "$outfile" echo "✓ $count Links nach '$outfile' geschrieben." } # tm [name] -> Session anlegen oder hinwechseln (Default: main) function 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 } # ----------------------------------------------------------------------------- # 9. Abschluss # ----------------------------------------------------------------------------- # zshrc selbst kompilieren - zsh benutzt die .zwc-Datei automatisch, solange sie # neuer ist als das Original. Nach jeder Aenderung wird sie hier neu gebaut. if [[ ! -s ~/.zshrc.zwc || ~/.zshrc -nt ~/.zshrc.zwc ]]; then zcompile ~/.zshrc 2>/dev/null fi if [[ -n "$ZSHRC_PROFILE" ]]; then zprof fi