The fundamentals that make Vim a text manipulation language, not just a text editor.
- The Vim Grammar
- Motions How You Move
- Text Objects Surgical Precision
- The Dot Command Repeat Anything
- Registers 48 Clipboards
- Marks Teleportation Points
- Visual Mode Selection Done Right
- Search Find Anything Instantly
- Undo & Redo Fearless Editing
Vim commands follow a grammar: {operator}{count}{motion/text-object}
d 3 w
│ │ │
│ │ └── motion: word
│ └────────── count: 3 times
└────────────────── operator: delete
Learn 10 operators × 10 motions = 100 commands. This is why Vim scales.
| Key | Operator |
|---|---|
d |
Delete |
c |
Change (delete + enter insert mode) |
y |
Yank (copy) |
v |
Visual select |
> |
Indent right |
< |
Indent left |
= |
Auto-indent |
gU |
Uppercase |
gu |
Lowercase |
g~ |
Toggle case |
gq |
Format/wrap text |
Every operator works with every motion and every text object. Learn them independently, combine them infinitely.
| Key | Motion |
|---|---|
h j k l |
Left, down, up, right |
f{char} |
Jump to next {char} on line |
F{char} |
Jump to previous {char} on line |
t{char} |
Jump till (before) next {char} |
T{char} |
Jump till (after) previous {char} |
; |
Repeat last f/F/t/T forward |
, |
Repeat last f/F/t/T backward |
| Key | Motion |
|---|---|
w |
Next word start |
W |
Next WORD start (space-delimited) |
b |
Previous word start |
B |
Previous WORD start |
e |
End of word |
E |
End of WORD |
ge |
End of previous word |
| Key | Motion |
|---|---|
0 |
Start of line |
^ |
First non-blank character |
$ |
End of line |
g_ |
Last non-blank character |
+ |
First non-blank of next line |
- |
First non-blank of previous line |
| Key | Motion |
|---|---|
{ |
Previous blank line (paragraph up) |
} |
Next blank line (paragraph down) |
% |
Matching bracket () {} [] |
[[ |
Previous section/function start |
]] |
Next section/function start |
| Key | Motion |
|---|---|
gg |
First line of file |
G |
Last line of file |
42G or :42 |
Go to line 42 |
H |
Top of visible screen |
M |
Middle of visible screen |
L |
Bottom of visible screen |
<C-d> |
Half-page down |
<C-u> |
Half-page up |
<C-f> |
Full page down |
<C-b> |
Full page up |
dw " Delete to next word
d$ " Delete to end of line
d} " Delete to next blank line
ct) " Change till closing paren
yf; " Yank to next semicolon (inclusive)
gUw " Uppercase next word
>} " Indent to next blank line
=G " Auto-indent from here to end of fileText objects define a region of text. They come in two flavors:
i= inner (inside the delimiters)a= around (includes the delimiters)
| Text Object | Inner (i) |
Around (a) |
|---|---|---|
" |
ci" → change inside quotes |
ca" → change including quotes |
' |
di' → delete inside single quotes |
da' → delete including quotes |
` |
ci`` → change inside backticks |
ca`` → includes backticks |
( or ) |
ci( → change inside parens |
ca( → includes parens |
{ or } |
di{ → delete inside braces |
da{ → includes braces |
[ or ] |
yi[ → yank inside brackets |
ya[ → includes brackets |
< or > |
ci< → change inside angle brackets |
ca< → includes angles |
t (tag) |
cit → change inside HTML tag |
cat → includes the tags |
| Text Object | Meaning |
|---|---|
iw |
Inner word |
aw |
A word (includes trailing space) |
iW |
Inner WORD (space-delimited) |
aW |
A WORD |
is |
Inner sentence |
as |
A sentence |
ip |
Inner paragraph |
ap |
A paragraph (includes trailing blank line) |
" You're inside: function(arg1, arg2, arg3)
ci( " Delete all args, start typing new ones
" You're inside: "Hello, World!"
ci" " Replace string content, keep quotes
" You're inside: <div class="container">content</div>
cit " Replace tag content, keep tags
" You're inside a paragraph of text
dap " Delete entire paragraph
" You're on: const name = "value";
ciw " Replace just the word under cursor
" Indent inside braces
>i{ " Indent everything inside {}
" Yank a function body
yi{ " Copy everything inside the braces. repeats your last change. The key insight: design your edits to be dot-repeatable.
/pattern<CR> " Find first occurrence
cgn replacement<Esc> " Change it (cgn = change next match)
. " Repeat on next match
. " And the next
n " Skip one (just move, don't change)
. " Change this onecgn is magical because it's a single change operation that includes the search making . repeat both the find and the replace.
A;<Esc> " Append semicolon to end of line
j. " Next line, repeat
j. " And againysiw" " Surround word with quotes (vim-surround)
w. " Move to next word, repeat
w. " And againA change is everything from entering insert mode to leaving it, OR a single normal-mode command that modifies text:
ddone change (delete line)iHello World<Esc>one change (insert text)ciwreplacement<Esc>one change (change word)3xone change (delete 3 chars)>apone change (indent paragraph)
" BAD: Multiple separate changes
i(<Esc>ea)<Esc> " Two changes dot only repeats the second
" GOOD: One atomic change
ysiw) " One surround operation dot repeats it allEvery delete, yank, and change goes into a register. You have dozens of them.
"ayy " Yank line into register 'a'
"bdd " Delete line into register 'b'
"ap " Paste from register 'a'
"bp " Paste from register 'b'
" Uppercase = APPEND to register
"Ayy " Append this line to register 'a'| Register | Contains |
|---|---|
"" |
Last deleted/yanked text (default) |
"0 |
Last yanked text (yank-only) |
"1-"9 |
Delete history (1 = most recent) |
"_ |
Black hole (delete without saving) |
"+ |
System clipboard |
"* |
Selection clipboard (X11) |
"% |
Current filename |
"# |
Alternate filename |
". |
Last inserted text |
": |
Last ex command |
"/ |
Last search pattern |
"= |
Expression register |
" Delete without polluting default register
"_dd " Line gone, registers untouched
" Paste from yank register (ignores deletes)
"0p " Always pastes what you last yanked, not deleted
" Paste in insert mode
<C-r>a " Insert contents of register 'a'
<C-r>" " Insert default register
<C-r>=5*42<CR> " Insert result of expression (210)
<C-r>=system('date')<CR> " Insert shell command output
" View all registers
:registersThe classic frustration: you yank text, delete something, then paste and get the deleted text instead.
Solution 1: Use "0p register 0 always holds the last yank.
Solution 2: This config maps visual-mode paste to not overwrite:
vnoremap p "_dP " Paste over selection without losing yanked textma " Set mark 'a' at cursor
'a " Jump to line of mark 'a'
`a " Jump to exact column of mark 'a'mA " Set global mark 'A' (remembers file + position)
'A " Jump to file and line of mark 'A'Strategy: Use global marks for your working set:
mMMain file you're editingmTTest filemHHeader filemCConfig file
Jump between them instantly: 'M, 'T, 'H, 'C.
| Mark | Position |
|---|---|
`` |
Position before last jump |
'. |
Position of last edit |
'^ |
Position where insert mode was last exited |
'[ '] |
Start/end of last changed or yanked text |
'< '> |
Start/end of last visual selection |
<C-o> " Jump back (older position)
<C-i> " Jump forward (newer position)
:jumps " View jump historyEvery gd, /search, gg, G, 'mark adds to the jump list. <C-o> is your "back button."
| Key | Mode | Selects |
|---|---|---|
v |
Character-wise | Individual characters |
V |
Line-wise | Entire lines |
<C-v> |
Block-wise | Rectangular columns |
" Select then operate
viw " Select word
vip " Select paragraph
vi{ " Select inside braces
V5j " Select 5 lines down
" Then apply operator
d " Delete selection
c " Change selection
y " Yank selection
> " Indent selection
U " Uppercase selection
u " Lowercase selection
: " Run ex command on selection<C-v>jjjI// <Esc> " Insert '//' at start of 4 lines (comment)
<C-v>jjjA;<Esc> " Append ';' to end of 4 lines
<C-v>jjjd " Delete a column
<C-v>jjjr* " Replace column with '*'
<C-v>jjjg<C-a> " Increment numbers: 0,0,0,0 → 1,2,3,4gv " Reselect the last visual selection| Key | Action |
|---|---|
/pattern |
Search forward |
?pattern |
Search backward |
n |
Next match |
N |
Previous match |
* |
Search word under cursor (forward) |
# |
Search word under cursor (backward) |
/\cpattern " Case insensitive (regardless of smartcase)
/\Cpattern " Case sensitive (force)
/\<word\> " Whole word only (word boundaries)
/\vpattern " Very magic (regex without escaping)/\v\w+\( " Find function calls
/\vTODO|FIXME " Find all TODOs and FIXMEs
/\v^\s*$ " Find blank lines
/\v\S+\s*$ " Find trailing whitespace
/\v(.)\1+ " Find repeated characters:%s/old/new/g " Replace all in file
:%s/old/new/gc " Replace all with confirmation
:'<,'>s/old/new/g " Replace in visual selection| Key | Action |
|---|---|
u |
Undo |
<C-r> |
Redo |
U |
Undo all changes on current line |
Vim's undo is a tree, not a linear stack. Every branch is preserved.
<leader>u " Open visual undo tree (undotree plugin)This config enables persistent undo your undo history survives Vim restarts. Close Vim, reopen the file next week, and u still works back to your first edit.
:earlier 5m " Revert to state 5 minutes ago
:later 5m " Go forward 5 minutes
:earlier 10 " Go back 10 changes- Think in text objects Don't "move cursor, select, delete." Think
dap(delete a paragraph). - Compose, don't memorize
d+iw= delete inner word.c+i"= change inside quotes. Same grammar, infinite combinations. - Make it repeatable Design edits so
.does what you want next. - Use counts
5ddnotdd dd dd dd dd.3wnotw w w. - Stay in normal mode Insert mode is for inserting text. Everything else happens in normal mode.
- Trust the undo tree Be bold. Experiment.
ualways has your back.
"Vim is not about typing faster. It's about thinking in text transformations."