In the buffer
The cursor is on the a of admin, inside two levels of parentheses:
if (user.active && (user.role === "admin" || doc.owner === user.id)) {
^
After typing vi(, the selection covers the inner condition. The dashes mark the selected text and ^ marks the cursor at the end of the selection:
if (user.active && (user.role === "admin" || doc.owner === user.id)) {
--------------------------------------------^
After typing i( again, the selection grows to the inside of the outer parentheses:
if (user.active && (user.role === "admin" || doc.owner === user.id)) {
--------------------------------------------------------------^
You are still in visual mode. Type c and ok to replace the whole condition, and the line becomes if (ok) {.
When you would reach for it
You are deep inside a nested expression, a function call wrapped in another call or a condition grouped inside a bigger condition, and you want to act on one of the outer layers. Counting the levels in advance to type v2i( is the kind of arithmetic nobody does correctly mid-thought. Starting with vi( and pressing i( until the highlight covers what you want lets you see each step before you commit to d, c, y, or >.
Gotchas
- The grow-outward behaviour depends on the current selection already matching the object. If you start with
vand move a few characters before typingi(, the firsti(selects the innermost block instead of the next one out. Start the selection with the text object itself. - When there is no further enclosing pair, Vim beeps and the selection stays where it was. Nothing breaks, but the beep goes unnoticed and you may press
i(again expecting a change. - Only brackets that surround the cursor count. In
if (a) {, with the cursor ona,vi{does not select the brace block that opens later on the same line. It searches outward for a{before the cursor, and may select a much larger enclosing block instead. - Quotes do not nest, so
i"does not climb.vi"i"grows once to include the quote characters and then stops. To get from a string to the call around it, switch objects:vi"i(jumps straight to the inside of the surrounding parentheses.
Variants
va(a(
The same climb with the around object. Each repeat selects the next enclosing pair including its parentheses, so the second a( in the example selects (user.active && (...)).
vi{i{
Works on braces across lines. In a nested JSON object, with the cursor on "min" inside "pool": { ... }, the first i{ selects the pool’s keys and the second selects everything inside "database": { ... }, nested block included.
d2i(
No visual mode needed when you already know the depth. A count before the object reaches that many levels out, so this deletes the contents of the outer parentheses in one step. v2i( selects the same text as vi(i(.