ADR 0100: Text objects¶
Status¶
Accepted
Context¶
ciw, di(, ca" — text objects are the most-used part of vim after
motions, and the editor had none. i and a meant only "insert" and
"append", so diw deleted a word's worth of nothing and ci( did not
exist.
They are also the feature where vim's behaviour is least guessable. Half the rules below were not what I expected before running vim and reading the bytes back.
Decision¶
The rules came from vim, not from memory¶
Every case was derived by running the keys through
vim -u NONE -i NONE -N -es with nofixeol and diffing the written
file. Both flags matter and both had already produced a wrong answer
once: without -i NONE, viminfo carries registers between runs; without
nofixeol, vim appends a trailing newline and two identical results
look different.
iw is a run of one character class, not a word — on whitespace it
selects the whitespace, on punctuation the punctuation. iW is a run of
non-blanks.
aw takes the trailing blanks, or the leading ones when there are
none. That second half is why daw on a line's last word removes the
space before it rather than leaving a ragged end.
A bracket object uses the enclosing pair, and the cursor counts as
inside when it is on either bracket. When there is no enclosing pair it
searches forward on the line — di( from column 1 of aaa (bbb) ccc
deletes bbb, and from after the ) does nothing. Quotes behave the
same way, which is how di" works with the cursor before the string.
a" takes surrounding blanks; a( does not. No principle links
them; it is simply what vim does.
An inner block spanning whole lines is linewise¶
The rule I would not have guessed. di{ on
C {
d
} D
leaves C { and } D — the inner line goes entirely. But with the
brace mid-line (A { a / b } B) the same keys join the lines
charwise. The promotion happens only when the { is last on its line
and the } is preceded by nothing but blanks.
ci{ on such a block keeps the closing newline so there is a blank line
to type on — the same split C has from D
(ADR 0099).
i and a are recognised above the operator guard¶
vimApplyNormalKey abandons a pending operator before its switch —
"operator pending, but this is neither a repeat nor a motion". i hit
that and the d was gone before the object could resolve. This is the
third feature to land on that guard, after marks and d'a; it is the
first thing to check when an operator silently does nothing.
Visual mode needed its own case, because there the operator is empty and
that guard never runs. Without it vi(d deleted the single character
under the cursor and looked almost right.
Consequences¶
40 of 40 comparisons against vim match: iw aw iW aW, i( a( ib,
i{ a{ iB, i[ a[, i< a<, i" a", i' a', in Normal and Visual,
under d, c and y, across single-line, nested and multi-line
blocks, plus . repeat and the cases where nothing matches and the
buffer is left alone.
Typing cost is unchanged — the new work is two branches on the i/a
keys and a range computation that runs once per object, none of it in
refreshCache.
Not implemented: ip/ap (paragraph), it/at (tag), and counts
(d2i{). Nothing uses them yet and each is a separate shape.
Quote objects search only within the line, so a string spanning lines is not an object. vim has the same limit.