Intermediate to advanced VIM users love to extract common workflows to hotkeys. You might want to optimise workflows like running tests, adding files to git/doing a commit, or reformatting text. The thing is, as you add more and more workflows, organising these hotkeys can become a nightmare. For the past several years, I’ve had a philosophy to organising mine that has really helped me.
The leader key
For me it all starts with the leader key, \
by default, but some people use ,
. In a VIM setup that isn’t at all customised, no hotkeys follow the leader key, and this makes it an excellent place to hang functionality from. In my .vimrc
there’s a whole section filled with lines that look like this:
map <leader>jna :!nosetests <CR>
This says “when I press \jna
run the shell command nosetests
”.
The first protip I have for you is fairly simple: hanging everything off the leader key is a great place to start your hotkeying.
Prefix trees
Consider a world in which I had my leaders set up like this:
map <leader>j :!bundle exec rspec<CR>
map <leader>jf :!bundle exec rspec %<CR>
This creates an ambiguity, if you press <leader>j
, VIM can’t know whether or not you want to execute the first or second command: until you press the f
key, or wait a for some period of time. This creates an unnatural pause if you do want to run the j
leader, which breaks flow. Additionally, if you press <leader>j
and then pause for a second because you are interrupted for any reason, you’re going to run bundle exec rspec
executing your entire test suite, instead of executing a command over a single file (the % character passes the current filename to the command, which RSpec will then execute as a single file test, rather than all of them).
This problem is because one leader j
is a valid prefix of jf
. The way to fix this is to use a “prefix tree”, where there are no valid commands which overlap in their pefixing. Rather every command is valid and distinct, causing you to type an entire sequence before anything will execute. In my dotfiles, I have all of my leader sequences mapped such that they form a prefix tree, have a look:
map <leader>jca :!bundle exec cucumber --no-color<CR>
map <leader>jra :call RunAllSpecs()<CR>
map <leader>jrf :call RunCurrentSpecFile()<CR>
map <leader>jrn :call RunNearestSpec()<CR>
map <leader>jrl :call RunLastSpec()<CR>
map <leader>jr! :call RunLastFailure()<CR>
map <leader>jna :!nosetests <CR>
map <leader>jnf :!nosetests %<CR>
map <leader>jma :!ruby test/test_helper.rb <CR>
map <leader>jmf :!bundle exec ruby % <CR>
map <leader>jta :!./script/test<CR>
map <leader>jmf :!ruby %<CR>
map <leader>rpf :!python3 % <CR>
map <leader>rrf :!bundle exec ruby %<CR>
map <leader>rb :!bash <CR>
map <leader>rv :so ~/.vimrc <CR>
map <leader>rm :!touch % && make <CR>
map <leader>rc :!ctags -R<CR>
map <leader>cr :!cargo run<CR>
map <leader>cb :!bash -c 'cargo build 2>&1 \| head -n 10'<CR>
map <leader>ct :!cargo test<CR>
map <leader>eir o<ESC>ogem "rspec-core", :github => "rspec/rspec-core"<CR>gem "rspec-expectations", :github => "rspec/rspec-expectations"<CR>gem "rspec-mocks", :github => "rspec/rspec-mocks"<CR>gem "rspec-support", :github => "rspec/rspec-support"<CR><ESC>
map <leader>eipr o<ESC>ogem "rspec-core", :path => "/Users/sam/dev/rspec/rspec-dev/repos/rspec-core"<CR>gem "rspec-expectations", :path => "/Users/sam/dev/rspec/rspec-dev/repos/rspec-expectations"<CR>gem "rspec-mocks", :path => "/Users/sam/dev/rspec/rspec-dev/repos/rspec-mocks"<CR>gem "rspec-support", :path => "/Users/sam/dev/rspec/rspec-dev/repos/rspec-support"<CR><ESC>
map <leader>eid orequire 'pry'; binding.pry<ESC>
map <leader>li gg/def.*init<CR>
map <leader>lcd gg/class.*<CR>
map <leader>lrp /^ *p <CR>
map <leader>arp :Ack "^ *p " <CR>
map <leader>apr :Ack "binding.pry " <CR>
map <leader>eal :Align & <CR>
map <leader>eap :Align => <CR>
map <leader>edc mzO<ESC>j:s/,/,\r/g<CR>(%i<CR><ESC>%a<CR><ESC>(%=%:%s/ *$//g<CR>:noh<CR>'zkdd
map <leader>ecl :silent! %s/\[x\]/\[\]/g<CR>gg
map <leader>eae :Align =<CR>
map <leader>ea{ :Align {<CR>
map <leader>erf i.fetch\x1Blr{r(f]r)
map <leader>erh xea:\x1Bllxxx
map <leader>epl :PromoteToLet<cr>
map <leader>etw mp:%s/ *$//g<CR>:noh<CR>'p
map <leader>ert :%s/\t/ /g<CR>
map <leader>orf :call OpenSpec()<CR>
map <leader>orv :call VsplitSpec()<CR>
map <leader>onrv :call VsplitSpec()<CR>irequire "spec_helper"<CR><ESC>
map <leader>ors :tabe .rspec<CR>
map <leader>ogf :tabe Gemfile<CR>
map <leader>ovr :tabe ~/.vimrc<CR>/map.*leader<CR>:noh<CR>
map <leader>bi :!bundle install<CR>
map <leader>ft zfa
map <leader>uft zR
map <leader>gt :!go test -tags=integration -ldflags -s ./...<CR>
map <leader>gb :GoBuild<CR>
map <leader>snp :set nopaste<CR>
map <leader>sp :set paste<CR>
map <leader><space> :noh<CR>
A quick scan of this will show you that there are no issues with command prefixing, where if I type part of a command, another command will execute.
We can visualise some of this as follows this as follows
What this shows is that there is no sequence of keys that can be pressed that is ambiguous to VIM as to whether or not it should run a command. In addition, not all commands sit at the same level. For example \snp
and \sp
are of different lengths, but both are terminal and unambiguous.
Conclusion
To quickly conclude: VIM leader commands are a useful tool for vimming faster. You can extract them by finding common sequences you type frequently, and putting them in your .vimrc
. I’ve found this style of organisation, where each letter you type while building a command is a prefix that cannot ambiguously resolve to another command. Thanks for reading!