Advanced Motions & Text Objects

Vim Intermediate/Advanced

Chapter 3 — Advanced Motions & Text Objects

Beyond Basic Motions

Learning Vim covered motions as ways of moving the cursor — w to the next word, f{char} to a character, G to the end of the file. Combined with an operator (d, c, y), a motion tells Vim where to stop — but you still have to think in terms of "where," which usually means precise cursor positioning.

Text objects are a different idea entirely: instead of "where," they describe what — "the word," "the paragraph," "the thing inside these parentheses" — regardless of exactly where your cursor happens to sit inside it. This chapter is about that shift, and it's one of the biggest jumps in editing speed once it becomes automatic.

The Text Object Model: Inner vs. Around

Every text object comes in two forms, always following the same pattern: {operator}{i or a}{object}.

i — inner

Just the content itself — the word, the text between the brackets, the paragraph's lines — with no surrounding delimiter or trailing whitespace included.

a — around

The content plus its natural surroundings — the delimiter characters themselves, or trailing whitespace/blank line, depending on the object.

# cursor anywhere inside "hello":
di"     # delete inner quotes  ->  ""          (quotes remain, content gone)
da"     # delete around quotes ->             (quotes AND content both gone)

This i/a distinction is consistent across every text object below — learn it once, and it applies everywhere.

Word & WORD Text Objects

diw     # delete inner word — just the word itself
daw     # delete a word — the word PLUS one side of surrounding whitespace
diW     # delete inner WORD — a whitespace-delimited chunk, ignoring punctuation boundaries

Lowercase word objects respect punctuation as boundaries (user_id and user-id are treated differently by iw, since _ counts as a word character but - doesn't); uppercase WORD objects only care about whitespace, treating my-variable.name() as one single unit. This mirrors the same lowercase/uppercase word-vs-WORD motion distinction from Learning Vim's basic motions — text objects just extend it into operator targets.

The cursor doesn't need to be at the start of the word. Unlike a motion like dw (which deletes from the cursor forward to the next word boundary), diw selects the whole word the cursor is currently anywhere inside — including characters to the left of the cursor. Position no longer has to be exact.

Paragraph & Sentence Text Objects

dip     # delete inner paragraph — the lines of text, not the blank line after
dap     # delete a paragraph — the lines PLUS the trailing blank line separating it from the next
dis     # delete inner sentence
das     # delete a sentence, including trailing whitespace

A "paragraph," to Vim, is any run of non-blank lines — no special Markdown or prose awareness needed. dap is a fast, reliable way to delete an entire block (a function, a config section, a prose paragraph) in one motion, as long as it's separated from its neighbors by a blank line.

Bracket & Quote Text Objects

di(  # or di) — inner parentheses
di{  # or di} — inner curly braces
di[  # or di] — inner square brackets
di"  # inner double quotes
di'  # inner single quotes

These work from anywhere inside the delimiters — you don't need your cursor on the opening or closing character, or even know exactly where they are. This is the single biggest practical upgrade over manual motions like f(: no hunting for the matching bracket first.

Nested delimiters use the cursor's current level by default. Inside outer(middle(inner)), with the cursor on inner, di( deletes only inner — the innermost pair the cursor is directly inside. To act on the next level out instead, prefix with a count: 2di( deletes middle(inner)'s contents, treating the second-innermost pair as the target.

Tag Text Objects (HTML/XML)

dit     # delete inner tag — the content between <tag> and </tag>, tags left intact
dat     # delete a tag — the content AND both the opening and closing tags
# cursor anywhere inside:  <strong>Job Reference:</strong>
citJob Ref<Esc>   # ->  <strong>Job Ref</strong>   (only the text content changed)

Tag objects only apply in filetypes where Vim recognizes tag syntax (HTML, XML, and similar) — but for editing markup, dit/dat/cit replace what would otherwise be a fiddly manual selection of exactly the right span of characters.

Combining Operators with Text Objects

Every text object works with every operator — the same small set of building blocks recombines into dozens of practical commands:

CommandEffect
ciwChange inner word — delete the word, drop into Insert mode
ya{Yank around braces — copy the braces and everything inside
va"Visually select around quotes — see the exact span before acting
dipDelete inner paragraph
>i{Indent everything inside braces one level

v (Visual mode) combines with text objects too, letting you inspect the exact selection before committing to an operator — useful the first time you try a new object on unfamiliar content.

A Worked Example

Workflow — changing a function argument buried in nested parentheses

1 /timeout Search to land the cursor anywhere near the target — no need to be precise.
2 ci( Change inner parens — deletes everything between the nearest enclosing ( and ) and enters Insert mode, regardless of where inside them the cursor landed.
3 timeout=30<Esc> Type the replacement and exit Insert mode — done in three total commands, no manual bracket-matching required.

Compare this to the manual-motion equivalent: locate the opening paren with f(, move one right, visually select to the matching close with some combination of % and adjustments, then delete. Text objects collapse that whole sequence into one memorable, position-independent command.

Commands Introduced in This Chapter

Chapter 3 — Command Reference

The model
{operator}i{object}Act on the object's content only ("inner")
{operator}a{object}Act on the content plus its surroundings ("around")
Word objects
iw / awWord — respects punctuation boundaries
iW / aWWORD — whitespace-delimited only
Paragraph & sentence objects
ip / apParagraph — a run of non-blank lines
is / asSentence
Delimiter objects
i( / i{ / i[Inner parens/braces/brackets — works from anywhere inside
i" / i'Inner double/single quotes
{count}i(Target an outer level of nested delimiters
Tag objects
it / atInner/around an HTML or XML tag