junegunn / fzf-git.sh

bash and zsh key bindings for Git objects, powered by fzf
612 stars 53 forks source link

Help wanted: CTRL-G CTRL-H keybinding conflicts with tmux #16

Open wookayin opened 2 years ago

wookayin commented 2 years ago

Hello! Thanks for the great plugin.

I know this is more related to zsh or zsh keybinding rather than fzf-git per se, but I'm asking for help because many of the users may run into the same problem.

CTRL-G CTRL-H for commit Hashes do not work in my environment, because it conflicts with CTRL-H mappings:

❯❯❯ bindkey    # (excerpt)
"^G" fzm
"^G^H" fzf-git-hashes-widget
"^H" backward-delete-char

I pressed ^G^H within the KEYTIMEOUT limit (40 ms) but in this case the fzf-git widget does not appear. However, ^G H or other keys (e.g., ^G ^S) works fine. In a similar vein, ^G ^E seems conflicting with ^E (end-of-line) but ^G ^E works OK. Any idea why ^G ^H is not working for me?

This SuperUser thread seems relevant, but there is no working solution posted there (KEYTIMEOUT does not work).

zsh: zsh 5.9 (arm-apple-darwin21.3.0)

wookayin commented 2 years ago

Oops; I found that it is not the zsh-bindkey issue, tmux is eating the CTRL-H sequence (outside tmux it works OK). A similar case as #3.

# tmux.conf

is_vim='echo "#{pane_current_command}" | grep -iqE "(^|\/)g?(view|n?vim?)(diff)?$"'
bind -n C-h if-shell "$is_vim" "send-keys C-h" "select-pane -L"
bind -n C-j if-shell "$is_vim" "send-keys C-j" "select-pane -D"
bind -n C-k if-shell "$is_vim" "send-keys C-k" "select-pane -U"
bind -n C-l if-shell "$is_vim" "send-keys C-l" "send-keys C-l"
wookayin commented 2 years ago

One workaround here.

To map a sequence keymap in tmux, one could do the following (e.g. CTRL-G CTRL-H):

# tmux.conf
bind-key -n -T root        C-g switch-client -T ctrl_g_mode
bind-key    -T ctrl_g_mode C-h send-keys C-g C-h

However, its downside is that it will eat all the 'CTRL-g' as a prefix, so other commands like C-g C-b will not be passed through unless explicitly specified. One could either enumerate a complete list of all known keymaps:

bind-key -n -T root C-g switch-client -T ctrl_g_mode
bind-key -T ctrl_g_mode C-B send-keys C-g C-B
bind-key -T ctrl_g_mode C-E send-keys C-g C-E
bind-key -T ctrl_g_mode C-F send-keys C-g C-F
bind-key -T ctrl_g_mode C-H send-keys C-g C-H
bind-key -T ctrl_g_mode C-R send-keys C-g C-R 
bind-key -T ctrl_g_mode C-S send-keys C-g C-S
bind-key -T ctrl_g_mode C-T send-keys C-g C-T
bind-key -T ctrl_g_mode b send-keys C-g b 
bind-key -T ctrl_g_mode e send-keys C-g e
bind-key -T ctrl_g_mode f send-keys C-g f 
bind-key -T ctrl_g_mode h send-keys C-g h
bind-key -T ctrl_g_mode r send-keys C-g r
bind-key -T ctrl_g_mode s send-keys C-g s
bind-key -T ctrl_g_mode t send-keys C-g t       

or do some clever scripting as follows.

(1) in your zshrc, we export an environment variable so that TMUX can fetch all the zsh keybindings prefixed with ^g:

+# Export all the known keymaps with prefix CTRL-g so it can be mapped in tmux
+export FZF_GIT_BINDKEYS=$(bindkey -p '^g')

(2) in tmux.conf: Add a run-shell line

 # vim-tmux-navigator
 bind -n C-h if-shell "$is_vim" "send-keys C-h" "select-pane -L"  
 bind -n C-j if-shell "$is_vim" "send-keys C-j" "select-pane -D"
 bind -n C-k if-shell "$is_vim" "send-keys C-k" "select-pane -U"
 bind -n C-l if-shell "$is_vim" "send-keys C-l" "send-keys C-l"

+run-shell '~/.tmux/fzf-git-tmux.sh'

(3) ~/.tmux/fzf-git-tmux.sh:

#!/bin/zsh

# Define <CTRL-G ...> keymaps to make fzf-git-tmux work.
# see ~/.zshrc for export of $FZF_GIT_BINDKEYS

tmux bind-key -n -T root C-g send-keys C-g \; switch-client -T ctrl_g_mode

for i in $(echo $FZF_GIT_BINDKEYS); do
    if [[ "$i" =~ "\"\^G([^\"]+)" ]]; then
        local key=${match[1]/\^/C-}
        tmux bind-key -T ctrl_g_mode "$key" send-keys C-g "$key"
    fi
done

return 0;

What this script does is:

The $FZF_GIT_BINDKEYS variable are read and processed only when tmux initializes (or reloads the config) from the "parent zsh shell", so tmux bindkeys can go out of sync when keymaps in zshrc change, but this should be practically of no problem if we don't change the setting frequently.

But one downside is CTRL-G is remapped everywhere, so any key other sequence that would follow CTRL-G will be eaten (e.g. if you have a keymappings with CTRL-G in vim)