Skip to main content

Module dispatch

Module dispatch 

Source
Expand description

Renderer-neutral action dispatch.

Phase 5.5 / slice 5.5.A scaffolding. After Phase 5.4 closed the input side (every KeyEvent → KeyChord → Action path lives in lattice-host), this module is the seam for the output side that 5.5 fills in: Action → state mutation. The renderer’s App::apply body (today ~2.6k LoC of match action { ... } in lattice-ui-tui::app::dispatch) will relocate here sub-slice by sub-slice as 5.5.B → 5.5.H land.

§Why this module exists today

5.5.A defines the surface (Editor::dispatch + DispatchOutcome + RendererSignal) but leaves the body empty. Renderer App structs keep doing all the work in their own apply paths; the stub is a no-op. Fixing the public shape up front means subsequent sub-slices are mechanical moves rather than design decisions, and the future lattice-ui-gpui has a stable function signature to compose against from day one.

Sub-slices populate the stub:

  • 5.5.B – macro-recording capture, partial-chord clear, read-only-help guard (the preamble).
  • 5.5.C – simplest match action { ... } arms (mutate editor fields directly with no helper call). First emission of RendererSignal::Quit.
  • 5.5.D – pure-editor-mutation helpers (clamp_cursor_to_buffer, ensure_cursor_visible, dismiss_popup, …).
  • 5.5.E – ex-command effect handlers (the ~60-variant apply_effect table + the do_* family). First emission of RendererSignal::ThemeChanged (from Effect::SetOption on ui.* keys). 5.5.E.1 scaffolded Editor::handle_effect and migrated the three helper-free arms (Effect::None, Effect::ClearSearchHighlight, Effect::Echo); 5.5.E.2 adds the echo-only ex-command listers (Effect::EchoMarks -> Editor::do_list_marks, Effect::EchoRegisters -> Editor::do_list_registers) and co-moves the preview_register free fn; 5.5.E.3 migrates store_yank to Editor::store_yank, unlocking Effect::Yank; 5.5.E.4 migrates set_selections_blocking + publish_selections_changed to Editor and visual_kind_to_mode to a host-side free fn, unlocking Effect::SelectionChange. Subsequent E.* sub-slices land the remaining helper-bearing arms as their do_* bodies move host-side; the apply_edit_blocking / handle_edits cluster is gated on the render-coupled shift_highlights_for_edit cache and follows the visible-highlights slice (out of scope per 5.5 design doc).
  • 5.5.F – mode-lifecycle helpers (do_open_file_tree / do_open_oil / do_open_hover / …).
  • 5.5.G – final remnants; App::apply collapses to the dispatch call + signal-handling wrapper.
  • 5.5.H – render-coupled cleanup; removes now-vestigial App methods.

Focused design doc: docs/dev/architecture/phase-5-dispatch-extraction.md.

Structs§

DispatchOutcome
Result of Editor::dispatch. Carries the renderer-side side-effects the caller must surface after the host-side state mutation completes.
DisplayBufferRequest
5.5.F.1: payload for RendererSignal::DisplayBuffer. Carries the lattice_help::HelpContent the host built + the category the renderer should dispatch it under. The renderer resolves the category to a lattice_core::ui::display::BufferDisplay (via its existing resolve_display) and routes through the matching surface. Why a struct instead of inline variant fields: keeps the RendererSignal variant size constant when more host-side do_* arms migrate over and want to attach side-channel data (e.g. a buffer-id token the renderer should mirror into its registry on completion); subsequent slices can add fields here without churning every RendererSignal match arm in every renderer.
EffectiveCompletionConfig
Effective insert-completion config for a given language. Materialised by Editor::effective_completion_for from the per-language overrides + global typed options + spec fallbacks. Phase 5.8.AD.4: hoisted from TUI App.
FusedDispatch
Slice I.7 — result of Editor::dispatch_fused, the one-round-trip keystroke apply.
HostDisplayResolver
VM.3g-2: the display geometry gj / gk / g0 / g$ read. Owned, because it crosses the actor — so it carries the wrap width plus row counts for a BOUNDED window of lines around the cursor, never the whole buffer. A display motion walks at most count lines, so that window is all it can reach; a line outside answers 1, which is what an unwrapped line occupies anyway.
HostFoldResolver
VM.3i: zj / zk’s view of the fold table for one dispatch.
PluginMeta
PI.2: prose / short labels for a plugin-API interface’s world-derived direction and host-authored capability, rendered by the :describe-plugin-api / :list-plugin-apis builders. PI.4: resolved metadata for one loaded plugin. The doc is the plugin’s OWN documentation (its embedded WIT world doc-comment, or its manifest doc field) — both fixed at the plugin’s build/package time, so it is extracted ONCE at load and kept here, never re-fetched per :describe-plugin (a component’s embedded WIT is immutable at editor runtime; only a reload re-extracts, PH7.12).
PluginMetaRegistry
PI.3/PI.4: host-side plugin-id → PluginMeta map. Backs both provenance display (SourceLayer::Plugin(id) renders as the manifest name, else <plugin:id>) and the loaded-plugin introspection surfaces (:describe-plugin / :list-plugins). A newtype (not a bare RwLock<HashMap>) so the ServiceRegistry TypeId can’t collide. Registered empty at boot; the Phase-8 plugin loader is the populator (none exists yet — the seam is ready). RwLock gives the interior mutability a post-boot populate needs behind the shared Arc<ServiceRegistry>.
SnapshotIndentResolver
IN.7: answers “how deep should line N sit” for the = operator, over a syntax snapshot captured at dispatch time.

Enums§

CompletionComputeError
5.5.G.23.cmdline: error variants for Editor::compute_completion_state. Routed through refresh_completion_popup to decide whether to keep the popup open with zero candidates (NoMatches) or drop it entirely (other variants — slot moved to a no-completion region).
DoEditOutcome
Outcome of Editor::do_edit. Renderer peers translate each variant into the appropriate side-effect:
ExternalFormatOutcome
IN.8b: what an external formatter run produced, carried back to the editor thread.
IndentSource
IN.2: where predictive indent gets its answer for one keystroke.
RendererSignal
Host-to-renderer side-effect signal.

Constants§

POSITION_HISTORY_CAP
5.5.F.4.2: position-history ring cap, co-located with Editor::push_position_history.
SNIPPET_COMPLETION_KIND_ID
Extension::kind_id discriminant for snippet-sourced candidates (Phase 4.2.g.4). Sidecar metadata decoded by Editor::snippet_meta_for. Phase 5.8.AD.4: hoisted from TUI App.

Functions§

accept_action_to_outcome
Slice 3c.unify.picker-registry-cutover (7d.0). Translate a unified lattice_completion::AcceptAction into the host-internal lattice_picker::PickerAcceptOutcome for dispatch. The two enums encode the same concept; this fn is the bridge until 7e+ retires PickerAcceptOutcome entirely.
accept_action_to_routing_payload
Slice 3c.unify.picker-registry-cutover (7d.1). Inverse of accept_action_to_outcome: build a RoutingPayload that encodes the MRU identity of an AcceptAction. Used when seating an engine-shape SourceRegistration (PreSupplied) into the Picker’s pair-based candidate store — the picker needs ONE routing per candidate for MRU bonus computation, even though the 7d.0 accept dispatch reads accept_action directly.
action_escapes_focused_popup
PIC.2: actions that escape / reconfigure the world BEHIND a focused popup overlay — tab navigation and pane-tree management. A focused popup (State B) is an exclusive overlay surface, not a pane you can navigate away from; these chords are consumed while it holds focus so a stray gt / <C-w>… doesn’t refocus or reshape the buffer behind it (and, for gt, drag the single-slot popup overlay onto the new tab). Motions / scroll / search / dismiss / follow-link are NOT here — they keep operating on the popup’s own cursor.
action_is_document_mutation
doesn’t fall through onto the underlying document.
action_mutates_fold_state
Returns true for actions that mutate the document and therefore must no-op when a read-only help buffer holds focus.
autoread_watch_fingerprint
AR.3: order-independent fingerprint of a desired watch set, used as the cheap “did the set change since the last sync?” gate. Sorting the dirs and each dir’s basenames makes the hash independent of HashMap iteration order.
bound_watch_set
AR.3: bound a desired watch set to at most cap directories. The active buffer’s directory is always kept (the file you’re looking at must be watched); the remainder fill deterministically (sorted) up to cap. Excess dirs are dropped — their buffers rely on the on-activate stat fallback. Pure, so the cap logic is unit-testable without a filesystem.
choose_indent_source
The staleness decision (design §5), as a pure function so the policy is testable without racing the reparse worker.
dedup_rendered_by_text
5.5.G.23.insert-prep: cross-source visual dedup for the insert-completion popup (Phase 4.2.g.7 polish). Keeps the FIRST occurrence of each raw.text; subsequent rows with the same text drop out. Called after the ranker has sorted descending by score, so the surviving row is the highest-ranked entry per text — the buffer-words copy of outer outranks the tree-sitter copy at the spec’s 100/80 priority split, so the popup row for outer carries the buffer-words tag.
effect_mutates
5.5.G.23: True if the Effect produced a buffer mutation. Used by dot-repeat to decide whether to record the invocation — yank-only invocations (vim’s y) are NOT eligible for ., only changes.
effect_mutates_or_yanks
5.5.G.23: True if the Effect indicates an operator-class action (the buffer changed or content was yanked). Used by Visual mode to decide whether to auto-exit after the dispatch — motions in Visual should not exit; d / y / c should.
flatten_workspace_edit
Flatten a WorkspaceEdit into a per-file Vec<(Uri, Vec<TextEdit>)>. Handles both the legacy changes map and the modern document_changes shape (create/rename/delete ops skipped in v1; rename returns plain text edits for ~100% of identifier rewrites).
folding_range_to_fold
Translate an LSP FoldingRange into the renderer-neutral Fold shape. Hashes (start_line, end_line, kind) for stable fold identity across re-fetches. Phase 5.8.AA.i: hoisted from lattice-ui-tui::app::folding_range_to_fold.
normalize_user_path
Tilde-expand + absolutise a user-typed path. ~/rest → $HOME/rest; ~ alone → $HOME; relative paths join the current working directory. Phase 5.8.AA.j: hoisted from lattice-ui-tui::app::normalize_user_path so the host-side do_edit migration can reach it without dragging in the App.
normalize_user_path_with_cwd
Like normalize_user_path but uses the provided cwd for relative path resolution instead of std::env::current_dir().
picker_action_for
Translate a first-party source id into the PickerAction tag. Phase 5.8.AA.n: hoisted from TUI App.
picker_buffer_entry
Translate a host-side crate::buffer_registry::BufferEntry into the picker’s renderer-agnostic lattice_picker::BufferEntry. Pure function on the input + buffer-locals map; called by Editor::build_picker_context for every registry entry.
picker_source_for
Translate a first-party source id into the PickerSource tag the picker primitive stores. Phase 5.8.AA.n: hoisted from TUI App; pure constant mapping.
prefer_aliases_for_command_candidates
5.5.G.23.cmdline: rewrite command candidates from canonical names to user-facing aliases. The parser accepts both forms; this is a pure UX rewrite. Public so the App-side test module’s existing prefer_aliases_* tests can call it through the re-export.
preview_register
Render a register’s content into a one-line preview (truncated and with newlines escaped). Used by Editor::do_list_registers (:reg) and the picker-source register listing. Moved here from lattice-ui-tui::app in 5.5.E.2.
raw_buffer_candidates
Build the buffer-picker candidate set: every entry in the registry, with the active buffer floated to the bottom and tagged (current) in marginalia. Free function because the picker module is renderer-agnostic; this composes both RawCandidate + RoutingPayload shapes against a BufferRegistry borrow. Phase 5.8.AC.1: hoisted from TUI App.
visual_kind_to_mode
Project a grammar VisualKind onto the protocol-side VisualMode. Used when constructing a Selection from a modal-state visual mode so the document actor’s selection set carries the visual flavour. Moved here from lattice-ui-tui::app::visual in 5.5.E.4 alongside the Effect::SelectionChange arm.