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:
-
Composition - Vim commands tend to build on each other very well. Search leads into substitute, functions can be launched by keymaps, macros drive from registers, etc. In the end, I see Vim very much as a “whole is greater than the sum of the parts” situation.
-
Iteration - Vim has a crazy awesome undo system that will let me unwind changes all the way back to when I first opened the file. Armed with this knowledge I feel free to try out a command, fail, rewind, try again, fail… until I finally get the behavior I’m looking for.
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:
-
Allows Repeated Sourcing - At any time, I should be able to
:source $MYVIMRC
and activate any new functionality I’ve added to my vimrc since startup. In the past I’ve had lines in my vimrc that caused errors when resourcing, or settings that should only be run once. I’ve since made it a priority to ensure I can safely and repeatedly source my vimrc without anything breaking. Unfortunately this can take some effort if you have a lot of snippets in your vimrc that you have pulled in from outside sources, but it is well worth the effort. At least twice now I’ve declared “Vim config bankruptcy” and started with a fresh, empty, vimrc. I then added code back in bit by bit, checking along the way that the configuration was clean and would allow me to re-source as needed. -
Short feedback loop - In order to make it easier to make changes on the fly, I use the two keymaps listed below. The first opens my vimrc for editing, the second sources it to update my settings and functions with anything new I’ve added.
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:
-
Deferred commits - When working with other projects, I tend to work on a discrete feature until I’ve succesfully implimented it and then immediately commit the new code in git. With my Vim configuration, I find it preferable to implement a new bit of configuration and then defer commiting it for a few days to allow for testing new features. With Vim configuration I find this testing to be much more qualitative (does this feel right? does it mesh well with my other configurations? do I remember / use it?) than with my other repositories. Every few days, I will review the changes in my repo and either reset them (git checkout) or commit them.
-
Selective Commits - One of the main things I love about git is that it provides a clear distinction between coding and version control concerns. I am free to make any changes I want, related or not, and leave the version control bits until later. I can use
git add <file>
to select specific files from a group of changed files, or even usegit add -p
to select specific changed lines from within a file. This allows me to tease apart the unrelated changes I tend to make over the course of a day or two.
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:
-
Vim wiki - The Vim wiki contains an impressive collection of tips for using Vim. They vary in quality, but the total collection is the best I’ve seen. For the adventurous among you, try starting at the first tip and going through them all.
-
Vim Scripts - The Vim scripts site is the home page for a project working to make all Vim.org scripts available as repos on github. This makes using any script with plugin managers, ala pathogen or vundle, a breeze.
-
Vimcasts - A great collection of screencasts on Vim. The author hasn’t updated in a while, but solid content none the less.
-
Derek Wyatt’s Videos - Some of the best video content I’ve seen for Vim. Highly recommended. Covers both introductory stuff, as well as more advanced content.
-
Steve Losh - Learn Vimscript the Hard Way I can’t officially speak to the quality of the this particular content, but I’m such a fan of the author’s Coming home to Vim blog post, that I am more than happy to recommend this longer form content sight unseen.
-
Vimtips list - A crazy long list of tips and commands for bending Vim to your will.
-
Destroy All Software - This is Gary Bernhardt’s weekly screencast site that covers unix, rails, Vim, etc. It isn’t free ($9 monthly), and it isn’t purely focused on Vim, but I’ve pulled out a bunch of Vim tips from it and would highly recommend checking it out.
-
Vim subreddit - This subreddit contains continuously updating list of links to new web content about Vim.
Bit by Bit
The following list are some of the associated habits I’ve developed as part of my Vim workflow:
-
Document Successes - Whenever I write up an interesting function, search, command, etc I will try to document it in my vimrc. At a minimum I will add a comment with it, but often I will wrap it in a function to name the piece of functionality.
-
Capture Annoyances - I use the github issues tracker tied to my dotfiles repo as a place to unload a summary of things that I wish worked differently in Vim and my configuration. I can then forget about it and get back to work, knowing that I can always come back to it. This helps keep my focus on the code I am actually working on rather than constantly fiddling with my editor. Likewise, the github issues interface allows me to collect links and details so I can update the issues over time as I get more clarity on possible solutions.
-
Avoid Yak Shaving - I have a very informal measure that I use to keep myself from getting distracted. At a minimum, I have to have the same thought three times before I will let myself pursue the topic. This can be writing a new function, investigating a bug / error, or anything else that is not related to the actual code I am working on. This goes hand in hand with having the github issues tracker to dump these kind of things into.
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.
Search
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:
-
Hlsearch - The hlsearch setting in Vim (enabled with
:set hlsearch
) causes Vim to progressively highlight the closest text that matches the pattern, as you enter the pattern. This sort of immediate feedback is critical to me when I’m working with features that I’m less familiar with. Note you can remap<Esc>
in normal mode to also clear hlsearch highlighting with the following map definitionnmap <Esc> <Esc>:nohlsearch<CR>
. -
q/ - Vim stores the last hundred or so search patterns, and you can view and modify them using
q/
. This will open a buffer containing the search history, one pattern per line, and let you modify or rerun any previous search. This is a standard Vim buffer so you have all the normal editing power (and searching power) of Vim available to help you refine searches. Once you have the new search ready, pressenter
to execute the search on the current (non search) buffer. -
Reusing Searches - After using
q/
to refine your search pattern down, you can then reuse it in other more complicated commands. For a start,//
will rerun the last search. Likewise, if you omit a pattern in a substitute command:s//<replacement>/
or global (see below):g//<cmd>
, Vim will reuse the last search. This means that you can iterate on a search pattern independent of the command (substitute, global, etc) that uses 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:
0
- the last yank"
- the last delete/
- the last search*
- the system clipboard (most of the time)
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:
qq
- to start the recording (into the “q” register specifically)- perform your edits as if it’s any other day
q
- stop recording@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:
- Define the search (and review it via hlsearch). Iterate as needed with
q/
- Record a macro to define the edit. Iterate as needed with
<c-r>q
and"qd
- 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:
- Edit function body
- Press
,rn
- Watch the function do its thing
- 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.