Change
🌱 This post is in the growth phase. It may still be useful as it grows up.
Vim is hard to learn.
Additionally so because people suck at teaching Vim.
So if you’ve wanted to learn, but haven’t yet, it’s not really your fault 😁.
I propose we learn a little differently.
Instead of starting at the beginning (with the basics),
Let’s start at the end — with Vim’s most powerful macros.
I figure…
🥉 Worst case: you learn nothing.
🥈 Second-worst case: you learn one of Vim’s best capabilities (and nothing else).
🥇 Best case: you master a few commands and get hyped for more more.
Obviously I think you and I can get to that bast case scenario or I wouldn’t have written this guide.
Wanna learn some Vim?
Contents
- Intent: the power of Vim
- How to read this guide
- Change inside quotes
- Change inside brackets, braces, and parentheses
- Change inner word, sentence, or paragraph
- Repeat operations
- Change around
- What is a text object?
- Delete, don’t change
- Count revisited
- The composability of operations
- Use plugins for extended functionality
- Take selection farther with motions
- Debug text object selections with Visual mode
- Common workflows
Intent: the power of Vim
Vim is the most powerful editing tool I’ve ever used.
And it’s powerful because of a simple idea: editing intent.
Think about how much time you spend making text selections with a mouse or arrow keys.
Changing text inside quotes, delete extranious adjectives, moving paragraphs…
You make a text selection, then do something with it.
This is different in Vim.
If we want to change the text in-between quotes, we don’t need have to make a visual selection first.
We communicate our full intent through commands and macros.
ci” does exactly what we want with just four characters.
This guide covers 100 such operations in this guide with practical, real-world editing examples.
How to read this guide
Lessons in this guide include 3 parts:
- A description of the editing intent
- An table of Vim operations that fulfull that intent
- A few considerations, shortcomings, and exceptions to consider
We start with the base operation ci
(change inner).
This is my favorite editing intent, and Vim’s most capable.
Change “inner” is the termonoligy you’ll find in the docs.
But I use “inside” and “in-between” where it sounds better to say.
Finally, every one of these commands is run from Normal mode
.
In normal mode, you’ll type each character in the operation.
Entry speed should not matter in stock Vim/neovim.
Change inside quotes
Change text inside the current (or next) pair of quotes.
Operation | Description |
---|---|
ci’ | change inside single quotes |
ci” | change inside double quotes |
ci` | change inside backticks |
Note: The cursor does not need to be inside the target quotation to be changed. But it does need to be before the quotation.
Change inside brackets, braces, and parentheses
Change text inside the current (or next) pair of braces, brackets, and parentheses.
operation | Alternative | Alias | Description |
---|---|---|---|
ci( | ci) | cib | change inside parentheses |
ci{ | ci} | ciB | change inside curly braces |
ci[ | ci] | change inside square brackets | |
ci< | ci> | change inside angle brackets |
Both opening and closing characters target the same selection.
But I recommend committing to the opening character.
Here’s why:
- Committing to an operation creates stronger muscle memory.
- Minimal, columnar-style keyboards often remove physical representation of closing characters. So choosing the right side could require additional keystrokes, if a columnar-style keyboard is in your future.
Note: Parentheses and curly braces have aliases that are easier to type.
Change inner word, sentence, or paragraph
Change the word, sentence, or paragraph under the cursor. This works indifferent to the cursor’s position inside that selection.
Operation | Description |
---|---|
ciw | change inner word |
ciW | change inner WORD (includes punctuation) |
cis | change inner sentence (determined by punctuation) |
cip | change inner paragraph (determined by whitespace) |
Repeat operations
It can be useful to repeat an operation.
Prefixing an operation with a number will execute that operation the number of times spcefied.
This number is referred to as an operation’s count
.
These are the operations from the previous lesson with various counts.
operation | Description |
---|---|
2ciw | change inner word under cursor and the next |
3ciW | change inner WORD under cursor and next 2 WORDs (includes punctuation) |
3cis | change inner sentence under cursor and the next |
2cip | change inner paragraph under cursor and the next |
Change around
OK. Let’s talk about text selection composition now.
Vim operations are composable.
For the curious, the existance of an inner
distinction suggests an “outer”.
And Vim delivers.
This distinction is around
(think “including”).
- “Change around (including) double quotes.”
- “Change around sentence (including whitespace and punctuation).”
- “Change around block (including curly braces).”
For every inner
operation we’ve learned, there is a corresponding around
operation, which includes characters and/or whitespace.
This table is every ci
command we’ve learned, but using the ca
(around) variation.
Operation | Alternative | Alias | Description |
---|---|---|---|
ca’ | change around (including) single quotes | ||
ca” | change around (including) double quotes | ||
ca` | change around (including) backticks | ||
ca( | ci) | cib | change around (including) parentheses |
ca{ | ci} | ciB | change around curly braces |
ca[ | ci] | change around (including) square brackets | |
ca< | ci> | change around (including) angle brackets | |
caw | change arround word (including whitespace) | ||
caW | change arround WORD (including punctuation and whitespace) | ||
cas | change around sentence (determined by punctuation. including whitespace) | ||
cap | change arround paragraph (determined by whitespace. including whitespace) |
What is a text object?
Text objects are an important concept in Vim.
And one you now have experience with.
i"
(inside double quotes) is a text object.
ab
(around parentheses) is a text object.
iW
(inner WORD) is a text object.
Think of these as queries &mdash or text selections.
I’ve referred to them as “selections” up to this point.
These text objects are composable.
They can be used with commands other than c
(change).
Let’s learn how…
Delete, don’t change
Let’s talk more command composition.
Our base c
(change) command is actually two commands in one: d
(delete) followed by i
(insert).
And anything composed can be decomposed.
So what happens if we swap the decomposed d
command, instead of the (composed) c
command?
We get a new set of operations that delete text objects and keep us in Normal mode.
This is helpful when text objects need only be deleted (not replaced).
Let’s look at the board to see every c
operation, as a d
operation.
Operation | Alternative | Alias | Description |
---|---|---|---|
da’ | delete around (including) single quotes | ||
da” | delete around (including) double quotes | ||
da` | delete around (including) backticks | ||
da( | ci) | cib | delete around (including) parentheses |
da{ | ci} | ciB | delete around (including) curly braces |
da[ | ci] | delete around (including) square brackets | |
da< | ci> | delete around (including) angle brackets | |
daw | delete around word (including whitespace) | ||
daW | delete around WORD (including punctuation and whitespace) | ||
das | delete around sentence (determined by punctuation. including whitespace) | ||
dap | delete around paragraph (determined by whitespace. including whitespace) | ||
di’ | delete inner single quotes | ||
di” | delete inner double quotes | ||
di` | delete inner backticks | ||
di( | ci) | cib | delete inner parentheses |
di{ | ci} | ciB | delete inner curly braces |
di[ | ci] | delete inner square brackets | |
di< | ci> | delete inner angle brackets | |
diw | delete inner word (including whitespace) | ||
diW | delete inner WORD (including punctuation and whitespace) | ||
dis | delete inner sentence (determined by punctuation. including whitespace) | ||
dip | delete inner paragraph (determined by whitespace. including whitespace) |
Count revisited
Now that you know know about commands and text objects,
There are things you need to consider when using counts.
Applying a count runs the command in sequence &mdash as if you typed it multiple, consecutive times.
Consider 3ci” (3x, change inner double quotes).
- Delete existing text in the double quotes and places the insert cursor inside the quotes.
- (Now inside quotes) perform the the same task — resulting in no descernable change.
- (Still inside quotes) perform the the same task — resulting in no descernable change.
This is even more confusing when manipulating words, sentences, and paragraphs.
Consider 3cis (3 times change inner word).
- Delete the sentence under the cursor, ignoring whitespace.
- (Now over the remaining whatespace) perform the same task — deleting that remaining whitespace.
- (Now with a new sentence under the cursor) perform the same task — deleting the next sentence (also ignoring whitespace).
This effect repeats, feeling like an off-by-one error.
For these reasons, repeating over inner
operations is largely flawed.
So I favor around
for when performing counted operations.
Note: See plugins for the ability to add a count to text objects.
The composability of operations
We now have 44 discrete operations, which can be repeated indefinitely. That’s a lot.
So how can we think about them composably?
Let’s start with anatomy.
(<count>) <command> <text object>
Let’s break down a few as examples.
ciw
segment | value | description |
---|---|---|
count | none | (default: 1x) |
command | c | change |
text object | iw | inner word |
2cib
segment | value | description |
---|---|---|
count | 2 | 2x |
command | c | change |
text object | ib | inner parentheses |
3dap
segment | value | description |
---|---|---|
count | 3 | 3x |
command | d | delete |
text object | ap | around paragraph |
Every operation you’ve learned can be decomposed into these three segments.
Use plugins for extended functionality
The mini.nvim/ai
plugin includes additional selection tools and aliases.
Check out their documentation for additional features.
Here are are a few operations that I use often.
Operation | Description |
---|---|
ci* | Change inside (arbitrary) * (useful in Markdown) |
ci_ | Change inside (arbitrary) _ (useful in Markdown, or renaming snake_case variables) |
ci/ | Change inside (arbitrary) / (useful with Regex) |
ci? | Change 2 <interactive: prompt for left and right edges> |
ciq | Change inside quotes (' , " , ` ) |
cinq | Change inside next quotes |
cilq | Change inside last quotes |
cif | Change inside function call |
cia | Change inside argument |
c2ia | Change inside 2nd argument |
d2aa | Delete 2nd argument in function call/definition |
c2it | Change 2 inner tags (atomic selection) |
Take selection farther with motions
We did not cover motions.
Motions are are not text objects.
They are Vim’s way of moving the cursor.
However, like text objects, they can be used with commands to perform operations.
This is a comprehensive (but not exhaustive) list of motions composed with the c
command.
command | alternative | alias | description |
---|---|---|---|
cb | change to beginning of the previous word | ||
cB | change to beginning of the previous WORD (includes punctuation and whitespace) | ||
ce | change to end of the word (includes punctuation and whitespace) | ||
cE | change to end of the WORD (includes punctuation and whitespace) | ||
cf <character> | change through next (find) <charecter> | ||
cF <character> | change through previous (find) <charecter> | ||
cgg | change to start of file | ||
cG | change to end of file | ||
ck | c⬆︎ | change current and previous line | |
cj | c⬇︎ | change current and next line | |
cl | c➡︎ | s | change character under cursor |
ch | c⬅︎ | change character before cursor | |
cH | change to top of screen (excluding padding) | ||
cM | change to the middle of the screen | ||
cL | change to bottom of screen (excluding padding) | ||
cn | change to next search result | ||
cN | change to previous search result | ||
cp | change to previous search result | ||
cP | change to previous search result | ||
ct <character> | change to next <charecter> | ||
cT <character> | change to previous <charecter> | ||
cw | change to start of next word | ||
cW | change to start of next WORD (includes punctuation and whitespace) | ||
c0 | change (from cursor) to start of line | ||
c$ | C | change (from cursor) to the end of the line | |
c# | change to previous word under cursor (word, if none found) | ||
c* | change to next word under cursor (word, if none found) | ||
c% | change to matching character pair (forward or backward) | ||
c^ | change to first non-whitespace character on the current line | ||
c) | change to the beginning of the current sentence | ||
c) | change to the end of the current sentence | ||
c{ | change to the beginning of the current paragraph | ||
c} | change to the end of the current paragraph | ||
c- | change to the first non-whitespace character on the previous line | ||
c+ | change to the first non-whitespace character on the next line |
Note: a few omissions: [[, [], ]], ][, etc. :help object-motions.txt
These are additional text object text selections.
command | alias | description |
---|---|---|
cl | s | change character |
cgn | change the next search pattern match | |
cg_ | change from cursor to the last non-blank character of the line | |
cc | change current line | |
c$ | C | change from the cursor to the end of the line |
There are no interactions with the g
command prefix. Test.
Debug text object selections with Visual mode
You may feel uneasy about text objects.
This is natural as we’re used to visualizing our selections.
This is where Vim’s Visual mode comes into play.
We can replace the c
and d
commands with v
to first perform a visual selection.
Then, with that selection, post-fix the c
or d
command.
Here are a few examples using Visual mode with text objects and motions:
operation | description |
---|---|
viwc | visual select inner word and change |
vapd | visual select around paragraph and delete |
vi”c | visual select inside double quotes and change |
vt*d | visual select to * and delete |
vT-c | visual select backward to - and delete |
Note: Count is not supported in Visual mode. This makes sense when you consider that count repeats commands, it does not compound them. Instead prefixing v
with a number will select 11 ×
Common workflows
HTML/XML
operation | description |
---|---|
cit | change inside tag |
ci” | change inside double quotes |
ct” | change to double quote (to beginning of string attribute) |
cT” | change back-to doble quote (to beginning of string attribute) |
ci< | change tag name |
ct␣ | change to space (handy for changing individual class names and other string attributes) |
cT␣ | change back-to space (handy for changing individual class names and other string attributes) |
ciw | change string attribute segment (separated by non-word characters, e.g., - , _ , . , etc.) |
Note: Remember that that these operations can use d
(delete) and v
(visual) commands and/or a
(arround) text objects selections.
Additional commands with mini.nvim/ai
operation | description |
---|---|
c<count> it | change inside |
cilq | change inside last quotes (universal) |
c<count> inq | change inside next <count> quotes (universal) |
c<count> inB | change inside next <count> curly brarces |
React
All operations from HTML section apply.
operation | description |
---|---|
cib | change inside parentheses |
ciB | change inside curly braces |
ca”{ | change attribute in quotes and insert { to interpolate variable. |
Additional commands with mini.nvim/ai
and [mini.nvim/surround
][mini/surround]
operation | description |
---|---|
cif | change function call arguments |
cia | change argument under cursor (or next) |
<count> cia | change argument at <count> position |
sr”` | surround replace " with ` to allow interpolation |
<count> dia | change argument at position <count> |
daa | (ripple) delete first argument |
d<count> aa | delete argument at position <count> |
c<count> inB | change inside next <count> curly brarces |
srq? | replace surrounding quotes (universal) with <interactive> ({` , `} ) (for changing string to variable interpolation) |
Markdown/prose
[[: go to previous heading ]]: go to next heading ci*/ci_: change inside em/strong ci*/ci_: change inside em/strong cis/cas: change sentance cip/cap: change paragraph