Skip to main content

expand_grammar_rows

Function expand_grammar_rows 

Source
pub fn expand_grammar_rows(
    handle: &KeymapHandle,
    commands: &CommandRegistry,
    builtins: &Builtins,
    layer: KeymapLayer,
) -> usize
Expand 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 (var extends 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::Action is 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.