Skip to content

Instantly share code, notes, and snippets.

@tarruda
Last active September 27, 2021 13:21
Show Gist options
  • Star 53 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save tarruda/5158535 to your computer and use it in GitHub Desktop.
Save tarruda/5158535 to your computer and use it in GitHub Desktop.
Tmux/Vim integration

Some scripts/configurations that greatly improve tmux/vim workflows. The shell scripts target zsh but should be adaptable without much effort for other unix shells.

Features:

  • Transparently move between tmux panes and vim windows
  • Using the shell, open files in one vim instance per project or directory
  • Fully integrated copy/paste between tmux, vim and x11 using simple keybinds(need to install the xclip program)
  • Easily send text to any tmux pane without breaking your edit workflow(needs slimux

'vim-tmux-move.zsh', '.vimrc' and '.tmux.conf' cooperate so you can move transparently between tmux panes and vim windows using ALT + (arrow keys or jkhl). It was based on this gist

'vim-tmux-open.zsh' allows you to open files in a existing vim pane(one per project or dir). It requires tmux from git as it uses new features not available in 1.7. Based on this gist

'vim-tmux-move.zsh', '.vimrc' and '.tmux.conf' cooperate so copy/paste text from vim and tmux using the same set of keybindings(alt+y/p). It is also possible to copy/paste from/to the x11 clipboard. It should be adaptable to OSX if it provides an equivalent to xclip.

The last mapping in vimrc is project-specific, so you should probably use with the local vimrc plugin

# Lower esc delay so you won't accidentally switch panes/windows when leaving insert mode
set -g escape-time 5
# Navigate between panes/vim windows using Alt + [jkhl] | arrows
bind -n M-j run 'zsh ~/.bin/vim-tmux-move.zsh j'
bind -n M-k run 'zsh ~/.bin/vim-tmux-move.zsh k'
bind -n M-h run 'zsh ~/.bin/vim-tmux-move.zsh h'
bind -n M-l run 'zsh ~/.bin/vim-tmux-move.zsh l'
bind -n M-Down run 'zsh ~/.bin/vim-tmux-move.zsh j'
bind -n M-Up run 'zsh ~/.bin/vim-tmux-move.zsh k'
bind -n M-Left run 'zsh ~/.bin/vim-tmux-move.zsh h'
bind -n M-Right run 'zsh ~/.bin/vim-tmux-move.zsh l'
# use alt+y/alt+p to seamless copy and paste between tmux, vim and the
# x11 clipboard (if you use emacs keybinding in tmux replace vi-copy with emacs-copy)
bind -t vi-copy M-y copy-pipe 'xclip -i -selection clipboard'
bind -n M-p run 'zsh ~/.bin/vim-tmux-xpaste.zsh'
if $TMUX != ''
" integrate movement between tmux/vim panes/windows
fun! TmuxMove(direction)
" Check if we are currently focusing on a edge window.
" To achieve that, move to/from the requested window and
" see if the window number changed
let oldw = winnr()
silent! exe 'wincmd ' . a:direction
let neww = winnr()
silent! exe oldw . 'wincmd'
if oldw == neww
" The focused window is at an edge, so ask tmux to switch panes
if a:direction == 'j'
call system("tmux select-pane -D")
elseif a:direction == 'k'
call system("tmux select-pane -U")
elseif a:direction == 'h'
call system("tmux select-pane -L")
elseif a:direction == 'l'
call system("tmux select-pane -R")
endif
else
exe 'wincmd ' . a:direction
end
endfun
function! TmuxSharedYank()
" Send the contents of the 't' register to a temporary file, invoke
" copy to tmux using load-buffer, and then to xclip
" FIXME for some reason, the 'tmux load-buffer -' form will hang
" when used with 'system()' which takes a second argument as stdin.
let tmpfile = tempname()
call writefile(split(@t, '\n'), tmpfile, 'b')
call system('tmux load-buffer '.shellescape(tmpfile).';tmux show-buffer | xclip -i -selection clipboard')
call delete(tmpfile)
endfunction
function! TmuxSharedPaste()
" put tmux copy buffer into the t register, the mapping will handle
" pasting into the buffer
let @t = system('xclip -o -selection clipboard | tmux load-buffer -;tmux show-buffer')
endfunction
nnoremap <silent> <c-w>j :silent call TmuxMove('j')<cr>
nnoremap <silent> <c-w>k :silent call TmuxMove('k')<cr>
nnoremap <silent> <c-w>h :silent call TmuxMove('h')<cr>
nnoremap <silent> <c-w>l :silent call TmuxMove('l')<cr>
nnoremap <silent> <c-w><down> :silent call TmuxMove('j')<cr>
nnoremap <silent> <c-w><up> :silent call TmuxMove('k')<cr>
nnoremap <silent> <c-w><left> :silent call TmuxMove('h')<cr>
nnoremap <silent> <c-w><right> :silent call TmuxMove('l')<cr>
vnoremap <silent> <esc>y "ty:call TmuxSharedYank()<cr>
vnoremap <silent> <esc>d "td:call TmuxSharedYank()<cr>
nnoremap <silent> <esc>p :call TmuxSharedPaste()<cr>"tp
vnoremap <silent> <esc>p d:call TmuxSharedPaste()<cr>h"tp
set clipboard= " Use this or vim will automatically put deleted text into x11 selection('*' register) which breaks the above map
" Quickly send text to a pane using f6
nnoremap <silent> <f6> :SlimuxREPLSendLine<cr>
inoremap <silent> <f6> <esc>:SlimuxREPLSendLine<cr>i " Doesn't break out of insert
vnoremap <silent> <f6> :SlimuxREPLSendSelection<cr>
" Quickly restart your debugger/console/webserver. Eg: if you are developing a node.js web app
" in the 'serve.js' file you can quickly restart the server with this mapping:
nnoremap <silent> <f5> :call SlimuxSendKeys('C-C " node serve.js" Enter')<cr>
" pay attention to the space before 'node', this is actually required as send-keys will eat the first key
endif
program="`tmux display -p '#{pane_current_command}'`"
if [[ $program == "vim" ]]; then
# let vim handle it
tmux send-keys 'Escape' 'C-w' $1
else
# do the normal tmux thing
case $1 in
j) tmux select-pane -D ;;
k) tmux select-pane -U ;;
h) tmux select-pane -L ;;
l) tmux select-pane -R ;;
esac
fi
# Open one vim instance per project or dir. Additional files of that
# project/dir are opened in the same instance.
# Alias this script in zsh if running inside tmux
# Eg: [[ -n $TMUX ]] && alias vi="zsh ~/.bin/vim-tmux-open.zsh"
vim_ensure_is_open() {
dir=$1
tmux wait -L "vim-edit:$dir" # to be safe, synchroninze access to the vim pane
[[ -z $vim_pane ]] && vim_pane=`tmux show -v "@vim-edit:$dir" 2> /dev/null`
if [[ -z $vim_pane ]] || ! tmux display-message -pt $vim_pane &> /dev/null; then
# vim is not running in any pane, so start a new instance
channel="`uuidgen`"
tmux split-window -d -p 70 "vim \
-c \"cd $dir\" \
-c ':silent !tmux set -q \"@vim-edit:$dir\" \"\$TMUX_PANE\"'\
-c ':silent !tmux wait -S \"$channel\"'"
tmux wait $channel
vim_pane=`tmux show -v "@vim-edit:$dir" 2> /dev/null`
fi
tmux wait -U "vim-edit:$dir"
}
while (( $# != 0 )); do
file=${1:a}
orig_dir=${file:h}
dir=$orig_dir
# search our working directory
while [[ $dir != '/' ]] ; do
vim_pane=`tmux show -v "@vim-edit:$dir" 2> /dev/null`
# Is there a vim instance open in this directory?
[[ -n $vim_pane ]] && break
# Only work with svn 1.7 +
[[ -d "$dir/.git" ||\
-d "$dir/.svn" ||\
-d "$dir/.hg" ||\
-d "$dir/.bzr" ]] && break
# go up one level
dir=${dir:h}
done
if [[ $dir == '/' ]]; then
dir=$orig_dir
fi
file=${file#$dir/}
# ensure vim is running for that project/dir
vim_ensure_is_open $dir
# open the file in the already running vim pane
tmux send-keys -t $vim_pane 'Escape' ":e ${file:q}" 'Enter'
shift
done
if [[ -z $file ]]; then
vim_ensure_is_open $PWD
fi
window_uid="`tmux display-message -pt \"$vim_pane\" '#{window_id}'`"
tmux select-window -t $window_uid
tmux select-pane -t $vim_pane
program="`tmux display -p '#{pane_current_command}'`"
# use xclip to get the clipboard contents and load into the tmux buffer
xclip -o -selection clipboard | tmux load-buffer -
if [[ $program == "vim" ]]; then
# if vim is focused, send the keys so it will handle the paste itself
tmux send-keys 'M-p'
else
# paste as normal terminal input into the current pane
tmux paste-buffer
fi
@unphased
Copy link

Hey, I got a question for you at this position (line 11): https://gist.github.com/tarruda/5158535#file-vimrc-L11

I don't think this line does anything... In order to go to a target window you must use :{num}wincmd w as far as I am aware.

You really don't need to the starting window index. (and then re-set to target window it if the check passes). Just leave it.

@JanosSarkoezi
Copy link

Hi, I use your scripts. It works very well, but in case of call

git difftool -t vimdiff

I cannot switch between splitted windows in vim. I get the message:

'zsh ~/.bin/vim-tmux-move.zsh h' returned 1.

What do I wrong? My solution would be to change line https://gist.github.com/tarruda/5158535/#file-vim-tmux-move-zsh-L3 from

if [[ $program == "vim" ]]; then

to

if [[ $program == "vim" || $program == "git" ]]; then

Is this a good idea? Pleas help. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment