Skip to content

ADR 0078: Visual-mode r, and a selection that is off by one

Status

Accepted

Context

r replaces one character (ADR 0076) and R keeps replacing until Escape (ADR 0077). Visual-mode r is the third form: replace every character of the selection at once.

Implementing it surfaced something larger than itself.

Decision

Line breaks inside a selection are left alone

A selection spanning three lines stays three lines. Every other character becomes the target. Verified against vim: VjrX over abc/defg/hi gives XXX/XXXX/hi in both, and a three-line file is still three lines afterwards.

It replaces exactly what visual d would delete

Not what vim would replace. Those are different, and the difference is not in r.

This editor's visual selection is exclusive of the character under the cursor; vim's is inclusive. Measured with d, which predates all of this:

keys ase vim
vd deletes 0 characters deletes 1
vld deletes 1 deletes 2
vlld deletes 2 deletes 3

It follows from the cursor model: the cursor sits at a byte offset here rather than on a character, the gap ADR 0046 named and left open. Every visual operator inherits it — d, y and c are all one character short of vim.

So visual r matches d, and is one character short of vim in charwise Visual for the same reason. Making r alone inclusive would have been worse than the bug: the highlight would show one span and r would act on another, and vd then u then vrX would touch different text.

Linewise Visual is unaffected and matches vim exactly, because the linewise normalisation already snaps the range to whole lines.

. repeats it, by replaying the whole visual command

ADR 0075 said visual operators were not recorded, because vim repeats them over a same-sized region and that looked like a different mechanism. It is not: replaying the keys vlrX from a new cursor re-selects the same span and replaces it, which is a same-sized region. Recording the keys gives vim's behaviour for free.

One fix was needed. vimRecordKey() cleared the recording whenever a key arrived with nothing pending, which in Visual mode is every key — so v, l, r each wiped what came before. It now leaves the recording alone while in Visual mode, because a visual command is one command from the first key to the operator.

Consequences

The off-by-one was fixed straight afterwards in ADR 0079, which also had to correct $ as a motion: the two errors were cancelling each other. What follows is the reasoning as it stood before that.

The off-by-one is documented here but not yet fixed. Fixing it means making the selection inclusive of the cursor character across d, y, c, r and the highlight rendering at once, and deciding what that means for the non-vim selection model the same code serves (ADR 0019). That is its own change, and a larger one than any key.

Visual r with a non-printable target does nothing and leaves Visual mode, rather than replacing with a control character.