From a5bf3e9194a5c77f6f111dc719375c4cfb65d06d Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Wed, 30 Sep 2026 11:54:14 -0400 Subject: [PATCH] feat(config): switch tv pickers to --inline and improve prompt redraw handling --- dot_config/bash/functions/gho.bash | 4 +- dot_config/bash/keybinds.bash | 77 ++++++++++++++----- dot_config/fish/functions/_gho_bind.fish | 13 ++++ dot_config/fish/functions/_pj_bind.fish | 21 +++++ dot_config/fish/functions/_pj_bind_down.fish | 10 +-- dot_config/fish/functions/_tv_inline.fish | 10 +++ .../functions/fish_user_key_bindings.fish | 10 ++- dot_config/fish/functions/gho.fish | 4 +- dot_config/zsh/functions/gho.zsh | 4 +- dot_config/zsh/keybinds.zsh | 77 ++++++++++++++----- 10 files changed, 173 insertions(+), 57 deletions(-) create mode 100644 dot_config/fish/functions/_gho_bind.fish create mode 100644 dot_config/fish/functions/_pj_bind.fish create mode 100644 dot_config/fish/functions/_tv_inline.fish diff --git a/dot_config/bash/functions/gho.bash b/dot_config/bash/functions/gho.bash index 9008f3f..2791d18 100644 --- a/dot_config/bash/functions/gho.bash +++ b/dot_config/bash/functions/gho.bash @@ -1,8 +1,8 @@ # Pick a ghq repo / Obsidian vault with tv (ghq-repos cable) + cd. gho() { local dir - dir=$(tv ghq-repos) || return - [ -n "$dir" ] || return + dir=$(tv ghq-repos --inline) || return + [ -n "$dir" ] || return 1 cd "$dir" || return clear if [ -d .git ]; then diff --git a/dot_config/bash/keybinds.bash b/dot_config/bash/keybinds.bash index 259531b..669a6ff 100644 --- a/dot_config/bash/keybinds.bash +++ b/dot_config/bash/keybinds.bash @@ -4,33 +4,67 @@ # ctrl-p → pj (pj cable) down → pj on an empty prompt # ctrl-t → tv smart autocomplete (tv init bash ships no ctrl-t) # Keys owned by kitty.conf never reach the shell: ctrl-h/j/k/l (pass_keys.py). +# +# Every picker runs tv --inline: it opens on the line below the prompt and +# leaves the cursor on that line when it exits. When nothing was printed +# below, the binding steps back up (_kb_up) so readline redraws the prompt in +# place after bind -x; output that must stay (a pj task) is left alone. -# bind -x runs in the shell process, so gho's cd persists — but readline keeps -# drawing the prompt it started with. Each key is therefore a macro: \C-xN -# stashes the typed line and runs the command, \C-m accepts the now-empty -# line (bash prints a fresh prompt for the new cwd; nothing reaches history), -# and \C-x0 restores the stash into the new line. -_kb_run() { +_kb_up() { printf '\033[A' >/dev/tty; } + +# ctrl-e: enter opens nvim, which restores the cursor to tv's line. +_kb_git_files() { + tv git-files --inline /dev/tty + else + # cd + clear + onefetch: put the prompt below onefetch's output. + printf '\n' >/dev/tty + end + commandline -f repaint +end diff --git a/dot_config/fish/functions/_pj_bind.fish b/dot_config/fish/functions/_pj_bind.fish new file mode 100644 index 0000000..bc265d7 --- /dev/null +++ b/dot_config/fish/functions/_pj_bind.fish @@ -0,0 +1,21 @@ +function _pj_bind --description 'ctrl-p / down: pj (tv --inline), then redraw the prompt in the right place' + # pj reports through $PJ_STATE: `open` = the picker was cancelled and + # left the cursor on the line below the prompt; empty = no task sources, + # nothing drawn — but fish's repaint after an external command still + # lands one line low, so both step back up. `ran` = a task printed + # output: new line, prompt below it. + set -lx PJ_STATE (mktemp) + if contains -- --quiet $argv + set -lx PJ_QUIET 1 + end + pj + set -l state (cat $PJ_STATE) + switch "$state" + case open '' + printf '\033[A' >/dev/tty + case ran + printf '\n' >/dev/tty + end + rm -f $PJ_STATE + commandline -f repaint +end diff --git a/dot_config/fish/functions/_pj_bind_down.fish b/dot_config/fish/functions/_pj_bind_down.fish index fed1e24..948aa39 100644 --- a/dot_config/fish/functions/_pj_bind_down.fish +++ b/dot_config/fish/functions/_pj_bind_down.fish @@ -13,9 +13,9 @@ function _pj_bind_down --description "down arrow: pj tv cable on an idle empty p return end - # pj is ~/.local/bin/pj (also ctrl-p), which opens the `pj` tv cable. - # PJ_QUIET keeps it silent when the tree has no task source at all, so - # down is a no-op outside projects instead of printing an error per press. - PJ_QUIET=1 pj - commandline -f repaint + # _pj_bind runs ~/.local/bin/pj (also ctrl-p), which opens the `pj` tv + # cable. --quiet (PJ_QUIET) keeps it silent when the tree has no task + # source at all, so down is a no-op outside projects instead of printing + # an error per press. + _pj_bind --quiet end diff --git a/dot_config/fish/functions/_tv_inline.fish b/dot_config/fish/functions/_tv_inline.fish new file mode 100644 index 0000000..af2b544 --- /dev/null +++ b/dot_config/fish/functions/_tv_inline.fish @@ -0,0 +1,10 @@ +function _tv_inline --description 'tv --inline below the prompt, cursor back on the prompt line; stdout is the pick' + # tv --inline opens on the line below the prompt and leaves the cursor on + # its own first row when it exits; step back up so the following + # `commandline -f repaint` redraws the prompt in place instead of one line + # lower. /dev/tty keeps the move out of callers that capture stdout (gho). + tv $argv --inline + set -l st $status + printf '\033[A' >/dev/tty + return $st +end diff --git a/dot_config/fish/functions/fish_user_key_bindings.fish b/dot_config/fish/functions/fish_user_key_bindings.fish index b9040ce..5e8184e 100644 --- a/dot_config/fish/functions/fish_user_key_bindings.fish +++ b/dot_config/fish/functions/fish_user_key_bindings.fish @@ -11,13 +11,15 @@ function fish_user_key_bindings # ctrl-g → lazygit overlay, ctrl-a → omp hsplit, ctrl-y → yazi vsplit, # ctrl-h/j/k/l → vim-aware split nav (pass_keys.py). # ctrl-e: tv git-files (bat preview, enter opens nvim). ctrl-o, ctrl-p - # and down also open tv cables — same keys in zsh and bash. - bind -M $mode ctrl-e 'tv git-files; commandline -f repaint' + # and down also open tv cables — same keys in zsh and bash. tv runs + # --inline below the prompt; _tv_inline/_gho_bind/_pj_bind put the + # prompt back in place (or below any output) afterwards. + bind -M $mode ctrl-e '_tv_inline git-files; commandline -f repaint' bind -M $mode ctrl-b purple repaint bind -M $mode ctrl-y clip-last - bind -M $mode ctrl-o 'gho; commandline -f repaint' - bind -M $mode ctrl-p 'pj; commandline -f repaint' + bind -M $mode ctrl-o _gho_bind + bind -M $mode ctrl-p _pj_bind bind -M $mode ctrl-a 'omp; commandline -f repaint' bind -M $mode ctrl-w 'atuin ai inline; commandline -f repaint' diff --git a/dot_config/fish/functions/gho.fish b/dot_config/fish/functions/gho.fish index 6ba08c1..67e7fd3 100644 --- a/dot_config/fish/functions/gho.fish +++ b/dot_config/fish/functions/gho.fish @@ -1,6 +1,6 @@ function gho --description 'pick a ghq repo / Obsidian vault (tv ghq-repos cable) + cd' - set -l dir (tv ghq-repos); or return - test -n "$dir"; or return + set -l dir (tv ghq-repos --inline); or return + test -n "$dir"; or return 1 cd $dir; or return clear if test -d .git diff --git a/dot_config/zsh/functions/gho.zsh b/dot_config/zsh/functions/gho.zsh index 9008f3f..2791d18 100644 --- a/dot_config/zsh/functions/gho.zsh +++ b/dot_config/zsh/functions/gho.zsh @@ -1,8 +1,8 @@ # Pick a ghq repo / Obsidian vault with tv (ghq-repos cable) + cd. gho() { local dir - dir=$(tv ghq-repos) || return - [ -n "$dir" ] || return + dir=$(tv ghq-repos --inline) || return + [ -n "$dir" ] || return 1 cd "$dir" || return clear if [ -d .git ]; then diff --git a/dot_config/zsh/keybinds.zsh b/dot_config/zsh/keybinds.zsh index e3a1f43..f421014 100644 --- a/dot_config/zsh/keybinds.zsh +++ b/dot_config/zsh/keybinds.zsh @@ -8,49 +8,84 @@ unsetopt flow_control # free ^Q/^S in zle so the ctrl-q exit bind fires # 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` open television cables (~/.config/television/ -# cable) — the same keys do the same in fish and bash. +# 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 $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 + gho /dev/tty + zle reset-prompt + fi +} +zle -N gho-inline _zle-gho + +# ctrl-p, and down on an idle empty prompt (fish's _pj_bind_down; otherwise +# normal history / multiline movement). pj reports through $PJ_STATE: `open` +# = cancelled, `ran` = a task printed output, empty = no task sources +# (PJ_QUIET keeps down silent outside projects). +_zle-pj() { + if [[ $WIDGET == pj-down && -n $BUFFER ]]; then + zle down-line-or-history + return + fi + local state + state=$(mktemp) || return + zle -I + if [[ $WIDGET == pj-down ]]; then + PJ_STATE=$state PJ_QUIET=1 pj /dev/tty; zle reset-prompt ;; + ran) ;; + *) zle reset-prompt ;; + esac + rm -f $state +} +zle -N pj-inline _zle-pj +zle -N pj-down _zle-pj + bindkey '^E' tv-git-files bindkey '^B' run-purple bindkey '^Y' run-clip-last -bindkey '^O' run-gho -bindkey '^P' run-pj +bindkey '^O' gho-inline +bindkey '^P' pj-inline bindkey '^A' run-omp # ctrl-f: yazi; yy cd's to yazi's exit dir on quit. bindkey '^F' run-yy bindkey '^G' run-lazygit - -# down: the pj cable on an idle, empty prompt; otherwise normal history / -# multiline movement (fish's _pj_bind_down). PJ_QUIET makes it a silent no-op -# outside projects. -_zle-pj-down() { - if [[ -n $BUFFER ]]; then - zle down-line-or-history - return - fi - zle -I - PJ_QUIET=1 pj