# ~/.config/zsh/keybinds.zsh — zle ports of fish_user_key_bindings.fish. # Keys owned by kitty.conf never reach the shell and are NOT bound here: # ctrl-h/j/k/l → vim-aware split nav (pass_keys.py). bindkey -e # pin emacs keymap ($EDITOR containing "vi" would pick viins) unsetopt flow_control # free ^Q/^S in zle so the ctrl-q exit bind fires # iris (~/.config/iris) learns a command is running from the preexec/precmd # hooks, which never fire for programs a widget starts. Without this, iris # keeps treating the screen as an idle prompt while tv, yazi or lazygit run: # it swallows up/down/tab and can draw its menu over them. Same messages as # iris's own hooks; a no-op outside iris. fish: functions/_iris_busy.fish. _iris_busy() { [[ -n $IRIS_FD ]] || return 0 case $1 in start) print -u $IRIS_FD -N -r -- IRIS_CMD_START 2>/dev/null ;; stop) print -u $IRIS_FD -N -r -- IRIS_CMD_STOP:0 2>/dev/null ;; esac return 0 } # Generic runner: widget `run-` executes (function or binary) with # the tty as stdin, then repaints — fish's `commandline -f repaint`. # cwd changes from gho/yy persist: widgets run in the shell process. # gho, pj and `tv git-files` (below) open television cables (~/.config/ # television/cable) — the same keys do the same in fish and bash. _zle-run() { local cmd=${WIDGET#run-} zle -I _iris_busy start $cmd /dev/tty zle reset-prompt } zle -N tv-git-files _zle-tv-git-files # ctrl-o: gho (ghq-repos cable); cd + clear + onefetch when something was # picked. _zle-gho() { local before=$PWD zle -I _iris_busy start gho /dev/tty zle reset-prompt fi } zle -N gho-inline _zle-gho # ctrl-p: pj. pj reports through $PJ_STATE: `open` = cancelled, `ran` = a task # printed output, empty = no task sources. _zle-pj() { local state state=$(mktemp) || return zle -I _iris_busy start PJ_STATE=$state pj /dev/tty; zle reset-prompt ;; ran) ;; *) zle reset-prompt ;; esac rm -f $state } zle -N pj-inline _zle-pj # ctrl-z at the prompt (fish's _jobs_bind): fg the only job, or pick one from # the `jobs` tv cable, which reads the job table from $TV_JOBS (records: # "% ", id, process group). While a job holds the # terminal ctrl-z is still the tty's suspend, so the key toggles. # fg runs as a real command line (push-input keeps typed text for the next # prompt); __jobs_bind_fg erases that echoed line and fg's "[1] continued" # notice. The leading space keeps it out of history (HIST_IGNORE_SPACE). __jobs_bind_fg() { printf '\033[A\033[2K\r' >/dev/tty fg %$1 2>/dev/null } _zle-jobs() { local -a ids recs local id pick file ids=(${(On)${(k)jobstates}}) (( $#ids )) || return 0 for id in $ids; do recs+=("$(printf '%%%s %-9s %s\t%s\t%s' $id ${jobstates[$id]%%:*} \ "${jobtexts[$id]}" $id ${${jobstates[$id]#*:*:}%%=*})") done if (( $#ids == 1 )); then pick=$ids[1] else file=$(mktemp) || return print -rl -- $recs >$file zle -I _iris_busy start pick=$(TV_JOBS=$file tv jobs --inline /dev/tty rm -f $file if [[ -z $pick ]]; then zle reset-prompt return fi fi zle push-input BUFFER=" __jobs_bind_fg $pick" zle accept-line } zle -N jobs-fg _zle-jobs bindkey '^E' tv-git-files bindkey '^B' run-purple bindkey '^Y' run-clip-last bindkey '^O' gho-inline bindkey '^P' pj-inline # ctrl-a: omp-session resumes or starts omp for the repo; after a cancelled # omp-sessions picker it moves the cursor back up itself. bindkey '^A' run-omp-session # ctrl-f: yazi; yy cd's to yazi's exit dir on quit. bindkey '^F' run-yy bindkey '^G' run-lazygit bindkey '^Z' jobs-fg # ctrl-q mirrors ctrl-d's preset (`exit`) — quit the shell. _zle-exit() { exit } zle -N _zle-exit bindkey '^Q' _zle-exit # ctrl-c at the prompt: wipe any typed text AND clear screen + kitty # scrollback (2J/3J drops it, like the fish `clear` wrapper). ^C is the tty # INTR char, so this lives in TRAPINT — bindkey '^C' would never fire. # Mid-command SIGINT behaves normally (128+SIGINT). BUFFER is read-only in # trap context, so builtin widgets are invoked via `zle .name` instead. TRAPINT() { if zle; then zle .kill-buffer printf '\033[2J\033[3J\033[1;1H' zle .reset-prompt return 0 fi return $(( 128 + $1 )) }