Skip to content

Instantly share code, notes, and snippets.

@pengwynn
Forked from mislav/_readme.md
Created March 22, 2013 15:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pengwynn/5222339 to your computer and use it in GitHub Desktop.
Save pengwynn/5222339 to your computer and use it in GitHub Desktop.

I use tmux splits (panes). Inside one of these panes there's a Vim process, and it has its own splits (windows).

In Vim I have key bindings C-h/j/k/l set to switch windows in the given direction. (Vim default mappings for windows switching are the same, but prefixed with C-W.) I'd like to use the same keystrokes for switching tmux panes.

An extra goal that I've solved with a dirty hack is to toggle between last active panes with C-\.

Here's how it should work:

  1. If I'm in "vim window 2", going in left (C-h) or down (C-j) direction should switch windows inside vim.
  2. However, if I'm in "vim window 3", going right (C-l) or down (C-j) should select the next tmux pane in that direction.

The solution

The solution has 3 parts:

  1. In ~/.tmux.conf, I bind the keys I want to execute a custom tmux-vim-select-pane command;
  2. tmux-vim-select-pane checks if the foreground process in the current tmux pane is Vim, then forwards the original keystroke to the vim process. Otherwise it simply switches tmux panes.
  3. In .vimrc, I set bindings for the same keystrokes to a custom function. The function tries to switch windows in the given direction. If the window didn't change, that means there are no more windows in the given direction inside vim, and it forwards the pane switching command to tmux by shelling out to tmux select-pane.
#!/usr/bin/env bash
# Like `tmux select-pane`, but if Vim is running in the current pane it sends a
# keystroke to Vim instead to let it decide whether it's going to switch windows
# internally or switch tmux panes.
set -e
# gets the tty of active tmux pane
active_tty="$(tmux list-panes -F '#{pane_active}#{pane_tty}' | grep '^1')"
# checks if there's a foreground Vim process in attached to that tty
if ps c -o 'state=,command=' -t "${active_tty#1}" | grep '+' | grep -iE '\bvim?\b' >/dev/null ; then
direction="$(echo "${1#-}" | tr 'lLDUR' '\\hjkl')"
# forward the keystroke to Vim
tmux send-keys C-$direction
else
tmux select-pane "$@"
fi
# Smart pane switching with awareness of vim splits
bind -n C-k run-shell 'tmux-vim-select-pane -U'
bind -n C-j run-shell 'tmux-vim-select-pane -D'
bind -n C-h run-shell 'tmux-vim-select-pane -L'
bind -n C-l run-shell 'tmux-vim-select-pane -R'
bind -n "C-\\" run-shell 'tmux-vim-select-pane -l'
let g:tmux_is_last_pane = 0
au WinEnter * let g:tmux_is_last_pane = 0
" Like `wincmd` but also change tmux panes instead of vim windows when needed.
function TmuxWinCmd(direction)
let nr = winnr()
let tmux_last_pane = (a:direction == 'p' && g:tmux_is_last_pane)
if !tmux_last_pane
" try to switch windows within vim
exec 'wincmd ' . a:direction
endif
" Forward the switch panes command to tmux if:
" a) we're toggling between the last tmux pane;
" b) we tried switching windows in vim but it didn't have effect.
if tmux_last_pane || nr == winnr()
let cmd = 'tmux select-pane -' . tr(a:direction, 'phjkl', 'lLDUR')
exec 'silent !'.cmd
redraw! " because `exec` fucked up the screen. why is this needed?? arrghh
let g:tmux_is_last_pane = 1
else
let g:tmux_is_last_pane = 0
endif
endfunction
" navigate between split windows/tmux panes
nmap <c-j> :call TmuxWinCmd('j')<cr>
nmap <c-k> :call TmuxWinCmd('k')<cr>
nmap <c-h> :call TmuxWinCmd('h')<cr>
nmap <c-l> :call TmuxWinCmd('l')<cr>
nmap <c-\> :call TmuxWinCmd('p')<cr>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment