Tutorial Goals

This tutorial’s goal is to get you comfortable editing files in Vim.

By the end of this tutorial, you should be able to:

  1. Survive in Vim (modes, moving around, saving, quitting)
  2. Understand the grammar of Vim (operators, motions, and text objects)
  3. Edit text using operators, motions, and text objects together

Warning

Don’t worry if Vim’s not your thing yet, Nano is totally fine. This tutorial is meant to show you what’s possible.

Why Vim

Say your program is misbehaving. To see what’s wrong, you scatter printfs through your code. You tag each one with the word debug, so they’re easy to find later:

#include <stdio.h>
 
int area(int width, int height) {
    printf("debug: width = %d\n", width);
    printf("debug: height = %d\n", height);
    int a = width * height;
    printf("debug: area = %d\n", a);
    return a;
}
 
int perimeter(int width, int height) {
    printf("debug: entered perimeter\n");
    int p = 2 * (width + height);
    printf("debug: p = %d\n", p);
    return p;
}
 
void report(int width, int height) {
    printf("Rectangle %d x %d\n", width, height);
 
    printf("debug: about to call area\n");
    printf("Area: %d\n", area(width, height));
 
    printf("debug: about to call perimeter\n");
    printf("Perimeter: %d\n", perimeter(width, height));
}
 
int main(void) {
    printf("debug: starting\n");
    report(3, 4);
    report(10, 7);
    printf("Done. Run with --debug for the full trace.\n");
    printf("debug: exiting\n");
    return 0;
}

The bug is fixed, and now every debug print has to go before you submit. They’re scattered across different functions, buried among real code.

How would you remove them in Vim? Have a think before opening the solution below.

Getting Started

Go to https://code.cs1010.org, and use the environment T1: Vim.

The files for the non-graded exercises are already available in ~/tutorial/. Open a terminal and change your current directory to the tutorial folder. Refer to Tutorial 0 if you’ve forgotten how to do it.

Once you’ve done that, open the run.c file with vim as follows:

vim run.c

Why Vim Feels Weird (At First)

Unlike traditional text editors, Vim comes with different modes, meaning that each key does a different thing depending on the situation. The mode can be seen at the bottom left corner. For instance, if you’re in insert mode, it should say “--INSERT--”.

The default mode when you first enter Vim is known as normal mode. You are expected to be in normal mode most of the time, therefore, there is no indicator that you’re in normal mode.

Since Vim is a modal editor, your keys work differently depending on which mode you’re in. You will often run commands in the wrong mode. In such a crisis, focus on returning to Normal mode by spamming the Esc key. Once you’re back in Normal mode, you can focus on undoing the chaos that has ensued.

Surviving in Vim

Tip

Commands might feel abstract and hard to recall. Fortunately, most of the commands have built-in mnemonics that make them super easy to remember. Vim isn’t about memorising shortcuts (mostly), you’re actually learning to speak a “language”. “Delete a word” becomes daw.

CommandDescriptionMnemonic
iInsert before the cursorinsert
aInsert after the cursorappend
IInsert before the first non-blank characterinsert
AInsert after the last character in lineappend
EscExit insert modeescape
uUndo last changeundo
Ctrl-RRedo undone changeredo
:wSave filewrite
:q!Quit without savingquit
:wqSave and quitwrite, quit

The four insert commands differ only in where they start Insert mode. The lowercase commands work relative to the cursor: i starts before it, while a starts after it. Their uppercase versions go to the corresponding boundary of the line: I starts before the first non-blank character, while A starts at the end. Whichever command you use, press Esc to return to Normal mode.

In run.c, try each insert command. Before each attempt, place your cursor in the middle of an indented line.

  1. Type iX, then Esc. Where did the X appear?
  2. Undo with u, then try aX and Esc
  3. Undo again, then try IX and Esc
  4. Undo again, then try AX and Esc
  5. Undo once more to restore the file

So how does i, a, I, A differ from each other?

Everyone uses Vim differently, but A and I are among the best commands for quickly editing text.

A practical use of A is appending to the end of a line, such as adding a semicolon that you forgot. In run.c, four lines in the main function are missing one.

  1. Put your cursor anywhere on the line int row = 3
  2. Press A, type a semicolon, then press Esc
  3. Undo with u to restore the line

Leave the other three alone. We’ll come back to them.

Reading the Friendly Manual (RTFM)

In Vim, you can type :h <command> to get help on any <command>. Try reading the help page for the i command by typing :h i, followed by Enter.

To close the help window, type :close, then Enter.

Speaking Vim

Just like in English, Vim has its own set of grammar. A typical command structure follows Count-Operator-Motion, or “COM”, which conveniently makes up the first 3 letters of the word “command”.

IntentCommandCountOperatorMotion
Change wordcwcw
Delete worddwdw
Delete 3 wordsd3w or 3dw3dw

Tip

You may notice that Vim commands can follow either a COM or OCM structure. “OCM” flows more naturally when thinking in English, where delete 3 words becomes d3w. “COM”, on the other hand, is easier to remember thanks to the mnemonic: the first three letters of “command”.

Use whichever structure fits your thinking best.

Let’s try some stuff first…

  1. Delete the first word in run.c with dw
  2. Change the next word: cw, type anything you want. Once done, press Esc
  3. Undo all your changes using u

Operators can be thought of as verbs that describe the action you want to perform on the text (e.g. delete, change). Motions can be thought of as the location of the text you want to perform the action on (e.g. word, line, etc). The subsequent sections will define a substantial subset of operators and motions that will make you efficient in Vim.

Motions: Say Where

The keystrokes described in this section only move the cursor. It might seem weird why there is a need to have such an expressive language for just moving the cursor. You’ll see its power when you combine it with operators.

Arrow Keys

You can use h, j, k, l, for left, down, up, right respectively. It isn’t that important to switch to hjkl right away. If you don’t feel comfortable using hjkl, it’s okay to stick to the arrow keys (for now) 1.

Vertical Movement

The only other vertical movement discussed here is G. When prefixed with a number, it goes to that line number.

CommandDescriptionMnemonic
GGoes to last lineGo
{line number}GGoes to line numberGo

Try these in run.c.

  1. Press G
  2. Press 12G. You should now be on int row = 3
  3. Get back to the first line of the file

  • What does 3G do?

  • How do I go to the first line of the file?

Moving Within a Line

You can use the following keys to go to the front and end of line.

CommandDescription
0Beginning of line
$End of line
^First non-whitespace character

Tip

If you are familiar with regex, ^ and $ mean the start and end of line respectively.

While you might be able to use the Home and End keys to go to the start and end of a line, there does not seem to be an equivalent key for ^.

Put your cursor on the line int row = 3, which is indented.

  1. Press $
  2. Press 0
  3. Press ^

How does 0 differ from ^?

Let me tell you about _

There is another way to reach the first non-whitespace character: _. If you find it easier to type than ^, use it instead. It even comes with a counterpart, g_, which goes to the last non-whitespace character of the line. _ and ^ may seem the same, though, except that paired with an operator, _ acts differently.

Jumping to a specific character

Another interesting and powerful command is the t and f commands.

CommandDescriptionMnemonic
f{character}Finds next {character}find
t{character}Jumps till {character}till

In run.c, go to the second line, which should read: #include <stdio.h>

  1. Type t< and notice how the cursor is 1 character before the <
  2. Type f< and notice how the cursor is on the <

Can you now explain the difference between t and f?

Moving by Words

This section describes mainly how to traverse by words.

CommandDescriptionMnemonic
wStart of next wordword
eEnd of next wordend
bStart of previous wordbackward

In most editors, you can achieve this by pressing Ctrl in conjunction with the arrow keys. However, there does not appear to be an equivalent to the e command.

Try these on the long comment on the first line of run.c.

  1. Press w a few times
  2. Press 0, then press e a few times
  3. Press b a few times to walk back the other way

How is e different from w?

Operators: Say What

Warning

Operators do not work on their own. They have to be paired with motions or text objects.

Operators describe the verb on the text. Here we shall introduce two operators: c and d, which stand for change and delete (or disintegrate) respectively.

As mentioned previously, a command is made up of an operator and motion.

Do you know the keys to perform the following actions?

  • Delete until end of word

  • Change until f

  • Delete until end of line

What is the difference between c and d? Try dw and cw on the same word and compare what happens after each.

Now run some of them against real code. Go to the line printRow('@', col, 199); and press ^.

  1. Type dt(. What’s left of the line?
  2. Undo with u, then type df( instead. Which character did f take that t left behind?
  3. Undo again, then type d$ to clear from the cursor to the end of the line
  4. Undo once more to restore the line

Notice that you learnt no new keys here. You paired an operator you know with motions you already knew.

Acting on Whole Lines

You can double an operator (e.g. cc or dd) to act on the entire line. This is the most direct way to delete or change a whole line.

Scroll down to the filler at the bottom of run.c, where the line printRow('@', 10, 199); repeats several times.

  1. Put your cursor anywhere on one of them, then press dd. Did it matter where in the line the cursor was?
  2. Undo with u
  3. Press cc on that same line. What happened to the indentation?
  4. Press Esc, then undo again

Doubled operators take a count too:

  1. Put your cursor on one of those lines, press 3dd, then undo

Text Objects: Name the Thing

Operators do not have to be paired only with motions; they can also be paired with text objects. Informally, you can categorise text objects into two categories.

  1. Word-like objects: Objects that are separated primarily by whitespace.
    e.g. words
  2. Surrounding-pair objects: Objects that are defined by opening and closing delimiters.
    e.g. quotes, brackets

Here are some commonly used text objects.

Warning

Just like motions, text objects need to be paired with operators.

Text objectsi varianta variant
Wordiw (inside word)aw (around word)
Single quotesi' (inside ‘quotes’)a' (around ‘quotes’)
Double quotesi" (inside “quotes”)a" (around “quotes”)
Parenthesesi) (inside parentheses)a) (around parentheses)
Square bracketsi] (inside brackets)a] (around brackets)
Curly bracesi} (inside braces)a} (around braces)

Let’s make some changes using text objects.

  1. In run.c, go to the start of the line with the following content (using your favourite way): printf("Printing rectangle...\n");
  2. ci" to change the content inside the quote (Note: Even if you’re not inside quotes, Vim will look ahead for the nearest set of quotes)
  3. Change to something else (e.g. "Hello World\n") then press Esc when done

Since text objects do not work on their own, pair them with the d operator in this exercise.

  • What’s the difference between diw and daw?

  • What’s the difference between di) and da)?

  • Place your cursor in the middle of a word, what’s the difference between bdw and daw? (Hint: Try the dot operator after learning about it later - what’s the difference?)

Copying and Selecting

Copying and Pasting

The operator for copying is y, which stands for yank. Since it is an operator, it can be used with the motions and text objects described above.

What are the commands to copy the following?

  • A word
  • The entire line
  • Three lines at once

After yanking, use p to paste.

Visual Mode: Select the Text

Sometimes it is easier to select the text first, then decide what to do with it. That is what Visual mode is for.

CommandDescription
vSelect by character
VSelect whole lines
Ctrl-VSelect a rectangular text block
EscReturn to Normal mode

Once text is selected, you can use operators such as d, c, or y on the whole selection.

Visual and Visual Line Modes

  1. Place your cursor at the start of a word and press v
  2. Extend the selection with a motion, then press y to yank it
  3. Press V, select three whole lines with j, then press y
  4. Paste the copied lines with p, then undo the paste with u

Repeating and Scaling Up

To apply the same change at multiple locations, some editors adopt the multi-cursors approach. However, in Vim, the workflow is to apply a change in one location, then repeat the change in all other locations.

The Dot Operator

A change such as d3w or dw can be repeated by pressing . (dot).

In run.c, four lines in the main function are missing semicolons. You could fix each one by hand, pressing A, ;, Esc on every line in turn: A;, A;, A;, A;. Is there a better way?

  1. Go to the first line missing a semicolon, int row = 3. Press A, type a semicolon, then press Esc
  2. Move to the next line missing one (they are not all adjacent)
  3. Press . to repeat the entire A; change
  4. Repeat steps 2 and 3 until all four lines end with semicolons

Searching

CommandDescriptionMnemonic
/patternForward search for {pattern}
?patternBackward search for {pattern}
nRepeats latest searchnext
NRepeats latest search in opposite direction

  1. In run.c, search for the word “print”: Type /, followed by print, then press the Enter key
  2. Use n and N to see the next and previous results

Search and Replace

Vim calls search and replace substitution. The basic command is :s/{pattern}/{replacement}/.

CommandDescription
:s/old/new/Replace the first match on the current line
:s/old/new/gReplace every match on the current line
:%s/old/new/gReplace every match in the file
:%s/old/new/gcReplace matches in the file, asking you each time

The % selects every line in the file. The g flag replaces every match on each selected line, while c asks for confirmation before each replacement. This g is a flag for :s; it is different from the :g command introduced later.

In run.c, replace row with row1, inspecting each match before deciding:

  1. Run :%s/row/row1/gc
  2. Press y to replace a match, n to skip one, or q to stop
  3. When you are done experimenting, press u to undo the substitution

Doing Something on Every Matching Line: :g

If we had to comment out every debug line scattered throughout run.c, repeating the same edit by hand would get tedious. Since the same action should happen on every matching line, Vim can perform the whole cleanup in one command:

:g/printf("debug/norm I//

:g/pattern/command finds every line matching pattern, and runs command on it. Here the command is norm I//: run the keys I// on every match at once.

Want to delete the lines instead of commenting them? Just swap the command:

:g/printf("debug/d

This is where Vim stops feeling like an editor and starts feeling like a little language. :g is only the doorway, there is a lot more out there.

Graded Task(s)

The graded tasks are in a separate folder called tutorial-graded, which is in your home folder.

Enter the Graded Task Folder

Change to ~/tutorial-graded before continuing (refer to Tutorial 0 if you’ve forgotten how to do this)

Bob’s refactoring his codebase. He’s sick of making the same repetitive edits over and over again. So he asks you for help, figuring it’s more fun to grind through it with a friend. The codebase is still too big for two. You could round up more friends… but there’s only so many friends to go around.

Bob’s code never works on the first try, so like any good programmer he talks it through with his rubber duck. Over the years, all that debugging rubbed off. The duck gained sentience. And it’s convinced Bob will mess this up. So it left you some utilities.

Sentient Rubber Duck

The grading check is quite stringent, so you must not modify unnecessary things. The duck gives you four tools:

  • verify <filename> checks whether a file matches the expected state
  • changes <filename> shows what you’ve changed from the original, so you can spot anything you broke
  • reset <filename> restores a file to its original state if you mess it up
  • make format formats the code; the checker ignores whitespace differences

Task 1

Bob has been told that a += 1 increments a. He does not believe it.

One example is not enough, either. He wants to watch the value change over and over again. Fifteen times should do it.

In increment.c:

  1. Inside main, declare int a = 5; as the first line

  2. Add these two lines:

    printf("%d\n", a);
    a += 1;
  3. Repeat that pair until it appears exactly 15 times (30 lines total)

  4. Do not replace the repeated statements with a loop or change them into equivalent code

  5. The return 0 statement at the end of the program should remain

  6. Compile and run the program. It should print the numbers 5 through 19, one per line

  7. Run verify increment.c when you are done

Task 2

Bob has already tried the experiment again, but this time for b -= 1. He was even less certain about decrementing, so he repeated it 30 times.

After staring at all those repetitions, he realises that b is not a very helpful variable name. He would rather call it variable_i_am_decrementing.

  1. In decrement.c, rename every use of the variable b to variable_i_am_decrementing
  2. Run verify decrement.c when you are done

Task 3

Bob’s next job: the debug prints. While chasing a bug he dropped printf("debug: ...) lines all over comment.c, and now they clutter everything. He’s not ready to delete them (what if the bug comes back?), so he wants them commented out for now.

  1. In comment.c, comment out every debug line
  2. Run verify comment.c when you are done

Task 4

Of course, the bug came back. Bob wants his debug prints working again, but they’re all commented out in uncomment.c from last time. While you’re at it, he finds the debug tags (e.g. dbg:, [d]) inconsistent, so retag each one to [DEBUG].

  1. In uncomment.c, uncomment every commented-out debug printf line and change its prefix to [DEBUG]
  2. Run verify uncomment.c when you are done

Before You Finish

Run verify for every task and make sure each one is successfully submitted:

  • verify increment.c
  • verify decrement.c
  • verify comment.c
  • verify uncomment.c

The tutorial is not complete until all four tasks have been successfully submitted.

Before you leave: verify your progress

Run this command inside your Tutorial environment:

progress

It shows whether you have successfully completed the tutorial and which individual tasks have been recorded as complete.

The progress command is available only inside a Tutorial environment, not in the general CS1010 WebTop environment.

Footnotes

  1. Another Sriram anecdote: as much as I like Vim, in more than 10 years, I have never switched to h, j, k, and l. I still use arrow keys. This is arguably a heinous crime but honestly, you do you. ↩

  2. Another Sriram anecdote: I do not actually have a .vimrc file because I like training myself to handle default Vim when connected to a random server to fix it. ↩

  3. Sriram’s anecdote: this is how I use Vim, either inside VSCode when programming locally or directly when connected to a remote server. Even this website’s publishing system, Obsidian, supports Vim keys ↩