← back to latest

Grow a selection outward one bracket level at a time

VISUALvi(i(Grow a selection outward one bracket level at a time

anatomy

v
Start characterwise visual mode at the cursor.
i(
Inner parentheses text object. Selects everything between the nearest ( and ) that surround the cursor, not the parentheses themselves.
i(
Typed again while the selection already covers that inner block, it moves out one level and selects the inside of the next enclosing pair. Each repeat climbs one more level.

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 v and move a few characters before typing i(, the first i( 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 on a, 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(.

lineage

Text objects are a Vim invention. Bram Moolenaar added them in Vim 5.0 in 1998; vi had operators and motions but nothing that selected a region around the cursor. The block objects share one implementation, which is why repeating them in visual mode climbs outward the same way for (, [, {, and <.

-- INSERT --vim-hacks.com028-028-grow-a-selection-one-bracket-at-a-time.html
utf-81,1All