An Incremental Approach to Vim

This post is a collection of the notes for a talk I gave on March 19th for the Boston Vim meetup. The talk covered my approach to learning Vim over the long haul, and some tactics for learning Vim in a more continuous way.

Note: this talk was aimed at intermediate Vim users who are familiar with functions, macros, substitute, etc, but may not feel comfortable using them as part of their day to day editing. If you’re new to Vim, I highly starting starting with more of a general introduction to Vim, such as the Vim tutor or A Byte of Vim.

My view is that learning Vim is better approached as an ongoing process, rather than a task to be completed. There is of course an initial learning curve to get past that requires a focused effort, but with that behind me, I can’t imagine ever being “done” learning Vim.

Instead, my approach is to invest in the process of learning and experimenting with Vim. If I can get to a point where I’m regularly learning new features, adapting my configuration to the languages and projects at hand, and generally feeling free to experiment with Vim, then I will be well poised to use Vim to its fullest in my editing.

There are two aspects of Vim that make it lend so well to this style of incremental learning:

Experimenting

One of the most critical aspects of this approach to Vim is a comfort with and ease of experimentation. If I’m worried about breaking my Vim setup or don’t know how to implement configuration changes, then I won’t be able to get far.

Vim Config

The heart of all of this is my Vim configuration (vimrc and .vim directory), which is stored on github in my dotfiles. Two key aspects of my configuration that facilitate my incremental approach are:

map <leader>re :execute "edit " . $MYVIMRC<CR>
map <leader>rs :execute "source " . $MYVIMRC<CR>

Know Your Leader

The “leader” key in Vim is a key reserved for user (or often plugin) defined mappings. I tend to map a ton of things under the leader key (60 as of this writing), and while I definitely think this is a good thing, I’ve run into issues with conflicting <leader> mappings from time to time.

By default the leader is mapped to \ which is pretty far from the cozy home row. Thankfully you can move the leader key to wherever you like. If you don’t have any other leader key allegiance, I highly suggest using , which can be set with the following line added to your vimrc: let mapleader = ",". With that in place, a keymap such as :nmap <leader>sa :s// will actually be run as ,sa.

In hopes of better tracking my usage of the <leader> namespace for my key mappings, I wrote the following function:

function! ListLeaders()
     silent! redir @a
     silent! nmap <LEADER>
     silent! redir END
     silent! new
     silent! put! a
     silent! g/^s*$/d
     silent! %s/^.*,//
     silent! normal ggVg
     silent! sort
     silent! let lines = getline(1,"$")
endfunction

It queries Vim for all <leader> prefixed mappings currently in place, reformats and sorts the list, and dumps it into a new buffer for review. It is pretty simple, but it lets me see my <leader> maps all in one place which I find useful.

Plugin Management

I’ve recently transitioned to using Vundle for managing my plugins. The OCD micro manager within me really appreciates that plugins are configured by lines in my vimrc which means that I can track the history of my plugins alongside the rest of my Vim configuration and settings. Likewise, the minimalist in me likes that all I need is a github user/repo string to identify a plugin, ie “tpope/vim-fugitive”, and then I can treat the actual plugin files as build output and ignore them. In addition, it allows me to update my plugins at runtime by adding a new bundle directive to my vimrc, sourcing it, then running :BundleInstall. The new plugin is then pulled down from github, cloned into my bundle directory, and added to the runtime path so it is ready for use immediately. Again, I prefer the shortest feedback loop possible and Vundle certainly delivers.

Using Git

In general, using version control provides a freedom to experiment that is extremely valuable. Git is especially accomodating to the approach I use as a result of the “index” which acts as a middle layer between my changes in the working directory, and my repository history. This allows me to draw a sharp line between the concerns of coding and version control. The two applications of the index that I include within my Vim configuration workflow are:

Learning

Vim is big. I’ve been working with it for around two and a half years now, and I am still learning new things on a regular basis. I find it useful to approach the learning aspect from two different angles simultaneously. The first is a more formal / reference type approach typically using Vim’s built in help system, and the second is a more exploratory approach with a focus on blogs and the like.

The Help System

Vim has a pretty solid help system that is always at my finger tips. The following set of mappings make it that much easier to get to the answer I want, facilitating the kind of iteration discussed in the rest of this post.

" Help File speedups, <enter> to follow tag, delete for back
au filetype help nnoremap <buffer><cr> <c-]>
au filetype help nnoremap <buffer><bs> <c-T>
au filetype help nnoremap <buffer>q :q<CR>
au filetype help set nonumber
set splitbelow " Split windows, ie Help, make more sense to me below
au filetype help wincmd _ " Maximze the help on open

With these settings in place, I can quickly search for a command or mapping in the help documentation, then follow tags using <enter> building up a stack of help buffers, then dropping back down in the stack using <bs> (delete). This makes browsing help buffers very much like browsing the web and lets me shift the level of detail up and down very quickly as I read through a help file.

Inspiration

Although my general approach is to learn Vim by doing and tackling problems while I work on actual code, I do see a need to expose myself to more of what Vim has to offer. There is plenty of Vim functionality that I would’ve never encountered without having someone else explain it first. I’ve found the following sites to be great sources of ongoing exposure and inspiration for all that Vim can do:

Bit by Bit

The following list are some of the associated habits I’ve developed as part of my Vim workflow:

Incremental Features

The following is a collection of some of the core features in Vim with a suggestion of how to approach each of them in an incremental way. Each section is intended to present a highly simplified approach that can help you to learn the topic by experimenting a little bit at a time.

Vim has extremely powerful regex based searching functionality build into it, but it is definitely not something you can learn in a few minutes. The following are three features associated with searching that have enabled me to experiment and get more comfortable with Vim search syntax each time I use it:

Registers

Rather than having a single clipboard that only stores what you explicitly put into it, Vim has multiple “registers” that each act as a temporary storage location for commands, deletions, etc. This is another aspect of Vim that I was uncomfortable with at first, but there is a simple command that makes registers a whole lot more approachable. While in insert or command mode press <C-r>, followed by the register you wish to view, ie q, /, *. The contents of that register will then be dumped into the buffer or command line for you to examine. Here are some interesting ones to try out:

In addition, if you run the command :registers, you can see the contents of all the registers at the same. :h registers for a more thorough listing.

Macros

Macros let you record edit operations and then replay them making it much easier to handle repetitive tasks. This can be a life saver. Recording a macro can be a bit confusing at first, so to simplify, here is what you do:

  1. qq - to start the recording (into the “q” register specifically)
  2. perform your edits as if it’s any other day
  3. q - stop recording
  4. @q - to execute the macro (profit!)

If you’ve never tried it, give it a shot. What’s more, since macros simply record keystrokes into a register (a simplification, but it works), then we can examine a macro by dumping it out using <c-r>q as descirbed above in the registers section. You can then modify the contents of the register using your Vim editing powers, then suck it back into the “q” register by visually selecting it and entering "qd.

Global

The :g or “global” command allows you to execute any command on all the lines that match a search pattern. This sounds more complicated than it is. Thankfully a global command can be composed a bit at a time in a very iterative way building on the steps laid out in the “search” and “macro” sections above. The rough steps are:

  1. Define the search (and review it via hlsearch). Iterate as needed with q/
  2. Record a macro to define the edit. Iterate as needed with <c-r>q and "qd
  3. Launch the global with :g//normal <C-r>q (dump the “q” macro register into the command)

Functions

I personally stayed away from functions for a long time before trying them out, and I really regret this. I was under the impression that I would need to know all of Vim before I could even approach using functions. Eventually I realized that functions are actually pretty straightforward in Vim. I use following code to allow me to experiment with functions very easily:

function! DoAThing()
    echo 'Hello World!'
endfunction

nmap <leader>rn :source %<cr>:call DoAThing()<cr>

This foundation is all you need to get up and running with functions. Drop this into a new buffer (the actual file can be anywhere in your filesystem since the source mapping uses % which provides the absolute path to the file), then run :source % and hit enter. This will source in the function and map definition and set you up for rapid fire iteration. From there you simply fill in the function body with the code you want to run, type ,rn to “run now”, observe the outcome, and repeat as needed. Each time you press ,rn the file will be re-sourced and update the function definition, then the function will be called. This may sound like a lot of work, but in practice it allows for extermely efficient experimentation. The short version is:

  1. Edit function body
  2. Press ,rn
  3. Watch the function do its thing
  4. Lather, rinse, repeat

Conclusion

In the end my approach is characterized by a desire to experiment, a focus on short iterations with rapid feedback, use of version control to create a sense of freedom with regard to changes, and a desire to take what I learn and wrap it in named abstractions. I find it interesting that this approach seems to parallel my general approach to development. I didn’t start this talk with that in mind, but I am pleased to see that it ended there none the less.

Solid code, the right features, and a strong team

I would love to help you build your platform, empower your team, and tackle everything in between. Let's talk!