Turning my Workflow Inside Out

Published: 07/17/2024

Subscribe Premium $10/month

Checkout with Stripe

For the longest time I've used vim and tmux together for development. But in 2024 terminal support in vim and neovim is very good and there is very little reason to use tmux [1]. In vim or neovim you can open a new terminal by running :term. The exact bindings to do things like switch back to normal mode differ between the two. Commands in this post are only confirmed to work for neovim.

Opening your terminal sessions in vim feels like a small change but makes many things possible:

Part of the reason I find this such an unlock is a skill issue: I never learned even 5% of the affordances tmux provides. You can make this issue less pronounced by adding a bunch of things to your tmux config:

~/.tmux.conf

vim-like pane switching

set-window-option -g mode-keys vi bind-key -T copy-mode-vi v send-keys -X begin-selection bind-key -T copy-mode-vi y send-keys -X copy-selection unbind p bind p paste-buffer bind -r k select-pane -U bind -r j select-pane -D bind -r h select-pane -L bind -r l select-pane -R bind -r ^ select-window -l

But I find there are subtle differences in the behavior between the vim motions in vim and tmux. And some vim motions I've come to rely on like <C-o> and <C-i> don't work in tmux.

Customization

The default chord to enter normal mode from a terminal <C-\><C-n> is a little painful so I recommend a bunch of vimrc settings to make it more pleasant to work with neovim's terminal. I personally use <C-x> to open a new terminal and to go from terminal mode to normal mode. And I rebind all the ctrl commands that I use in normal mode to work in terminal mode. Some of these conflict with the default emacs mode shell bindings so I recommend you switch to vim mode if you're already a vim user. You can see how to do that in my vim tmux zsh tips post. Or customize the bindings to what makes sense for your workflow.

" ~/.config/nvim/init.vim
nnoremap <C-x> :vsp<CR>:term<CR>
tnoremap <C-x> <C-\><C->n
tnoremap <C-p> <C-\><C-n>:CtrlP<CR>
tnoremap <C-o> <C-\><C-n><C-o>
tnoremap <C-h> <C-\><C-n><C-w><C-h>
tnoremap <C-j> <C-\><C-n><C-w><C-j>
tnoremap <C-k> <C-\><C-n><C-w><C-k>
tnoremap <C-l> <C-\><C-n><C-w><C-l>
function! RenameTerminalBufferToCurrentCommand()
  let l:historyFile = "~/.zsh_history"
  let l:mostRecentCommand = system("tail -1 " . l:historyFile . " | cut -f2- -d\\;")
  let l:mostRecentCommand = fnameescape(trim(l:mostRecentCommand))
  if (l:mostRecentCommand == "q" || l:mostRecentCommand == "quit" || l:mostRecentCommand == "exit")
      exec "q"
      return
  endif
  " i prepend "term" for easy buffer searching
  let l:newFileName = "term " . l:mostRecentCommand
  silent! execute "keepalt file " . l:newFileName
  normal a
endfunction
tnoremap <Enter> <Enter><C-\><C-n>:call RenameTerminalBufferToCurrentCommand()<Enter>

The point of RenameTerminalBufferToCurrentCommand is to make it easy to find terminals with vim. If you start some long running commend e.g. your server, npm run start, a database connection, psql whatever you can then close the buffer and later find it with CtrlP and it will be named after the last command you ran in it.

One last tip: if you write python you can add the following cool line to your vimrc:

nnoremap i :let currentfile = @% \| new \| execute 'terminal python -i '.currentfilei

What it does is open a python interpreter with the current file loaded when you type <leader>i. Very useful for iterative development.

[1] I wanted to give this post a more clickbaity subheading like "ditching tmux" but I still open all my vim sessions in tmux. Partly to handle flaky terminal sessions (I'm not sshing anywhere mostly but I have a lot of ctrl-W muscle memory and alacritty doesn't give you any kind of confirmation), to enable switching terminals later and just in case I actually do want to use it to make a split.