Skip to content

ADR 0084: %, */#, and where a search lands

Status

Accepted

Context

Two more motions, and a correction to one already shipped.

Decision

% matches the bracket at or after the cursor

Vim does not require the cursor to be on a bracket: it scans forward along the line for the first one and matches that, which is what makes % usable from the start of a line of code. Nesting is counted, the partner is searched for over the whole buffer, and (), [] and {} are all handled.

As an operator target it is inclusive in both directions, so d% from either bracket takes the pair and everything between. Brackets inside strings and comments are not skipped, because plain vim does not skip them either.

{count}% — vim's "jump to count percent of the file" — is not implemented; a count is ignored.

* and # search for a whole word

The word under the cursor, or the next one on the line when the cursor is not on one. Matching is whole-word, so * on foo skips foobar and xfoo, which needed a m_findWholeWord flag on the match scan that / and the find bar leave off.

They set the search pattern, so n afterwards keeps going — the needle already outlives its panel (ADR 0074), so this came for free.

They also step off the current occurrence deliberately: forward searches from past the word's end, backward from its start. Without that, * on a word lands back on the word it started from.

A search lands on the match, not past it

jumpToMatch() put the cursor at the end of the match and selected it, which is what the find bar's replace acts on. Vim's search puts the cursor on the first character and selects nothing.

So /, ?, n, N, * and # now ask for the non-selecting form. This is a correction to ADR 0074, which got the search working but left it landing a match-length too far along — /beta stopped at column 11 where vim stops at column 7.

The find bar keeps the selecting form, and its replace is byte-identical before and after, checked against the previous build.

Consequences

Nineteen cases were checked against vim 9.2 by running the same keys through vim -u NONE -N and comparing the cursor line and column, or the buffer for the operator cases: % on each bracket kind, from a closing bracket, nested inside and out, off a bracket, unmatched, across lines, with d, y and c, and repeated with .; *, * twice, * then n, #, # twice, # then N, * off a word; and /, n, N, ? for the landing position. All match.

Two comparisons initially looked like failures and were neither: vim's normal! abandons the rest of a sequence when a motion fails, so a failed % swallowed the x after it, and one test sent four cursor keys where vim got five.