teaching machines

You won’t believe what happens when you type these 19 things into Vim!

February 4, 2014 by . Filed under public, vim.

A student group at my university asked me to come talk about Vim at their upcoming meeting—which was two days away. I said no. Then they wised up and asked me to speak at a meeting sometime next semester. I said okay. Following are 19 of my favorite Vim editing tricks.

Insert Mode

Complete filenames

Suppose you’re shelling around:

$ ls
utilities.c main.c
$ vi makefile

In the makefile, you go to type the names of the sources files upon which your executable depends. To minimize spelling errors and time spent typing, starting typing just the first few letters of the filename and then hit <Control-x><Control-f>:

main: u<Control-x><Control-f>

This completes the filename under the cursor:

main: utilities.c

If multiple files match the prefix that’s been typed, cycle through them with <Control-f>.

Enter evaluation of an expression

You’re typing in a document and a need to do some math. While in Insert mode, hit <Control-r>=. You’ll be prompted to enter a Vimscript expression. Simple arithmetic is supported. For example:

The number of pixels is <Control-r>=1024 * 768<CR>.

yields

The number of pixels is 786432.

Factor out a variable

Suppose you’ve got a literal in an expression that you’d like to pull out into a variable:

double nsamples = nseconds * 22050;

22050 would be better named SAMPLE_RATE. In very few keystrokes, we can rip out the 22050, replace it with a new name, and insert a declaration on the preceding line. Our method will make use of the following truths:

  1. Normal mode’s cw deletes the word under the cursor and goes into Insert mode so you may type it’s replacement. Think of it as “change word.”
  2. Deleted text may be recalled by accessing the " register.
  3. O in normal mode opens a new line above the current one.
  4. Control-A in insert mode enters the text entered during the last insert mode.

Put your cursor at the beginning of 22050. Then enter cwSAMPLE_RATE<Escape>Oint <Control-a> = <Control-r>";. When the dust settles, you’ve got:

int SAMPLE_RATE = 22050;
int nsamples = nseconds * SAMPLE_RATE;

Shortcut System.out.println

Eclipse offers a shortcut for generating System.out.println. We simply type sysout and then hit <Control-Space> to expand.

We can do something similar in Vim using abbreviations. In Normal mode, enter :iabbr sysout System.out.println. Now, if we type sysout followed by some non-identifier character like (, Vim will correctly expand our abbreviation.

Normal Mode

Increment and decrement numbers

Whether it’s because you’ve got off-by-one errors or because you’re copying and pasting array element processing code, we often need to increment or decrement integer literals. This can be done quickly in Normal mode by placing the cursor anywhere on or before the number to be altered and hitting <Control-a> or <Control-x>. For example, suppose the cursor is on the first line of the following:

states[1] = "Virginia";
states[2] = "Wyoming";

If you hit <Control-x>j<Control-x>, you’ll end up with:

states[0] = "Virginia";
states[1] = "Wyoming";

Repeat the last command

Suppose you’ve got this text:

f(param0);
  ..
  f(param0);
    ..
    f(param0);
f(param0);

Suddenly, the signature for f changes. It gets a new parameter, and at each call site, you’d like to add param1. You could do a find/replace, but sometimes that’s too much overhead. Instead, we can add the text at one call site, and repeat at our action at the remaining with .. With the cursor on the first 0, we enter a, param1/0.n.n. and end up with:

f(param0, param1);
  ..
  f(param0, param1);
    ..
    f(param0, param1);
f(param0, param1);

Open file under cursor

Maybe you’re editing a makefile. Maybe you’re editing a Java source file. It doesn’t matter. At one time or another, you want to open the file associated with the word under the cursor. The word might be a filename, or it might be the name of a class. Hit <Control-w><Control-f> and it opens.

Train yourself away from the cursor keys

“If you can keep your fingers on the home row,” people will tell you, “then you can type much faster.” I don’t find typing speeds an issue, because usually I have to think as much as I type—and thinking is the real bottleneck. However, I don’t want to strain my body unnecessarily. Let’s keep those fingers on the home row.

The cursor keys are far from the home row. Good news. In normal mode, h is the same as <Left>, l is <Right>, j is <Down>, and k is <Up>. However, old habits die hard. You can wean yourself off of the cursor keys by mapping them to do nothing:

nmap <Left> <Nop>
nmap <Right> <Nop>
nmap <Down> <Nop>
nmap <Up> <Nop>

Move cursor quickly to a character

You cursor is sitting at the beginning of the line. It needs to be at the hyphen between those two words really far away. How do you get there? The mouse? Hardly. Use the f command. In particular, f- will advance to the first comma after the cursor. 2f- to the second. 3f- to the third. And so on. But you don’t have to count if you don’t want to. Just hit f- and then ; to jump to the next match. If you overshoot, hit , to jump to the previous match.

If the cursor appears after the target, use F- instead.

f and F will take you right to the match. If you’d like to stop short at the character right next to the target, use t and T. For example, maybe you’re in the open and close of an XML/HTML element and you want to land on the first character after the opening tag. T> will do the trick.

Change, yank, and delete with precision

The commands c, y, and d are used to change, yank, and delete text, respectively. You follow each of these commands with a motion to indicate what text is affected. For example, cw changes a word, dd deletes a line, and y2w yanks two words.

These commands can also be followed by [ fFtT]. To delete from the cursor until the semi-color, hit dt;. To yank through the next closing parenthesis, hit yf). A count can be provided to target the nth character from the cursor.

Vim also supports quick processing of delimited text. To delete everything between parentheses, hit di(. This also works for [], {}, “”, ”. Suppose your delimiters are not single characters, but tags. Use dit. Suppose you want to delete not just the contents of the delimiters, but the delimiters too. Use da(.

Capturing complex edits with macros

Once I needed to create a 50-element array of State objects, each holding its states capital and name. I found a list in this format online:

Madison, Wisconsin
Des Moines, Iowa
Montpelier, Vermont
...

To convert each line into the construction of a State, I first transformed just the first line—but I recorded my keystrokes into register a as I did so using the record macro command q. I entered qaInew State("<Escape>f,i"<Escape>Wi"<Escape>A"),<Escape>j. Then I hit @a to play back the macro I stored in register a on the next line. After seeing that it worked, I hit 48@@ to play back the last register 48 more times.

I ended up with:

new State("Des Moines", "Iowa"),
new State("Madison", "Wisconsin"),
new State("Montpelier", "Vermont"),
...

All that remained was to surround all the states in  {} and assign the result to a State[].

Regular expressions would also have worked.

Visual Mode

Often I find myself needing to edit text that is vertically aligned. Maybe several lines in a row are assigning values to some array elements, and I need to alter the index expression in some uniform way. Maybe I’ve copied some numbers that are formatted in columns, and I need to separate each column with a comma. I could use a macro, or I could use a regular expression and substitution. But there’s also Visual Block mode.

Say I’ve got the numbers problem. I need to turn these numbers into a CSV file:

0.048909 0.524034 0.394847 0.317906
0.693668 0.608016 0.962821 0.887913
0.224440 0.929916 0.536071 0.421397
0.422353 0.911034 0.282418 0.496423

(If the file contained no the other spaces, :%s/ /,/g would do the trick. Let’s assume the file does contain other spaces.)

With the cursor at the beginning, I hit f<Space> to go to the space separating the first two columns, <Control-v> to go into Visual Block mode, 3j to select the next three lines, s to substitute the selected text and go into Insert mode, , to replace the text on the first line, and <Escape> to apply the substitution to the remaining lines. Once satisfied with my edit, I hit ;. to repeat on the next column and then ;. once more.

Ex Mode

Substitution

Because I care, I am constantly renaming things. Next to typing in Insert mode, finding and replacing is probably my most frequent action I take in Vim. At its simplest, I just type:

:%s/oldname/newname/g

I can shorten my work a little bit by placing the cursor on a word I want to replace and hitting # or *. Then, I can leave the search pattern blank:

:%s//newname/g

I can confine the substitution to just the visually selected lines just by being in Visual mode. The range of operation is automatically set to '<,'> when I press ::

:'<,'>s/oldname/newname/g

Usually I qualify the substitution to confirm each replacement, at least until I’m reasonably sure my pattern doesn’t lead to false positives and the substitution is correct:

:%s/oldname/newname/gc

Autocomment

You are coding and want comment out some lines. We can do this in many text editors by selecting some lines and hitting <Control-/>. We can make Vim do this too.

First, let’s do this without a special keybinding. Select some lines by going into visual line mode with V. Move the cursor to expand your selection. Prefix all the selected lines with // by entering :'<,'>normal i//. Remove the comment characters with :'<,'>normal xx. These two Ex commands apply a normal mode command to each line in the visually selected range.

We don’t want to type those Ex commands all the time. We can map <Space>/ to prepend the comment character with vmap <Space>/ :normal i//<CR>. The logic to toggle comments can be wrapped up inside a function to make this solution better.

Operating on multiple files

Suppose you need to change a function name that’s used in many files. What a pain, right? No, substituting in a new name across multiple files is only mildly more difficult than substituting it in one. The argdo command lets us apply a command to each file that we sent to Vim as a command-line argument. For example:

:argdo %s/oldfunc/newfunc/

This command essentially iterates through the argument list and applies the subsequent command. You or your settings may require that the current file be written before advancing to the next. Simply append a second write command:

:argdo %s/oldfunc/newfunc/ | w

Cropping search results for smarter substitution

Imagine you’re editing and need to substitute some text. To match the text that needs updating, you must include some context that you don’t plan on changing. For example, maybe you’ve got an HTML file and you want to replace all the anchor tag links with #, because you’re starting to do things with Javascript now. The file has other links embedded in the text, so you need to be careful that you don’t oversubstitute.

One approach is to capture the surrounding context and explicitly reinsert the unaltered context back in with :%s/\(<a href="\)[^"]\+\(>\)/\1#\2/g. Zero-width assertions, however, would let you match the context without trying to substitute over it:  :%s/\(<a href="\)\@<=[^"]\+\(>\)\@=/#/g. Who thinks this is ugly? I do.

The \zs and \ze commands achieve the same effect much more simply by allowing you to delimit which portion of the matched text gets substituted: :%s/<a href="\zs[^"]\+\ze">/#/g.

Vimrc

Autocommands

Many settings or keybindings depend on the kind of file one is editing. For instance, I like to map <Space>x to execute the script I’m editing without having to go out to the shell. I can do that laboriously with Ex command :!interpreter %, in which  % expands to the path of the current file. Language-specific keybindings make it way better:

autocmd BufEnter *.py
\ map <Space>x :!python %<CR>

autocmd BufEnter *.rb
\ map <Space>x :!ruby %<CR>

autocmd BufEnter *.hs
\ map <Space>x :!runhaskell %<CR>

Restoring cursor position on load

Our edits to a file are often spatially coherent. If we change a line in a file, save it, and reopen it, we’re likely to make more edits near our last edit. On reopening, however, we find that Vim’s cursor is sitting right at the beginning of the file. It’d be really, really nice if our cursor was automatically placed at our last edit.

It turns out that Vim automatically stores the last cursor position as a mark. We can jump to it manually with `" or automatically using an autocommand:

autocmd BufReadPost *
\ if line("'\"") > 0 && line("'\"") <= line("$") |
\   execute "normal! g`\"" |
\ endif

Others

Comparing two files

Say you’ve got a file a.txt. However, this a.txt is on both your hard drive and a USB drive. The modification times are inconclusive. You fear that each may have changes the other does not. How can you compare and possibly merge them? Just use Vimdiff:

vimdiff path/to/one/a.txt path/to/other/a.txt

The two panes will show the two files with differences highlighted and similarities collapsed. Scrolling in one will scroll in the other. [c and ]c will navigate between differences.