Ismail Badawi

The Compositional Nature of Vim

I use vim. I’ve used vim since I started programming; the very first program I wrote — hello world in C, following along a cprogramming.com tutorial — was typed out in vim, inside a cygwin environment on Windows. Naturally, at first it was hard and intimidating. I didn’t know how to do anything, least of all edit text. I learned about insert mode and normal mode. I learned about navigating using hjkl, and deleting the current line with dd, and saving and quitting with :wq, and for a long time that was it.

Over time I learned more and more. I learned that I could copy the current line with yy, and paste it somewhere with p. This meant that yyp duplicated the current line! I learned that I could indent the current line with >>, and also that I could indent the next 5 lines with 5>>. I learned that gg jumped to the top of the file. I learned that I could jump to line 34 with 34G. I also learned a strange incantation — I could write %s/foo/bar/g to replace all occurrences of foo with bar in the whole file. I used this all the time, and vim felt really powerful!

I went on like this for years. What I’m trying to get at is that I never really took the time to learn how vim worked. I had no clue about the big picture. I didn’t know any concepts. Even though I used vim for hours each day, and I felt like I was constantly improving and learning new things, and my peers in university thought me knowledgeable enough to come to me with their vim questions, really I was just getting by on ad-hoc memorization.

I think this is actually not uncommon. There is this mystical aura around vim; people have a tendency to think that becoming an effective vim user just means memorizing enough arcane commands that eventually you’ll just know which incantation to type in to do what you want in any given situation. Want to copy the whole file to the clipboard? Easy — it’s gg"*yG, right? Yes, that’s it. I have it here in my notes — “copy file to clipboard: gg"*yG”. Copying the next two paragraphs? Gee, I don’t know, let me look that up — ah, here. It’s {"*y2}. I’ll just write that down and next time it comes up I’ll know.

Some people are (rightly) turned off by this. Others — like me — take it in stride and spend years memorizing opaque formulas like the above, motivated in large part by the oft-touted idea that knowing vim inside and out will make you orders of magnitude more productive (and in no small part by garden-variety hipsterism). I know from experience that this approach can take you quite a long way. It’ll only take you so far though — in the end it’s as silly as learning to speak English by trying to memorize all the possible sentences you can say, without learning about verbs or nouns.

Composition

The magic of vim is that it’s comprised of lots of small primitives that compose well together. Among these primitives are motions, operators, and text objects.

Motions

Pressing l moves your cursor one character to the right. Pressing h moves it one character to the left. j moves it one character down, and k one character up.

w moves it to start of the next word. e moves it to the end of the next word. b moves it to the start of the previous word.

$ moves it to the end of the current line, and 0 to the start of the current line. gg moves it to the top of the file, and G to the bottom of the file. { moves it to the start of the paragraph, and } to the end of the paragraph.

You can use G with a line number to jump to that line number.

The t and f motions move forward until a character. For instance, tb moves forward until (but not including) the next occurrence of b. fb moves forward until (and including) the next occurrence of b. Both of these have uppercase versions that move backwards instead of forwards.

You can search forwards with /regex, and backwards with ?regex. After searching, you can use n to jump forwards to the next match, and N to jump backwards to the last match.

Where it makes sense, the previous motions can be prefaced with a count. For instance, 10j moves down 10 lines instead of 1, and 3tg moves forward to the third occurrence of g.

Now it might seem like I’m just confirming the prejudice I was decrying earlier. To a certain extent, these primitives are just things you should commit to muscle memory. What you get in return is substantial, however.

Operators

We just learned a bunch of motions. Now suppose we want to delete something, instead of moving. There is only one new thing to learn: the d operator. d stands for delete. What can you delete? Any motion, for one thing. j moves down a line, so dj deletes down a line (that is, from the current position until the same column on the next line). gg moves to the top of the file, so dgg deletes to the top of the file. /foo jumps to the next occurrence of foo, so d/foo deletes until the next occurrence of foo.

Another operator is c, which stands for change. c is like d, except that it puts you in insert mode after. So cj behaves like dj, except that it puts you in insert mode. Similarly cgg behaves like dgg and so on.

Another operator is y, which stands for yank. It copies things into registers. You can learn more about registers later; to start with, you can just use y to yank things into the default register, and use p to put them somewhere else. As with c and d, you can use yj to yank down a line, ygg to yank to the top of the file, and so on.

Another operator is gu, which is used for lowercasing. gu4j lowercases four lines down, gugg lowercases to the top of the file, guG lowercases to the bottom of the file, and so on. There’s also gU for uppercasing, g~ for swapping case and (strangely enough) g? for rot13 encoding.

Another operator is >, which handles indentation. You can use >j to indent down a line, >4k to indent up four lines, >gg to indent to the top of the file, >} to indent to the end of the paragraph, and so on. The < operator is similar, only it dedents instead of indenting. These two are a little different than the previous ones, since they operate on lines. For instance, >l will indent the entire line.

Another operator is gq, which is used for formatting. The specifics are configurable, but by default it reflows text so it’s wrapped to textwidth characters. This is useful if you’re looking to use no more than 80 characters per line, for instance. As before, you can use gqgg to reflow text to the top of the file, gqG to reflow text to the bottom of file, gq10k to reflow ten lines up, and so on.

The nice thing here is that each operator we learn about can be composed with all the motions we know. Operators can also be used with these other things called text objects.

Text objects

Text objects are like motions in that they can be passed as arguments to operators. They’re not like motions in that they don’t move you; instead, they just refer to a region of text.

For example, the _ text object refers to the current line. You can write d_ to delete the current line, c_ to delete it and enter insert mode, y_ to yank it, >_ to indent it, gu_ to lowercase it, gU_ to uppercase it, g~_ to toggle its case, g?_ to rot13 encode it and so on. Since operating on the current line is very common, repeating an operator is shorthand for applying it on the current line. For instance, dd does the same thing as d_, cc does the same as c_, and so on.

Another text object is iw. If your cursor is within a word, then iw refers to the whole word. You can write diw to delete the word under the cursor, ciw to delete it and enter insert mode, yiw to yank it, guiw to lowercase it, gUiw to uppercase it, g~iw to toggle its case, g?iw to rot13 encode it and so on.

Another text object is ip. If your cursor is within a paragraph, then ip refers to the whole paragraph. You can write dip to delete the paragraph under the cursor, cip to delete it and enter insert mode, yip to yank it, guip to lowercase it, gUip to uppercase it, g~ip to toggle its case, g?ip to rot13 encode it, and so on. Personally, I find gqip very useful when writing prose.

There are text objects that refer to regions of text between delimiters, which can be very useful when editing code. If your cursor is within a double quoted string, then i" refers to the text between the quotes, and a" refers to the same but also includes the quotes. Similarly you have i' and a', i( and a(, i[ and a[, i{ and a{, i< and a<, and i` and a`. As with the other text objects, you can use d, c, y, gu, gU, g~, and any other operators you know.

The power of composition

There’s a combinatorial effect here. If I know about o operators, m motions and t text objects, I can do up to o * (m + t) different things. Every new operator I learn lets me do up to m + t new things, and every motion or text object I learn lets me do up to o new things. Once you internalize vim’s language for editing text, then not only does editing text efficiently become easier, but you also start learning at a much faster rate, as every new thing you learn interacts with all the things you already know.

This doesn’t just apply to the functionality built in to vim. There are many ways in which one can extend vim through plugins. Assuming these plugins are well behaved, then they too benefit from composing with everything else.

One can add new operators, for instance. An example is the commentary plugin, which adds the gc operator to toggle commenting lines. Since this is an operator, it can be used like d or c; gcc comments the current line, gc4j comments four lines down, gcgg comments to the top of the file, and so on.

One can also add new text objects. For example, the textobj-rubyblock plugin adds the ar and ir text objects to refer to the current ruby block, or just its contents, respectively. This lets you write things like dar to delete the entire block the cursor is in, or <ir to dedent its contents.

One can also add new motions. For example, the CamelCaseMotion plugin defines camel-case analogues of the w, b, and e motions, so that, for instance, you can jump to the start of the next word in a camel-cased identifier. It also defines text objects analogous to iw and so on for camel-cased words.

The interaction between plugins like these is what led me to a sort of aha moment a while ago. At some point I’d installed commentary, and a while later I’d installed textobj-rubyblock, never thinking of them together. One day, I happened to want to comment out the contents of a ruby block, and intuitively I reached for gcir. This wasn’t something I’d learned. It certainly wasn’t documented anywhere that this would work; the two plugins had been written independently by two different people. Not only did I not learn this, but I didn’t even explicitly think about it. This was just intuition — since one of the operators at my disposal is gc, and one of the text objects at my disposal is ir, then gcir ought to work. And it did!

Parting thoughts

While vim sports many more features than just normal mode editing like this (and there are many good resources for learning about these), internalizing this idea of composing together many small text editing primitives is one of the most important steps towards efficient vim use, and is the main thing I try to impress upon beginning vim users whenever the opportunity arises. Having this pointed out to me would certainly would have saved me a lot of time, as many of the “tricks” that I learned piecemeal during my first few years using vim were just instances of this sort of composition.

Comments