pub fn expand_grammar_rows(
handle: &KeymapHandle,
commands: &CommandRegistry,
builtins: &Builtins,
layer: KeymapLayer,
) -> usizeExpand description
Derive the rows a grammar binding implies but nobody wrote: a motion’s Visual / Select / operator-pending peers, and a text object’s.
§Why this exists
A motion is an nvo command — vim’s word for “lives in Normal, Visual and
operator-pending”, and the shape paramount-goal #3 asks for when it says the
grammar IS the public command API. Lattice used to get that by HAND-LISTING
the same motions in four places: [motion_rows] consumed by
register_normal_bindings, register_operator_bindings,
keymap_visual, and keymap_select.
Four copies of a list drift, and they did, silently. gg, f / F / t /
T, <C-d> / <C-u> and <PageUp> / <PageDown> were all registered
straight into the Normal binder without ever reaching that table, so vgg,
vf), v<C-d> and dgg were dead — not because Visual could not extend a
selection (it extends it from Editor::cursor at the end of every dispatch,
so ANY reachable cursor-mover works), but because the chord resolved to
nothing there. A plugin motion had it worse: bind_mode_keymap binds one
declared binding-mode and stops, so org’s [[ moved the cursor in Normal
and did nothing in Visual.
So the mode-set stops being a list and becomes a DERIVATION: whatever the command is decides where it is live, uniformly for builtin, host-mode and plugin bindings. Two mechanisms carry it (keymap-architecture.md §15): the keymap mirrors a motion’s Visual and Select rows at every write (VM.4), and this pass adds the rows that need the operator vocabulary.
§Why the operator half is here and not at bind time
Operator rows need Builtins, the host-resolved operator ids, which live
downstream of lattice-plugin-host, so lattice-keymap can’t produce them.
The host runs the pass instead, once per layer, after that layer’s bindings
exist. That framing is also the honest one: the host applies its UNIVERSAL
operator vocabulary to a contribution, exactly as it does for builtins,
while the contributor still declares only chord + command.
register_operator_bindings is pub for the same shape of reason (N.1.3,
the provider-contributed zn operator).
A motion’s Visual and Select rows used to be written here too. They need
only the command’s kind, which the keymap can ask, and a pass that runs at
two moments missed every write outside them: a re-pushed mode layer,
init.rs, plugin register-binding. VM.4 moved them into the keymap.
§What it does per binding
Reads the layer’s Normal trie, and for each terminal binding whose command
resolves in commands to:
Motion— keeps the Normal binding (]]still moves on its own) and adds<op-prefix><chord>for every composable operator. Its Visual and Select rows come from the keymap itself (VM.4).TextObject— REPLACES the Normal binding, because a text object invoked standalone in Normal means nothing, and adds a Visual row (varextends the selection) plus<op-prefix><chord>. No Select row: a text-object path starts with a printable, and in Select a printable overtypes (select-mode.md §4).- anything else — left alone. Note that a cursor-moving command registered
as
CommandKind::Actionis NOT a motion as far as this pass is concerned; that is a statement about the command, not about the keymap.
§Bind-if-absent
A derived row NEVER overwrites one somebody wrote deliberately. Visual’s x
/ s / r aliases, the find-char paths’ Args::Char capture routing under
an operator, a mode’s own Visual override — all of them are explicit
statements, and a default that clobbers them is worse than no default. The
pass snapshots each mode’s existing paths first and only fills gaps, which
also makes it idempotent: re-running adds nothing, so a plugin reload cannot
accumulate rows and boot order stops mattering.