Files
dotfiles/.config/zsh/zshrc.d/60-keys.zsh
Julian Prein 3cf445e739 zsh:keys: Modify fzf's cd widget to be "in-prompt"
Currently, fzf's cd widget executes the cd command via accept-line, so
the command is also visible in the scrollback buffer. In contrast to
this, the cd-{forward,backward,up} suite changes the directory in-place,
without touching BUFFER, with an additional prompt redraw (one could say
"in-prompt"). Since I find this more appealing, overwrite the default
fzf widget with a custom one that has the same behaviour.
2025-08-06 12:30:35 +02:00

284 lines
7.7 KiB
Bash

## Author: druckdev
## Created: 2019-04-17
# Vim bindings
bindkey -v
# Text object selection
# Copied and slightly modified from:
# https://github.com/softmoth/zsh-vim-mode/blob/abef0c0c03506009b56bb94260f846163c4f287a/zsh-vim-mode.plugin.zsh#L214-#L228
autoload -U select-bracketed select-quoted
zle -N select-bracketed
zle -N select-quoted
for m in visual viopp; do
for c in {a,i}{\(,\),\[,\],\{,\},\<,\>,b,B}; do
bindkey -M "$m" "$c" select-bracketed
done
for c in {a,i}{\',\",\`}; do
bindkey -M "$m" "$c" select-quoted
done
done
# Decrease delay when switching into NORMAL mode.
# A timeout is still necessary as otherwise multi character bindings (for
# example in vicmd) do not work.
export KEYTIMEOUT=20
function zle-line-init zle-keymap-select {
# Switch cursor style depending on mode
case $KEYMAP in
vicmd) echo -ne "\e[1 q";; # block
viins|main) echo -ne "\e[5 q";; # beam
esac
# Make sure that the terminal is in application mode when zle is active,
# since only then values from $terminfo are valid
! (( $+terminfo[smkx] )) || echoti smkx
}
zle -N zle-line-init
zle -N zle-keymap-select
function zle-line-finish {
# See above (echoti smkx)
! (( $+terminfo[rmkx] )) || echoti rmkx
}
zle -N zle-line-finish
bindkey '^H' run-help
bindkey '^E' edit-command-line
bindkey -M vicmd "^E" edit-command-line
bindkey '^S' vi-pound-insert
# 'Fixed terminal input sequences'
#
# https://www.leonerd.org.uk/hacks/fixterms/
# https://www.leonerd.org.uk/code/libtermkey/
# some shift-<key> combinations should behave like without shift
bindkey -s '^[[32;2u' ' ' # shift-space
bindkey -s '^[[127;2u' '^?' # shift-backspace
bindkey -s '^[[13;2u' '^M' # shift-return
## Navigation
bindkey "$terminfo[kcbt]" reverse-menu-complete # shift-tab
bindkey "$terminfo[khome]" beginning-of-line # home
bindkey "$terminfo[kend]" end-of-line # end
bindkey "$terminfo[kbs]" backward-delete-char # backspace
bindkey "$terminfo[kdch1]" delete-char # delete
bindkey '^[[127;5u' backward-kill-word # ctrl-backspace
bindkey '^W' backward-kill-word # ctrl-W
bindkey '^[[1;5D' vi-backward-word # ctrl-left
bindkey '^[[1;5C' vi-forward-word # ctrl-right
bindkey '^[[3;5~' kill-word # ctrl-delete
bindkey '^Q' push-input # ctrl-Q
# cd-{rotate,backward,forward} and redraw-prompt are partially modified copies
# from (Specifically see fn/-z4h-{rotate,redraw-prompt,init-zle}):
# https://github.com/romkatv/zsh4humans/tree/421937693f3772c99c4bdd881ac904e5e9f
function redraw-prompt {
# hide cursor
! (( $+terminfo[civis] && $+terminfo[cnorm] )) || [[ ! -t 1 ]] ||
echoti civis
local f
for f in chpwd "${chpwd_functions[@]}" precmd "${precmd_functions[@]}"; do
[[ "${+functions[$f]}" == 0 ]] || "$f" &>/dev/null || true
done
# zsh-syntax-highlighting
typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER=
typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=0
zle .reset-prompt
zle -R
# reset cursor
! (( $+terminfo[cnorm] )) || [[ ! -t 1 ]] || echoti cnorm
}
function cd-rotate() {
while (( $#dirstack )) && ! pushd -q $1 &>/dev/null; do
popd -q $1
done
redraw-prompt
(( $#dirstack ))
}
function cd-backward() {
(( ${cd_cycle:=0} != $#dirstack )) || return
cd-rotate +1
(( cd_cycle++ ))
}
zle -N cd-backward
function cd-forward() {
(( ${cd_cycle:=0} )) || return
cd-rotate -0
(( cd_cycle-- ))
}
zle -N cd-forward
function cd-up {
pushd -q ..
redraw-prompt
}
zle -N cd-up
# Modified version of
# https://github.com/junegunn/fzf/blame/f864f8b5f7ab/shell/key-bindings.zsh#L81-L99
# that changes the directory "in-prompt" like cd-{forward,backward,up}; meaning
# that the `cd` command is executed in the background with a prompt redraw
# instead of via `$BUFFER` and `accept-line`.
fzf-cd-inprompt-widget() {
setopt localoptions pipefail no_aliases 2>/dev/null
local dir="$(
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) < /dev/tty)"
if [[ -z "$dir" ]]; then
zle redisplay
return 0
fi
# --- modifications start here ---
pushd -q "$dir"
redraw-prompt
}
zle -N fzf-cd-inprompt-widget
# cycle through `dirs` with ^o and ^i similar to the jumplist in vim.
# Need AUTO_PUSHD (see options.zsh)
bindkey '^O' cd-backward
bindkey '^[[105;5u' cd-forward # ^I
# move one directory up with ^U (mnemonic: 'Up')
bindkey '^U' cd-up
# Open file in EDITOR selected with fzf
function edit-fuzzy-file {
local fzf_fallback="find . -type f"
local -a fzf_args=(--height "40%" --reverse)
file="$(eval ${FZF_DEFAULT_COMMAND:-$fzf_fallback} | fzf "$fzf_args[@]")"
[[ -z $file ]] || $EDITOR "$file"
# Fix prompt
zle redisplay
}
zle -N edit-fuzzy-file
# Simulate <leader>f from vim config
bindkey -M vicmd " f" edit-fuzzy-file
# Modified version (end with a trailing slash) of:
# https://github.com/majutsushi/etc/blob/1d8a5aa28/zsh/zsh/func/rationalize-dots
function rationalize_dots {
if [[ $ZLE_STATE == *overwrite* ]]; then
# Don't do anything in overwrite-mode (i.e. vim's Replace-mode)
zle self-insert
return
fi
# Rationalize dots at BOL or after a space or slash.
if [[ "$LBUFFER" =~ "(^|[ /])\.\./$" ]]; then
# ../ + . -> ../../
LBUFFER+=../
elif [[ "$LBUFFER" =~ "(^|[ /])\.\.$" ]]; then
# .. + . -> ../../
# NOTE: This scenario occurs only if backspace or `default_dot` was
# used.
LBUFFER+=/../
elif [[ "$LBUFFER" =~ "(^|[ /])\.$" ]]; then
# . + . -> ../
LBUFFER+=./
else
LBUFFER+=.
return
fi
# Print currently typed path as absolute path with "collapsed"/reversed
# filename expansion.
zle -M "${(D)${(Az)LBUFFER}[-1]:a}"
}
zle -N rationalize_dots
bindkey . rationalize_dots
# Keep the normal dot self-insert on Ctrl-. (e.g. for typing `../.local`)
function default_dot { LBUFFER+=. }
zle -N default_dot
bindkey '^[[46;5u' default_dot
CMDS_ON_ENTER=(ll) # gs)
REQUIREMENTS_CMDS_ON_ENTER=(true) # "git rev-parse")
function cmd-on-enter {
if [[ -z "${PREBUFFER}${BUFFER}" ]]; then
# Overwrite BUFFER and default to first option
BUFFER=" ${CMDS_ON_ENTER[${cmd_on_enter_idx:=1}]}"
# Cycle through options
local idx=$cmd_on_enter_idx
idx=$((idx < $#CMDS_ON_ENTER ? idx + 1 : 1))
until
[[ $idx = $cmd_on_enter_idx ]] \
|| $REQUIREMENTS_CMDS_ON_ENTER[$idx] &>/dev/null
do
idx=$((idx < $#CMDS_ON_ENTER ? idx + 1 : 1))
done
cmd_on_enter_idx=$idx
else
# Reset if other command is executed
cmd_on_enter_idx=1
fi
zle accept-line
}
zle -N cmd-on-enter
bindkey "^M" cmd-on-enter
ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=(cmd-on-enter)
# Fuzzy PWD selector of all open shells
function shcwd-fzf {
shcwd \
| grep -vFx "$PWD" \
| FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse
--bind=ctrl-z:ignore $FZF_DEFAULT_OPTS" \
$(__fzfcmd) "$@"
}
function go-shcwd {
dir="$(shcwd-fzf +m)"
[[ -z $dir ]] || pushd -q "$dir"
redraw-prompt
}
zle -N go-shcwd
bindkey '^G' go-shcwd
function insert-shcwd {
dir="$(shcwd-fzf --multi)"
# (q) escapes newlines as $'\n'
[[ -z $dir ]] || LBUFFER+="${${(q)dir}//\$\'\\n\'/ }"
redraw-prompt
}
zle -N insert-shcwd
# Ctrl-Shift-G
bindkey '^[[71;6u' insert-shcwd
## History
# Ctrl-Up
bindkey '^[[1;5A' fzf-history-widget
# Ctrl-K in normal mode
bindkey -M vicmd '^K' fzf-history-widget
# Fuzzy finder bindings:
# ^T fzf-file-widget
# \ec (Alt-C) fzf-cd-widget
# ^R fzf-history-widget
# TODO: ^R should insert the history line in BUFFER to differ from ctrl-up
FZF_CTRL_T_COMMAND="${FZF_DEFAULT_COMMAND} 2>/dev/null"
# See .zprofile for FZF_ALT_C_COMMAND
comp-source "$ZDOTDIR/plugins/fzf/shell/key-bindings.zsh"
# Overwrite fzf's Alt-C binding to be "in-prompt" (see above)
bindkey -M vicmd '\ec' fzf-cd-inprompt-widget
bindkey -M viins '\ec' fzf-cd-inprompt-widget