~$ delxium

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. Esc always returns here.
  • Insert — where you type text. Enter with i; leave with Esc.
  • 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? Esc Esc. You’re now in Normal mode.
  • Made a mess? u undoes; Ctrl-r redoes; U undoes the whole line.
  • :q! won’t let you quit? That’s :q! to quit without saving; :wq saves first.
  • Forgot a command? :help ciw opens 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.

← all field notes