Working Code
Imagine this JavaScript code is open in Vim:
const greeting = "Hello, World!";
function add(a, b) {
return a + b;
}
With the cursor anywhere on Hello, try these commands:
ci" → delete the content inside the quotes and enter Insert mode
result: const greeting = "|"; (| is the cursor)
di( → delete only the content inside the parentheses
result: function add() {
daw → delete the word under the cursor + surrounding whitespace
Text objects work like tweezers. No matter where the cursor sits, Vim recognizes the structure and grabs exactly the right range.
Try It Yourself
Edit this HTML code:
<div class="container">
<p>Old content.</p>
<span style="color: red">Warning message</span>
</div>
- Place the cursor anywhere on
Old content.and presscit. Only the text inside the tag changes. - Place the cursor inside
"container"and pressci". You can change just the class name. - On
style="color: red", pressda"to delete the value and the quotes together.
"Why?"
inner vs. around
Text objects come in two flavors: i (inner) and a (around).
i(inner): content only — like tweezers extracting only the insidea(around): content + surrounding delimiters — grabs the whole thing
"Hello, World!"
di" → "" (deletes content inside quotes)
da" → (deletes content including quotes)
diw → keeps surrounding whitespace (word only)
daw → removes surrounding whitespace (word + whitespace)
Key text objects
| Text object | inner (i) | around (a) | Description |
|---|---|---|---|
| Word | iw | aw | Whitespace-separated word |
| Double quotes | i" | a" | Content inside "..." |
| Single quotes | i' | a' | Content inside '...' |
| Parentheses | i( or ib | a( or ab | Content inside (...) |
| Braces | i{ or iB | a{ or aB | Content inside {...} |
| Brackets | i[ | a[ | Content inside [...] |
| HTML tag | it | at | Content inside <tag>...</tag> |
| Paragraph | ip | ap | Paragraph separated by blank lines |
| Sentence | is | as | Sentence ending in . |
Real-world before/after
ci" — change string content:
Before: const name = "Alice";
cursor here ──^
type ci"World then Esc
After: const name = "World";
di( — delete function arguments:
Before: calculateTotal(price, tax, discount)
cursor here ──^
di(
After: calculateTotal()
dap — delete a paragraph entirely:
Before:
This is the first paragraph.
It spans multiple lines.
This is the second paragraph.
With the cursor on the first paragraph, press dap →
After:
This is the second paragraph.
Cursor position doesn't matter
The great thing about text objects is that they work no matter where the cursor is within the range. Press ci" and you change the entire content of the quotes regardless of cursor position. Unlike motions, you don't have to line up the starting point.
Deep Dive
Combining with operators
Text objects combine freely with the operators from the previous lesson:
diw → delete word
ciw → change word (enter Insert mode)
yiw → yank word
di" → delete inside quotes
ci( → change inside parentheses
ya{ → yank including braces
Formula: operator + i/a + object
Refactoring practice
Open the code below in Vim and edit it using text objects:
const config = {
apiUrl: "https://old-api.example.com",
timeout: 3000,
headers: { "Content-Type": "text/plain" },
};
function fetchData(url, options, callback) {
console.log("Fetching data...");
}
Goals:
- Change the API URL to
"https://new-api.example.com"(useci") - Change Content-Type to
"application/json"(useci") - Delete every argument of
fetchData(usedi() - Change the string inside
console.log(useci")
question: "What's the difference between ci\" and ca\"?" options:
question: "Given getData(id, name, email) with the cursor on name, what's the result of di(?" options:
question: "Which command changes only the text inside the HTML tag <p>Hello</p>?" options: