I'm Matthew J. Morrison.

A Passionate, professional software developer & hobbyist; Language nerd & regular user of Unix, Python, Ruby & JavaScript.

Fork me on GitHub

Moving your cursor (without the mouse)

October 03, 2014

Alright, full disclosure, I did not learn this today but I did use it today and almost every other day, so I decided to write about it anyway. I use Vim and Tmux (unfortunately, I am actually a bit embarrassed for both of those websites - remember they are great tools, with or without great websites). I also use splits in both. Moving around can be a bit tricky when you have one set of shortcuts to use when jumping between Tmux panes and a separate set of shortcuts to use when jumping between Vim panes.

By default moving your cursor from pane to pane in Vim uses Control-w (or <C-w>) followed by h, j, k, l to move left, down, up or right respectively. I find it simpler to use Control followed by h, j, k or l. That is a pretty simple thing to configure Vim to do:

nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-h> <C-w>h
nnoremap <C-l> <C-w>l

That works great, however I want to take it to the next level. When I try to move down a pane and there are no more vim panes below my current pane but there is a tmux pane I want it to automatically jump down into the tmux pane. This is a bit more tricky, but can be handled with a function added to our .vimrc file and remapping our keys to use the function.

function! s:NavigateTermSplits(direction)
  let windowNumber = winnr()
  execute 'wincmd ' . a:direction
  if windowNumber == winnr()
    " We didn't move to a new vim split. Now try to move tmux splits
    silent call system('tmux select-pane -' . tr(a:direction, 'hjkl', 'LDUR'))
  endif
endfunction

nnoremap <silent> <C-h> :call <SID>NavigateTermSplits('h')<CR>
nnoremap <silent> <C-j> :call <SID>NavigateTermSplits('j')<CR>
nnoremap <silent> <C-k> :call <SID>NavigateTermSplits('k')<CR>
nnoremap <silent> <C-l> :call <SID>NavigateTermSplits('l')<CR>

In Tmux, moving your cursor between panes is different. The primary one I use is <Leader>-o. Unfortunately, that will just scroll your cursor from pane to pane counterclockwise one pane at a time, which is not quite as nice as moving left, down, up and right. The alternative is using <Leader-q>. <Leader-q> will display a number on each pane. Pressing one of the displayed numbers will move your cursor to that pane. That is fine, but I prefer to use something that works the same way as moving between Vim splits. Adding the following mappings in your .tmux.conf file will enable the same type of moving using Control followed by h, j, k, or l between tmux panes.

bind -n C-h select-pane -L
bind -n C-j select-pane -D
bind -n C-k select-pane -U
bind -n C-l select-pane -R

Again, that works great, however I want to take it to the next level. I want a smooth transition moving from tmux pane to vim pane and back. Thanks to all of the hard work of @JarrodCTaylor on the dotfiles (throw him a github star or six - he has earned it) it is possible to do just that. The last little bit of configuration to make it work goes in .tmux.conf.

is_view_vim_diff="echo '#{pane_current_command}' | grep -iqE '(view|vim)(diff)?'"
bind -n C-h run "$is_view_vim_diff && tmux send-keys C-h || tmux select-pane -L"
bind -n C-j run "$is_view_vim_diff && tmux send-keys C-j || tmux select-pane -D"
bind -n C-k run "$is_view_vim_diff && tmux send-keys C-k || tmux select-pane -U"
bind -n C-l run "$is_view_vim_diff && tmux send-keys C-l || tmux select-pane -R"

Here is a quick video of jumping around between Vim and Tmux splits interchangeably using only Control and h, j, k and l.

Moving In Action

Somewhere, something incredible is waiting to be known. - Carl Sagan