aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/dot_config/zsh
diff options
context:
space:
mode:
authorLibravatar sommerfeld <sommerfeld@sommerfeld.dev>2026-04-21 01:23:18 +0100
committerLibravatar sommerfeld <sommerfeld@sommerfeld.dev>2026-04-21 01:23:18 +0100
commitd00bf2cb2d5087164fa509a4f20a2be62a519044 (patch)
treee788986ca341fa7a9d7327b48c58d2e0c0c43115 /dot_config/zsh
parent9b2af4dd6c73ea57cc921f41120db7a2700e806d (diff)
downloaddotfiles-d00bf2cb2d5087164fa509a4f20a2be62a519044.tar.gz
dotfiles-d00bf2cb2d5087164fa509a4f20a2be62a519044.tar.bz2
dotfiles-d00bf2cb2d5087164fa509a4f20a2be62a519044.zip
refactor: restructure to chezmoi source state
Rename home/ contents to chezmoi naming conventions: - dot_ prefix for dotfiles and dot-dirs - private_dot_ for .gnupg and .ssh directories - private_ for 0600 files (nym.pub) - executable_ for scripts in .local/bin and display-toggle.sh - symlink_ for mimeapps.list symlink
Diffstat (limited to 'dot_config/zsh')
-rw-r--r--dot_config/zsh/dot_zprofile116
-rw-r--r--dot_config/zsh/dot_zshrc322
2 files changed, 438 insertions, 0 deletions
diff --git a/dot_config/zsh/dot_zprofile b/dot_config/zsh/dot_zprofile
new file mode 100644
index 0000000..30b5b21
--- /dev/null
+++ b/dot_config/zsh/dot_zprofile
@@ -0,0 +1,116 @@
+# Login shell configuration — sourced once per session by zsh.
+# Sets environment variables, XDG paths, tool config, and host-specific overrides.
+
+# Guard against double-sourcing (e.g. nested login shells)
+[[ -n $__ZPROFILE_SOURCED ]] && return
+__ZPROFILE_SOURCED=1
+
+# ── PATH ──────────────────────────────────────────────────────────────────────
+typeset -U path # deduplicate PATH entries
+path=("$HOME/.local/bin" "$HOME/.local/share/nvim/mason/bin" $path)
+
+# ── XDG Base Directories ─────────────────────────────────────────────────────
+export XDG_CONFIG_HOME="$HOME/.config"
+export XDG_DATA_HOME="$HOME/.local/share"
+export XDG_STATE_HOME="$HOME/.local/state"
+export XDG_CACHE_HOME="$HOME/.cache"
+
+# ── Locale ────────────────────────────────────────────────────────────────────
+export LANG=en_US.UTF-8
+
+# ── Terminal ──────────────────────────────────────────────────────────────────
+case $TERM in
+ *256color|*truecolor) export COLORTERM=24bit ;;
+esac
+
+export TERMINAL='ghostty'
+export BROWSER='linkhandler'
+export OPENER='xdg-open'
+
+# ── Editors ───────────────────────────────────────────────────────────────────
+export EDITOR='nvim'
+export VISUAL='nvim'
+export DIFFPROG='nvim -d'
+export MANPAGER='nvim +Man!'
+export MANWIDTH=999
+
+# ── less ──────────────────────────────────────────────────────────────────────
+export LESS="-F --RAW-CONTROL-CHARS"
+[[ -r /usr/bin/source-highlight-esc.sh ]] && export LESSOPEN="| /usr/bin/source-highlight-esc.sh %s"
+
+# ── GPG / SSH ─────────────────────────────────────────────────────────────────
+unset SSH_AGENT_PID
+export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
+
+# ── FZF ───────────────────────────────────────────────────────────────────────
+export FZF_DEFAULT_COMMAND="fd --type file --follow --hidden --exclude .git --color=always"
+export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
+export FZF_DIRS_COMMAND="fd --type d --follow --hidden --exclude .git --color=always"
+export FZF_DEFAULT_OPTS="--ansi --layout=reverse --inline-info --cycle --color=dark --color=fg:-1,bg:-1,hl:#5fff87,fg+:-1,bg+:-1,hl+:#ffaf5f --color=info:#af87ff,prompt:#5fff87,pointer:#ff87d7,marker:#ff87d7,spinner:#ff87d7"
+export FZF_CTRL_T_OPTS="--preview 'bat --color=always --style=numbers --line-range=:500 {}' --select-1 --exit-0"
+export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window down:3:hidden:wrap --bind '?:toggle-preview' --sort --exact"
+export FZF_ALT_C_OPTS="--preview 'tree -C {} | head -200'"
+
+# ── Git prompt ────────────────────────────────────────────────────────────────
+export GIT_PS1_SHOWDIRTYSTATE=1
+export GIT_PS1_SHOWSTASHSTATE=1
+unset GIT_PS1_SHOWUNTRACKEDFILES
+export GIT_PS1_SHOWUPSTREAM="verbose"
+export GIT_PS1_SHOWCONFLICTSTATE="yes"
+export GIT_PS1_DESCRIBE_STYLE="branch"
+export GIT_PS1_SHOWCOLORHINTS=1
+export GIT_PS1_HIDE_IF_PWD_IGNORED=1
+
+# ── GCC ───────────────────────────────────────────────────────────────────────
+export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
+
+# ── Java ──────────────────────────────────────────────────────────────────────
+# System AA fonts, GTK L&F, XDG prefs dir, GTK2 for compatibility
+export _JAVA_OPTIONS="-Dawt.useSystemAAFontSettings=on -Dswing.aatext=true -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel -Djava.util.prefs.userRoot=$XDG_CONFIG_HOME/java -Djdk.gtk.version=2"
+# Fix for non-reparenting WMs (sway, dwm, etc.)
+export _JAVA_AWT_WM_NONREPARENTING=1
+
+# ── Miscellaneous ─────────────────────────────────────────────────────────────
+export QT_QPA_PLATFORMTHEME=qt6ct
+export NO_AT_BRIDGE=1 # suppress GTK accessibility bus warnings
+export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/podman/podman.sock"
+export INPUTRC="$XDG_CONFIG_HOME/sh/inputrc"
+
+# ── Wayland ───────────────────────────────────────────────────────────────────
+export XDG_CURRENT_DESKTOP=sway
+export MOZ_ENABLE_WAYLAND=1
+
+# ── XDG cleanup: keep $HOME tidy ─────────────────────────────────────────────
+# https://wiki.archlinux.org/title/XDG_Base_Directory#Partial
+export CARGO_HOME="$XDG_DATA_HOME/cargo"
+export CUDA_CACHE_PATH="$XDG_CACHE_HOME/nv"
+export GOPATH="$XDG_DATA_HOME/go"
+export GRADLE_USER_HOME="$XDG_DATA_HOME/gradle"
+export NODE_REPL_HISTORY="$XDG_DATA_HOME/node_repl_history"
+export PASSWORD_STORE_DIR="$XDG_DATA_HOME/password-store"
+export RUFF_CACHE_DIR="$XDG_CACHE_HOME/ruff"
+export RUSTUP_HOME="$XDG_DATA_HOME/rustup"
+export WGETRC="$XDG_CONFIG_HOME/wget/wgetrc"
+export WINEPREFIX="$XDG_DATA_HOME/wineprefixes/default"
+
+# ── Host-specific ─────────────────────────────────────────────────────────────
+case $(uname -n) in
+ halley2)
+ export LIBVA_DRIVER_NAME="iHD"
+ export MESA_LOADER_DRIVER_OVERRIDE="iris"
+ export VAAPI_MPEG4_ENABLED=true
+ ;;
+ hercules)
+ export OCL_ICD_VENDORS=nvidia
+ [[ -r "$XDG_CONFIG_HOME/sh/work-envrc" ]] && source "$XDG_CONFIG_HOME/sh/work-envrc"
+ ;;
+esac
+
+# ── Secrets (from pass) ──────────────────────────────────────────────────────
+(( $+commands[pass] )) && export FIRECRAWL_API_KEY="$(pass show copilot/firecrawl-api-key)"
+
+# ── Auto-start sway on VT1 ────────────────────────────────────────────────────
+if [[ -z $WAYLAND_DISPLAY && $XDG_VTNR == 1 ]]; then
+ export XDG_SESSION_TYPE=wayland
+ exec sway
+fi
diff --git a/dot_config/zsh/dot_zshrc b/dot_config/zsh/dot_zshrc
new file mode 100644
index 0000000..fad4bca
--- /dev/null
+++ b/dot_config/zsh/dot_zshrc
@@ -0,0 +1,322 @@
+# Interactive zsh configuration.
+
+# ── Terminal ──────────────────────────────────────────────────────────────────
+stty -ixon # disable XON/XOFF flow control (frees Ctrl-S/Ctrl-Q)
+ttyctl -f # freeze terminal state; programs can't leave it broken
+
+# ── Options ───────────────────────────────────────────────────────────────────
+# Note: appendhistory, nomatch, notify are zsh defaults — not set here.
+setopt autocd # cd by typing directory name
+setopt extendedglob # extended glob patterns (#, ~, ^)
+setopt interactivecomments # allow # comments in interactive shell
+setopt rmstarsilent # don't confirm rm *
+setopt prompt_subst # expand variables/functions in prompt
+setopt auto_pushd # cd pushes old dir onto stack (cd -<TAB> to browse)
+setopt pushd_ignore_dups # don't push duplicate dirs onto stack
+unsetopt beep # no terminal bell
+
+# ── History ───────────────────────────────────────────────────────────────────
+HISTFILE="$XDG_STATE_HOME/zsh/history"
+HISTSIZE=50000
+SAVEHIST=50000
+setopt extended_history # save timestamp and duration per entry
+setopt share_history # share history across concurrent sessions
+setopt hist_ignore_all_dups # remove older duplicate when adding new entry
+setopt hist_find_no_dups # skip duplicates when searching history
+setopt hist_reduce_blanks # trim superfluous whitespace from entries
+setopt hist_ignore_space # commands starting with space are not saved
+
+# ── Emacs keybindings ─────────────────────────────────────────────────────────
+bindkey -e
+
+# ── Prompt ────────────────────────────────────────────────────────────────────
+autoload -Uz colors && colors
+source /usr/share/git/completion/git-prompt.sh
+PROMPT='%B%{$fg[green]%}%n%{$reset_color%}@%{$fg[cyan]%}%m%{$reset_color%}:%b%{$fg[yellow]%}%~%{$reset_color%}$(__git_ps1 " (%s)")%(?..[%{$fg[red]%}%?%{$reset_color%}]) %(!.#.>) '
+
+# ── Completion ────────────────────────────────────────────────────────────────
+fpath=($XDG_DATA_HOME/zsh/completion $fpath)
+autoload -Uz compinit && compinit -d "$XDG_CACHE_HOME/zsh/zcompdump"
+
+zstyle ':completion:*' menu select # arrow-key driven menu for ambiguous completions
+zstyle ':completion:*' completer _expand_alias _complete _ignored _match _approximate
+# │ │ │ │ └ fuzzy match (typo tolerance)
+# │ │ │ └ try pattern matching
+# │ │ └ include normally hidden completions
+# │ └ standard completion
+# └ expand aliases before completing
+zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS} # colorize file completions like ls
+zstyle ':completion:*' use-cache on # cache completions (speeds up pip, dpkg, etc.)
+zstyle ':completion:*' cache-path "$XDG_CACHE_HOME/zsh"
+zstyle ':completion:*:match:*' original only # only show original when pattern-matching
+zstyle ':completion:*:functions' ignored-patterns '_*' # hide internal completion functions
+zstyle ':completion:*:*:kill:*' menu yes select # interactive menu for kill completion
+zstyle ':completion:*:kill:*' force-list always # always show process list for kill
+zstyle ':completion:*:cd:*' ignore-parents parent pwd # cd never completes . or ..
+zstyle ':completion::complete:*' gain-privileges 1 # use doas/sudo for privileged completions
+zstyle -e ':completion:*:approximate:*' \
+ max-errors 'reply=($((($#PREFIX+$#SUFFIX)/3))numeric)' # allow 1 typo per 3 chars typed
+
+_comp_options+=(globdots) # include hidden files in completion
+
+# ── Terminal key setup ─────────────────────────────────────────────────────────
+# Application mode ensures terminfo values are valid during line editing.
+# Without this, some terminals send wrong sequences for special keys.
+if (( ${+terminfo[smkx]} && ${+terminfo[rmkx]} )); then
+ autoload -Uz add-zle-hook-widget
+ function zle_application_mode_start { echoti smkx }
+ function zle_application_mode_stop { echoti rmkx }
+ add-zle-hook-widget -Uz zle-line-init zle_application_mode_start
+ add-zle-hook-widget -Uz zle-line-finish zle_application_mode_stop
+fi
+
+# Up/Down stored for history-substring-search bindings (set after plugin source)
+typeset -g -A key
+key[Up]="${terminfo[kcuu1]}"
+key[Down]="${terminfo[kcud1]}"
+
+# ── Custom keybindings ────────────────────────────────────────────────────────
+bindkey \^U backward-kill-line
+
+# Word navigation (Ctrl-Right also accepts autosuggestion word-by-word — fish-like)
+bindkey '^[[1;5C' forward-word # Ctrl-Right
+bindkey '^[[1;5D' backward-word # Ctrl-Left
+bindkey '^[[1;3C' forward-word # Alt-Right
+bindkey '^[[1;3D' backward-word # Alt-Left
+bindkey '^H' backward-kill-word # Ctrl-Backspace
+bindkey '^[[3;5~' kill-word # Ctrl-Delete
+
+# Ctrl-Z: toggle foreground/background (no need to type 'fg')
+toggle-fg-bg() {
+ if (( ${#jobstates} )); then
+ zle .push-input
+ BUFFER="fg"
+ zle .accept-line
+ else
+ zle .push-input
+ zle .clear-screen
+ fi
+}
+zle -N toggle-fg-bg
+bindkey '^Z' toggle-fg-bg
+
+# Ctrl-D exits even on non-empty line
+exit_zsh() { exit }
+zle -N exit_zsh
+bindkey '^D' exit_zsh
+
+# Ctrl-X Ctrl-E: edit command in $EDITOR
+autoload -Uz edit-command-line
+zle -N edit-command-line
+bindkey "^X^E" edit-command-line
+
+# Ctrl-Y: copy current command line to clipboard (OSC 52 — terminal-native)
+copy-line-to-clipboard() { printf '\033]52;c;%s\a' "$(echo -n "$BUFFER" | base64)" }
+zle -N copy-line-to-clipboard
+bindkey '^Y' copy-line-to-clipboard
+
+# ── Word style ────────────────────────────────────────────────────────────────
+# Ctrl-W/Alt-B/Alt-F use shell quoting rules for word boundaries
+autoload -Uz select-word-style
+select-word-style shell
+
+# ── Smart dot expansion ───────────────────────────────────────────────────────
+# Typing .. automatically expands: ... → ../.. , .... → ../../.. , etc.
+rationalise-dot() {
+ if [[ $LBUFFER = *.. ]]; then
+ LBUFFER+=/..
+ else
+ LBUFFER+=.
+ fi
+}
+zle -N rationalise-dot
+bindkey . rationalise-dot
+
+# ── Window title ──────────────────────────────────────────────────────────────
+autoload -Uz add-zsh-hook
+
+xterm_title_precmd() { print -Pn -- '\e]2;%~\a' }
+xterm_title_preexec() { print -Pn -- '\e]2;%~ %# ' && print -n -- "${(q)1}\a" }
+
+if [[ "$TERM" == (xterm-ghostty|st*|screen*|xterm*|rxvt*|tmux*|putty*|konsole*|gnome*) ]]; then
+ add-zsh-hook -Uz precmd xterm_title_precmd
+ add-zsh-hook -Uz preexec xterm_title_preexec
+fi
+
+# ── Zellij tab naming (dir:cmd like tmux) ────────────────────────────────────
+if [[ -n "$ZELLIJ" ]]; then
+ _zellij_dir() { [[ "$PWD" == "$HOME" ]] && echo '~' || echo "${PWD##*/}"; }
+ _zellij_tab_idx() { echo $(( $(zellij action current-tab-info 2>/dev/null | grep -oP 'position: \K\d+') + 1 )); }
+ _zellij_tab_precmd() { zellij action rename-tab "$(_zellij_tab_idx):$(_zellij_dir)" 2>/dev/null; }
+ _zellij_tab_preexec() { zellij action rename-tab "$(_zellij_tab_idx):$(_zellij_dir):${1%% *}" 2>/dev/null; }
+ add-zsh-hook precmd _zellij_tab_precmd
+ add-zsh-hook preexec _zellij_tab_preexec
+fi
+
+# ── Recent directories ────────────────────────────────────────────────────────
+autoload -Uz chpwd_recent_dirs cdr
+add-zsh-hook chpwd chpwd_recent_dirs
+[[ -d ${XDG_STATE_HOME}/zsh ]] || mkdir -p "${XDG_STATE_HOME}/zsh"
+zstyle ':chpwd:*' recent-dirs-file "$XDG_STATE_HOME/zsh/chpwd-recent-dirs"
+zstyle ':completion:*:*:cdr:*:*' menu selection
+
+# ── OSC 7 — report CWD to terminal (zellij uses this for new pane/tab CWD) ──
+_osc7_chpwd() {
+ printf '\e]7;file://%s%s\e\\' "${HOST}" "${PWD}"
+}
+add-zsh-hook chpwd _osc7_chpwd
+_osc7_chpwd
+
+# ── Help system ───────────────────────────────────────────────────────────────
+autoload -Uz run-help run-help-git run-help-ip
+(( $+aliases[run-help] )) && unalias run-help
+alias help=run-help
+
+# ── Bracketed paste ───────────────────────────────────────────────────────────
+autoload -Uz bracketed-paste-magic
+zle -N bracketed-paste bracketed-paste-magic
+
+# ── Aliases ───────────────────────────────────────────────────────────────────
+# Files
+alias l='lsd -l'
+alias la='lsd -lA'
+alias lt='lsd --tree'
+alias mkdir='mkdir -p'
+alias du='du -h'
+alias df='df -h'
+alias free='free -h'
+
+# Grep / diff with color
+alias grep='grep --color=auto'
+alias fgrep='grep -F --color=auto'
+alias egrep='grep -E --color=auto'
+alias diff='diff --color=auto'
+alias dmesg='dmesg --color=auto'
+alias dm='dmesg --color=always | less -r'
+
+# Networking
+alias ip="ip -color=auto"
+alias lsip="ip -human -color=auto --brief address show"
+alias ipa="ip -stats -details -human -color=auto address show"
+alias ipecho='curl ipecho.net/plain'
+alias ss='doas ss -tupnl'
+
+# Privilege escalation
+alias sudo='doas'
+alias sudoedit='doasedit'
+alias gimme='doas chown $USER:$(id -gn $USER)'
+alias pacdiff='doas pacdiff'
+
+# Pacman
+alias pacopt='comm -13 <(pacman -Qqdt | sort) <(pacman -Qqdtt | sort)'
+
+# Git
+alias g='git'
+
+# Systemd
+alias sys='systemctl'
+alias ssys='doas systemctl'
+alias sysu='systemctl --user'
+
+# Navigation
+alias c='clear'
+
+# Yazi: cd-on-exit wrapper
+y() {
+ local tmp="$(mktemp -t "yazi-cwd.XXXXXX")"
+ command yazi "$@" --cwd-file="$tmp"
+ IFS= read -r -d '' cwd < "$tmp"
+ [[ "$cwd" != "$PWD" ]] && [[ -d "$cwd" ]] && builtin cd -- "$cwd"
+ rm -f -- "$tmp"
+}
+
+# Tools
+alias stow='stow -R --no-folding --adopt'
+alias curl='curlie'
+alias cpr='rsync --archive -hh --partial --info=stats1,progress2 --modify-window=1'
+alias mvr='rsync --archive -hh --partial --info=stats1,progress2 --modify-window=1 --remove-source-files'
+alias sub='subliminal download -l en'
+
+# Neovim
+alias n='nvim'
+alias ndiff='nvim -d'
+alias nd='nvim -d'
+alias nview='nvim -R'
+alias nv='nvim -R'
+alias ng='nvim +Neogit'
+
+# Zellij: smart attach — 0 sessions: create, 1: attach, many: welcome picker
+za() {
+ if [[ -n $ZELLIJ ]]; then
+ echo "Already inside zellij" >&2
+ return 1
+ fi
+ local -a sessions=("${(@f)$(zellij list-sessions -ns 2>/dev/null)}")
+ sessions=(${sessions:#})
+ case ${#sessions} in
+ 0) zellij ;;
+ 1) zellij attach "${sessions[1]}" ;;
+ *) zellij -l welcome ;;
+ esac
+}
+
+# Just
+alias j='just'
+
+# LLVM / Clang tooling
+alias ncmake='cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS="$DEV_CFLAGS" -DCMAKE_CXX_FLAGS="$DEV_CFLAGS" -DCMAKE_INSTALL_PREFIX=build/install -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build'
+alias ircc='clang -S -emit-llvm -fno-discard-value-names -O0 -Xclang -disable-O0-optnone -o -'
+alias irfc='flang -S -emit-llvm -O0 -o -'
+alias astcc='clang -Xclang -ast-dump -fsyntax-only'
+alias astfc='flang -fc1 -fdebug-dump-parse-tree'
+alias symfc='flang -fc1 -fdebug-dump-symbols'
+alias gdbr='gdb -ex start --args'
+
+# GitHub Copilot CLI
+alias copilot='gh copilot --autopilot --enable-all-github-mcp-tools --yolo --resume'
+
+# ── Alias completions ─────────────────────────────────────────────────────────
+compdef g=git
+compdef j=just
+compdef n=nvim ndiff=nvim nd=nvim nview=nvim nv=nvim
+compdef sys=systemctl ssys=systemctl sysu=systemctl
+compdef l=lsd la=lsd lt=lsd
+
+# ── GPG agent ─────────────────────────────────────────────────────────────────
+# Set GPG_TTY to this shell's actual TTY (not the login console) and tell
+# the agent so pinentry prompts appear in the right terminal
+export GPG_TTY=$TTY
+gpg-connect-agent updatestartuptty /bye &>/dev/null
+
+# ── Zoxide (smart directory jumping) ──────────────────────────────────────────
+# z foo → jump to frecency-ranked dir matching "foo"
+# zi → interactive picker with fzf
+eval "$(zoxide init zsh)"
+
+# ── FZF ───────────────────────────────────────────────────────────────────────
+source <(fzf --zsh)
+
+# Ctrl-X Ctrl-R: search history with fzf and immediately execute
+fzf-history-widget-accept() {
+ fzf-history-widget
+ zle accept-line
+}
+zle -N fzf-history-widget-accept
+bindkey '^X^R' fzf-history-widget-accept
+
+_fzf_compgen_path() { fd --hidden --follow --exclude ".git" . "$1" }
+_fzf_compgen_dir() { fd --type d --hidden --follow --exclude ".git" . "$1" }
+
+# ── Plugins (must be sourced last) ────────────────────────────────────────────
+# Highlight config must be set BEFORE sourcing the plugin
+ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets pattern)
+typeset -A ZSH_HIGHLIGHT_STYLES
+ZSH_HIGHLIGHT_STYLES[comment]='fg=yellow'
+source /usr/share/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
+
+source /usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh
+bindkey '^[[Z' autosuggest-accept # Shift-Tab to accept suggestion
+
+source /usr/share/zsh/plugins/zsh-history-substring-search/zsh-history-substring-search.zsh
+[[ -n "${key[Up]}" ]] && bindkey -- "${key[Up]}" history-substring-search-up
+[[ -n "${key[Down]}" ]] && bindkey -- "${key[Down]}" history-substring-search-down