Erik Ramsgaard Wognsen

Thoughts & technology

The Language of vi, Improved

In a previous post I described the “language” metaphor of the text editor vi. To recap,

  • Verbs are commands such as change c, delete d, yank y, and put p
  • Nouns (text objects): a paragraph ap, a string a", etc.
  • Prepositional phrases (motions): “to the next word” w, “to the next regex” /
  • Numerals (counts) as seen in “indent ten lines” 10>> and “to the 27th line” 27gg.

This time around, we’ll see how Vim (vi improved) adds a new word order to make you speak more clearly, and meta-verbs to blow your mind!

Speak Before You Think

Near the end of my previous post we got to the numerals, or counts as they’re officially called. Counts are useful, especially when large numbers are involved, but sometimes it pays off to use Vim without thinking too much ahead. Just start speaking:

Delete … a bit more … a bit more … bit more … ok, stop.

While this is the way many people delete text on their computer, Vim makes it a lot more efficient than the typical awkward bursts of holding down the delete key until you delete enough (and probably too much). For example, dw.... deletes one word, then the next, and so on, five in total. (Remember,dw deletes one word, and the dot . command repeats this action.) This can be faster than the equivalent d5w because you don’t need to count the words before you start deleting . The running feedback you get by seeing the words disappear one by one is very helpful and avoids the mistakes that could have happened due to miscounting. It is especially useful for objects that vary considerably in size (sentences, paragraphs), because they are harder to count quickly.

But this approach doesn’t work for common operations such as change c or yank y. When you yank (copy), you most likely want all the text in the clipboard at the same time; not one word replacing the next until you are left with only the last word in the clipboard. Probably for the same reason, the repeat . command doesn’t consider yanking a repeatable action. (By the way, I say ‘the clipboard’ because it is a familiar term to many, but in Vim, they’re called registers, and there’s a whole lot more than one of them!) Instead of repeat, we can use visual mode, which gives both pleasant feedback and works with all actions.

… or Speak Like Yoda

[Fernschreiber Modell T100] [fernschreiber_link] by [Nightflyer] [fernschreiber_author] / [CC BY-SA 3.0] [fernschreiber_cc]Fernschreiber Modell T100 by Nightflyer / CC BY-SA 3.0

To explain visual mode, we will first pay the 1970s a brief visit. In vi, “visual mode” was a mode that was “more visual” than the Teletype oriented editors vi superseded and subsumed (ed and ex, respectively). It meant simply that you could see the file while you edited it. However, this form of visual feedback was soon considered normal. Case in point, vi’s visual mode is Vim’s normal mode.

The way to be more visual than Vim’s normal mode is to highlight the text that will be changed before the change is executed. This is similar to selecting text in mainstream editors with the mouse or by using the arrow keys with the shift key held down. In these, typing while text is selected replaces the selected text with the typed text. Vim’s visual mode however retains the keyboard’s “gamepad role” (see my previous post). (Vim also has a “mainstream” select mode but it is mainly used to imitate lesser other editors.)

You enter visual mode using v, then use text objects and motions to highlight the text you want to affect. This reverses the word order in the vi language, effectively making you command Vim like Yoda: “These lines delete (you must!)” Compare:

  • das: “Delete a sentence.”
  • vasd: “A sentence delete.”
  • dasdw: “Delete a sentence. Delete the next word.”
  • vased: “A sentence and the next word delete.”

So, visual mode is nice because it gives visual feedback on the area you are going to operate on before you commit to it and type the operator. But it doesn’t stop there.

Visual Basics

What would you do if you had a lot of gamepad real estate to go along with visual mode?

First of all, visual mode actually comes in three flavors: Character v, line V, and block <c-v>. Here I switch around between them a bit:

(The numbers in the margin show the line number for the line the cursor is on, and the distance to the other lines. This feature makes is easy to use counts with line-based operators.)

Visual character mode is the one you will find most familiar: The selection starts somewhere, continues along the direction of the text, wraps at the ends of lines, and ends somewhere. In visual line mode, every whole line from the one the selection starts in to the one it ends in, is selected. This makes it easy to select whole lines without moving the cursor to the start and end of a line. Especially useful in programming, where line is often synonymous with statement. Finally, visual block mode selects a rectangle of text. Applying operators to blocks is very powerful and enables things that would take a long time without block mode.

Block mode can also be used to insert text at several lines simultaneously. From block mode, insert I and append A start insert mode, and when the insertion is done, that text is then inserted in every line along the left or right edge of the block, respectively. Also, the $ motion can be used to extend the selection to the ends of lines. If the lines have different lengths, the result is a jagged block. Appending A to this inserts text at the end of each line, wherever the end of that line is. In the video I demonstrate both I and A. Here’s a breakdown:

1
2
3
4
5
6
7
8
9
10
<c-v>jj$Ifoo(<esc>gvA)<esc>

<c-v>                         Ctrl-v to start visual block mode
     jj$                      Move cursor two down and to the end of line
        I                     Start block insert mode
         foo(                 Write "foo("
             <esc>            Escape to normal mode (completes the other lines)
                  gv          Start visual mode with previous selection
                    A         Start appending in block insert mode
                     )<esc>   Write ")" and escape to normal mode

All this might look scary, but that’s of course part of what makes it so cool!

In the end of the video I do the same thing in one step using Tim Pope’s surround.vim plugin. With the same selection active, I use Sf to “surround with function-call”, type “foo” and enter.

Neat Tricks

Have you tried selecting text in a mainstream editor and finding out you started the selection in the wrong place? So you do the selection again, and do it right this time. Well, Vim is all about efficiency: In visual mode, o moves the cursor to the other end of the selection, so you can adjust it without starting over. As amazing as it is simple! A soft, smiling kitten is born every time you use this feature.

Also, with visual mode, you can crawl up the DOM tree: at extends the selection to the enclosing tag-block (Vim-speak for an HTML/XML element). Applied repeatedly, a bigger and bigger block of markup is selected, corresponding to nodes further up the DOM tree.

Both visual mode and repeat . can be used in the “speak before you think” manner, but visual mode works with all operators. It also has another advantage over repeat: The operation is “committed” in one step, so it can be undone in one step. This brings us to …

Meta-Verbs

Undo is a verb. It’s a verb for a command that reverts the latest change, no surprises there. But what happens when you undo two times in a row?

In vi, undo is a change like any other. Thus, if the latest change is an undo, undo will revert that undo action. In other words, typing uu is a no-op! Vim, being “vi improved”, can emulate vi or use its own improved undo, which could be called a meta-verb: Verbs change text, but meta-verbs change changes. So undo u and redo <c-r> go back and forth in the change history, as is common in most modern programs. But there’s more:

Let’s say you are writing a blog post. Well into the process you remember an earlier phrasing that you changed, but now you want it back. You take numerous steps back in history (perhaps employing a count: 25u) until you find it. You copy it, and you’re ready to continue. But before you remember that you should redo to return to the newest version of the document, you make a new change. If you used a simpler editor that version would now be lost forever.

Vim knows that undo/redo is not a linear history, it is a tree. Normal, sequential changes extend a single branch of the tree. But when you go back and make a new change as in the example above, that change sprouts another branch. Vim stores this tree and let’s you recover your work.

In this video, I demonstrate the undo tree with all changes being adding small bits of text, and the layout of the text representing the branches of the tree. Undo u and redo <c-r> always go up and down the current branch (in this video literally up and down). The commands g- and g+ go through older and newer changes to the text, which can be spread around the tree. I go backwards with g- (watch for the ‘g’ in the lower right corner) through the events of entering “six” and “five” to the state after entering “four”, which is in the original branch, before undoing “three” and “four”. Using g+ brings me forward again to the changes in the new branch.

Because all changes are ordered chronologically (no matter the undos and redos done between them and the sprouting of new branches), they can be accessed in a linear history with g- and g+. You can even travel in time explicitly: :earlier 3m (minutes), :later 2h (hours). But in practice all you might need is a few g-es once in a blue moon.

On top of that we have persistent undo between sessions, which I think makes Vim’s undo the most powerful undo functionality I have seen in a program. If you know of any contenders, please leave a comment!

Vim itself never actually shows you the tree directly, but Steve Losh’s graphical undo a.k.a. Gundo plugin looks nice for that purpose (screencast here).

Now you have seen two powerful features that are in Vim but not vi: visual mode and the undo system. Here’s one more before we end:

Record and Replay Changes

Another meta-verb is record q — it doesn’t change the text by itself, but it records the use of other verbs. The recording can then be played back by (regular verb) execute @ to use the same changes in a new context.

The xkcd forums user EvanED once demonstrated how he used emacs to efficiently

1
2
3
4
5
6
7
8
9
// turn this ...
void stateDoOperation1(state* state, int param1);
int stateDoOperation2(state* state, int param1);
int stateDoOperation3(state* state, int param1, double param2);

// ... into this:
doOperation1(int param1);
doOperation2(int param1);
doOperation3(int param1, double param2);

I defended the honor of Vim with this salvo:

1
2
3
4
5
6
7
8
9
10
11
qqdfe~fsdf,x<enter>q2@q

qq                        start recording to register 'q'
  dfe                     delete to next 'e' (the end of "state")
     ~                    invert case of letter under cursor (the 'D' in "Do")
      fs                  move cursor to next 's' (the beginning of "state")
        df,               delete to next ','
           x              delete (the space after the ',')
            <enter>       move to beginning of next line
                   q      stop recording
                    2@q   replay 'q' two times

Here I will do the same using visual mode so you can better see what’s going on:

He and I parted ways amicably! As he said

vi does seem faster in the hands of someone who’s skilled. But it also seems that emacs is faster in the hands of someone who isn’t particularly skilled

And that’s probably true!

Comments