vim · editor · productivity
Vim: A Pocket Companion
Stop fighting the editor. The moves that matter — learned by building a small Python program without ever leaving home row.
Vim feels hostile for about a week, then it disappears — you stop thinking about
editing and just edit. This is the field guide to that week. We’ll learn the moves
that matter by building one real thing: todo.py, a tiny command-line task tracker.
Every key below earns its place by doing actual work on that file.
$ vim todo.py
The one idea: modes
Vim isn’t a text box. It’s a small language for editing, and it has modes — the same key means different things depending on which mode you’re in. Master this and everything else follows.
- Normal — where you move and operate. You start here.
Escalways returns here. - Insert — where you type text. Enter with
i; leave withEsc. - Visual — where you select. Enter with
v. - Command-line — where you run
:commands (save, quit, search-replace).
Live in Normal mode
Beginners stay in Insert mode and treat Vim like Notepad. The whole point is the
opposite: spend your time in Normal mode, drop into Insert for a quick burst, then
Esc straight back out. If in doubt, hit Esc.
Getting text in
We have an empty todo.py. Press i to enter Insert mode and type the skeleton:
import sys
tasks = []
def add(task):
tasks.append(task)
Then Esc. The commands for entering Insert mode are worth learning as a set —
they place the cursor for you:
| Key | Enters insert… |
|---|---|
i / a |
before / after the cursor |
I / A |
at the first non-blank / end of the line |
o / O |
on a new line below / above |
o is the one you’ll use constantly — open a fresh line below and start typing.
Moving without arrow keys
Movement is the heart of it. Your right hand stays on home row:
h ← left j ↓ down k ↑ up l → right
But character-by-character is the slow way. Move in meaningful units:
| Key | Moves to |
|---|---|
w / b |
next / previous word |
0 / $ |
start / end of line |
gg / G |
top / bottom of file |
} / { |
next / previous blank line (paragraph) |
% |
matching bracket ()[]{} |
Ctrl-d / Ctrl-u |
half-page down / up |
In todo.py, jump to the def add line with /def add + Enter, hit % on the
( to bounce to its closing ), then gg to fly back to the top. You’re navigating
by structure, not by holding a key down.
Counts multiply motions
Almost any motion takes a count. 3w jumps three words, 5j drops five lines,
2} skips two paragraphs. Vim’s grammar is count + motion.
Editing is a grammar: verb + motion
Here’s the payoff. Operators (verbs) combine with motions (where) to form edits:
d = delete c = change y = yank (copy)
So dw = delete word, d$ = delete to end of line, y% = yank to matching
bracket. The grammar composes: once you know a verb and a motion, you know their
combination for free.
dd delete the whole line yy copy the whole line
ciw change inner word p paste after the cursor
cc change the whole line u undo Ctrl-r redo
x delete one character . repeat the last change
Back in todo.py: we typed tasks.append(task) but want tasks.append(task.strip()).
Put the cursor on task inside the parens and press ciw — it deletes the word and
drops you into Insert mode — type task.strip(), then Esc. One motion, exact edit.
Meet the dot: your force multiplier
. repeats your last change. Rename task to item in three places: do the first
with ciw item Esc, then jump to the next with n (after /task) and just press
.. Build a small repeatable edit, then spam .. This is the single biggest speedup
in Vim.
Text objects: edit inside things
Motions move; text objects select structured regions — perfect for code. They read
as i (inner) or a (around) + a target:
ciw change inner word ci( change inside parentheses
ci" change inside quotes di{ delete inside braces
ca( change around parens (incl. the brackets) yi] yank inside brackets
Our add() takes one argument; to rewrite everything between the parens, put the
cursor anywhere inside (...) and press ci( — the arguments vanish and you’re typing
the new ones. You never had to select by hand or aim at the exact characters.
Search, and search-and-replace
# search: / forward, ? backward; n / N for next / previous match
/append
Substitution is a : command — and it’s how you do project-wide renames:
:s/task/item/ replace first 'task' on this line
:s/task/item/g every 'task' on this line
:%s/task/item/g every 'task' in the file
:%s/task/item/gc ...asking for confirmation each time
Renaming the task parameter to item across todo.py is one line:
:%s/\<task\>/item/g (the \<...\> matches whole words only, so tasks is left alone).
Visual mode: when you’d rather see the selection
Some edits are easier when you can see what you’re grabbing.
v character-wise select V line-wise select
Ctrl-v block (column) select
Select with motions, then hit an operator: Vjjd selects three lines and deletes them;
vi(y visually selects inside the parens and yanks. Block mode is the party trick —
Ctrl-v, move down several lines, I# Esc comments out a whole column at once.
Working across the file
A real edit session needs more than one window:
:e file open another file :w write (save)
:sp / :vsp split horizontally / vertically :q quit :wq save & quit
Ctrl-w h/j/k/l move between splits :x save & quit (only if changed)
Split todo.py with :vsp test_todo.py to write a test beside the code, and hop
between them with Ctrl-w l / Ctrl-w h.
A starter ~/.vimrc for Python
A few lines make Vim feel modern without drowning in config:
syntax on
set number relativenumber " hybrid line numbers — great for counts (5j)
set expandtab tabstop=4 shiftwidth=4 " 4 spaces, like PEP 8
set autoindent smartindent
set incsearch hlsearch ignorecase smartcase
set scrolloff=4 " keep context around the cursor
relativenumber is the secret weapon: it shows how far each line is from the cursor,
so the count for j/k/d is always right in front of you.
Then, plugins — but not yet
Once the motions are muscle memory, add a plugin manager (vim-plug) and a few
essentials: fzf for fuzzy file-opening, vim-fugitive for git, and an LSP client
for completion. Don’t start here — a pile of plugins won’t help if you’re still
fighting hjkl.
When you’re stuck
- Stuck in a weird mode?
EscEsc. You’re now in Normal mode. - Made a mess?
uundoes;Ctrl-rredoes;Uundoes the whole line. :q!won’t let you quit? That’s:q!to quit without saving;:wqsaves first.- Forgot a command?
:help ciwopens the manual for any key or command.
Pocket cheat-sheet
| Do | Keys |
|---|---|
| Insert before / after / new line | i / a / o |
| Word / line ends / file ends | w b / 0 $ / gg G |
| Delete / change / yank + motion | d / c / y |
| Whole line: delete / copy / paste | dd / yy / p |
| Change inside word / parens / quotes | ciw / ci( / ci" |
| Repeat last change | . |
| Undo / redo | u / Ctrl-r |
| Search / next | /text / n |
| Replace in file (confirm) | :%s/old/new/gc |
| Split / switch / save / quit | :vsp / Ctrl-w l / :w / :q |
Spend a week editing one real file this way and the keys stop being keys — they become
how you think about changing text. vimtutor (run it from your shell) is the best 30
minutes you can spend; :help user-manual is the rest of the iceberg.
Get new field notes by email
Occasional, practical write-ups on building and shipping software. No spam — unsubscribe anytime.