Skip to main content

Module keymap_visual

Module keymap_visual 

Source
Expand description

Visual-mode binding registration + drift-test helpers.

Audit slice 8.e. The second mode migrated off input::translate’s hand-rolled match table; follows the slice 8.d template (register_<mode>_bindings + dispatch_<mode> + drift test against a frozen reference body of the legacy translator).

§Surface

Visual mode is the same chord table for charwise / linewise / blockwise – the kind only changes how Range::Selection resolves at operator-dispatch time (see lattice-grammar::dispatcher §5.2.3). Two block-only exceptions land before the trie lookup:

These are pre-dispatch overrides rather than a separate BindingMode::VisualBlock – the architecture’s eventual model is a minor-mode layer pushed at blockwise entry / popped at exit (see docs/dev/architecture/keymap-architecture.md §5.3); slice 8.e keeps the surgical pre-check until that layer machinery lands. The drift test below pins the kind branch so a future graduation to push_layer is mechanical.

Common-to-all-kinds bindings registered by register_visual_bindings:

  • Exits: <Esc> / v / V -> ExitVisual.
  • Motions (extend the selection): h / <Left> / j / <Down> / k / <Up> / l / <Right> / 0 / <Home> / $ / <End> / ^ / w / b / e / W / B / E / } / { / ) / ( / G. Each binds to CommandInvocation::of(motion.0) – the non-legacy_action path; the dispatcher returns Action::Invoke(command.command.clone()).
  • Operators on selection: an operator acts on the active selection BY DESIGN, so its Visual binding is generated once at operator registration, NOT hand-listed here. Every operator (crate::keymap_normal::register_operator_bindings – builtin d / c / y / > / <, case gU / gu / g~, contributed zn, …) binds its trigger chord to CommandInvocation::of(op.0).with_range(Range::Selection); the operator dispatcher’s range walker resolves Range::Selection against the active visual selection. The ONLY operator entries that remain in register_visual_bindings are the two Visual-only ALIASES x -> delete and s -> change (in Normal x / s are different commands, so they are not the operators’ trigger chord and cannot come from the registration).
  • Text objects (set the selection to the object’s span): i<obj> / a<obj> for every object in the SHARED [crate::keymap_normal::text_object_rows] table – viw, vaw, vi{, vap, vaf (function), vac (class), vaC (comment), … These are TWO-key chords resolved by the same partial-chord machinery Normal mode uses (i / a absorbs into the host’s partial_chord, the object char resolves the pair); see dispatch_visual. Each binds to a bare CommandInvocation::of(tobj.0), which the grammar’s execute_text_object turns into an Effect::SelectionChange spanning the object. There is ZERO per-object code: the binder iterates the shared table, so Visual and the Normal operator-pending resolver can never drift.

Slice 8.e’s net win: every motion / operator binding moves off the legacy_action bridge and onto a real CommandInvocation. Only the three ExitVisual shapes plus the two block-only Enter* paths still carry a legacy_action – they don’t have a CommandInvocation peer today.

Functions§

dispatch_visual
Dispatch a Visual-mode key event through the keymap registry.
register_visual_bindings
Register every chord the legacy input::translate_visual recognised into the supplied handle’s Builtin layer under BindingMode::Visual. Called at App startup; the registration captures builtins’s motion / operator ids by value, so the resulting BoundCommands never re-resolve at lookup time.