Skip to main content

Editor

Struct Editor 

Source
pub struct Editor {
Show 294 fields pub publish_cache: Mutex<PublishCache>, pub macros: HashMap<char, Vec<Action>>, pub macro_recording: Option<MacroRecording>, pub last_played_macro: Option<char>, pub unnamed_register: Option<UnnamedRegister>, pub marks: HashMap<char, Position>, pub registers: HashMap<Register, UnnamedRegister>, pub yank_ring: YankRing, pub pending_register: Option<Register>, pub position_history: Vec<PositionEntry>, pub position_history_cursor: usize, pub error_list: ErrorList, pub compilation_severity: Arc<HashMap<BufferId, Arc<Vec<(u32, GutterSeverityLevel)>>>>, pub compilation_location_lines: Arc<HashMap<BufferId, Arc<Vec<(u32, u32, u32)>>>>, pub code_block_lines: Arc<HashMap<BufferId, Arc<Vec<u32>>>>, pub provider_diff_signs: Arc<HashMap<BufferId, Arc<DiffSignMap>>>, pub compilation_theme_colors: Arc<(u32, u32)>, pub recent_files: Vec<PathBuf>, pub tag_stack: Vec<TagStackEntry>, pub pending_tag_origin: Option<TagStackEntry>, pub search_line: Option<SearchLine>, pub pending_prompt_submit_action: Option<String>, pub pending_prompt_buffer_name: Option<String>, pub pending_transient_argument: Option<PendingTransientArgument>, pub last_search: Option<LastSearch>, pub current_match: Option<Range>, pub all_matches: Vec<Range>, pub substitute_preview: Option<SubstitutePreview>, pub command_line_decorations: Option<CommandLineDecorations>, pub pending_count: u32, pub op_count: u32, pub visual_anchor: Option<Position>, pub last_change: Option<CommandInvocation>, pub last_visual: Option<LastVisual>, pub last_find: Option<LastFind>, pub replace_history: Vec<ReplaceEntry>, pub auto_indent_line: Option<u32>, pub formatprg_deprecation_noted: bool, pub last_insert: Option<String>, pub pending_block_insert: Option<PendingBlockInsert>, pub recording_insert: Option<String>, pub popup_buffer: Option<BufferId>, pub prev_pane_for_popup: Option<PrevPaneState>, pub help_split_pane: Option<PaneId>, pub bury_target: Option<PrevPaneState>, pub popup_placement: PopupPlacement, pub popup_focused: bool, pub popup_anchor: Option<Position>, pub popup_doc_scroll_at_anchor: u32, pub popup_scroll: u32, pub popup_cursor: Position, pub popup_viewport_height: u32, pub popup_viewport_width: u32, pub completion_docs_buffer: Option<BufferId>, pub preview_buffer: Option<BufferId>, pub completion_docs_viewport_height: u32, pub completion_docs_viewport_width: u32, pub band_buffer: Option<BufferId>, pub band_viewport_height: u32, pub band_viewport_width: u32, pub curswant: Option<Curswant>, pub curswant_claimed: bool, pub curswant_report: Arc<Mutex<Option<Curswant>>>, pub focus_stack: Vec<MinibufferFocus>, pub popup_focus_depth: Option<usize>, pub last_message: Option<EchoMessage>, pub messages: Arc<Mutex<MessagesRing>>, pub pending_message_event_rx: Option<UnboundedReceiver<MessagePushed>>, pub pending_redraw: bool, pub command_history: Vec<String>, pub command_history_cursor: Option<usize>, pub command_history_pending: Option<String>, pub search_history: Vec<String>, pub search_history_cursor: Option<usize>, pub search_history_pending: Option<String>, pub auto_submit_after_chord: bool, pub chord_capture_seq: Vec<KeyChord>, pub lang_registry: Arc<LangRegistry>, pub syntax: Option<SyntaxHandle>, pub last_parsed_text_version: u64, pub pending_syntax_edits: Vec<EditDelta>, pub last_synced_syntax_version: u64, pub last_seen_text_version: HashMap<BufferId, u64>, pub pending_catalog_change_rx: Option<UnboundedReceiver<LanguagesRegistered>>, pub pane_tree: Versioned<PaneTree>, pub tabs: Versioned<Vec<TabSlot>>, pub active_tab: usize, pub picker_open_target: OpenTarget, pub picker_root: Option<PathBuf>, pub picker_fill_target: Option<FillTarget>, pub picker_fill_replace: Option<(usize, usize)>, pub stashed_picker: Option<Picker>, pub pending_theme_preview_restore: Option<(Palette, Vec<(ElementName, StyleSpec)>)>, pub document: ActiveDocument, pub snapshot_cache: SnapshotCache, pub picker: Option<Picker>, pub folds: Vec<Fold>, pub fold_registry: Arc<Mutex<FoldRegistry>>, pub _boot_tick_registrations: Vec<TickCallbackRegistration>, pub pane_groups: Vec<PaneGroup>, pub scrollbind_group_id: Option<PaneGroupId>, pub diffthis_group: Option<BufferId>, pub diffthis_members: Vec<PaneGroupMember>, pub picker_registry: PickerRegistryHandle, pub picker_mru: PickerMruIndex, pub picker_mru_path: Option<PathBuf>, pub pending_picker_init: Option<PendingPickerInit>, pub pending_picker_accept: Option<PendingPickerAccept>, pub pending_transient_build: Option<PendingTransientBuild>, pub live_picker_query: Option<LivePickerQueryState>, pub pending_picker_query: Option<String>, pub preview_overrides: HashMap<PaneId, PreviewOverride>, pub preview_overrides_version: u64, pub pane_buffer_history: HashMap<PaneId, PaneBufferHistory>, pub current_dir: Option<PathBuf>, pub config: Arc<ConfigRegistry>, pub option_cache: OptionCache, pub mode_registry: ModeRegistryHandle, pub builtin_sign_ids: BuiltinSignIds, pub invocation_runners: HashMap<ModeId, InvocationRunnerFn>, pub services: Arc<ServiceRegistry>, pub mode_guards: GuardStoreHandle, pub active_modes: Versioned<HashMap<BufferId, ActiveModes>>, pub deferred_mode_activations: Vec<(BufferId, ModeId)>, pub buffer_locals: Versioned<HashMap<BufferId, BufferLocals>>, pub resolved_options: HashMap<BufferId, ResolvedOptions>, pub on_disk_fingerprints: HashMap<BufferId, OnDiskFingerprint>, pub resolved_options_version: u64, pub buffer_local_overrides: HashMap<BufferId, OptionOverrideSet>, pub option_change_rx: Option<UnboundedReceiver<Event>>, pub help_topics: HelpTopicRegistryHandle, pub builtin_element_ids: BuiltinElementIds, pub modal: ModalState, pub partial_chord: Vec<KeyChord>, pub registry: CommandRegistryHandle, pub event_bus: Arc<EventBus>, pub builtins: Builtins, pub action_ids: ActionIds, pub keymap: KeymapHandle, pub completion_popup_layer: Option<LayerId>, pub completion_registry: CompletionRegistry, pub completion_state: Option<CompletionState>, pub insert_completion: Option<InsertCompletionState>, pub snippet_registry: Arc<ArcSwap<SnippetRegistry>>, pub snippet_activation_policy: SnippetActivationPolicyHandle, pub global_action_handler_regs: Vec<ActionHandlerRegistration>, pub insert_completion_snippet_meta: Vec<SnippetCandidateMeta>, pub completion_accept_freq: HashMap<(String, CandidateKind), u32>, pub pending_config_structural_sections: BTreeMap<String, Table>, pub per_language_completion: HashMap<String, PerLanguageOverrides>, pub completion_in_path_context: bool, pub snippet_session: SnippetSessionHandle, pub session_backed_minors: Vec<SessionBackedMinor>, pub snippet_dirs: Vec<PathBuf>, pub popup_back_stack: Vec<PopupSnapshot>, pub cursor: Position, pub scroll: u32, pub leftcol: u32, pub should_quit: bool, pub viewport_height: u32, pub row_weights: Arc<RowWeights>, pub terminal_width: Option<u16>, pub cell_metrics: Option<CellMetrics>, pub active_buffer: BufferKind, pub document_buffer_id: BufferId, pub buffers: BufferRegistry, pub modeline: ModelineServiceHandle, pub modeline_update_rx: Option<UnboundedReceiver<ModelineElementUpdate>>, pub lsp_selection_chain: Option<LspSelectionChain>, pub lsp_selection_chain_index: usize, pub lsp_document_highlights: Arc<ArcSwapOption<DocumentHighlightCache>>, pub last_document_highlight_issue_cursor: Option<Position>, pub lsp_folds_cache: PerBufferCache<LspFoldsCache>, pub last_recomputed_lsp_fold_version: Option<(BufferId, u64)>, pub last_folded_text_version: Option<(BufferId, u64)>, pub lsp_inlay_hints_cache: PerBufferCache<LspInlayHintCache>, pub wasm_decorations: WasmDecorationState, pub wasm_media: WasmMediaState, pub wasm_context: WasmContextState, pub lsp_document_links_cache: PerBufferCache<LspDocumentLinksCache>, pub lsp_code_lens_cache: PerBufferCache<LspCodeLensCache>, pub lsp_document_color_cache: PerBufferCache<LspDocumentColorCache>, pub lsp_semantic_tokens_cache: PerBufferCache<LspSemanticTokensCache>, pub lsp_pull_diagnostics_cache: PerBufferCache<LspPullDiagnosticsCache>, pub lsp: LspSupervisorHandle, pub lsp_progress_store: LspProgressStoreHandle, pub lsp_diagnostics: DiagnosticsLayer, pub inline_diag_line: Option<u32>, pub inline_diag_deadline: Option<Instant>, pub inline_diag_visible: bool, pub last_partial_chord_event: Option<PartialChordPending>, pub lsp_logger: LspLogger, pub lsp_watcher: Option<LspFileWatcherHandle>, pub lsp_watcher_subscriptions: HashMap<String, CachedSubscription>, pub lsp_watcher_watched_roots: HashSet<PathBuf>, pub autoread_watcher: Option<AutoreadWatcherHandle>, pub autoread_changes: Option<UnboundedReceiver<AutoreadChange>>, pub autoread_watch_fingerprint: u64, pub autoread_pending: HashMap<BufferId, AutoreadChange>, pub autoread_conflict_open: HashSet<BufferId>, pub render_state: Arc<ArcSwap<RenderState>>, pub overlay_wake: OverlayWake, pub syntax_static_overlay_quads_cell: Arc<ArcSwap<StaticOverlayQuads>>, pub cells_matrix_cell: Arc<ArcSwap<CellMatrix>>, pub cells_matrices: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<CellMatrix>>>>>, pub display_matrix_cell: Arc<ArcSwap<DisplayMatrix>>, pub display_matrices: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<DisplayMatrix>>>>>, pub indent_guides: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<IndentGuides>>>>>, pub sticky_contexts: Arc<Mutex<HashMap<PaneId, Arc<ArcSwap<StickyContext>>>>>, pub diff_subsystem: Arc<DiffSubsystem>, pub virtual_rows_matrix_cell: Arc<ArcSwap<VirtualRowMatrix>>, pub virtual_rows_matrices: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<VirtualRowMatrix>>>>>, pub virtual_rows_wake: VirtualRowsWake, pub virtual_row_providers: Arc<VirtualRowProviderRegistry>, pub diff_subscription_guard: Option<DiffSubscriptionGuard>, pub vcs_subscription_guard: Option<VcsSubscriptionGuard>, pub diff_forwarders: Arc<Mutex<HashMap<BufferId, JoinHandle<()>>>>, pub pending_programmatic_diff_rx: Option<UnboundedReceiver<ProgrammaticDiffRequest>>, pub programmatic_diff_accept_paths: HashMap<BufferId, PathBuf>, pub programmatic_diff_panes: HashMap<BufferId, ProgrammaticDiffPanes>, pub diff_fold_seen_revisions: HashMap<BufferId, u64>, pub overlay_fold_seen_versions: HashMap<BufferId, u64>, pub cells_wake: CellsWake, pub last_edit_for_cells: Option<EditDelta>, pub paint_request: Arc<Notify>, pub async_landed: Arc<Notify>, pub lsp_log_event_rx: Option<UnboundedReceiver<LspLogPushed>>, pub lsp_config_tree: Arc<ArcSwap<Table>>, pub buffer_uris: Versioned<HashMap<BufferId, Uri>>, pub pending_apply_edit_rx: Option<UnboundedReceiver<InboundApplyEdit>>, pub pending_show_message_request_rx: Option<UnboundedReceiver<InboundShowMessageRequest>>, pub lsp_pending_show_message_requests: HashMap<u32, InboundShowMessageRequest>, pub lsp_show_message_request_queue: VecDeque<u32>, pub lsp_next_show_message_request_id: u32, pub foreground_cancel: ForegroundCancelHandle, pub pending_hover_rx: Option<UnboundedReceiver<HoverOutcome>>, pub pending_hover_token: Option<CancellationToken>, pub pending_hover_anchor: Option<(Position, u32)>, pub pending_definition_rx: Option<UnboundedReceiver<Vec<Location>>>, pub pending_definition_token: Option<CancellationToken>, pub pending_nav_kind: Option<LspNavKind>, pub pending_references_terminus: ReferencesTerminus, pub refreshing_references_view: Option<BufferId>, pub pending_references_rx: Option<UnboundedReceiver<ReferencesOutcome>>, pub pending_references_token: Option<CancellationToken>, pub pending_symbols_rx: Option<UnboundedReceiver<SymbolsOutcome>>, pub pending_symbols_token: Option<CancellationToken>, pub pending_format_rx: Option<UnboundedReceiver<FormatOutcome>>, pub pending_external_format_rx: Option<UnboundedReceiver<ExternalFormatOutcome>>, pub pending_format_token: Option<CancellationToken>, pub pending_signature_help_rx: Option<UnboundedReceiver<SignatureHelpOutcome>>, pub pending_signature_help_token: Option<CancellationToken>, pub pending_completion_rx: Option<UnboundedReceiver<CompletionOutcome>>, pub pending_completion_token: Option<CancellationToken>, pub pending_completion_items: Option<Vec<CompletionItemRow>>, pub pending_moniker_rx: Option<UnboundedReceiver<String>>, pub pending_rename_rx: Option<UnboundedReceiver<RenameOutcome>>, pub pending_rename_token: Option<CancellationToken>, pub pending_code_action_rx: Option<UnboundedReceiver<CodeActionOutcome>>, pub pending_code_action_token: Option<CancellationToken>, pub pending_code_action_items: Option<Vec<CodeActionRow>>, pub pending_code_action_handle: Option<ServerHandle>, pub pending_selection_range_rx: Option<UnboundedReceiver<SelectionRangeOutcome>>, pub pending_selection_range_token: Option<CancellationToken>, pub pending_document_highlight_token: Option<CancellationToken>, pub pending_folding_range_token: Option<CancellationToken>, pub pending_document_links_token: Option<CancellationToken>, pub pending_code_lens_token: Option<CancellationToken>, pub pending_code_lens_refresh_rx: Option<UnboundedReceiver<LspCodeLensRefresh>>, pub pending_code_lens_items: Option<Vec<CodeLens>>, pub pending_code_lens_server: Option<Arc<str>>, pub pending_document_color_token: Option<CancellationToken>, pub pending_color_presentations: Option<Vec<ColorPresentation>>, pub pending_color_range: Option<Range>, pub pending_inlay_hint_token: Option<CancellationToken>, pub pending_semantic_tokens_token: Option<CancellationToken>, pub pending_pull_diagnostics_token: Option<CancellationToken>, pub pending_diagnostic_refresh_rx: Option<UnboundedReceiver<LspDiagnosticRefresh>>, pub pending_inlay_hint_refresh_rx: Option<UnboundedReceiver<LspInlayHintRefresh>>, pub inlay_refresh_pending: HashSet<BufferId>, pub semantic_tokens_refresh_pending: HashSet<BufferId>, pub pending_semantic_tokens_refresh_rx: Option<UnboundedReceiver<LspSemanticTokensRefresh>>, pub pending_lsp_detach_rx: Option<UnboundedReceiver<LspBufferDetached>>, pub pending_mode_lifecycle_rx: Option<UnboundedReceiver<ModeEvent>>, pub pending_major_entered_rx: Option<UnboundedReceiver<Event>>, pub pending_mode_enablement_rx: Option<UnboundedReceiver<Event>>, pub pending_buffer_option_override_rx: Option<UnboundedReceiver<Event>>, pub pending_provider_view_refresh_rx: Option<UnboundedReceiver<ProviderViewRefreshRequested>>, pub pending_insert_completion_async_rx: Option<UnboundedReceiver<AsyncCompletionOutcome>>, pub pending_insert_completion_async_token: Option<CancellationToken>, pub pending_completion_resolve_rx: Option<UnboundedReceiver<CompletionResolveOutcome>>, pub pending_completion_resolve_token: Option<CancellationToken>, pub pending_renderer_signals: Vec<RendererSignal>, pub pending_renderer_effects: Vec<Effect>, /* private fields */
}
Expand description

Renderer-agnostic editor state.

The renderer-agnostic half of every editor App. Each renderer’s App struct composes one of these alongside its renderer-specific caches. Host-level code (mode lifecycle, dispatch, picker sources, LSP supervisor, …) takes &mut Editor directly; renderer-side code takes &mut App and reaches the editor via app.editor.

Field set grows per-cluster. Each 5.B.x slice migrates a logical cluster of fields here from lattice-ui-tui::App. As clusters land, this struct accumulates state; in parallel, App’s direct field set shrinks. When the migration completes, every renderer- agnostic field on App lives here, every renderer-agnostic method on App lives in this crate’s impl Editor blocks, and App becomes a thin wrapper holding editor: Editor plus renderer-specific caches only.

Clusters landed so far:

  • 5.B.4 – macro recording state (macros, macro_recording, last_played_macro).
  • 5.B.5 – marks + registers (marks, registers, pending_register, unnamed_register).
  • 5.B.6 – position history + tag stack (position_history, position_history_cursor, recent_files, tag_stack, pending_tag_origin).
  • 5.B.7 – search state (search_line, last_search, current_match, all_matches, substitute_preview).
  • 5.B.8 – vim repeat + visual state (pending_count, op_count, visual_anchor, last_change, last_visual, last_find).
  • 5.B.9 – replace + insert state (replace_history, last_insert, recording_insert, pending_block_insert).
  • 5.B.10 – popup (subset) (popup_buffer, prev_pane_for_popup, popup_placement). Skipped: popup_back_stack – holds PopupSnapshot which still lives in lattice-ui-tui::app::popup; follow-up slice moves the snapshot type to host before migrating the field.
  • 5.B.11 – cmdline + echo (command_line, last_message, messages, pending_message_event_rx, pending_redraw, command_history, command_history_cursor, command_history_pending, auto_submit_after_chord).
  • 5.B.12 – syntax (lang_registry, syntax, last_parsed_text_version, pending_syntax_edits, last_synced_syntax_version, visible_highlights, pane_highlights). Skipped: visible_highlights_key – its type VisibleHighlightsKey lives in lattice-ui-tui::app::highlights with pub(super) visibility; follow-up slice promotes it to host first.
  • 5.B.13 – picker (picker, picker_registry, picker_mru, picker_mru_path, pending_picker_init, live_picker_query, previewing). Picker support types (PendingPickerInit, LivePickerQueryState, InFlightLiveQuery, LIVE_PICKER_DEBOUNCE) also moved from lattice-ui-tui::app to lattice_host::state.
  • 5.B.14 – config + modes (config, option_cache, mode_registry, services, mode_guards, active_modes, buffer_locals, resolved_options, buffer_local_overrides, option_change_rx, help_topics, host_theme).
  • 5.B.15 – modal + dispatch (modal, partial_chord, registry, event_bus, builtins, action_ids, keymap, completion_popup_layer).
  • 5.B.16 – active-pane state (subset) (cursor, scroll, should_quit, viewport_height, terminal_width, active_buffer, document_buffer_id, buffers). Skipped: document, snapshot_cache, pane_tree – their types have no natural Default (actor handles + tree-with-root invariants). Follow-up slice removes #[derive(Default)] from Editor in favour of an Editor::new(...) constructor so these can migrate.
  • 5.B.17 – LSP per-buffer caches (lsp_progress, lsp_selection_chain, lsp_selection_chain_index, lsp_document_highlights, last_document_highlight_issue_cursor, lsp_folds_cache, lsp_inlay_hints_cache, lsp_document_links_cache, lsp_code_lens_cache, lsp_document_color_cache, lsp_semantic_tokens_cache, lsp_pull_diagnostics_cache).
  • 5.B.18 – all remaining LSP fields: subsystem handles (lsp, lsp_diagnostics, lsp_logger, plus lsp_log_event_rx, lsp_progress_event_rx, lsp_config_tree, buffer_uris), server-initiated channels (pending_apply_edit_rx, pending_show_message_request_rx, lsp_pending_show_message_requests, lsp_show_message_request_queue, lsp_next_show_message_request_id), and all per- feature request channels (the pending_*_rx / pending_*_token pairs for hover, definition, references, symbols, format, signature-help, completion, moniker, rename, code-action, selection-range, document-highlight, folding-range, document-links, code-lens, document-color, inlay-hint, semantic-tokens, pull-diagnostics, plus the refresh channels and the lifecycle / detach channels). LspSupervisorHandle and DiagnosticsLayer gained placeholder Default impls (dropped-receiver channels; production overwrites in boot.rs). lsp_file_watcher stays on App for now: its inner type LspFileWatcher lives in lattice-ui-tui::app:: lsp_watcher – migrates with a follow-up that moves the watcher into a host module.
  • 5.B.19 – call-site migration for all LSP per-feature request channel fields scaffolded in 5.B.18: updated all self.pending_* / app.pending_* accesses in app/lsp.rs, app/boot.rs, app/picker.rs, app/mode.rs, app/completion.rs to self.editor.pending_*; removed the now-redundant duplicate declarations from App. Completion cluster (completion_registry, completion_state, insert_completion, etc.) stays on App – next slice.
  • 5.B.20 – completion cluster tail + popup back-stack + pending config bucket. insert_completion, snippet_registry, insert_completion_snippet_meta, completion_accept_freq, per_language_completion, completion_in_path_context, active_snippet, snippet_dirs, popup_back_stack (popup #7 tail), and pending_config_structural_sections move from App to Editor. SnippetCandidateMeta moved from lattice-ui-tui::app to lattice-host::state so the sidecar type lives next to the field that owns it; lattice-ui-tui::app re-exports the type for compatibility. After this slice the only fields left on App are the renderer-specific caches (theme, pane_render_registry) plus the LspFileWatcher wrapper – App becomes a thin renderer wrapper.

Fields§

§publish_cache: Mutex<PublishCache>

Perf plan B.4: identity-preserving sub-state cache for build_render_state. Cached Arc<SubState> slots keyed by the u64 version captured from the corresponding Versioned<T> field. std::sync::Mutex (not RefCell) because Editor is shared across threads as Arc<Editor> and therefore must be Sync; uncontested in practice because only build_render_state (called on the actor thread) takes the lock. See crate::render_state::PublishCache for the slot inventory and rebuild contract.

§macros: HashMap<char, Vec<Action>>

Completed macro recordings keyed by register name. Replays go through the dispatch layer’s PlayMacro action handler. v1 records Action streams; insert- mode keystrokes ARE captured (every Action::Insert is recorded), but dot-repeat-style replay of insert content from c/i/a remains a §15 follow-up.

§macro_recording: Option<MacroRecording>

In-flight macro recording. Some while between q<reg> start and the matching q stop; pushed Actions append to actions.

§last_played_macro: Option<char>

The most recently played macro register, for @@ repeat.

§unnamed_register: Option<UnnamedRegister>

Unnamed register – destination of y / d / c, source of p / P. None until something has been yanked.

§marks: HashMap<char, Position>

User-set marks. v1 stores them flat by name (a-z, A-Z, 0-9); uppercase / numbered global marks treat all marks as buffer-local since the v1 TUI runs against a single document.

§registers: HashMap<Register, UnnamedRegister>

Named registers "a-z, "A-Z, numbered "0-"9, etc. Stores content + kind. "" (the unnamed register) is Self::unnamed_register; this map covers everything else.

§yank_ring: YankRing

YR.1: every yank and every delete, newest first. Distinct from registers, which is addressed by name — this is addressed by recency, and it is what the yank picker lists and what YR.2’s "0–"9 projection reads.

§pending_register: Option<Register>

Register selected for the next operator / paste ("a prefix). Consumed-and-cleared by run_invocation (operators) and do_paste (paste). None means use unnamed.

§position_history: Vec<PositionEntry>

Unified position-history ring (DESIGN.md §5.1.1). Every entry is tagged by source so different keybindings walk filtered views of the same data (Ctrl-O / Ctrl-I walk AutoJump + PluginPush; g; / g, walk NamedMark).

§position_history_cursor: usize

Cursor into Self::position_history – the next entry the navigation action would visit.

§error_list: ErrorList

CM.2 (2026-07-22): the error list — a persistent, cross-file list of navigable locations walked by generic :cnext / ]q dispatch. Core/host state shaped like Self::position_history, NOT owned by any mode: compilation (CM.3), diagnostics, and search are producers that populate it via Self::set_error_list. See docs/dev/architecture/compilation-mode.md §3.

§compilation_severity: Arc<HashMap<BufferId, Arc<Vec<(u32, GutterSeverityLevel)>>>>

CM.3c (2026-07-22): per-buffer severity gutter index for the *compilation* buffer, keyed by lattice_core::BufferId. Written by the AppEffect::CompilationGutterSet arm (the off-thread compilation drain’s host-state seam) and snapshotted into RenderState::compilation_severity at publish. Shaped like a producer-fed cache (compilation is the only producer today); the outer Arc lets the publish clone be O(1) and the inner per-buffer Arc<Vec<..>> lets the renderer read wait-free.

§compilation_location_lines: Arc<HashMap<BufferId, Arc<Vec<(u32, u32, u32)>>>>

CM.3c (2026-07-22): per-buffer compilation location-line index for theme-based highlighting. Twin of compilation_severity above. Written by the AppEffect::CompilationLocationLines arm and snapshotted into RenderState::compilation_location_lines at publish. The outer Arc lets the publish clone be O(1) and the inner per-buffer Arc<Vec<(u32,u32,u32)>> lets the renderer read wait-free.

§code_block_lines: Arc<HashMap<BufferId, Arc<Vec<u32>>>>

MC.2b: per-buffer set of source lines inside a fenced/indented code block, for the full-width syntax.code_block background tint. Recomputed from the syntax tree in recompute_folds_because (the same reparse-driven trigger folds ride) and snapshotted into RenderState::code_block_lines. Outer Arc = O(1) publish clone; inner per-buffer Arc = wait-free read.

§provider_diff_signs: Arc<HashMap<BufferId, Arc<DiffSignMap>>>

MG.21a (2026-07-29): per-buffer diff sign maps published by a mode, for buffers whose content is itself a unified diff. Written by the AppEffect::DiffLineSigns arm and merged into RenderState::diff.sign_maps by diff_sign_maps_by_buffer, so the existing line-tint path in both renderers picks them up unchanged.

Kept as a slot separate from the session-derived maps because the two have different lifetimes: a session map is recomputed as the user edits, while these are rewritten only when the mode regenerates the buffer. Sessions win on the (currently impossible) key collision — see diff_sign_maps_by_buffer.

§compilation_theme_colors: Arc<(u32, u32)>

CM.3d (2026-07-22): resolved compilation.location theme colours — published by the mode during activation so the TUI and GPUI renderers read from the theme rather than hardcoding RGB. bg is the location-line background tint; fg is the link-like file-path foreground. Defaults: surface2 bg, blue fg.

§recent_files: Vec<PathBuf>

MRU list of canonical paths the user has opened via :edit (or any path flowing through do_edit). Newest first; deduplicated; capped at MAX_RECENT_FILES. Source for the :recent picker.

§tag_stack: Vec<TagStackEntry>

Vim-style tag stack (DESIGN.md §5.1.1 follow-up). Distinct from the jump list: each “drill-down” navigation (gd / gD / gy / gI and their multi-result picker accept variants) pushes one entry; <C-t> pops the most recent entry. <C-o> walks all jumps chronologically; <C-t> pops only the LIFO tag-style drill-downs.

§pending_tag_origin: Option<TagStackEntry>

Pre-jump origin captured when an LSP nav request fires; transferred to Self::tag_stack on the actual jump (single-result drain or multi-result picker accept). Cleared on picker dismiss / nav cancellation / drain with no results.

§search_line: Option<SearchLine>

In-progress / or ? search. Some only while modal == ModalState::Search(_).

§pending_prompt_submit_action: Option<String>

The action:* name to fire on <CR> while a generic Effect::OpenPrompt prompt is focused (modal == ModalState::Prompt). Set by open_prompt_line, consumed (.take()) by do_prompt_line_submit.

§pending_prompt_buffer_name: Option<String>

OC.3a: the synthetic name the prompt was opened with, held so the submit can hand it back to the action.

open-prompt-payload.buffer-name is documented as the channel a caller uses to “smuggle state through a multi-step flow”, and a NATIVE handler reads it off the prompt buffer it is handed. A plugin action cannot: it receives a buffer-id over WIT and has no way to turn that into a name. So the host carries it back in the fired invocation’s args instead — same channel, reachable from both sides.

§pending_transient_argument: Option<PendingTransientArgument>

MG.17b: the transient a prompt was opened from, held across the surface switch.

A transient’s TransientState already survives flag toggles — the Flag arm mutates it in place and the menu stays open, and the preview closure re-reads it every frame. What it cannot survive on its own is an Argument: the prompt is a different surface that takes the editing buffer and the modal state, so the picker is torn down and something has to own the menu until it can be re-seated. That is this.

Some only between opening an argument’s prompt and its submit or cancel.

§last_search: Option<LastSearch>

Most recent submitted search; consulted by n / N.

§current_match: Option<Range>

Range of the most recent search match, used to draw the primary highlight in the buffer view. Cleared on Esc and on cursor motion.

§all_matches: Vec<Range>

Every occurrence of the most recent search pattern, used to draw the secondary “hlsearch” overlay. Cleared on Esc; persists after submit until the next search.

§substitute_preview: Option<SubstitutePreview>

In-progress substitute preview. Populated as the user types :s/pat...; the renderer overlays match ranges (and the typed replacement once the second / has been entered) so the user sees the substitution before pressing Enter. Cleared when the cmdline closes or the input no longer parses as a substitute (DESIGN.md §5.9.10).

§command_line_decorations: Option<CommandLineDecorations>

MB.4: live : command-line decorations — syntax spans, a validation error, and a parameter hint. Recomputed on every command-line edit (refresh_command_line_decorations, on the actor thread) and published into the modeline render state. None when the command line is closed / empty.

§pending_count: u32

In-progress count prefix being typed (3 of 3w, 12 of 12dd). 0 means “no count typed”. The next Action::Invoke consumes this and resets it to 0.

§op_count: u32

Count latched when an operator key was pressed (2 of 2d3w). Multiplied with the motion’s count (3) to give the final count the operator dispatches with (6). 0 means “no operator count”.

§visual_anchor: Option<Position>

Anchor position when Visual mode was entered. None outside Visual; restored on Esc. The head of the selection follows the cursor; the anchor stays put so the selection extends or contracts as the user moves.

§last_change: Option<CommandInvocation>

Last operator-class invocation that mutated the buffer. . re-dispatches it from the current cursor. v1 records operator + motion / operator + range / Visual-mode operator; insert-mode text replay remains a §5.2.4 gap.

§last_visual: Option<LastVisual>

Last Visual-mode selection extents, captured on exit so gv can re-enter Visual with the same anchor / head / kind.

§last_find: Option<LastFind>

Last f/F/t/T find on this buffer, for ; / ,.

§replace_history: Vec<ReplaceEntry>

Per-Replace-session log of overwritten bytes so backspace can restore the original (rather than deleting). Cleared on entry, pushed on each OverwriteChar, popped on ReplaceUndoLast.

§auto_indent_line: Option<u32>

IN.1: line that was auto-indented and has gained nothing since. Vim strips such indent when the line is left, so o<Esc> leaves no trailing whitespace on a line the user never typed into. Set by note_auto_indent, cleared by the first content typed into the line, consumed by strip_pending_auto_indent on leaving Insert.

§formatprg_deprecation_noted: bool

RF.5: whether the formatprg deprecation note has been emitted this session.

Once, not per format: a note repeated on every :w with formatonsave on is noise the user learns to scroll past, which is the opposite of what a deprecation notice is for.

§last_insert: Option<String>

Text inserted during the most recently completed Insert session. Captured on Esc out of Insert; replayed by dot-repeat after the operator part. None if the last change had no insert phase.

§pending_block_insert: Option<PendingBlockInsert>

In-flight blockwise-visual I / A session. Captured at mode-entry time (block extents + per-line insert column); consumed when Insert exits, at which point the recorded text is replicated to every line in the block other than the top row (the top row’s insert is the recording itself). None outside a block-visual insert.

§recording_insert: Option<String>

Text being captured during the current Insert session. Promoted into Self::last_insert when leaving Insert.

§popup_buffer: Option<BufferId>

Active popup buffer slot, if a popup overlay is open. The concrete content lives in the crate::buffer_registry::BufferRegistry keyed by this id, flagged BufferFlags { listed: false, hidden: true }.

§prev_pane_for_popup: Option<PrevPaneState>

Pane state captured before activating help – used by dismiss_popup to restore the user to whatever buffer / cursor / scroll they came from. Set by the in-pane help activation path; cleared by dismiss.

Popup lifetime only. The bury-back address for an in-pane synthetic buffer lives in Self::bury_target and is deliberately a different field – see there.

§help_split_pane: Option<PaneId>

The pane Editor::open_help_in_split created for a help buffer, when help was opened in its OWN split (not layered over an existing pane). dismiss_popup reads it to decide between CLOSING that pane (help brought it into being, so <Esc> removes it) and restoring the buffer an active-pane help displaced. None for popup / active-pane help and once the pane is gone. Cleared on dismiss.

§bury_target: Option<PrevPaneState>

Where Effect::BuryBuffer (magit’s q) returns the pane after an in-pane synthetic buffer is closed. Written by open_synthetic_buffer_seeded, consumed by Editor::bury_buffer.

Its own field because the two mechanisms have different lifetimes. Both used to share prev_pane_for_popup, and whichever teardown ran first consumed whatever was in the slot regardless of who wrote it. Reported against org-capture: a transient menu was still up (State A) when the *org-capture* draft opened, so the draft’s bury address landed in the shared slot; dismissing the menu then hand-restored the pane to the pre-capture buffer without going through activate_buffer – silently, since only that function echoes. C-c C-c’s Effect::BufferDelete resolves its target from active_pane_buffer_id(), so it deleted the buffer the user came from and left the draft alive, unsaved and unreachable. MG.47 had patched one call site of this on the reasoning that a State-A popup “leaves prev_pane_for_popup as None – so this guard cannot move the pane”; that holds only until something opens a synthetic buffer underneath the popup.

See crates/lattice-host/tests/a_popup_dismiss_must_not_move_the_pane.rs.

§popup_placement: PopupPlacement

Where the popup overlay sits on screen when one is open. Lives on the editor (not on the buffer) because the popup is a generic rectangular surface inside which any buffer kind renders – placement is a property of the popup, not of whatever buffer happens to be its content.

§popup_focused: bool

PU refactor (2026-07-22): decoupled focus-state bool, extracted from active_buffer == BufferKind::Help which was an architectural leak — a focus flag smuggled inside a content-identity field. true when a Steal popup has keyboard focus (State B). Set in focus_help_popup, activate_help_in_pane, open_popup_buffer (Steal); cleared in dismiss_popup. Published in render state so both TUI and GPUI renderers read it directly.

§popup_anchor: Option<Position>

Cursor position snapshot at popup-open time, used as the anchor for CursorAnchored popups. Captured in open_floating_popup / open_popup BEFORE any cursor mutation so the renderer paints the popup at the symbol the user pressed K on, not the cursor’s current position. Issue 2026-05-22 (third triage round): without this the popup follows the cursor — moving with motions rather than staying anchored. None when no popup is open OR for centered popups (anchor irrelevant).

§popup_doc_scroll_at_anchor: u32

Document scroll captured at popup-open time so CursorAnchored renderers can convert popup_anchor.line to a screen row in State B (where self.scroll is the POPUP’s scroll, not the document’s).

§popup_scroll: u32

PU.1a: the popup’s persisted view state when it is NOT the focused buffer (State A), and the stash loaded into self.scroll / self.cursor when focus moves into the popup (State B). Replaces the old HelpBuffer.{scroll,cursor} registry fields now that help content is an actor-backed Document with no per-view cursor of its own. Reset to 0 / ZERO on every popup open; updated by snapshot_active_pane when an in-pane help buffer is stashed.

§popup_cursor: Position§popup_viewport_height: u32

PU.1b-3: inner-rect geometry of the floating popup overlay, fed back from the renderer each frame (mirrors the per-pane set_pane_viewport hand-off). The renderer is the single authority on the popup’s inner rect — it computes it from the buffer area + popup_outer_size + placement — so it pushes the resolved (height, width) here. build_cells_panes reads them to size the synthetic popup-pane DisplayMatrix (wrap width = popup_viewport_width). Both 0 until the first feedback after a popup opens; the synthetic pane is gated on popup_viewport_width > 0 so a zero-geometry frame is skipped (the renderer’s plain-text fallback covers that one frame).

§popup_viewport_width: u32§completion_docs_buffer: Option<BufferId>

PU.5c: the ephemeral registry buffer backing the Insert-mode completion-docs side popup. None when no docs are shown. Reconciled from insert_completion.doc_popup.body once per cycle (reconcile_completion_docs_buffer in run_tick_pending): created when docs appear, text-replaced when they change, and garbage-collected when they vanish — a single chokepoint instead of the scattered insert_completion = None teardown sites. It is a help-flavoured BufferData::Help Document with the ephemeral flag, so it reuses markdown syntax + link styling + the nonu/signcolumn=no/wrap help-mode options and renders through the shared compose seam (PaneId::COMPLETION_DOCS).

§preview_buffer: Option<BufferId>

Picker live-preview reusable slot. A single ephemeral, read-only buffer the find-file (and other file-opening) picker previews load content into as the selection changes — instead of running the full do_edit open path per keystroke (which synchronously parses + attaches LSP + leaks a registry buffer per candidate, freezing the UI thread). do_preview reuses this one buffer; do_edit is reserved for the final accept. GC’d on picker dismiss.

§completion_docs_viewport_height: u32

PU.5c: inner-rect geometry of the completion-docs side popup, fed back from the renderer each frame (the second synthetic popup, peer of popup_viewport_*). build_cells_panes reads them to size the PaneId::COMPLETION_DOCS matrix; the synthetic pane is gated on completion_docs_viewport_width > 0.

§completion_docs_viewport_width: u32§band_buffer: Option<BufferId>

WK.12: the minibuffer band’s buffer — a popup opened with PopupPlacement::MinibufferBand lands HERE rather than in Self::popup_buffer, and the two are independent: opening the band never dismisses a popup, which is the bug this slot exists to remove. Never focused, so it has no anchor, scroll or focus-stack frame.

§band_viewport_height: u32

Renderer-fed inner geometry for the band, exactly as completion_docs_viewport_* is fed. The synthetic PaneId::MINIBUFFER_BAND pane is gated on band_viewport_width > 0.

§band_viewport_width: u32§curswant: Option<Curswant>

VM.3g-1: vim’s curswant — the column j / k aim for, which survives a short line in between. None until something sets one.

§curswant_claimed: bool

VM.3g-1: set when THIS dispatch was a motion that keeps or pins the goal column (j / k / $). Everything else — every other motion, every edit, Insert exit and yank — lets the dispatch tail set the goal from wherever the cursor ended up, which is vim’s rule.

§curswant_report: Arc<Mutex<Option<Curswant>>>

VM.3g-3: where a motion reports the goal column it AIMED at, when that is not the column it reached. Only gj / gk do: they aim at a screen column the landing display row may be too short to hold, and vim keeps the aim (measured: a clamped gj landing at column 100 records 160).

A long-lived slot on the Editor rather than one built per dispatch because dispatch_blocking takes &self — it can clone this Arc into the DispatchEnv but cannot hand anything back. The dispatch tail, which has &mut self, take()s it.

Taken rather than read: a motion writes it on every motion dispatch (None for all but two), but an operator or action never touches it, so leaving a value behind would let one gj’s aim resurface after an unrelated command.

§focus_stack: Vec<MinibufferFocus>

FS.1: the focus stack — one frame per surface that has taken focus away from the pane’s own buffer, innermost last.

MB.1 introduced this as a single Option, which was right while only one surface could hold focus: the : line, the / line, or a prompt. It is wrong the moment focus can nest, and it can — a popup holds focus, and / inside it takes focus again:

    []                       editing the file
    [popup]                  the popup has focus
    [popup, search-line]     …and `/` inside it

As an Option the second push recorded nothing (it was guarded on is_none()) and one restore returned to the FILE, skipping the popup — which is how <Esc> out of a search left the editor focused nowhere. See focused-surface.md §2.

Editor::focused_surface reads the top frame; use it rather than indexing, so “is something focused” and “what is focused” stay one question.

§popup_focus_depth: Option<usize>

FS.2: the Self::focus_stack depth a focused popup sits at, so dismissing it unwinds any surface opened INSIDE it (a / line, a prompt) rather than leaving that focused over a popup that is gone. None whenever no popup holds focus.

§last_message: Option<EchoMessage>

Most recent transient status / error message, displayed in the echo area until replaced.

§messages: Arc<Mutex<MessagesRing>>

Append-only chronological ring of every echo (set_message call). The :messages ex-command opens a *messages* buffer rendered from this ring (the emacs *Messages* analogue). Bounded by MessagesRing::capacity. Wrapped in Arc<Mutex<>> so the boot-installed MessagesLayer (a tracing::Layer running on whatever thread emitted the event) can push into the same ring the App reads on the main thread for backlog seeding.

§pending_message_event_rx: Option<UnboundedReceiver<MessagePushed>>

Receiver for lattice_runtime::MessagePushed events published by set_message. The runtime’s per-tick drain coalesces bursts and rebuilds the *messages* buffer view once per frame.

§pending_redraw: bool

Set by Action::RedrawScreen (<C-l>); the runtime clears this on its next frame after issuing a full terminal-clear so any leftover ANSI / stale glyph state gets repainted from scratch.

§command_history: Vec<String>

Submitted : command history. Newest at the back. Bounded.

§command_history_cursor: Option<usize>

While in Command modal: index into Self::command_history of the entry currently shown (None = the user’s in-progress text).

§command_history_pending: Option<String>

Snapshot of the user’s typed command_line on the first Up so Down can return to it after walking through history.

§search_history: Vec<String>

MB.5b: search-line history (peer of command_history). Each submitted / or ? pattern is pushed here; <C-p>/<C-n> walk this in the search line.

§search_history_cursor: Option<usize>

MB.5b: index into search_history during history walk.

§search_history_pending: Option<String>

MB.5b: user’s typed pattern saved on first <C-p> so <C-n> can return to it.

§auto_submit_after_chord: bool

Chord-capture overlay flag. Set when the user submitted a Chord-arg-required command with no value (:describe-key<CR> or the K.3.2 <C-h>k binding); the cmdline pre-fills with the command word + space and translation routes every key through translate_command_chord_capture so plain letters appear as chord tokens (g → g, <C-c> → <C-c>, <Up> → <Up>, …). The renderer reads this through auto_submit_hint to draw the chord-capture cmdline hint. Reset on cancel / submit.

K.3.5.fix (2026-06-03): the field’s original purpose also included auto-submitting on the FIRST captured chord token — that auto-submit was dropped because chord arguments are sequences (gg, <C-w>v, ]e, <leader>fz), not single chords. The user now types the full chord text and submits with <CR>. Field name kept for backward compat across the renderer / context boundaries; behavior is “chord-capture mode active,” no longer “auto-submit on chord.”

DK.2 (2026-09-07): the explicit <CR> is gone again, but not by reinstating auto-submit-on-first-chord. The trie decides — see Self::chord_capture_seq.

§chord_capture_seq: Vec<KeyChord>

DK.2: the chords captured so far in the current chord-capture session.

Capture has to accumulate a SEQUENCE (gg, <C-w>v, <leader>fz), so it cannot submit on the first keystroke; but making the user terminate with <CR> meant <CR> — and the <Esc> and <BS> reserved beside it — could never themselves be described. The keymap trie already distinguishes “waiting for more” (Partial) from “this is the answer” (Bound / Unbound) on every ordinary keystroke, so capture asks it instead of asking the user, and no key needs reserving.

Parallel to the command line’s TEXT rather than derived from it: the line also holds the command word and any earlier args, and re-parsing a chord argument back out of it would mean re-deriving the arg span on every keystroke to answer a question this vector answers directly. Cleared when a prompt arms, and on submit / dismiss.

§lang_registry: Arc<LangRegistry>

Tree-sitter language registry. Services the document buffer’s Syntax and every HelpBuffer constructed by :describe-* / :apropos / :keymap (help bodies render with markdown highlighting + fenced- block injections sourced from this same registry).

§syntax: Option<SyntaxHandle>

Per-document tree-sitter state. None when the document’s language is Plain (no grammar bundled). Reparses run on a worker task; reads against the latest snapshot are wait-free via ArcSwap.

§last_parsed_text_version: u64

text_version last sent to the syntax handle’s reparse channel. Used to skip republishing identical state when no text mutation has happened since the previous frame.

§pending_syntax_edits: Vec<EditDelta>

Tree-sitter-shaped edit deltas accumulated since the last maybe_reparse_syntax call. Pushed by publish_document_changed after each Buffer::apply_edit; drained by maybe_reparse_syntax and shipped to the syntax worker as Vec<EditDelta> for incremental reparse.

§last_synced_syntax_version: u64

text_version the syntax worker’s tree is known to be at. Sent as from_version on the next reparse request so the worker can verify edits apply to the correct tree baseline.

§last_seen_text_version: HashMap<BufferId, u64>

OWC: per-buffer text version last seen after a host-issued edit. Used to detect owner writes: when the active document’s text_version exceeds this, the host did not issue the edit and should adopt the document’s primary selection head.

§pending_catalog_change_rx: Option<UnboundedReceiver<LanguagesRegistered>>

LA.2: the LanguagesRegistered drain — a plugin whose load changed the mode/language catalog lands here, and run_tick_pending re-resolves the majors and languages of buffers that were opened against the old one.

A channel rather than a flag, and drained here rather than acted on in the subscription, because the re-resolution needs &mut Editor and the bus forwarder runs on the runtime. EventBus::publish_typed calls the forwarder synchronously, so a publish is visible to the very next tick — the paired wake forwarder in editor_boot is what makes that tick happen without a keypress.

None when nothing subscribed it (bare Editor::default() in tests); the drain is then a no-op.

§pane_tree: Versioned<PaneTree>

Pane tree (DESIGN.md §5.9). Always represents the ACTIVE tab’s panes — when switching tabs we mem::swap between this field and tabs[target].panes. Perf plan B.4: wrapped in Versioned so the panes sub-state cache in build_render_state can reuse its prior Arc<PanesRenderState> when the tree hasn’t moved since the last publish. Deref reads (e.g. editor.pane_tree.active()) do NOT bump; DerefMut accesses (split/close/set_active) fire one u64 increment.

§tabs: Versioned<Vec<TabSlot>>

Issue #29 (2026-05-22): tab pages. Each TabSlot carries one tab’s pane tree + optional label. The active tab’s panes field is a default placeholder while live — its real tree sits on editor.pane_tree. Inactive tabs hold the full stashed tree. Always non-empty; default boot state is one tab whose pane_tree matches editor.pane_tree. Perf plan B.4.b: wrapped in Versioned so the tabs sub-state cache can detect when the tab list shape changes (push / remove / reorder). The composite cache key for tabs also includes active_tab, pane_tree.version(), and buffers.version() because label resolution reads across all four inputs.

§active_tab: usize

Index of the active tab in tabs. Always valid (clamped on tab close).

§picker_open_target: OpenTarget

Issue #32 (2026-05-22): override for the next picker accept’s open routing. Set by <C-s> / <C-v> / <C-t> chords on picker overlays before dispatching the accept; read + cleared by apply_picker_outcome for the file- targeting variants (OpenFile / SwitchBuffer / JumpInBuffer / JumpToLocation). Default for <CR>.

§picker_root: Option<PathBuf>

YR.3: where a FillCaller accept should put its text, recorded when the picker was opened.

Not resolved at accept: by then the picker has been dismissed and the modal state that identified the caller is gone, so resolving would read whatever context happens to be current. That is right often enough to pass a single-level test and wrong in exactly the picker-inside-a-prompt case this exists for.

None means the picker was opened to act, not to answer, and a FillCaller arriving against it is a wiring bug the host reports rather than swallows. PC.1: the root the OPEN picker resolves against, overriding the active buffer’s project until it closes.

Beside its picker-scoped peers rather than threaded through build_picker_context, because a live source re-queries through on_query_changed — which sees the context and NOT the open’s args — so the root has to outlive the single init call that carried it.

None is the ordinary case: every picker before PC.1 resolved from the active buffer and still does.

§picker_fill_target: Option<FillTarget>§picker_fill_replace: Option<(usize, usize)>

YR.6: the byte range on the : line a FillCaller should REPLACE rather than insert before.

An argument picker is opened while the user is part-way through typing that argument (:magit-checkout ma), so the picked value has to take the place of ma. A plain insert — which is right for YR.5’s <C-r><C-r>, where nothing was being replaced — would produce mamain, silently, and only for users who typed a prefix before opening the picker.

Captured at open for the same reason the fill target is: by accept time the cursor has moved and the slot that identified the range is gone. None = insert at the cursor.

§stashed_picker: Option<Picker>

YR.5b: the picker the yank picker was opened over.

do_picker_accept takes self.picker before applying the outcome, and open_picker replaces it — so without stashing, a yank picker opened from :files would destroy the :files picker on the way in and have nothing to fill on the way out. Restored both on accept (then filled) and on dismiss, because pressing Esc in the yank picker must return you to the list you were filtering rather than closing both.

§pending_theme_preview_restore: Option<(Palette, Vec<(ElementName, StyleSpec)>)>

T.12a: the theme to restore if the colorscheme picker is dismissed (<Esc>). Captured on the FIRST live preview as a (palette, overrides) snapshot of the theme active when the picker opened. <Esc> calls ThemeRegistry::set_theme with these to undo the preview; <CR> clears it (keeps the previewed theme). None when no colorscheme preview is in flight.

§document: ActiveDocument

Handle to the per-document actor (or, in M.1+, a composing multibuffer handle) and its snapshot cache. M.0: typed as the [ActiveDocument] newtype around Arc<dyn Document> so the slot can hold either a RopeDocumentHandle or a MultibufferDocumentHandle without kind-branching at the use site. Default:: default() populates this with a placeholder rope handle whose actor is already gone — production code overwrites the slot before any traffic flows.

§snapshot_cache: SnapshotCache§picker: Option<Picker>

Active picker overlay. None outside picker mode.

§folds: Vec<Fold>

Manual folds. v1 supports non-nested folds defined by line range.

§fold_registry: Arc<Mutex<FoldRegistry>>

D.3.f.0 (2026-05-29): fold-provider registry. Holds the five built-in Primary providers (Manual / Indent / Markdown / Syntax / Lsp) and the list of registered Overlay providers, which are mode-owned (DX.3-C7): diff-mode’s HunkFoldSource, multibuffer’s excerpt + file-boundary sources, all registered via the FoldOverlayService on mode activation). See docs/dev/architecture/fold-architecture.md. M.7: shared behind Arc<Mutex> so FoldOverlayServiceImpl can call add_overlay/remove_overlay from mode-activation context (outside &mut Editor) without blocking the UI thread.

§_boot_tick_registrations: Vec<TickCallbackRegistration>

BC.3b: boot-lifetime tick-callback registration tokens handed off from the BootContext via into_registrations(). A subsystem install(boot) that wires an off-keystroke boot.inbound::<T> drain (the first is the Claude Code IDE peer’s write bus) produces an RAII token here; holding it for the editor’s lifetime keeps the drain registered (dropping it would unregister the drain mid-session). Empty when no subsystem installs an inbound/tick drain at boot. Never read — held purely to keep the drains alive; the leading _ documents that.

§pane_groups: Vec<PaneGroup>

D.4.a (2026-05-29): scroll-binding pane groups. Each entry binds a set of (pane, buffer) pairs through a pluggable RowMapper; propagation runs at publish_render_state tail. Membership keyed on the pair so buffer changes within a pane suspend the binding automatically. Subsystems (diff D.4.d, future :set scrollbind, zen mode, :windo) add/drop their groups around lifecycle. See docs/dev/architecture/pane-groups.md.

§scrollbind_group_id: Option<PaneGroupId>

D.0b (2026-06-08): id of the singleton identity-mapper pane group that backs :set scrollbind. None when no panes currently have scrollbind=true (the group is dropped when the last member opts out). Rebuilt by rebuild_scrollbind_group on every scrollbind option-change cascade.

§diffthis_group: Option<BufferId>

D.8.e (2026-05-31): session key of the singleton :diffthis group, if any. :diffthis toggles per-buffer membership in this one group; other diff sessions (:diffsplit, AI-driven openDiff flows, future magit) run as independent DiffSessions and don’t affect this field.

State transitions (per docs/dev/architecture/n-way-diff-membership.md §6.2):

  • None → user runs :diffthis in any pane: create N=1 dormant session keyed under the active buffer; set this to Some(active_buf).
  • Some(g) + active buffer not in g: extend the group via add_participant; arity grows.
  • Some(g) + active buffer in g: shrink the group via remove_participant_buffer; if arity drops to 0 the subsystem auto-drops the session and this clears back to None.
§diffthis_members: Vec<PaneGroupMember>

D.8.e (2026-05-31): pane members corresponding to each participant of the diffthis group, in :diffthis-call order. The first entry is the pane the FIRST :diffthis invocation came from; subsequent entries are appended as the user invokes :diffthis in new panes. Used to construct / reshape the pane group when arity transitions across 2 (need scroll-bind + fillers).

Cleared in lockstep with diffthis_group — both reset to empty / None when the group drops to arity 0.

§picker_registry: PickerRegistryHandle

Picker source registry – :picker source kinds. Held behind ArcSwap (PickerRegistryHandle) so the plugin loader can register a loaded picker plugin’s source at runtime by copy-on-write RCU while the picker-open path reads it wait-free (PL8.B). Shared as a service so lattice-plugin-loader reaches it without a host dep.

§picker_mru: PickerMruIndex

Per-source MRU index that biases the picker’s initial candidate ordering toward recently-accepted picks.

§picker_mru_path: Option<PathBuf>

Optional on-disk persistence path for Self::picker_mru. None for ephemeral / test installs.

§pending_picker_init: Option<PendingPickerInit>

In-flight async picker init, if the active picker source’s init returned a Future.

§pending_picker_accept: Option<PendingPickerAccept>

In-flight async picker accept, if the accepted source’s accept_async returned a Future (a WASM plugin source). Its resolved outcome commits via drain_pending_picker_accept.

§pending_transient_build: Option<PendingTransientBuild>

TR.2: in-flight async transient build, if the named menu’s builder is guest-backed. Seated by drain_pending_transient_build on the async-landed wake — never by a keystroke, which is what makes a plugin menu open on the chord that asked for it.

§live_picker_query: Option<LivePickerQueryState>

Live-picker query state – present only when the active picker source has spec().live == true.

§pending_picker_query: Option<String>

CD.6a: the query the next picker to SEAT starts with, set by Effect::OpenPicker { query }. Consumed at seat for any source — unlike a live source’s own seed, which only a live source has — and cleared if the open is refused.

§preview_overrides: HashMap<PaneId, PreviewOverride>

PI.1 (preview isolation): per-pane preview projection. A pane keeps its committed PaneState.buffer_id; the entry here (keyed by PaneId) records the buffer it currently displays plus the preview cursor / scroll. Baked into the published pane-tree leaves at build_render_state time so the renderers show the displayed buffer while :ls / modeline / dispatch read the committed one. Ephemeral: never persisted / snapshotted; cleared on accept / dismiss / selection-cleared. See docs/dev/architecture/preview-isolation.md §5 and crate::preview::PreviewOverride.

§preview_overrides_version: u64

PI.1: monotonic version bumped on every preview_overrides mutation. Folded into the panes-substate cache key (panes_v) so the published projection rebuilds when an override changes — the override lives outside pane_tree.version().

§pane_buffer_history: HashMap<PaneId, PaneBufferHistory>

PBH.1: per-pane buffer history — the trail of buffers each pane has shown, walked with <C-6> / <C-7>.

A side table keyed by PaneId, deliberately not a field on PaneState: that type is Copy and PaneTree::split_active copies it field-wise (PaneState { id: PaneId::next(), ..new_state }), so a history field there would be inherited by the split — the one behaviour this feature must not have. PaneId::next() is process-monotonic and never reuses ids, so a freshly split pane has no entry here and therefore starts with a fresh trail by construction, with nothing to remember to reset. Keeping it out of PaneState also keeps that type Copy, so split / close / layout stay allocation-free.

Reaped by [crate::dispatch::Editor::reconcile_pane_history], which retains only ids still present in the tree rather than hooking each pane-removal site. See docs/dev/architecture/pane-buffer-history.md §4.

§current_dir: Option<PathBuf>

Shared typed-options registry (DESIGN.md §5.12). Every option’s current value lives in here behind an ArcSwap<T>; :set parses against it; the customize buffer view (post-1.0) reads + writes through the same surface. User-set working directory (:cd). None falls back to std::env::current_dir() for path resolution.

§config: Arc<ConfigRegistry>§option_cache: OptionCache

Hot-path read cache for the option values. Repopulated by rebuild_option_cache after every :set. Accessor methods on App read this cached primitive directly (~1ns) instead of going through the registry’s mutex + ArcSwap + downcast (~33ns).

§mode_registry: ModeRegistryHandle

Mode registry (M.1). Owns the catalogue of registered modes; activation / deactivation routes through here.

§builtin_sign_ids: BuiltinSignIds

SG.4a: the interned ids of the built-in signs (diagnostics + diff), registered into the sign registry at boot. The builtin_element_ids shape — a producer emitting a mark per visible line reads a field rather than hashing a name per line.

§invocation_runners: HashMap<ModeId, InvocationRunnerFn>

2026-05-26: per-mode invocation runner table. Boot registers a runner function under each mode-id whose lattice_mode::Mode::invocation_runner returns Some(id); [Editor::run_invocation] looks the runner up by walking the active modes on the active pane’s buffer (minors first, then major) and calls the first match. Empty for modes that don’t own dispatch (text-mode, completion-mode, semantic-tokens-mode, …). Replaces the hardcoded match BufferKind block in run_invocation; plugin-installed modes for plugin- installed buffer kinds extend the dispatcher through this map without touching host code.

§services: Arc<ServiceRegistry>

Typed service map subsystems hand off to modes so Mode::on_activate can pull subsystem handles via ctx.service::<T>(). Populated at boot; read-only after init.

§mode_guards: GuardStoreHandle

Per-(buffer, mode) Guard storage. Modes return an owned Mode::Guard from on_activate; the dispatcher stashes it here keyed by (BufferId, ModeId). On deactivation the dispatcher drops the Guard, firing its Drop impl for synchronous cleanup. Wrapped in Arc<Mutex<>> because the spawned lifecycle task inserts from a worker thread.

§active_modes: Versioned<HashMap<BufferId, ActiveModes>>

Per-buffer active modes (major + minors).

Perf plan B.4: wrapped in Versioned so the modes sub-state cache can reuse its prior Arc across publishes when no mode toggle has fired. The .insert / .remove sites in dispatch autoref &mut self.active_modes, which bumps the version once per mutation.

§deferred_mode_activations: Vec<(BufferId, ModeId)>

TC.9b: activations refused only because the buffer did not offer a required capability YET, to be retried when it does.

A buffer opens, its modes activate, and its first parse has not run — so a mode requiring TREE_SITTER is refused at exactly the moment every mode is activated, and without this nothing ever asks again. The user sees a mode that is simply never on.

Only MissingCapability refusals land here. A conflict or a wrong kind is a decision, not a race, and retrying it would loop forever.

A Vec because it is empty in every ordinary session — the retry pass costs a length check — and because the natural operations are “walk all” and “remove one”, neither of which wants a map.

§buffer_locals: Versioned<HashMap<BufferId, BufferLocals>>

Per-buffer mode-owned local state. Modes populate locals via the BufferLocal typed-map during on_activate; the App routes &mut BufferLocals into the registry’s activation methods.

Perf plan B.4: wrapped in Versioned for the same reason as active_modes — most publishes don’t touch buffer_locals, so the deep typed-map clone in build_render_state can be avoided via Arc reuse.

§resolved_options: HashMap<BufferId, ResolvedOptions>

Per-buffer mode-resolved options cache. Refreshed eagerly on mode toggle and option write.

§on_disk_fingerprints: HashMap<BufferId, OnDiskFingerprint>

AR.0: on-disk fingerprint per file-backed Document buffer, stamped on load and after the buffer’s own :w. The autoread watcher (AR.2) compares an incoming filesystem event against this to suppress self-writes and skip no-op touches. Non-file buffers (oil, help, synthetic) never get an entry — the map is keyed by the property “has an on-disk backing”, not by BufferKind. See docs/dev/architecture/autoread.md.

§resolved_options_version: u64

PI.4: monotonic version bumped whenever Self::resolved_options changes. Keys the published ResolvedOptionsRenderState cache so both renderer peers read per-buffer resolved options through ONE renderer-agnostic seam (RenderState::resolved_option_for) instead of each peer resolving options its own way.

§buffer_local_overrides: HashMap<BufferId, OptionOverrideSet>

Buffer-local explicit overrides (:setlocal foo=bar) per buffer. Inputs to resolution; the resolver chains these with mode contributions before writing Self::resolved_options.

§option_change_rx: Option<UnboundedReceiver<Event>>

Receiver for OptionChanged events published by the option-cascade pipeline. Option only because the field needs to be take-able so the drain method can borrow &mut self for cascade work while iterating the receiver. Always Some between calls.

§help_topics: HelpTopicRegistryHandle

Free-form help topic registry (DESIGN.md §5.11). :help reads from this; built-ins are sourced from docs/user/*.md at build time. Plugins / future LSP integrations register additional topics through the same registry.

CR.1: a copy-on-write RCU handle, not a fixed Arc. Every read site takes one .load() snapshot for the duration of its work, so a plugin loading mid-render affects the next :help, never half of this one. Also registered as a boot service under HelpTopicRegistryHandle so the plugin loader’s drain can reach it without a host method.

§builtin_element_ids: BuiltinElementIds

T.4: builtin element ids interned once at boot from the ThemeRegistryHandle (looked up from Self::services). Snapshotted (Copy) into RenderState so a renderer read is resolved.get(ids.<elem>). The registry handle itself lives only in services (it is Arc<dyn ThemeRegistry>, which has no Default, so it cannot be a field on this derive(Default) struct); build_render_state looks it up to snapshot resolved().

§modal: ModalState

Buffer-level modal state machine (DESIGN.md §5.2). One of Normal / Insert / Visual / Op-pending / Command / Search / Replace.

§partial_chord: Vec<KeyChord>

In-flight partial-chord stack from the trie. When the trie returns LookupResult::Partial, the dispatch layer appends the chord here; the next keystroke runs through the trie with this stack as prefix. Cleared on every non-AbsorbPartialChord action.

§registry: CommandRegistryHandle

Grammar registry shared with the document actor by the ArcSwap handle. The actor calls lattice_grammar::execute with a wait-free snapshot from inside its own task. The App also reads it directly (.load(), or .load_full() where an owned snapshot must outlive a &mut self borrow) for the parser, completion pipeline, and introspection.

PL8.B / B3b: held behind ArcSwap (was Arc<CommandRegistry>) so the plugin loader can RCU-register a runtime grammar contribution and store it; every reader picks it up on its next .load(). See lattice_grammar::CommandRegistryHandle.

§event_bus: Arc<EventBus>

In-process event bus (DESIGN.md §5.10). The App publishes editor lifecycle events (DocumentChanged, SelectionsChanged, ModalModeChanged, BeforeSave, DocumentSaved, BeforeQuit, OptionChanged) after observing the corresponding state transitions.

§builtins: Builtins

Built-in command-ids (d, y, w, j, …) – the canonical CommandId values keymap registrations resolve against.

§action_ids: ActionIds

App-side typed action IDs (CommandKind::Action registrations). Each field is a CommandId resolving to an ActionSpec whose apply returns Effect::AppAction(AppEffect::Foo).

§keymap: KeymapHandle

Layered keymap registry (DESIGN.md §5.2.3). Populated at construction; the input dispatcher reads from it on every keystroke. Wait-free reads via internal ArcSwap; concurrent writes (mode push/pop, plugin registration, :bind) never stall the input path.

§completion_popup_layer: Option<LayerId>

LayerId of the active completion-popup minor-mode layer when the popup is open; None otherwise. Pushed / popped in lockstep with insert_completion.

§completion_registry: CompletionRegistry

Pluggable completion pipeline (DESIGN.md §5.11.3). Owned by the host editor.

§completion_state: Option<CompletionState>

Active command-line completion popup state (for : line).

§insert_completion: Option<InsertCompletionState>

Active Insert-mode completion popup (Phase 4.2.g). Distinct from completion_state (which drives the : line completion popup): this one floats over the buffer, shows candidates from sources (LSP / snippets / buffer-words / path / tree-sitter / plugin), and the host’s keystroke dispatcher routes through a “completion-popup minor mode” keymap layer while it’s Some. Behavioural spec lives in [docs/dev/architecture/insert-completion.md].

§snippet_registry: Arc<ArcSwap<SnippetRegistry>>

Per-language snippet registry (Phase 4.2.g.4). Loaded at startup from bundled / user / project paths via lattice-snippet::load; the gen:snippet source consults it per-popup-trigger. CSM.5: held as Arc<ArcSwap<...>> so the mode-captured handle stays valid across :reload-snippets. Source reads load the current snapshot via .load() (wait-free); the reload path swaps the inner via .store() so the mode’s next produce sees the fresh data.

§snippet_activation_policy: SnippetActivationPolicyHandle

SN.3b: shared cell holding the folded snippet-mode ActivationPolicy. register_snippet_modes creates it (default Global) and the snippet-mode gate reads it on every MajorEntered; boot + the snippet.activation / snippet.languages apply_option_cascade arm fold config into it via lattice_snippet::fold_activation_policy.

§global_action_handler_regs: Vec<ActionHandlerRegistration>

SN.3c.0: app-lifetime registration tokens for modes’ declarative global action handlers (Mode::action_handlers(), registered once at boot by crate::mode_action_handlers::register_mode_action_handlers). Held here so the handlers stay registered for the editor’s whole lifetime; dropped at shutdown when Editor drops.

§insert_completion_snippet_meta: Vec<SnippetCandidateMeta>

Sidecar metadata for snippet candidates in the active insert-completion popup. CSM.5: retired. Snippet candidates now carry their stable name in the Extension::payload field; the accept path re-resolves the body via Editor.snippet_registry.by_name. Field kept as an empty Vec for one slice so callers that haven’t migrated still compile; field deletion in a follow-up cleanup slice.

§completion_accept_freq: HashMap<(String, CandidateKind), u32>

Per-session accept-count map for the insert-mode completion popup (Phase 4.2.g.5). Each accepted candidate bumps the counter for its (text, kind) pair; the ranker reads this map and adds a bounded bonus (InsertRanker::FREQUENCY_BONUS_CAP) so recently-accepted items bubble above tied peers next time.

§pending_config_structural_sections: BTreeMap<String, Table>

TOML structural sections collected by the config loader at startup but not yet routed to their owners. Keyed by full dotted path (e.g. "completion.per-language.markdown", "plugin.rust-analyzer"); value is the sub-table verbatim. Phase 4.2.g.5 (3b/3) drains the completion.per-language.* entries into per_language_completion; the plugin host (Phase 7) will drain plugin.*.

§per_language_completion: HashMap<String, PerLanguageOverrides>

Per-language insert-completion overrides (Phase 4.2.g.5 (3b/3); spec at docs/dev/architecture/insert-completion.md §9).

§completion_in_path_context: bool

true while the active insert-completion popup is in path-completion mode (Phase 4.2.g.6 (2/2)).

§snippet_session: SnippetSessionHandle

Live snippet expansion (SN.2: relocated to a shared SnippetSession service so the SnippetActiveMode-owned <Tab> / <S-Tab> handlers can reach it). Active while a snippet is expanding; the session ends on $0 consumption / <Esc> / cursor leaving the tabstop ranges. The same Arc is registered in ServiceRegistry under SnippetSessionHandle.

§session_backed_minors: Vec<SessionBackedMinor>

Session-backed minor modes reconciled on the active buffer each sync_keymap_overlays cycle (one entry per service-driven minor). Each pairs a predicate — reading a shared, mode-owned session service — with the minor’s ModeId; the mode is active iff its predicate is true. Modes contribute these at boot (active-snippet-mode keys off the shared SnippetSession), so the generic overlay-sync carries no subsystem-specific is_active() literal (feedback_mode_owns_its_surface).

§snippet_dirs: Vec<PathBuf>

Per-language directories from which snippet packs are loaded on startup / :reload-snippets (Phase 4.2.g.4).

§popup_back_stack: Vec<PopupSnapshot>

LIFO stack of snapshots taken every time the popup’s content gets swapped in place by a help -> help link follow (e.g. :describe-buffer -> click [text-mode](mode:text-mode) -> :describe-mode text-mode). One popup buffer is reused across the navigation so jump-list / marks / search / register state stay coherent; this stack records what was in the buffer before each swap so <C-o> from inside the popup can restore the prior frame without leaving Help.

§cursor: Position

Active buffer’s cursor (DESIGN.md §5.1.1). Updated in lockstep with the active pane’s stash so cross- pane jumps restore the right position.

§scroll: u32

Sticky display-column target for gj/gk (vim’s w_curswant). Stores the byte offset within the current wrap segment so consecutive display-line moves try to land at the same column. None between any non-display-line motion. First visible line in the viewport (0-based).

§leftcol: u32

First visible display column in the viewport (0-based) — horizontal scroll for the active pane. Mirrors the active PaneState::leftcol; maintained by ensure_cursor_horizontally_visible. Always 0 when wrap is on (the body reflows, nothing is off-screen-right).

§should_quit: bool

Quit flag. The main loop reads this and tears down after the next paint. Set by :q / :qa / Ctrl-C / SIGINT.

§viewport_height: u32

Last height we were drawn at; used by motion clamping and viewport scrolling. Updated by the renderer before each frame.

IM.1: this is a budget in line-heights, not a count of rows. The two are the same number whenever rows are uniform — which is always, for the TUI — but they diverge once a row is taller than one line (a scaled heading, an IM.3 media block), because how many rows fit then depends on which rows. Spend it through Editor::row_weights; do not count rows against it.

§row_weights: Arc<RowWeights>

IM.1: per-source-line vertical cost overrides, in line-heights.

Empty for every TUI buffer and for any GPUI buffer with no scaled or media rows, and the scroll walks short-circuit on that — see lattice_cells::RowWeights::is_uniform. Published by the renderer peer, which is the only layer that knows how tall it draws things.

§terminal_width: Option<u16>

Last terminal width we were drawn at. Used by pane geometry (DESIGN.md §5.9 navigation needs to know which pane is horizontally adjacent). None until the renderer first records it.

§cell_metrics: Option<CellMetrics>

IM.7a: the drawing peer’s cell geometry in pixels, published through crate::action::Action::SetCellMetrics.

None until a peer that draws images reports it, and forever on one that does not — which is how a media block stays at its provisional reservation in the TUI without the host reading any image header.

§active_buffer: BufferKind

Which buffer the input pipeline currently routes to. When a help overlay is open this is Help; otherwise Document. Denormalized from pane_tree.active().buffer – updated in lockstep with the active pane.

§document_buffer_id: BufferId

Stable id for the active document buffer. Mirrors the active pane’s buffer_id whenever that pane holds a Document leaf.

§buffers: BufferRegistry

Unified buffer registry (DESIGN.md §5.9). Holds every open buffer regardless of kind – documents, file trees, future outline / diagnostics views.

§modeline: ModelineServiceHandle

ML.0b-2: shared modeline element service (descriptor registry + content store, ArcSwap-backed). The SAME Arc is registered into services at boot so modes reach it via ctx.service::<ModelineServiceHandle>(); the host reads modeline.snapshot() each build_render_state into RenderState.modeline_elements. Arc<ModelineService> is Default, so #[derive(Default)] on Editor still holds (the boot literal overrides it with the registered instance).

§modeline_update_rx: Option<UnboundedReceiver<ModelineElementUpdate>>

ML.3: actor-thread drain channel for lattice_mode::ModelineElementUpdate events pushed by modes/plugins over the event bus. Boot subscribes a sender; drain_modeline_element_updates (in run_tick_pending) applies each into modeline’s content store (single-writer). A separate boot subscription fires async_landed so a pushed update repaints off-keystroke (§12 wake). Option is Default (None), so #[derive(Default)] on Editor still holds.

§lsp_selection_chain: Option<LspSelectionChain>

Cached textDocument/selectionRange chain for the smart-expansion operator.

§lsp_selection_chain_index: usize

Current step inside lsp_selection_chain.ranges. 0 = innermost; chain.ranges.len() - 1 = outermost.

§lsp_document_highlights: Arc<ArcSwapOption<DocumentHighlightCache>>

Cached textDocument/documentHighlight for the active buffer + symbol position.

Phase 5.8.AF.5 / Slice 3b.0: the cache lives behind Arc<ArcSwapOption<...>> so the spawned task on the LSP runtime can store results directly when the response arrives — no channel, no UI-thread drain. Renderers read wait-free via editor.render_state.load().lsp.document_highlights.load().

§last_document_highlight_issue_cursor: Option<Position>

Cursor position at which the most recent documentHighlight request was issued.

§lsp_folds_cache: PerBufferCache<LspFoldsCache>

Per-buffer cache of the last textDocument/foldingRange response.

Phase 5.8.AF.5 / Slice 3b.1: PerBufferCache<T> so the spawned LSP request task can write results directly when the response arrives – no channel, no UI-thread drain. Renderers read wait-free via rs.lsp.folds.get_for(buffer_id).

§last_recomputed_lsp_fold_version: Option<(BufferId, u64)>

Phase 5.8.AF.5 / Slice 3b.1: the old drain (drain_pending_folding_range) called recompute_folds() inline after writing the cache so self.folds reflected the latest LSP response. The new shape has the task writing the cache off-thread; this tuple lets maybe_request_folding_range detect when the cache version has changed and trigger recompute_folds() on the renderer thread (where &mut self.folds is safe). Some((buffer_id, document_version)) records the cache state last reflected into self.folds.

§last_folded_text_version: Option<(BufferId, u64)>

OA.4d: (buffer, text_version) the current Editor::folds were computed from, so the tick can notice when they no longer match.

Folds are otherwise seeded in two places, and neither covers a view that fills in ASYNCHRONOUSLY: activate_buffer seeds them when the buffer is activated — which for a provider view is while it is still EMPTY, before the scan lands — and maybe_reparse_syntax recomputes on the edit path, which a view nobody typed into never takes. So the agenda had no folds at all until something forced a redraw, and <Tab> was a no-op because there was nothing under the cursor to cycle.

§lsp_inlay_hints_cache: PerBufferCache<LspInlayHintCache>

Per-buffer inlayHint cache.

Phase 5.8.AF.5 / Slice 3b.1: see lsp_folds_cache note. Renderers read wait-free via rs.lsp.inlay_hints.get_for(buffer_id).

§wasm_decorations: WasmDecorationState

PL8.E: WASM gutter-decoration wiring (per-buffer cache + producer registry handle + off-keystroke paint generation + single-flight bookkeeping), bundled so this struct grows by one field. The per-tick maybe_refresh_wasm_decorations drives the producers off the render path; renderers read rs.wasm_gutter_decorations. Inert in Editor::default() (no registry wired).

§wasm_media: WasmMediaState

IM.7: inline-media producer wiring — the cache the virtual-row builder reads, plus the registry the loader registers into.

§wasm_context: WasmContextState

TC.3a: WASM sticky-context wiring (per-buffer scope cache + producer registry handle + off-keystroke paint generation + single-flight bookkeeping). The per-tick maybe_refresh_wasm_context drives the producers off the render path; per-pane resolution reads the cache. Inert in Editor::default() (no registry wired).

§lsp_document_links_cache: PerBufferCache<LspDocumentLinksCache>

Per-buffer documentLink cache. Per-buffer textDocument/documentLink cache. Phase 5.8.AF.5 / Slice 3b.4: PerBufferCache<T> so the spawned LSP request task writes results directly. Renderers read via rs.lsp.document_links.get_for(buffer_id).

§lsp_code_lens_cache: PerBufferCache<LspCodeLensCache>

Per-buffer code-lens cache. Per-buffer textDocument/codeLens cache. Phase 5.8.AF.5 / Slice 3b.3: PerBufferCache<T> so the spawned LSP request task writes results directly. Renderers read via rs.lsp.code_lens.get_for(buffer_id).

§lsp_document_color_cache: PerBufferCache<LspDocumentColorCache>

Per-buffer documentColor cache. Per-buffer textDocument/documentColor cache. Phase 5.8.AF.5 / Slice 3b.4: PerBufferCache<T>.

§lsp_semantic_tokens_cache: PerBufferCache<LspSemanticTokensCache>

Per-buffer semantic-tokens cache. Per-buffer cache of the last textDocument/semanticTokens/* response. Phase 5.8.AF.5 / Slice 3b.2: PerBufferCache<T> so the spawned LSP request task writes results (Items / Delta-applied / Empty) directly when the response arrives. Renderers read wait-free via rs.lsp.semantic_tokens.get_for(buffer_id).

§lsp_pull_diagnostics_cache: PerBufferCache<LspPullDiagnosticsCache>

Per-buffer pull-diagnostics cache (keyed result_ids for Unchanged short-circuit). Per-buffer textDocument/diagnostic (pull) cache. Phase 5.8.AF.5 / Slice 3b.5: PerBufferCache<T>.

§lsp: LspSupervisorHandle§lsp_progress_store: LspProgressStoreHandle

ML.3c: handle to the lattice-lsp-owned progress/status store (decision A — the accumulator relocated out of the host). The modeline forwarder writes it; the host reads it here only for :lsp-progress-cancel (in-flight cancellable tokens). Arc<…> is Default, so #[derive(Default)] on Editor still holds.

§lsp_diagnostics: DiagnosticsLayer§inline_diag_line: Option<u32>

L4a.2 (lsp-architecture.md §15): inline cursor-line diagnostic-summary idle gate. inline_diag_line is the line the gate is currently timing (the cursor line at arm time); inline_diag_deadline is the [tokio::time::Instant] at which its summary becomes visible (the actor’s pinned sleep targets it); inline_diag_visible flips true when that deadline passes and back to false on re-arm (new cursor line) / Insert mode / ui.diagnostics.inline = off. The published summary in DiagnosticsRenderState::inline_summary is recomputed each build_render_state while visible, so diagnostics landing on the line after the gate fires refresh it for free. See update_inline_diag_gate / fire_inline_diag_gate.

§inline_diag_deadline: Option<Instant>§inline_diag_visible: bool§last_partial_chord_event: Option<PartialChordPending>

WK.4: the last PartialChordPending published, so the publisher can skip republishing an unchanged tuple. This is the publisher’s own dedup cache — not subsystem state — and it is what keeps an ORDINARY keystroke’s cost at a tuple compare that short-circuits on two empty slices. See publish_partial_chord_pending.

§lsp_logger: LspLogger§lsp_watcher: Option<LspFileWatcherHandle>

4.4.l.2 / 5.8.AA.o / 5.8.AF.5: file-watcher service handle. None until the first actor with workspace/didChangeWatchedFiles capability is observed; at that point the actual watcher + notify event loop is spawned on the LSP runtime (see crate::lsp_watcher::spawn_lsp_file_watcher_task). Editor only sends SyncSubscriptions commands through this handle — no notify API calls, no event drains, ever run on the renderer’s per-tick loop. Per paramount goal #4.

§lsp_watcher_subscriptions: HashMap<String, CachedSubscription>

Editor-side memo of server_id → CachedSubscription. Used to detect whether the actor roster or its compiled subscriptions changed since the last sync call; only non-trivial diffs are pushed to the task. Mirrors the per-server map the task itself holds, but lives here so the “did anything change?” check stays a cheap fingerprint compare on the renderer’s refresh_lsp_file_watcher path.

§lsp_watcher_watched_roots: HashSet<PathBuf>

Editor-side memo of currently-watched roots. Mirrors the task’s set so we can skip sending SyncSubscriptions when nothing changed.

§autoread_watcher: Option<AutoreadWatcherHandle>

AR.3: handle to the autoread watcher task (spawned lazily on the first file-backed buffer with autoread on; dropped → task exits when the last such buffer closes). Editor only sends Sync commands through it — no notify calls on the renderer thread. See docs/dev/architecture/autoread.md.

§autoread_changes: Option<UnboundedReceiver<AutoreadChange>>

AR.3: the change stream from the watcher task, drained host-side by AR.4’s reload policy. None until the watcher is spawned.

§autoread_watch_fingerprint: u64

AR.3: order-independent hash of the last-synced desired watch set. The cheap “did the watch set change?” gate on refresh_autoread_watcher so buffer-switches that don’t change the set skip the cmd-send.

§autoread_pending: HashMap<BufferId, AutoreadChange>

AR.4: the latest un-applied external change per buffer. The tick drain records watcher changes here (keyed by BufferId) and applies the active buffer’s entry immediately; a background buffer’s entry is applied when it next becomes active (vim’s checktime-on-BufEnter).

§autoread_conflict_open: HashSet<BufferId>

AR.5: buffers with an open autoread conflict diff (on-disk change + unsaved edits). While a buffer is in this set autoread stays hands-off for it — no re-opened resolver, no reload — so a resolve-then-save can’t loop. Cleared when the buffer is reloaded (:e! → new id) or closed.

§render_state: Arc<ArcSwap<RenderState>>

Phase 5.8.AF.5 / Slice 3a: renderer’s wait-free read contract. Published by Editor::publish_render_state at the end of every dispatch() tick. Renderers load via editor.render_state.load_full() once per frame and read every per-frame field through the returned snapshot.

§overlay_wake: OverlayWake

Phase 5.8.AF.5 / Slice X2: wake signal for the overlay worker. publish_render_state fires overlay_wake.0.notify_one() at its tail so the worker re-evaluates the syntax inputs published into RenderState.syntax and re-buckets overlay quads on a cache miss. Notify coalesces — a burst of publishes wakes the worker once, which is what we want (it always reads the latest published inputs anyway).

display-line B4.2: renamed from highlight_wake; the dead span/row prepaint cache the worker also fed was deleted.

§syntax_static_overlay_quads_cell: Arc<ArcSwap<StaticOverlayQuads>>

Perf plan B.2 slice B.2.a: parallel cell carrying the worker’s per-row pre-bucketed static-overlay quads (doc_highlight / all_matches / substitute) for the active pane’s visible window. The Arc identity lives on Editor so build_render_state clones it into every snapshot; the overlay worker writes directly and renderer peers read the latest write via the published Arc without a republish round-trip.

§cells_matrix_cell: Arc<ArcSwap<CellMatrix>>

S2.1 (2026-05-26): cell-grid renderer output cell. Same stability pattern as syntax_static_overlay_quads_cell: the Arc identity lives on Editor so build_render_state clones it into every snapshot; the cell-builder worker (S2.2+) holds a sibling clone and writes directly via cell.store(new_matrix). Empty CellMatrix until the worker lands.

§cells_matrices: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<CellMatrix>>>>>

D.4.d.0 (2026-05-29): per-document cells-matrix registry. Each visible buffer gets its own Arc<ArcSwap<CellMatrix>> so the cells worker can rebuild per buffer, and the renderer can pull the matrix matching each pane’s buffer at paint time (load-bearing for side-by-side diff — D.4 — where two panes show different buffers simultaneously).

The active-document entry is stored under document_buffer_id and shares its Arc identity with Self::cells_matrix_cell so the existing hot path (cells_worker writing through the field, renderer reading through RenderState.cells.matrix) stays bit-identical until the worker iteration upgrade lands in D.4.d.1.

Inserts are lazy via Self::cells_matrix_for — a buffer’s entry shows up the first time anything asks for its matrix. Pruning of stale entries is deferred until the worker actually consumes the registry; for now, entries accumulate without harm because [arc_swap::ArcSwap] over an empty CellMatrix is a cheap idle resource.

§display_matrix_cell: Arc<ArcSwap<DisplayMatrix>>

B2.1 (2026-06-04): per-line display-cache output cell — the substrate that retires cells_matrix_cell. Same stability pattern: the Arc identity lives on Editor so the publisher clones it into every snapshot; the worker (B2.2) holds a sibling clone and writes via cell.store(new_matrix). Empty DisplayMatrix until the worker build path lands. See docs/dev/architecture/display-line.md.

§display_matrices: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<DisplayMatrix>>>>>

B2.1 (2026-06-04): per-document display-matrix registry. Mirror of Self::cells_matrices for the per-line cache. Each visible buffer gets its own Arc<ArcSwap<DisplayMatrix>> so the worker can rebuild per buffer and the renderer can pull the matrix matching each pane’s buffer at paint time. The active-document entry is boot-seeded to share its Arc identity with Self::display_matrix_cell; other entries are inserted lazily via Self::display_matrix_for.

§indent_guides: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<IndentGuides>>>>>

IG.2 (2026-08-16): per-document indentation-guide registry. Exact peer of Self::display_matrices — the guide layer is built in the same worker pass, from the same snapshot, and carries the same MatrixVersion, so it is keyed and reached the same way. Inserted lazily via Self::indent_guides_for.

§sticky_contexts: Arc<Mutex<HashMap<PaneId, Arc<ArcSwap<StickyContext>>>>>

TC.3b: per-PANE sticky-context registry. The one per-pane layer keyed by PaneId rather than BufferId — see crate::sticky_context for why the usual buffer keying is wrong here. Inserted lazily via Self::sticky_context_for.

§diff_subsystem: Arc<DiffSubsystem>

D.2.d (2026-05-29): diff subsystem instance. Holds the per-buffer DiffSession registry, the routing inverse index, and the per-session lazy debouncer. Reads through the host’s BufferTextProvider impl (wired post-D.2.c when the first consumer slice lands) for live-rope baselines / current sources. :describe-diff reads diff_subsystem.build_describe_diff_content() directly. See docs/dev/architecture/diff-system.md §3.4.

§virtual_rows_matrix_cell: Arc<ArcSwap<VirtualRowMatrix>>

D.0a.1 (2026-05-29): virtual-rows worker output cell. Same stability pattern as cells_matrix_cell: the Arc identity lives on Editor so build_render_state clones it into every snapshot; the virtual_rows_worker holds a sibling clone and writes directly via cell.store(new_matrix). Empty VirtualRowMatrix until the first provider registers and the worker rebuilds.

§virtual_rows_matrices: Arc<Mutex<HashMap<BufferId, Arc<ArcSwap<VirtualRowMatrix>>>>>

D.4.d.2.0 (2026-05-29): per-document virtual-rows matrix registry. Mirror of Self::cells_matrices for the displacing-virtual-row primitive. Each visible buffer that takes the virtual-rows path gets its own Arc<ArcSwap<VirtualRowMatrix>> so the worker (after D.4.d.2.1.b) can rebuild per buffer, and the renderer can pull the right matrix per pane at paint time (load-bearing for side-by-side diff fillers — D.4 — where two panes show different hunks’ filler rows simultaneously).

The active-document entry is stored under document_buffer_id and shares its Arc identity with Self::virtual_rows_matrix_cell so the existing hot path (virtual_rows_worker writing through the field, renderer reading through RenderState.virtual_rows.matrix) stays bit-identical until the worker iteration upgrade lands in D.4.d.2.1.b.

Inserts are lazy via Self::virtual_rows_matrix_for — a buffer’s entry shows up the first time anything asks for its matrix. Pruning of stale entries is deferred until the worker actually consumes the registry.

§virtual_rows_wake: VirtualRowsWake

D.0a.1 (2026-05-29): wake signal for the virtual-rows worker. publish_render_state fires notify_one() after every dispatch tick (permit-style coalescing mirrors cells_wake). Provider state changes fire the same signal directly to wake the worker between dispatch ticks.

§virtual_row_providers: Arc<VirtualRowProviderRegistry>

D.0a.1 (2026-05-29): the provider registry the virtual-rows worker iterates on every wake. Consumers (D.3 inline diff deletion-block provider, M.2 multibuffer excerpt-header provider) register their providers here at slice-mount time and unregister at teardown.

§diff_subscription_guard: Option<DiffSubscriptionGuard>

D.3.a.1 (2026-05-29): the bus-subscription guard from DiffSubsystem::bind. Held for the editor’s lifetime; its Drop unsubscribes the bus + aborts the drainer task on editor teardown. Stored behind Option so the Editor::default() path (used by tests that don’t boot through editor_boot) can leave it unset without the bind machinery firing.

§vcs_subscription_guard: Option<VcsSubscriptionGuard>

VCS.2 (2026-07-25): the bus-subscription guard from VcsSubsystem::bind. Held for the editor’s lifetime; its Drop unsubscribes the bus + aborts the drainer task on editor teardown. Mirrors diff_subscription_guard.

§diff_forwarders: Arc<Mutex<HashMap<BufferId, JoinHandle<()>>>>

D.3.a.1 (2026-05-29): per-session wake-forwarder JoinHandles. :diff spawns a tokio task that awaits DiffSession::publish_notify().notified() and fires VirtualRowsWake on each publish; :diffoff aborts the task by BufferId and unregisters the provider. tokio::sync::Mutex is overkill here — mutation is :diff/:diffoff frequency, never per-frame.

§pending_programmatic_diff_rx: Option<UnboundedReceiver<ProgrammaticDiffRequest>>

I4 (Claude Code IDE peer, openDiff): host-drained inbound receiver for programmatic side-by-side diff requests. An off-thread producer (the IDE peer) sends a lattice_diff::ProgrammaticDiffRequest on the matching lattice_diff::ProgrammaticDiffBus (registered as a boot service); the send wakes the editor, and Self::drain_inbound_programmatic_diffs drains this receiver per tick, opening each diff on the actor thread. The open is irreducibly &mut Editor + lattice-diff types, so — like LSP workspace/applyEdit (pending_apply_edit_rx) — it is host-drained, not a mode-owned Effect handler.

§programmatic_diff_accept_paths: HashMap<BufferId, PathBuf>

I4: per-session “save the current (right) side to this path on Accept” map, keyed by the session’s primary BufferId (the proposed/right buffer). Set when Self::open_programmatic_diff registers a session; honored in tear_down_single_diff_session (a DiffOutcome::Accept writes the buffer here before firing the bound oneshot — the openDiff FILE_SAVED contract: the review is the save). Removed on teardown.

§programmatic_diff_panes: HashMap<BufferId, ProgrammaticDiffPanes>

I4 (openDiff) D-fix.1: per programmatic-diff-session pane teardown info, keyed by the session’s primary (proposed) BufferId. Recorded by open_programmatic_diff; consumed by finish_programmatic_diff_panes on :diff-accept / :diff-reject to close the transient diff panes and return focus to the originating (:claude) pane. Populated/cleared in lockstep with programmatic_diff_accept_paths.

§diff_fold_seen_revisions: HashMap<BufferId, u64>

D-fix.5: per diff-participant buffer, the last HunkIndex revision its folds were recomputed against. refresh_diff_folds (run each tick on the diff-publish wake) consults this to skip buffers whose hunks haven’t moved — so a diff session’s unchanged + hunk folds refresh off-keystroke when the async recompute publishes, without re-folding on every unrelated wake. Keyed by the participant BufferId (each side tracked independently, since each folds its own slot). Stale entries are harmless; the buffer’s diff-mode deactivation drops its fold sources, and the next recompute simply finds none.

§overlay_fold_seen_versions: HashMap<BufferId, u64>

Document version each buffer’s overlay folds were last computed at — see Editor::refresh_overlay_folds. Peer of diff_fold_seen_revisions, keyed on the document rather than a diff session so it covers overlays whose buffer is edited out of band (magit-status’s inline diff toggle).

§cells_wake: CellsWake

S2.1 (2026-05-26): wake signal for the cell-builder worker. publish_render_state fires notify_one() after every dispatch tick. The worker notified().awaits; permit-style coalescing handles bursts.

§last_edit_for_cells: Option<EditDelta>

S2.4.b (2026-05-26): single-edit tracker for the cell-builder’s incremental rebuild path. Some(delta) iff exactly one apply_edit_blocking (or LSP-applied edit) call has happened since the last build_render_state AND the previous publish cycle had no pending delta. Any second edit, batch, undo, redo, or other multi-edit path clears it back to None — conservatively forcing the worker to full-rebuild rather than risk applying a stale single-edit shift. build_render_state take()s and hands it to the cells substate.

§paint_request: Arc<Notify>

Phase 5.8.AF.6 / Slice X1b: paint-request signal. The highlights worker fires paint_request.notify_one() after every WorkerDecision::Recomputed so renderer peers can schedule a paint even when no user input was in flight. Notify coalesces; bursts of recomputes wake the bridge once and a single paint covers the latest spans. The TUI peer’s 100ms event-poll picks up the new cell naturally (no bridge needed); the GPUI peer spawns a foreground- executor future that awaits this Notify and calls cx.notify() to schedule a render.

§async_landed: Arc<Notify>

Slice B.1 (2026-06-03): “async work landed” wake. Fired by async completions that produce render-relevant state with no keystroke in flight — today the syntax reparse worker (via the on_publish Notify handed to each SyntaxHandle). The editor actor’s loop select!s on this and runs run_tick_pending + publish_render_state, so an idle reparse repaints without waiting for the next key (closes the X1b idle-arrival gap for syntax; LSP-response tasks can fire the same Notify as a follow-up). Distinct from paint_request, which is the downstream UI-redraw signal fired after a worker publishes.

§lsp_log_event_rx: Option<UnboundedReceiver<LspLogPushed>>§lsp_config_tree: Arc<ArcSwap<Table>>

Merged user+project lsp.* config tree. BC.8b: shared (Arc<ArcSwap<…>>) so the mode-owned workspace/configuration inbound handler (lattice_lsp::configuration::make_handler) reads the current tree; the host re-stores it on config reload.

§buffer_uris: Versioned<HashMap<BufferId, Uri>>

Perf plan B.4.b: wrapped in Versioned so the buffers sub-state cache can elide the per-publish HashMap clone. Mutators (buffer_uris.insert/remove) autoref &mut, fire DerefMut, and bump.

§pending_apply_edit_rx: Option<UnboundedReceiver<InboundApplyEdit>>§pending_show_message_request_rx: Option<UnboundedReceiver<InboundShowMessageRequest>>§lsp_pending_show_message_requests: HashMap<u32, InboundShowMessageRequest>§lsp_show_message_request_queue: VecDeque<u32>§lsp_next_show_message_request_id: u32§foreground_cancel: ForegroundCancelHandle

CG.1/CG.2: the foreground cancellation slot — work the user explicitly triggered that may hold up the next interaction (project search, an LSP command, a WASM plugin call). Background work (indexing, file watchers, the LSP boot handshake) owns its own long-lived tokens and is deliberately out of scope — see docs/dev/architecture/cancellation.md §3.

CG.2 moved this from an owned Option<CancellationToken> to a shared handle, and registered the same Arc as a service. The Editor is only one of its users: providers arm through services.get::<ForegroundCancelHandle>(), because the places that spawn cancellable work — action-handler closures, event subscriptions — hold &self services and never &mut Editor.

Arc<T: Default> is Default, so #[derive(Default)] on Editor still holds.

§pending_hover_rx: Option<UnboundedReceiver<HoverOutcome>>§pending_hover_token: Option<CancellationToken>§pending_hover_anchor: Option<(Position, u32)>

2026-05-27: cursor + scroll captured at K-press time, consumed by open_floating_popup when the LSP response arrives so the hover popup anchors to the invocation site rather than wherever the cursor has drifted to. One-shot — cleared after the popup opens (or when the request is cancelled).

§pending_definition_rx: Option<UnboundedReceiver<Vec<Location>>>§pending_definition_token: Option<CancellationToken>§pending_nav_kind: Option<LspNavKind>§pending_references_terminus: ReferencesTerminus

LR.2 / EP.6: which terminus the in-flight references request is for.

Recorded when the request is issued so the drain routes without guessing. Defaults to Picker because that is what gr has always done and what every existing caller means.

§refreshing_references_view: Option<BufferId>

LR.3: when the in-flight references request is a refresh, the view to rebuild in place. None means “open a new view”.

Without this a refresh would open a SECOND *references* buffer beside the one the user pressed gr in — the same mistake *problems* refresh had to avoid, since create_multibuffer_view mints a fresh BufferId every call.

§pending_references_rx: Option<UnboundedReceiver<ReferencesOutcome>>§pending_references_token: Option<CancellationToken>§pending_symbols_rx: Option<UnboundedReceiver<SymbolsOutcome>>§pending_symbols_token: Option<CancellationToken>§pending_format_rx: Option<UnboundedReceiver<FormatOutcome>>§pending_external_format_rx: Option<UnboundedReceiver<ExternalFormatOutcome>>

IN.8b: results from an external formatter run. Separate channel from the LSP one because the payloads differ – see ExternalFormatOutcome.

§pending_format_token: Option<CancellationToken>§pending_signature_help_rx: Option<UnboundedReceiver<SignatureHelpOutcome>>§pending_signature_help_token: Option<CancellationToken>§pending_completion_rx: Option<UnboundedReceiver<CompletionOutcome>>§pending_completion_token: Option<CancellationToken>§pending_completion_items: Option<Vec<CompletionItemRow>>§pending_moniker_rx: Option<UnboundedReceiver<String>>§pending_rename_rx: Option<UnboundedReceiver<RenameOutcome>>§pending_rename_token: Option<CancellationToken>§pending_code_action_rx: Option<UnboundedReceiver<CodeActionOutcome>>§pending_code_action_token: Option<CancellationToken>§pending_code_action_items: Option<Vec<CodeActionRow>>§pending_code_action_handle: Option<ServerHandle>§pending_selection_range_rx: Option<UnboundedReceiver<SelectionRangeOutcome>>§pending_selection_range_token: Option<CancellationToken>§pending_document_highlight_token: Option<CancellationToken>§pending_folding_range_token: Option<CancellationToken>§pending_document_links_token: Option<CancellationToken>§pending_code_lens_token: Option<CancellationToken>§pending_code_lens_refresh_rx: Option<UnboundedReceiver<LspCodeLensRefresh>>§pending_code_lens_items: Option<Vec<CodeLens>>§pending_code_lens_server: Option<Arc<str>>§pending_document_color_token: Option<CancellationToken>§pending_color_presentations: Option<Vec<ColorPresentation>>§pending_color_range: Option<Range>§pending_inlay_hint_token: Option<CancellationToken>§pending_semantic_tokens_token: Option<CancellationToken>§pending_pull_diagnostics_token: Option<CancellationToken>§pending_diagnostic_refresh_rx: Option<UnboundedReceiver<LspDiagnosticRefresh>>§pending_inlay_hint_refresh_rx: Option<UnboundedReceiver<LspInlayHintRefresh>>§inlay_refresh_pending: HashSet<BufferId>

2026-06-03: buffers whose server sent workspace/inlayHint/refresh since their hints were last requested. drain_inlay_hint_refresh marks here instead of wiping lsp_inlay_hints_cache, so the previously-resolved hints stay rendered until the refetch lands (no disappear-then-reappear flicker — feedback_decorations_update_in_place). maybe_request_inlay_hint consults this to force a refetch even when the document version is unchanged, and clears the entry once it issues the request.

§semantic_tokens_refresh_pending: HashSet<BufferId>

2026-06-03: same shape as Self::inlay_refresh_pending for workspace/semanticTokens/refresh. The semantic-token colour overlay renders directly from lsp_semantic_tokens_cache every frame, so wiping the cache on refresh blanked all LSP colouring until the refetch landed (whole-viewport flicker per keystroke). drain_semantic_tokens_refresh marks here instead; the prior tokens keep rendering and maybe_request_semantic_tokens forces a refetch (delta from the retained result_id) that swaps them in place. (Pull diagnostics render from the persistent DiagnosticsLayer and code lenses are picker-only, so neither needs this — audited 2026-06-03.)

§pending_semantic_tokens_refresh_rx: Option<UnboundedReceiver<LspSemanticTokensRefresh>>§pending_lsp_detach_rx: Option<UnboundedReceiver<LspBufferDetached>>§pending_mode_lifecycle_rx: Option<UnboundedReceiver<ModeEvent>>§pending_major_entered_rx: Option<UnboundedReceiver<Event>>

MA.2: receives Event::MajorEntered so the per-tick minor-activation resolver (drain_minor_activation) can auto-activate minors whose ActivationPolicy admits the just-entered major on this buffer’s kind.

§pending_mode_enablement_rx: Option<UnboundedReceiver<Event>>

CI.4: receives Event::ModeEnablementRequested (a plugin’s enable-mode) so the per-tick drain_mode_enablement flips the mode registry and re-activates open buffers.

§pending_buffer_option_override_rx: Option<UnboundedReceiver<Event>>

Receives Event::BufferOptionOverrideRequested (a plugin’s set-option-in-buffer) so the per-tick drain writes the buffer-local layer. pending_mode_enablement_rx’s shape and its reason: the layer lives here, on the Editor, and the guest holds only a ConfigRegistry handle — which is the global layer and the wrong scope.

§pending_provider_view_refresh_rx: Option<UnboundedReceiver<ProviderViewRefreshRequested>>

OA.15a: receives ProviderViewRefreshRequested (a plugin’s refresh-view) so the per-tick drain_provider_view_refresh re-opens the view. The guest cannot reach the activator, so the call is a request and this is where it is applied — pending_mode_enablement_rx’s shape, with a typed event so the wake comes for free.

§pending_insert_completion_async_rx: Option<UnboundedReceiver<AsyncCompletionOutcome>>§pending_insert_completion_async_token: Option<CancellationToken>§pending_completion_resolve_rx: Option<UnboundedReceiver<CompletionResolveOutcome>>§pending_completion_resolve_token: Option<CancellationToken>§pending_renderer_signals: Vec<RendererSignal>

M.2.b.2 (2026-06-01): RendererSignals accumulated by impl ModeActivator for Editor calls made through extension-crate code paths (create_multibuffer_view and future provider triggers). The trait surface returns () — keeping RendererSignal out of lattice-mode — so signals are stashed here until the App’s dispatch loop drains them via Editor::drain_pending_renderer_signals.

§pending_renderer_effects: Vec<Effect>

Implementations§

Source§

impl Editor

Source

pub fn do_open_dashboard(&mut self) -> Vec<RendererSignal>

Open (or re-compose + activate) the *dashboard* buffer. Idempotent: a second call re-seeds the existing buffer in place rather than creating a duplicate.

Source§

impl Editor

Source

pub fn dispatch(&mut self, action: Action) -> DispatchOutcome

Renderer-neutral dispatch entry point.

5.5.A scaffolding: body is a stub. Renderer App::apply paths still do all the work in their own crates. Calling this today returns an empty DispatchOutcome and changes no state – behaviour-preserving by construction.

After 5.5 lands: every Action flows through here. Renderer code becomes:

ⓘ
pub fn apply(&mut self, action: Action) {
    let outcome = self.editor.dispatch(action);
    for signal in outcome.renderer_signals {
        // renderer-specific handling
    }
    // render-coupled per-frame cache refresh stays here
}
Source

pub fn dispatch_fused( &mut self, action: Action, _pre_active: BufferKind, pre_popup_focused: bool, popup_up: bool, ) -> FusedDispatch

Slice I.7 — one-round-trip keystroke apply.

dispatch plus its deterministic post-dispatch tail used to cost the renderer five blocking actor round-trips per keystroke (dispatch, then ensure_cursor_visible, maybe_reparse_syntax, sync_keymap_overlays, run_tick_pending — each a separate mutate_editor* mailbox crossing). On WSL2 each crossing is ~0.5ms (futex/scheduler), so the tail alone was the ~3.5ms felt typing lag. All four tail ops are pure-host Editor methods (the actor’s own async_landed arm already calls run_tick_pending in-actor), so fusing them into the dispatch round-trip is the correct shape, not a hot-path hack.

The fuse is conditional: the tail runs in-actor only when the dispatch produced no renderer-coupled work (the overwhelmingly common case — every plain Insert, motion, and operator that doesn’t open a buffer / fire an LSP request) and no popup was up. When the dispatch carries effects / signals / next_actions (whose App-side do_* handlers must run BEFORE the tail so effect ordering holds — e.g. OpenBufferAt switches the active doc, then ensure_cursor_visible must clamp against the new doc), or consumed it, or a popup is up (the App-side State-A hover-dismiss state machine owns that path), tail_signals is None and the renderer runs the legacy multi-RPC tail with the original ordering.

Publishes coalesce: this opens an outer publish batch around the whole sequence, so dispatch’s tail flush plus each tail op’s flush are suppressed (depth > 0) and the single real publish is the one the actor’s mutate_* wrapper fires after this returns (the batch is left at depth 0 with publish_pending set, but not flushed here — the wrapper’s publish_render_state() does it once).

pre_active is the renderer’s pre-dispatch active BufferKind, passed in so the in-actor tail can replicate App::apply’s popup_dismissed skip (don’t ensure_cursor_visible on a Help→Document dismiss, whose viewport height is still the popup’s stale inner height). pre_popup_focused is the pre-dispatch popup_focused value, captured alongside pre_active so the same skip works after the PU migration. popup_up is whether a popup buffer is shown; when true the fuse is declined so the App-side popup logic runs unchanged.

Source

pub fn handle_effect(&mut self, effect: Effect) -> DispatchOutcome

Renderer-neutral entry point for lattice_grammar::Effect handling.

Today’s TUI crate::app::dispatch::apply_effect is a ~60-variant match that dispatches to App-side do_* helpers. 5.5.E migrates those arms here as the underlying helpers move onto Editor. App’s apply_effect clones the effect, calls editor.handle_effect(effect.clone()), surfaces any RendererSignals, then matches on the original with a grouped no-op arm covering every variant the host has already taken responsibility for.

5.5.E.1 covers the three trivially helper-free arms: Effect::None, Effect::ClearSearchHighlight, and Effect::Echo. Other variants fall through to the catch-all _ => {} until their helpers migrate. The Effect::Many recursion stays on App for now – it dispatches inner effects back through App’s apply_effect so non-migrated inner arms still resolve.

Source

pub fn build_render_state(&mut self) -> RenderState

Phase 5.8.AF.5 / Slice 3a. Build a fresh RenderState snapshot from the editor’s current backing fields.

Naive shape today: every sub-state is reconstructed from scratch. Most sub-states are still empty placeholders (see render_state.rs module docs) so the cost is one Arc::new(SubState::default()) per slot. Only DiagnosticsRenderState clones a real layer; that clone is one Arc bump because DiagnosticsLayer is internally Arc<ArcSwap<...>>-backed.

Slice 3b/3c will replace this with per-subsystem publication (each background task store()s its own sub-state Arc directly), at which point this whole-world rebuild method retires.

Source

pub fn binding_mode_for_modal(&self) -> BindingMode

L4a.2 (lsp-architecture.md §15): recompute the inline cursor-line diagnostic-summary idle gate from the current option / modal state / cursor line. Called from Self::publish_render_state (the one chokepoint every dispatch and typed setter funnels through), so it runs once per coalesced publish on the actor thread — never the UI thread.

  • ui.diagnostics.inline = off → fully disarmed.
  • Insert / Replace (active text entry) → suppressed: summary hidden and the armed line cleared, so the gate re-arms when the user returns to Normal/Visual.
  • cursor on a new line → (re)arm: stash the line, set the deadline INLINE_DIAG_IDLE out, hide any prior summary.
  • cursor still on the armed line → leave the deadline + visibility untouched, so an in-place edit refreshes the text via build_render_state without restarting the timer.

WK.4: which BindingMode the next keystroke resolves in, derived from the current modal state. The same mapping the dispatcher’s decline path makes; hoisted so the pending-chord publisher cannot drift from it.

Source

pub fn update_inline_diag_gate(&mut self)

All scope is treated as cursor-line here; the all-viewport fan-out is L5.

Source

pub fn fire_inline_diag_gate(&mut self)

L4a.2: the idle deadline elapsed — make the cursor-line summary visible and clear the deadline so the actor’s pinned sleep doesn’t refire for it. The caller (the editor actor’s timer arm) republishes + wakes so build_render_state emits the summary into DiagnosticsRenderState::inline_summary.

Source

pub fn publish_render_state(&mut self) -> bool

Phase 5.8.AF.5 / Slice 3a. Build a fresh RenderState and atomically install it into the editor’s Arc<ArcSwap<RenderState>>. One atomic release-store on the hot path; concurrent readers see either the previous snapshot or the new one with no torn observation. Returns true when this real publish moved RenderState::paint_revision — i.e. a render-visible surface that the cells / virtual-rows workers do NOT own changed. The actor’s off-keystroke arms (async_landed / inline-diag) use the return to fire paint_request; the keystroke path ignores it (it already paints via the renderer’s input wake). A suppressed (batched) publish returns false — the real publish at batch-unwind reports the net change.

Source

pub fn set_cursor(&mut self, cursor: Position)

Write self.cursor and publish. See module note above for when to use this vs. a raw field write inside dispatch.

Source

pub fn set_cursor_clamped(&mut self, cursor: Position)

Self::set_cursor, then clamp both axes into the now-active buffer.

For the Open*At effects, whose position is computed before the target buffer exists and therefore cannot be validated at the point it is produced. MG.50’s magit <CR> is the case that needs it: on a deletion row the byte offset is measured against the deleted text, while the line it resolves to holds whatever is there now — which may be shorter. Without the clamp the caret lands past end-of-line and the next motion behaves oddly.

A separate method rather than folding the clamp into set_cursor: callers that position within a buffer they already hold have nothing to clamp against and should not pay for it.

Source

pub fn set_cursor_line(&mut self, line: u32)

Write self.cursor.line and publish.

Source

pub fn set_cursor_byte(&mut self, byte: u32)

Write self.cursor.byte and publish.

Source

pub fn set_scroll(&mut self, scroll: u32)

Write self.scroll and publish.

Source

pub fn set_modal(&mut self, modal: ModalState)

Write self.modal and publish.

Source

pub fn do_command_line_dismiss(&mut self)

Tear the : line down unconditionally: drop history / preview / decoration state and restore the prior editing buffer, cursor and modal. No command dispatched, no history push.

Split out of the Action::CommandLineCancel arm so Self::reset_to_normal can reach the hard cancel directly — <C-c> is an escape hatch, so it must not stop at the two-stage “first Esc dismisses the completion popup” behaviour that arm layers on top.

Source

pub fn reset_to_normal(&mut self)

CG.1: snap back to a stable Normal state, whatever the editor was in the middle of. The mode-reset half of Self::cancel_foreground, and the reason <C-c> is safe to bind universally: with nothing armed it still leaves the user somewhere they can keep working.

Each modal state exits through its own teardown rather than a bare set_modal(Normal), because the minibuffer states own real buffers: dropping ModalState::Command without Self::do_command_line_dismiss would leave *command-line* focused with no way to reach it. Insert / Replace route through Self::enter_mode so the insert undo-group closes and the cursor pulls back one byte (vim’s insert-exit contract) — which is also why an already-Normal editor must NOT call it, or a bare <C-c> in Normal would walk the cursor left on every press.

Per cancellation.md §6 this does NOT discard unsaved edits (it is not :q!) and does NOT clear the register or the yank ring.

Source

pub fn arm_cancel(&mut self) -> CancellationToken

CG.1: arm a foreground cancellation token for a user-initiated async operation, returning the clone the spawned task holds.

Cancels the predecessor first, so a second :search before the first completes abandons the first scan instead of leaving a zombie task racing the new one for the same buffer.

CG.2: a convenience over ForegroundCancel::arm for the paths that already hold &mut Editor. Providers reach the same slot through services.get::<ForegroundCancelHandle>() — most spawn sites are &self closures and cannot come through here.

Source

pub fn cancel_foreground(&mut self)

CG.1: the <C-g> body. Flip the armed foreground token(s), then reset_to_normal — emacs keyboard-quit, which is defined as doing both.

Idempotent and safe when idle — nothing armed is the common case, and degrading to a plain mode reset is what makes the binding harmless to press speculatively.

pending_hover_token is flipped alongside active_cancel because hover still owns a separate token today; CG.3 folds it in and this second take() goes away.

Source

pub fn enrol_foreground(&self, token: &CancellationToken)

CG.3: join a user-triggered LSP request to the foreground set so <C-g> reaches it, without superseding anything else.

The per-feature pending_*_token stays: a second hover should supersede the first, and that is the granularity the LSP layer already gets right. What was missing is only that <C-g> had no way to reach any of them.

Enrol rather than arm_cancel: K is a reflexive inspect key, and arming would mean glancing at a symbol silently kills the project search you are waiting on.

Only user-triggered requests. lsp_completion_request, lsp_signature_help_request, do_lsp_on_type_formatting_request and the whole maybe_request_* family fire on keystrokes, cursor moves and ticks; they are not foreground (design §3), and enrolling them would make <C-g> cancel work the editor is doing on its own behalf — and, far worse, make every keystroke’s completion request a foreground event.

Source§

impl Editor

Source

pub fn do_diff_open(&mut self)

D.3.a.1 (2026-05-29): open an inline diff session for the active document against its on-disk content.

Surfaces an echo-area error and returns early if any precondition fails: no active document, no path on disk (scratch buffer), a session is already open for this buffer. On success, registers the DiffSession, mounts a DiffOverlayVirtualRowProvider on the virtual-rows worker, spawns a publish-notify → VirtualRowsWake forwarder task, and triggers an initial note_buffer_edited so the first recompute fires without waiting for the next user edit.

Source

pub fn add_pane_group( &mut self, members: Vec<PaneGroupMember>, mapper: Arc<dyn RowMapper>, ) -> Result<PaneGroupId, String>

Register a new pane group. Returns Err with a brief description if any new member’s (pane, buffer) pair already participates in an existing group’s currently-active member. Suspended memberships (registered pairs where the pane has since switched to a different buffer) are ignored by the conflict check — they’re inert until/unless the buffer returns.

Source

pub fn drop_pane_group(&mut self, id: PaneGroupId) -> bool

Drop the group with the given id. No-op if not registered. Subsystems call this on session teardown (:diffoff, zen-mode exit, etc.).

Source

pub fn rebuild_scrollbind_group(&mut self)

D.0b: rebuild the singleton identity-mapper pane group that backs :set scrollbind. Called from apply_option_cascade on every scrollbind change.

Walks pane_tree.leaves() and collects each leaf whose current buffer’s per-pane resolved scrollbind option is true. Drops the old group (if any), then:

  • ≥ 2 members → creates a fresh IdentityRowMapper group and stores its id in scrollbind_group_id.
  • 0 or 1 members → no group (identity scroll-binding on a single pane is a no-op); clears scrollbind_group_id.
Source

pub fn remove_pane_group_member( &mut self, id: PaneGroupId, member: PaneGroupMember, ) -> bool

Remove a single (pane, buffer) member from a group. If the group is left empty, the group itself is dropped. Returns true if the member was removed.

Source

pub fn apply_pending_diff_mode_changes(&mut self)

D.5.a (2026-05-30): drain the DiffModeBridge’s pending queue and apply each toggle via the existing mode_registry pattern. Called from publish_render_state at the dispatch tail so any session register/drop that happened during this tick (either dispatcher-initiated or via the doc-close auto-drop on a tokio worker) reflects in ActiveModes before the next render snapshot.

Errors on individual buffers (mode not registered, capability mismatch) log + skip rather than panic — downstream do/dp dispatch (D.5.b/c) gracefully no-ops when the bit isn’t set.

Source

pub fn propagate_pane_group_scroll(&mut self)

Propagate the active pane’s scroll to every other member of its group whose registered buffer still matches its pane’s current buffer. Called from publish_render_state at the dispatch tail. Idempotent — running every tick is safe and cheap.

Source

pub fn diff_signs_for_active(&self) -> Option<Arc<DiffSignMap>>

D.3.d.0 (2026-05-29): snapshot the active document’s diff sign map, if any. Returns None when no session is open for the active buffer; renderers fall back to the no-gutter path. Lock-free ArcSwap::load_full; renderer hot path.

Source

pub fn do_diff_off(&mut self, force: bool)

D.3.a.1 / D.4.d.3.a / D.6.g / D.8.f (2026-05-31) — close diff state for the active buffer.

force = false (:diffoff):

  • If the active buffer belongs to the singleton diffthis group, the membership semantic kicks in (vim parity for :set nodiff on the current window): route through Self::diffthis_toggle_off, which removes the buffer from the group via [DiffSubsystem::remove_participant_buffer]. Arity transitions auto-handle (drop pane group + fillers when arity falls below 2; auto-drop the session at arity 0). The other diffthis-group buffers stay diffed against each other if at least two remain.
  • Otherwise (inline :diff <buf> / :diffsplit): tear down the one session resolved by [DiffSubsystem::lookup_session_for] for the active buffer. These shapes are user-explicit fixed-arity sessions — collapsing the whole session on one-side removal is the right semantic. (The uniform per-buffer shrink for non-diffthis sessions is a future generalization; v1 keeps the user-visible shape stable for :diffsplit-style commands.)

force = true (:diffoff!, D.6.g cascade): preserved unchanged. Tears down every session the active buffer participates in via [DiffSubsystem::all_sessions_for].

Source

pub fn do_diff_accept(&mut self)

D.6.e (2026-05-31): resolve the active pane’s diff session with [DiffOutcome::Accept]. v1 semantics: equivalent to :diffoff + signal Accept on any bound completion channel. The buffer’s current content is the accepted resolution — plugins commit from there.

API surface for in-tree consumers (magit-style plugins, AI proposal flows): bind a oneshot sender to the session via crate::diff::subsystem::DiffSession::bind_completion after registration, then await the oneshot::Receiver<DiffOutcome>. When the user invokes :diff-accept, this method fires the Accept signal on the channel.

Source

pub fn do_diff_reject(&mut self)

D.6.e (2026-05-31): resolve the active pane’s diff session with [DiffOutcome::Reject]. v1 semantics: equivalent to :diffoff! + signal Reject.

Source

pub fn do_close_session_diffs(&mut self, origin_session: u64) -> usize

D-fix.6: tear down every programmatic diff opened by IDE-peer connection origin_session, as a Reject — fires each session’s bound completion oneshot with [DiffOutcome::Reject] (the agent’s blocked openDiff returns DIFF_REJECTED), then closes its transient panes + refocuses the origin pane (finish_programmatic_diff_panes). Returns the count torn down.

Session-scoped (the load-bearing isolation): only diffs whose ProgrammaticDiffPanes.origin_session equals origin_session are touched — a diff opened by a different agent connection (or a non-IDE producer, origin_session == 0) is never affected. This is what lets multiple concurrent agent sessions each cancel their OWN diffs without disturbing the others. Presentation-agnostic: it matches on the connection id, not on the diff’s tab/window/split.

Source

pub fn do_close_session_tab(&mut self, origin_session: u64, tab_name: &str)

D-fix.6 Effect::CloseSessionDiffs: reject connection origin_session’s diff(s); if it opened none, fall back to the legacy I3 active-buffer file-close when tab_name equals the active buffer’s path (the only remaining tab_name use — orthogonal to the presentation-agnostic diff teardown above).

Source

pub fn do_diffthis(&mut self)

D.8.e (2026-05-31): :diffthis — per-buffer toggle on the singleton diffthis group. Replaces the D.4.d.3.a “stage first, complete on second” pattern with vim’s :set diff membership semantic.

State transitions (see n-way-diff-membership.md §6.2):

  • diffthis_group == None: create an N=1 dormant session keyed under the active buffer; set diffthis_group = Some(active_buf).
  • Active buffer is already in the diffthis group: remove it via remove_participant_buffer. Arity→0 auto-drops the session; arity→1 leaves it dormant (no pane group / fillers); arity≥2 shrinks the pane group.
  • Active buffer is not in the diffthis group: extend via add_participant. Arity 1→2 creates the pane group + filler providers; 2→3 grows it (drop-and-recreate).

Other sessions (:diffsplit, AI flows, future magit) are entirely independent — :diffthis never touches them. A buffer can be in :diffsplit’s session AND the diffthis group simultaneously; the diff-mode bridge handles the refcount.

Errors:

  • Active pane is not a Document leaf (file tree, help, etc.): “diffthis: active pane is not a document”.
  • Active buffer already in a non-diffthis session (:diffsplit, AI, etc.) AND not yet in the diffthis group: rejected. v1 disallows double-participation through :diffthis until D.8 refines the multi-session UX. The subsystem supports it via the refcounted bridge, but :diffthis keeps the convention “one session per buffer at a time” so the operator chord (do / dp) dispatch stays unambiguous.
  • Engine rejection (4th :diffthis would push arity to N=4): surfaces the typed error as a clear user message naming the cap.
  • Stale members (a pane closed) are silently scrubbed from diffthis_members at the start of every call.
Source

pub fn do_diffsplit(&mut self, path: PathBuf, remote: Option<PathBuf>)

D.4.d.3.b (2026-05-30): :diffsplit <file> — open <file> in a new vertical split and immediately register a two-pane diff between the current pane (baseline) and the new pane (current). Composes the existing do_split_pane / set_active / do_edit primitives with register_pane_group_diff from D.8.f (replaces the prior D.4.d.3.a register_two_pane_diff / D.6.c register_three_pane_diff pair).

Cursor lands in the new pane (vim parity). On failure to open <file>, the new pane is closed and the user is returned to the original pane — otherwise the user would be stranded in an empty vsplit holding the active doc.

Errors:

  • Active pane is not a Document leaf → “diffsplit: active pane is not a document”.
  • Active buffer already has a diff session → “diffsplit: buffer N already has an active diff session; use :diffoff first”.
  • do_edit returns Failed / Directory(_) / NoFileName → the new pane is closed; the user keeps the existing error message surfaced by do_edit.
  • register_pane_group_diff returns Err (e.g. pane already in another active group) → reported via set_message; the new pane stays open with <file> loaded (the user can :diffoff or <C-w>q from there).
Source

pub fn drain_inbound_programmatic_diffs(&mut self) -> Vec<RendererSignal>

I4 (Claude Code IDE peer, openDiff): drain host-drained programmatic diff requests and open each on the actor thread. Mirrors Self::drain_inbound_apply_edits — the receiver is taken, drained, and restored so the host owns it across ticks (the open is irreducibly &mut Editor + lattice-diff types, hence host-drained rather than a mode-owned Effect handler).

Source

pub fn open_programmatic_diff( &mut self, req: ProgrammaticDiffRequest, ) -> Vec<RendererSignal>

I4 (Claude Code IDE peer, openDiff): open an interactive side-by-side diff for one lattice_diff::ProgrammaticDiffRequest and bind its completion oneshot to the resulting session, so the producer (the IDE peer’s openDiff call) unblocks with the user’s DiffOutcome on :diff-accept / :diff-reject (or a close-tab cancel).

Layout (D-fix.1, Option A — the persistent-panel convention shared by VS Code / Cursor / Zed): the originating pane (the :claude terminal) STAYS put; the diff opens in two fresh splits to its right — claude | baseline | proposed. The baseline file’s on-disk content fills the left diff buffer; the proposed text fills the editable right buffer (carrying new_file_path so an Accept can save it). Built on Self::register_pane_group_diff (slot 0 = baseline/left, slot 1 = current/right), so hunk compute + filler rows + do/dp all reuse the existing two-pane machinery. The transient panes + buffers are recorded in programmatic_diff_panes; finish_programmatic_diff_panes closes them and returns focus to the claude pane on :diff-accept / :diff-reject.

On open failure (pane-group registration error) the response sender is dropped, which the producer’s await surfaces as a reject — never a hang.

NOTE (I4.0 scope): the left baseline buffer is a plain editable Document for now (mirroring :diffsplit, which makes neither side read-only); the read-only-baseline refinement lands in I4.1 alongside accept-writes-to-disk.

Source

pub fn set_message(&mut self, level: EchoLevel, text: impl Into<String>)

Surface a one-line message in the echo area. Replaces the previous message; also appends a lattice_runtime::MessageRecord to the bounded *messages* ring and publishes a typed MessagePushed event so subscribers (the runtime’s per-tick drain, plugin hosts) can react. The renderer reads Self::last_message directly for the echo-area paint.

Source

pub fn set_ephemeral_echo(&mut self, level: EchoLevel, text: impl Into<String>)

Set the transient echo-area message without recording it to the *messages* ring (unlike Self::set_message). For high-frequency ephemeral notices — the vim showmode indicator (-- INSERT --, ML.5d) on a mode change — that belong in the echo area but must never flood message history (the per-event spam feedback_log_levels warns against).

Source

pub fn ensure_cursor_visible(&mut self)

Scroll so Self::cursor is inside [scroll, scroll + viewport_height). No-op when viewport_height == 0 (the renderer hasn’t recorded a draw yet).

Both peers feed the active pane’s content height into set_viewport_height per frame — for help popups (active_buffer == Help) the TUI runtime substitutes help_popup_inner_height(...) for the doc-area height so the popup-focused cursor scrolls against the popup’s inner window rather than the full pane. The host trusts the active viewport_height here without re-deriving a popup-specific cap.

Source

pub fn ensure_cursor_horizontally_visible(&mut self)

Horizontal analog of Self::ensure_cursor_visible: with wrap off, keep the cursor’s display column inside the visible body window [leftcol, leftcol + body_w) by adjusting Self::leftcol. Vim semantics — sidescroll == 0 jump-scrolls so the cursor lands at the window centre; sidescroll > 0 scrolls in that many-column steps; sidescrolloff keeps that many columns of context on each side. No-op when wrap is on (the body reflows, so nothing is off-screen-right) or when the renderer hasn’t published a pane width yet (body_w == 0, the same “no draw recorded” guard the vertical clamp uses).

Source

pub fn do_horizontal_scroll(&mut self, kind: HScroll)

HS.2: vim z{l,h,L,H,s,e} manual horizontal scroll. Mutates leftcol directly (no-op under wrap, like the cursor-follow clamp) and then keeps the cursor inside the new window — mirroring do_scroll_line’s vertical “move the cursor to stay visible” behaviour so the cursor never scrolls off-screen. zl/zh honour the pending count.

Source

pub fn active_pane_buffer_id(&self) -> BufferId

What :bn / :bp consider the “current” buffer for stepping. The active pane’s buffer_id is the source of truth (the active pane is what the user sees).

Source

pub fn set_active_picker(&mut self, p: Picker)

Central picker-attach helper. PI.5: the preview_origin stash it used to seed is gone — preview no longer swaps the active buffer, so there is no origin to remember (the pane stays committed to its own buffer throughout preview; accept clears the projection).

Source

pub fn active_buffer_id(&self) -> BufferId

Identity of the buffer whose state the input dispatcher currently routes to. Document / file-tree / oil all return the active pane’s id; Help routes through the popup overlay slot (which still lives outside the pane tree as a transient overlay).

Source

pub fn popup_buffer_content(&self) -> Option<Buffer>

PU generalization: the popup buffer’s text content, regardless of buffer kind. Tries document_handle() first (covers all document-shaped kinds: Document, Help, Messages, Multibuffer, Dashboard). Returns None when no popup is open or the buffer is a non-document kind not yet handled (FileTree, Oil, Terminal are not used in popups today).

Source

pub fn popup_help(&self) -> Option<HelpBuffer>

Snapshot the active popup’s HelpBuffer. The popup buffer stores BufferId only; the actual content lives in self.buffers with BufferFlags { listed: false, hidden: true }. Returns a cloned snapshot (the rope clones in O(1) via ropey’s internal Arc); None when no popup is open or the registry entry has been torn down.

Source

pub fn active_text(&self) -> Buffer

The active buffer’s text – a Buffer clone (rope is O(1)). Document, help, file-tree, oil all flow through this so motion / scroll / search code can read text without branching on BufferKind. self.cursor / self.scroll are the live position into this buffer.

PU generalization: when a popup has focus (State B), dispatch reads from the popup buffer via Self::popup_buffer_content instead of self.document. Works for any document-shaped popup buffer kind (Help, Document, Messages, Multibuffer, Dashboard). In State A (passive hover) the popup check is skipped — document keeps focus.

Source

pub fn active_document_path(&self) -> Option<Arc<PathBuf>>

The file behind the buffer Self::active_text reads from.

OM.6b: a plugin action’s document handle answers path() from this, so it MUST follow the same branch as active_text — a guest handed the text of one buffer and the path of another would archive a subtree into a file it never saw.

None for every buffer with no file on disk: a focused popup, a terminal, help, and any synthetic document (whose path slot is a name, not a location).

Source

pub fn active_cursor(&self) -> Position

Cursor of the currently active buffer. Reads Self::cursor when the document holds focus, the popup buffer’s cursor (via Self::popup_help) when a popup overlay holds focus, and the kind-specific cursor stash for file-tree / oil.

PU generalization: when a popup has focus (State B), dispatch reads self.cursor directly (it IS the live popup cursor in State B — open_popup_buffer(Steal) copies popup_cursor into self.cursor, then motions update it). State A skips this.

Source

pub fn clamp_cursor_to_active_buffer(&mut self)

Clamp Self::cursor to the active buffer’s bounds. Reads from Self::active_text so it works for help / file-tree / document / oil uniformly.

Source

pub fn clamp_cursor_to_buffer(&mut self)

Legacy alias for Self::clamp_cursor_to_active_buffer used by post-undo / post-redo paths that already named it clamp_cursor_to_buffer in the renderer. Identical behaviour; retained to keep call sites mechanical across the move.

Source

pub fn bury_buffer(&mut self) -> bool

Tear down the popup overlay. Drops the popup’s content slot from the registry, clears placement / back-stack state, and restores any pre-popup focus captured at open. Idempotent – closing when no popup is open is a no-op. Effect::BuryBuffer — return the active pane to the buffer it was showing before a full-pane synthetic buffer took it over.

The counterpart to Self::open_synthetic_buffer, and deliberately NOT Self::dismiss_popup: opening went through Self::activate_buffer, which swaps the pane, the active-document handle (Editor::document), the snapshot cache, and the syntax state together. Returning has to swap the same set back, so it goes through the same function rather than hand-restoring a subset.

That subset is exactly what the bug was: dismiss_popup restored pane.buffer_id / active_buffer / cursor / scroll / modal and left Editor::document pointing at magit. The pane named the file, the active render path painted magit, and a redraw could not help because the data — not the paint — was stale. document_buffer_id == pane_tree.active().buffer_id is an invariant, and hand-restoring one side of it broke the cache it keys.

Returns false when there is nothing buried, so a mode can bind q to this unconditionally.

The destination’s option-cache rebuild and its LSP-attach cascade are Self::activate_buffer’s job now, not this function’s — which is the whole point of folding the completion in there. The bug this used to have (q out of a magit view returned the file with magit’s Number = false still cached, so the line numbers vanished) is fixed at that seam rather than here.

Source

pub fn dismiss_popup_named(&mut self, name: &str) -> bool

Effect::DismissPopupNamed — dismiss the popup only if the one on screen is the named one, and answer whether it was.

The targeted peer of Self::dismiss_popup. There is one popup slot, so an untargeted dismiss closes whatever is in it — correct for a key the user pressed, and a bug for a mode dismissing on its own schedule, where the slot may hold someone else’s popup by the time the effect lands.

Which-key is why this exists: its timer-driven dismissal fired on every resolved chord, so a two-key sequence typed faster than the popup delay (zz, gg, dd) tore down whatever hover or diagnostic popup was showing. Naming the target turns the stale case into a no-op.

Matches on the buffer’s SYNTHETIC NAME — the same string Effect::OpenPopup was given, and what ensure_named_popup_buffer filed it under. A buffer with no name (a content popup opened through open_floating_popup rather than by name) matches nothing, which is right: it has no identity for a mode to claim.

Source

pub fn dismiss_band(&mut self)

WK.12: close the minibuffer band and drop its buffer’s registry entry. Deliberately NOT a peer of dismiss_popup’s focus unwinding — a band never took focus, so there is nothing to give back.

Source

pub fn dismiss_stale_band_registry(&mut self)

The band’s peer of Self::dismiss_stale_popup_registry: the same per-buffer side tables, keyed on the band slot, plus the renderer-fed geometry so synthetic_popup_panes stops emitting the band’s pane.

Source

pub fn dismiss_popup(&mut self)

Source

pub fn dismiss_stale_popup_registry(&mut self)

Tear down the registry / mode / option-cache state for the currently-bound popup buffer. Called by Self::dismiss_popup and by the popup-open paths (so back-to-back popups don’t accumulate stale registry entries). No-op when no popup is set.

Source

pub fn document_syntax_for(&self, id: BufferId) -> Option<&SyntaxHandle>

Mode-owned syntax handle for id. For the active document this is the live hot-path slot (Self::syntax); for inactive documents it routes through buffer_locals. Returns None for plain-language documents and non-document buffers.

Source

pub fn minor_mode_enabled_for( &self, buffer_id: BufferId, mode_id: ModeId, ) -> bool

Generic per-buffer minor-mode accessor used by every M.6 sub-mode reader. Returns false when no entry exists for buffer_id – matches the umbrella accessor’s shape.

Source

pub fn lsp_folding_mode_enabled_for(&self, buffer_id: BufferId) -> bool

4.4.f: is lsp-folding-mode active on buffer_id? Gates textDocument/foldingRange issuance and the LSP fold cache read.

Source

pub fn autoread_enabled_for(&self, buffer_id: BufferId) -> bool

AR.1: whether external-change autoread is enabled for buffer_id, resolving the per-buffer autoread option (default true). The autoread watcher (AR.3) reads this to decide whether to watch the buffer’s file. Non-file buffers are excluded upstream by having no on-disk path, not by this flag. See docs/dev/architecture/autoread.md.

Source

pub fn refresh_autoread_watcher(&mut self)

AR.3: recompute the desired autoread watch set and, if it changed, push it to the watcher task (spawning the task lazily on the first file-backed autoread buffer, tearing it down when the last one closes). A cheap order-independent fingerprint gate skips the cmd-send when a buffer switch didn’t change the set. Non-blocking: no notify calls run on the renderer thread. Call on buffer open / close / activate.

Source

pub fn drain_autoread_changes(&mut self) -> Vec<RendererSignal>

AR.4: per-tick drain of the autoread change stream. Pulls every pending AutoreadChange from the watcher task, records it against its buffer, then applies the active buffer’s change now (a background buffer’s change waits until it becomes active — vim’s checktime-on-BufEnter). Runs on the actor thread, so the reload’s file read is off the renderer thread (same as :e). Called from Self::run_tick_pending.

Source

pub fn lsp_mode_enabled_for(&self, buffer_id: BufferId) -> bool

5.5.F.5.1: is lsp-mode active on buffer_id? Pure-editor read used by the mode-lifecycle auto-activation hook (Self::maybe_auto_activate_lsp_mode, F.5.2) and by :describe-buffer / the LSP capability gates.

Source

pub fn lsp_hover_mode_enabled_for(&self, buffer_id: BufferId) -> bool

5.5.LSP.1: is lsp-hover-mode active on buffer_id? Gates do_lsp_hover_request (the K binding).

Source

pub fn lsp_completion_mode_enabled_for(&self, buffer_id: BufferId) -> bool

M.6.0: is lsp-completion-mode active on buffer_id? Phase 5.8.AD.4: hoisted from TUI App.

Source

pub fn completion_mode_active_for(&self, buffer_id: BufferId) -> bool

CSM.K1: is completion-mode active on buffer_id? Phase 5.8.AD.4.

Source

pub fn completion_popup_mode_active_for(&self, buffer_id: BufferId) -> bool

CSM.K1: is completion-popup-mode active on buffer_id? Phase 5.8.AD.4.

Source

pub fn completion_popup_active(&self) -> bool

Shorthand: is the insert-completion popup live on the active document buffer? Phase 5.8.AD.4.

Source

pub fn lsp_signature_mode_enabled_for(&self, buffer_id: BufferId) -> bool

5.5.LSP.4: is lsp-signature-mode active on buffer_id? Gates lsp_signature_help_request – silent gate (Insert- mode auto-trigger).

Source

pub fn check_lsp_mode_gate(&mut self) -> bool

5.5.LSP.1: shared gate for every LSP request entry point (hover / definition / completion / format / rename / code-action / symbols / signature / references). Returns true when lsp-mode is active on the current document; callers early-return on false. A single echo surfaces the gate state so users discover the mode – silent gates are a documented anti-pattern when editor defaults the user expects (K, gd) suddenly do nothing.

The echo level is Info (not Warn) – gated state is expected user-controlled, not a misconfiguration.

Source

pub fn check_lsp_sub_mode_gate( &mut self, sub_mode_id: ModeId, sub_mode_name: &str, ) -> bool

5.5.LSP.1: shared gate for a per-feature LSP sub-mode. Checks the umbrella first (so the user gets one consistent message-source-of-truth: enable lsp-mode first, then the sub-mode); returns true only when both are active. Echoes at Info matching the umbrella’s level.

Used by lsp_*_request methods that want a user-discoverable bail message. Insert-mode auto-triggers (insert completion, signature help, on-type formatting) skip the echo path entirely and check the bool directly – a typed character that doesn’t fire isn’t a moment to surface mode state.

Source

pub fn focus_help_popup(&mut self)

5.5.LSP.1: State A -> State B – focus moves into the hover popup. After this, the popup behaves like any other buffer (vim grammar, / search, : ex commands operate on the popup’s content); the doc behind is frozen. Dismiss with <Esc> / q returns focus to the doc at the cursor it was on. No-op when no popup is live.

Source

pub fn focused_surface(&self) -> Option<&MinibufferFocus>

FS.1: the innermost focused surface, or None when the pane’s own buffer has focus.

The one place the stack is read for “is anything focused” and “what is”. Callers that reached for the field directly are why the two questions could drift apart.

Source

pub fn command_line_active(&self) -> bool

MB.1: true while the *command-line* buffer is focused for editing (the : line is open). The shared focus stack is Some for BOTH prompts, so a prompt is the command line only when the search line is not active (MB.5a). See Self::focus_editing_buffer.

Source

pub fn search_line_active(&self) -> bool

MB.5a: true while the *search-line* buffer is focused for editing (a / or ? search is being typed). search_line carries the direction + origin; the pattern text lives in the focused buffer (Self::search_pattern).

Source

pub fn search_pattern(&self) -> String

MB.5a: the /·? pattern text — the first line of the focused *search-line* buffer, the single source of truth (peer of Self::command_line). Empty when no search line is open.

Source

pub fn command_line_expanded(&self) -> bool

MB.2: true while the : line is expanded into the tier-2 full-modal mini-buffer band (<C-x><C-e>). false in the one-row readline line (tier 1) and when the command line is closed. The renderer reads this to grow the echo-area row into a band (pushing the mode-line up); the dispatcher reads it to allow real modal editing on the *command-line* buffer.

Source

pub fn search_line_expanded(&self) -> bool

MB.5c: true while the /·? search line is expanded into the tier-2 band (its own <C-x><C-e>). Same shared expanded flag as the command line, scoped to the search prompt.

Source

pub fn command_line_expand_height(&self) -> ExpandHeight

MB.2e: the resolved command-line.expand-height policy driving how tall the expanded band grows. Read by both renderers (they apply it to the live frame height via [ExpandHeight::rows]). Falls back to the default (half) when the option isn’t registered — the boot-before-linkme + test-fixture path, mirroring build_tabs_render_state’s tabline.show read.

Source

pub fn command_line_full_text(&self) -> String

MB.2: the full (possibly multi-line) text of the : line — the expanded band renderer draws every line, unlike Self::command_line which returns only the first line (the tier-1 one-row view). Empty when the command line is closed.

Source

pub fn search_line_full_text(&self) -> String

MB.5c: the full (possibly multi-line) text of the /·? search line — the expanded band renderer draws every line, unlike Self::search_pattern which returns only the first line. Empty when the search line is closed. (Peer of Self::command_line_full_text.)

Source

pub fn do_command_line_toggle_expand(&mut self)

MB.2: <C-x><C-e> — toggle the : line between the one-row readline line (tier 1, ModalState::Command, insert-only) and the expanded full-modal band (tier 2). Expanding drops into Insert on the *command-line* buffer so the whole vim grammar is live (Normal / Insert / Visual, motions, operators, registers, undo); collapsing returns to ModalState::Command with the edited text for review — no auto-execute. No-op when the : line is closed.

Source

pub fn do_search_line_toggle_expand(&mut self)

MB.5c: <C-x><C-e> — toggle the /·? search line between the one-row readline line (tier 1, ModalState::Search, insert-only) and the expanded full-modal band (tier 2). Shares the MinibufferFocus.expanded flag with the command line. No-op when the search line is closed.

Source

pub fn command_line(&self) -> String

MB.1: the : line’s editable text — the first line of the focused *command-line* buffer, which is the single source of truth. Computed on read (there is no projection field); empty when no command line is open.

Source

pub fn set_search_line_text(&mut self, s: &str)

MB.5b: owner-write the /·? search line’s text — replace the whole *search-line* buffer content with s and place the cursor at the end. Used by history walk (peer of Self::set_command_line_text). No-op when the search line is not active.

Source

pub fn set_command_line_text(&mut self, s: &str)

MB.1: owner-write the : line’s text — open the command line if needed, then replace the whole *command-line* buffer content with s and place the cursor at the end. Used by history walk, completion accept, the missing-arg prefill, and programmatic / test callers. Interactive typing edits the buffer at the cursor through the Insert dispatcher, never through here.

Source

pub fn focus_editing_buffer(&mut self, id: BufferId)

MB.1 (the “audit finding to solve first”): swap the editing focus to id (the *command-line* buffer) WITHOUT touching the pane tree — the active pane keeps rendering its own buffer (the renderer routes it to the registry-keyed path via the published command_line_active flag). Stashes the prior editing focus in Self::focus_stack; restored by Self::restore_editing_buffer. No position-history push, no echo.

Source

pub fn focus_editing_buffer_at( &mut self, id: BufferId, cursor: Position, scroll: u32, )

FS.2: Self::focus_editing_buffer, but landing at a given cursor and scroll instead of the top.

The minibuffers want the top — they are one fresh line. A popup wants the view state it was last left at, which is the whole reason focusing one used to be hand-rolled instead of going through this seam.

Source

pub fn restore_editing_buffer(&mut self)

MB.1: pop the editing focus stashed by Self::focus_editing_buffer — re-fetch the prior document from the registry, restore its syntax/folds/cursor/scroll/modal, and clear the projection. No-op when no command line is open.

Source

pub fn open_command_line(&mut self, prefill: &str)

MB.1: open the : command line — ensure/focus the synthetic *command-line* buffer (its major mode’s Insert keymap supplies submit/cancel/history/completion), seed it with prefill, and set ModalState::Command. Keys route through the Insert dispatcher because the buffer is the focused Insert-editable surface.

Source

pub fn open_search_line(&mut self, direction: SearchDirection)

MB.5a: open the /·? search line — ensure/focus the synthetic *search-line* buffer (its major mode’s Insert keymap supplies submit/cancel), and set ModalState::Search(direction). Keys route through the Insert dispatcher because the buffer is the focused Insert-editable surface (mirrors Self::open_command_line).

Source

pub fn open_prompt_line( &mut self, prompt: String, initial: String, on_submit_action: String, buffer_name: Option<String>, ) -> Vec<RendererSignal>

Open a generic one-line minibuffer text prompt (see Effect::OpenPrompt) — ensure/focus a synthetic buffer (named buffer_name, or a default singleton when None), seed it with initial, set ModalState::Prompt, remember on_submit_action for do_prompt_line_submit, and show prompt as an info echo (there is no dedicated prompt-glyph rendering path — the echo area carries the label, same surface Effect::Echo already uses). Mirrors Self::open_command_line/Self::open_search_line’s shape.

Source

pub fn prompt_line_text(&self) -> String

Owner-write the focused prompt buffer’s text (analogous to Self::set_command_line_text/Self::set_search_line_text). No-op when no prompt is focused. MG.51: the *prompt-line* buffer’s text, or empty when no prompt is open.

Peer of Self::command_line, and gated the same way: the prompt buffer is the focused editing document while ModalState::Prompt is live, so its first line IS what the user has typed.

Source

pub fn do_prompt_line_submit(&mut self, out: &mut DispatchOutcome)

<CR> on a generic prompt — read the typed text, restore the prior editing buffer, then resolve on_submit_action (stashed by Self::open_prompt_line) through the same ActionHandlerRegistry magit’s global actions use, firing it with ActionContext::prompt_value set to the typed text.

The fired handler’s ActionContext::buffer_id/cursor are the PROMPT buffer’s own (captured before restoring focus), not whatever buffer was active before the prompt opened — so a handler can read the prompt buffer’s synthetic name back for any context the caller stashed there (mirrors magit’s blame/rebase/revision modes encoding their target the same way). A future caller that instead wants “the buffer the user was editing before the prompt” needs a different mechanism; this one always hands the handler its own invoking buffer, exactly like a transient item’s Action sees the buffer that had focus when the transient was opened.

Source

pub fn do_prompt_line_cancel(&mut self)

<Esc> / <C-c> on a generic prompt — cancel without firing anything, restore the prior editing buffer.

Source

pub fn do_insert_register(&mut self, name: char)

YR.5: vim’s insert-register — <C-r> then a register char.

Reads through read_register, so "+ reaches the system clipboard and "" the unnamed register exactly as a paste would. An unset register echoes rather than inserting nothing silently: the user typed two deliberate keys and is owed an answer.

Source

pub fn do_open_yank_picker(&mut self) -> Vec<RendererSignal>

YR.5: <C-r><C-r> — open the yank picker over the current surface.

The fill target is decided HERE, which is the whole of YR.3’s rule: this is open time, when the surface that asked is still the focused one. By the time the picker accepts it will have been dismissed and this question would have a different answer.

Source

pub fn open_picker_for_effect( &mut self, source: String, args: Vec<String>, root: Option<PathBuf>, fill_action: Option<String>, query: Option<String>, ) -> Vec<RendererSignal>

PC.11: the whole of Effect::OpenPicker, host-side.

Both peers used to inline this — set picker_root, call open_picker — and PC.11 would have added a third and fourth line to each, in two files, with the rollback below easy to get right in one and forget in the other. One method is what makes that unrepresentable rather than a discipline.

picker_root is written unconditionally, including the None: that write is what clears a previous picker’s override (PC.1).

The fill target is captured HERE, at open, for YR.3’s reason — by accept time the picker is dismissed and “who asked for this” has a different answer.

Source

pub fn do_picker_descend(&mut self) -> Vec<RendererSignal>

PC.10: <C-l> — go INTO the selected candidate.

The source decides what that means and whether it means anything: PickerSourceGenerator::descend defaults to None, so this is a no-op in every picker but dir-pick today. Deliberately silent on the None path — a key that echoed “this picker cannot descend” on every stray press would be noise, and unlike a FillCaller with no target there is no wiring bug to report.

Gated on the source being LIVE, because only a live source refetches: rewriting a static source’s query would fuzzy-filter the rows init already returned rather than fetching the ones inside the candidate.

Source

pub fn do_picker_ascend(&mut self) -> Vec<RendererSignal>

PC.10: <C-h> — the peer, one level out. Same gating and the same silence; the source answers from the QUERY rather than the selection, because going up says nothing about which row happens to be selected.

Source

pub fn do_picker_ascend_or_delete_word(&mut self) -> Vec<RendererSignal>

PH.1: <C-w> — Self::do_picker_ascend where the source has depth, the command-line’s delete-word everywhere else.

Decided by whether the source ANSWERS ascend at all, not — as Self::do_picker_tab decides — by whether the query moved. The difference is the fixed point: dir-pick at / answers Some("/"), and a moved-query test would read that as “no depth” and delete the /, leaving a directory picker with no directory.

Source

pub fn do_picker_help(&mut self) -> Vec<RendererSignal>

PH.1: <C-h> — close the picker and open its help page.

The page is chosen in three rungs, most specific first:

  1. the topic the source declares (PickerSourceSpec::help_topic), so a family of sources can share one page;
  2. picker-<id>, if a page by that name is registered — the one every builtin page is named for — or, for a plugin’s source, the page ending .picker-<id> that SAME plugin registered (its topics are namespaced, project.picker-projects). This is how a plugin documents its picker without a spec field crossing WIT;
  3. the general picker page, with an echo naming the source, so the user knows they are reading the shared keys and not this picker’s.

Pickers seated without a registry id (LSP locations, :lsp-log, :ai-log) answer rung 2 through lattice_picker::PickerSource::help_topic.

A DECLARED topic that is not registered warns rather than falling back quietly: it is a wiring bug (a renamed page, a plugin that failed to load), and the echo naming the topic is the only thing that points at it.

The picker closes first, including a parent it was stacked over. It is a modal overlay that owns every key; a help page opened beneath it could be neither scrolled nor dismissed.

Source

pub fn do_picker_tab(&mut self) -> Vec<RendererSignal>

PP.5: <Tab> — drill in if this source has depth, else select the next row.

Decided by whether the descend actually moved, not by asking the source a second question. descend already IS the depth declaration; a separate “does tab drill” flag would be a second knob that can disagree with the first, and the disagreement would show up as a <Tab> that drills in one picker and not in another with the same shape.

The query is the thing compared because that is exactly what a descend changes — do_picker_descend’s signals are not a usable indicator, it returns an empty vec both when it declined and when it descended into a directory with nothing to preview.

Source

pub fn do_picker_delete(&mut self) -> Vec<RendererSignal>

PD.1: <C-d> — remove the selected row from whatever backs the list.

The source names the verb (PickerSourceSpec::delete_command) and the host supplies the key and the refresh. A source that names none leaves this silent, exactly as <C-l> is silent in a picker with no depth — and for the same reason: a key that echoed “this picker cannot delete” on every stray press would be noise, and there is no wiring bug to report.

The picker stays open and re-lists. Deleting is a tidying action, and one that closed the picker would make removing three stale projects into three round trips. Re-running init rather than dropping the row locally is what makes the list agree with the store: the source owns what is in it, and a host that spliced a row out would be guessing that the command did what the row implied.

Source

pub fn do_open_arg_picker(&mut self) -> Vec<RendererSignal>

YR.6: open the picker registered for the argument under the cursor on the : line (<C-x><C-o>).

The second FillCaller consumer, and the reason YR.3 captured its target at open rather than resolving it at accept.

<C-x><C-o> is vim’s omni-completion chord, and the meaning carries over exactly: “ask whatever knows about this position”. <Tab> still runs inline completion from ArgSpec.completion; this opens the richer surface from ArgSpec.picker. An argument may declare both — they answer the same question at different weights.

Source

pub fn do_command_line_submit(&mut self, out: &mut DispatchOutcome)

5.5.G.23.cmdline: handle a :-line submit. Resolves the missing-arg prompt (DESIGN.md §B.1) — if the user submitted a bare command with a required first arg empty, prefill the cmdline + return without executing. Otherwise consume the line, push to history, restore the prior editing buffer, and dispatch through execute_ex_line (which feeds effects into out.effects for the App-side renderer-coupled tail).

Source

pub fn do_command_line_append(&mut self, c: char)

MB.1: append a character to the : line. The readline path (Insert dispatcher) is the production route; this method remains a buffer-backed typing primitive (programmatic / test callers), so it writes the *command-line* buffer rather than the projection.

Source

pub fn do_command_line_backspace(&mut self)

MB.1: pop a character from the : line; on empty + backspace, cancel the line (restore the prior editing buffer). Buffer-backed.

Source

pub fn do_command_line_clear(&mut self)

MB.1: <C-u> — clear the : line. Buffer-backed.

Source

pub fn do_command_line_delete_word_backward(&mut self)

MB.1: <C-w> — delete the word before the cursor on the : line. Buffer-backed (v1 cursor is at end-of-line).

Source

pub fn do_command_line_append_chord( &mut self, token: String, out: &mut DispatchOutcome, )

5.5.G.23.cmdline: append a chord token to the command line, and submit as soon as the accumulated sequence is one the trie has finished reading. Chord capture suppresses the completion popup (no useful candidates for chord input).

§The terminator, and why it is gone

K.3.5 auto-submitted on the FIRST captured chord, so <C-h>k j was one keypress. That made every multi-key chord undescribable — gg, <C-w>v, ]e, <leader>fz all submit after the first key — so K.3.5.fix required an explicit <CR> instead.

The <CR> in turn had to be reserved by the capture translator, along with <Esc> (abort) and <BS> (correct), which is why those three keys could not themselves be described. The comment claiming the missing-arg prompt was an escape hatch was wrong: that path sets the same chord_capture flag and hits the same reserved branch.

Both problems come from asking the USER to say when a sequence ends. The trie already knows — it is the Partial vs Bound/Unbound question the dispatch loop answers on every keystroke — so capture asks it instead. gg reads two chords because g is Partial; j submits on one because it is Bound; <M-k> that nothing binds submits on one because it is Unbound, which is the answer the user asking “why did that key do nothing” came for. This is emacs C-h k.

§Which keymap the question is asked OF (DK.4)

The trie is asked across every LAYER, not just the layers active where the user stands — lattice_keymap::KeymapHandle::any_layer_expects_more. Asking the activation-gated question here truncated any chord owned by a major mode or a plugin, because a chord prompt focuses the *command-line* buffer and the gated query then resolves against the MINIBUFFER’s modes. Org’s <C-c><C-x><C-b> submitted after two chords and described <C-c><C-x>; gg only ever worked because Builtin is always-on.

Sequence shape is a property of the keymap; what FIRES is a property of the buffer. build_describe_key_content still resolves the second one against the buffer the prompt was opened from (submit restores it before dispatching) and marks each layer [active] / [inactive].

A bare PREFIX (<Space>, g) still cannot be captured on its own — capture waits, correctly, for the rest. The string form (:describe-key <Space>) covers that, and now answers with the prefix’s continuation subtree rather than “not bound in any mode”.

Source

pub fn do_command_line_complete_or_advance(&mut self)

5.5.G.23.cmdline: <Tab> — open the popup if closed, advance the selection if open.

Source

pub fn do_command_line_describe_under_cursor( &mut self, out: &mut DispatchOutcome, )

5.5.G.23.cmdline: <C-h> (DESIGN.md §5.11.3 Q11). Walk the : line up to the cursor, identify the word under the cursor: (1) word resolves to a registered command — describe THAT; (2) slot is an arg of a known command — describe the parent scrolled to arg:<name>; (3) otherwise echo a status hint.

Source

pub fn apply_app_effect(&mut self, app: AppEffect, out: &mut DispatchOutcome)

5.5.G.24: AppEffect router collapse. Every Effect::AppAction emitted by the grammar registry routes here; the body mirrors the App-side apply_app_effect 1:1, except every self.apply(Action::X) becomes out.next_actions.push(Action::X) so the renderer’s existing next_actions drain (with should_quit short-circuit) processes them through the full apply loop. The one structural arm (AbsorbOperatorPrefix) mutates editor fields directly host-side – pending_count / op_count / partial_chord all live on Editor, and operator_prefix is already in lattice-host::keymap_normal.

Source

pub fn try_resolve_missing_arg_prompt(&self) -> Option<MissingArgPrompt>

5.5.G.23.cmdline: detect a missing required first arg. When the user submits a bare command with a required first arg empty, returns the prefill string + arg kind + prompt so the caller can transition into a chord-capture or arg-input mode.

Source

pub fn arm_missing_arg_prompt(&mut self, command_name: &str) -> bool

K.3.5 (2026-06-02): public API — arm the cmdline missing-arg prompt for command_name.

Looks up the command (accepts canonical names like "ex:describe-key" AND ex-command aliases like "describe-key"). If the command has a first arg with default: ArgDefault::Required, transitions the editor to ModalState::Command, prefills command_line with "<command_name> " (a trailing space puts the cursor in the first arg slot), arms auto_submit_after_chord for [ArgKind::Chord] args (so the next keystroke submits the prompt with the captured chord), and emits the schema’s prompt text into the echo area.

Returns true if the prompt was armed; false if the command doesn’t exist, isn’t an ex-command, or doesn’t have a required first arg. Callers receiving false can invoke the command directly with empty args (e.g. for no-arg commands like :help).

§Extensibility contract

Public so plugins, user init.rs code, and host-side keymap bindings reach the same prompt-armed state the cmdline-submit path produces. The K.3.2 help-prefix chord bindings (<C-h>k etc.) call this API directly for :describe-key / :describe-command / :describe-option / :describe-event / :describe-mode / :apropos — there is no command-apply indirection; both the keystroke and :<name><CR> typed in the cmdline funnel through this same API.

Internal implementation shares [apply_missing_arg_prompt] with [do_command_line_submit] so the field updates are identical regardless of call source.

Source

pub fn dispatch_chord( &mut self, chord: KeyChord, partial_chord: &mut Vec<KeyChord>, ) -> Action

K.4.1.b (2026-06-02): public chord-dispatch API.

Programmatically dispatch a single [KeyChord] through the host’s translate + handle_action pipeline. Plugins / init.rs / integration tests / scripted automation use this when they want to inject a chord WITHOUT going through the TUI’s crossterm-event layer. The shape matches what App.apply does internally for keyboard input, minus the App-side surface state (picker overlay, completion popup, snippet active, terminal modes) that defaults to “not active” for the programmatic-dispatch case.

§Multi-chord sequences

partial_chord is mutated to track in-progress multi-key sequences (gg, dw, <C-w>gd, ]e, …). Callers dispatching a sequence pass the same &mut Vec<KeyChord> through each call:

ⓘ
let mut partial = Vec::new();
editor.dispatch_chord(KeyChord::char('g'), &mut partial);
editor.dispatch_chord(KeyChord::char('g'), &mut partial);
// cursor now at line 0 (gg)

partial_chord is updated per the host’s existing partial- chord lifecycle (see handle_action’s line ~1395 comment): Action::AbsorbPartialChord(c) pushes; resolving / aborting actions clear; Action::PushDigit(_) and Action::EnsureCursorVisible are intentionally exempt (count accumulation between chord steps; renderer housekeeping).

§Return value

Returns the Action that translate produced. Host-side actions (Invoke, fold ops, visual mode, scroll, paste, undo/redo, etc.) are already applied to self by the internal handle_action call. App-only variants (e.g. PickerAccept, TerminalInput, LspWorkspaceSymbolRequest) are returned for the caller to handle if needed.

§Extensibility

Public per the standing extensibility principle: all keymap-grammar functionality should be backed by APIs usable from plugins / init.rs. The chord-dispatch path is one such API — both the TUI’s input layer and any programmatic caller funnel into the same translate + handle_action pipeline; dispatch_chord is the affordance for the programmatic case. Dispatch a chord and RUN it, discarding the effects it produced.

Kept as the common spelling because almost every caller wants only the resolved Action. When you need the effects too — a renderer, or a test that must apply them — use Self::dispatch_chord_with_outcome rather than re-dispatching the returned action: the action has ALREADY run, and running it again applies the edit twice.

Source

pub fn dispatch_chord_with_outcome( &mut self, chord: KeyChord, partial_chord: &mut Vec<KeyChord>, ) -> (Action, DispatchOutcome)

Self::dispatch_chord plus the DispatchOutcome it produced.

The outcome was always built here and then dropped, which left callers that needed the effects only one option: re-dispatch the returned Action. That RE-RUNS it. Org’s test harness did exactly that and every editing chord applied twice — one <leader><CR> inserting two items — under a comment asserting it did not. The effects are handed back instead.

Source

pub fn chord_capture_active(&self) -> bool

5.5.G.23.cmdline: true when the cmdline cursor sits on an ArgKind::Chord arg slot. Drives the input layer’s chord-capture overlay.

Source

pub fn execute_ex_line(&mut self, line: &str, out: &mut DispatchOutcome)

5.5.G.23.cmdline: parse + dispatch a :-line. Errors echo via set_message; successful dispatch routes the resulting effect through apply_effect_host (host migrated-arm pass + push to out.effects for the renderer-coupled tail).

Source

pub fn compute_completion_state( &self, ) -> Result<CompletionState, CompletionComputeError>

5.5.G.23.cmdline: compute the : completion popup state from the current command line. Reads the cursor-position slot (command-name / arg / empty) via lattice_completion::current_slot, resolves the matching generator, runs the pipeline, and post-processes command candidates to prefer alias names.

Source

pub fn refresh_substitute_preview(&mut self)

5.5.G.23.cmdline: refresh the :s/.../.../ live substitute preview from the current command-line buffer. Called after every cmdline edit (append / backspace / delete-word). When the cmdline doesn’t parse as a substitute, clears the preview. Mid-typing patterns that don’t compile yet preserve the last preview rather than flickering.

Source

pub fn refresh_command_line_decorations(&mut self)

MB.4: recompute the live : line decorations (syntax spans + validation error + parameter hint) from the command-line buffer. Runs on the actor thread (never the render thread), like Self::refresh_substitute_preview — the tokenize+validate pass is cheap (one line). Clears to None when the command line is closed or empty so the renderer draws a bare line.

Source

pub fn open_completion_popup(&mut self)

5.5.G.23.cmdline: open the : completion popup, or inline a single candidate when completion.auto_insert_single is on and the compute yields exactly one match. Errors echo via set_message. The <Tab> path drives this entry point.

On multiple matches the longest common prefix (LCP) of the candidate texts is inserted into the cmdline before the popup opens – vim’s wildmode=longest:full behaviour. Gives visual feedback for partial completions (e.g. :e crates/latt<Tab> extends to :e crates/lattice- when every match shares that prefix) instead of leaving the cmdline frozen at the user’s typed text while the popup silently lists alternatives.

Source

pub fn refresh_completion_popup(&mut self)

5.5.G.23.cmdline: refresh the : popup against the current command-line prefix. Called after every command-line edit (append / backspace / clear / delete-word) while a popup is open. NoMatches keeps the popup open with zero candidates; other errors drop the popup (slot moved to a no-completion region).

Source

pub fn do_play_macro(&mut self, register: char, out: &mut DispatchOutcome)

5.5.G.23.macros: host-side macro replay. Push every recorded action onto out.next_actions so the renderer’s apply loop drains them through its full dispatch pipeline (including arms that still live App-side — completion / cmdline / picker clusters). The dispatch wrapper’s drain honours editor.should_quit so a recorded :q short-circuits replay in lockstep with the pre-migration semantic.

Macro-recording capture is suppressed during playback by snapshot-and-restore of self.macro_recording — the recorded actions themselves trigger the dispatch loop’s “skip-management-actions” guard, but a q-started recording that overlaps playback should not absorb the replayed actions.

Source

pub fn do_play_last_macro(&mut self, out: &mut DispatchOutcome)

5.5.G.23.macros: @@ — replay the most recently-played macro. Errors when no prior replay has occurred.

Source

pub fn do_repeat_last_change(&mut self, out: &mut DispatchOutcome)

5.5.G.23.macros: . — dot-repeat the last change. Replays the captured invocation through run_invocation; if the captured invocation flipped into Insert and there’s a captured insert string, replay that too and exit back to Normal. Both run_invocation + do_insert_text are host-resident, so this stays a single host call with no next_actions indirection (effects + signals + LSP follow-ups still thread through out).

Source

pub fn do_find_repeat(&mut self, reverse: bool, out: &mut DispatchOutcome)

5.5.G.23.macros: ; / , — repeat the last f/F/t/T motion in the original (reverse = false) or opposite (reverse = true) direction. Builds a synthesized CommandInvocation against the appropriate builtin and routes through run_invocation.

Source

pub fn do_insert_text(&mut self, s: &str, out: &mut DispatchOutcome)

5.5.G.23.insert: host-side text insertion at the cursor (the Insert-mode keystroke path). Drives apply_edit_blocking, updates the dot-repeat insert recording, bumps the block- visual live-edit counter, and runs the insert-completion live-refresh + SignatureHelp / OnTypeFormatting trigger autopilots.

The LSP autopilots emit Action::LspOnTypeFormattingRequest / Action::LspInsertCompletionRequest via out.next_actions so the renderer can drive the async LSP plumbing (still App-resident: spawn_on_lsp_runtime + BatchingSink + pending_insert_completion_lsp_* channels). The host fires signature_help_request directly since that’s already host-resident.

Source

pub fn maybe_refresh_insert_completion_after_edit( &mut self, out: &mut DispatchOutcome, )

5.5.G.23.insert: host-side insert-completion popup refresh after a buffer edit lands on the document. Called by do_insert_text immediately after the edit; refilters the candidate set against the new query and dismisses the popup when the cursor moves past the anchor (e.g. inserted whitespace), or when the resulting query has zero matches and the last LSP response wasn’t isIncomplete.

On isIncomplete, emits Action::LspInsertCompletionRequest via out.next_actions so the App-side handler can re-fire the async LSP request (the request itself stays App-side because it reaches for spawn_on_lsp_runtime + BatchingSink + the pending_insert_completion_lsp_* channels).

Source

pub fn signature_help_trigger_chars(&self) -> Vec<char>

5.5.G.23.insert-prep: union of signatureHelp trigger characters across every LSP server attached to the active document. Empty when no server advertises the provider. Used by the host-side Editor::do_insert_text (forthcoming) to fire the autopilot signature-help request on the matching inserted char.

Source

pub fn on_type_formatting_trigger_chars(&self) -> Vec<char>

5.5.G.23.insert-prep: union of onTypeFormatting trigger characters across LSP servers attached to the active document. Used by the host-side Editor::do_insert_text (forthcoming) to fire textDocument/onTypeFormatting after a matching inserted char.

Source

pub fn lsp_signature_help_request(&mut self)

with supports_signature_help. First non-empty markdown body wins; the popup pipeline draws it via drain_pending_signature_help (still App-side).

Silent gate: matches insert-completion / on-type-format – a typed character that doesn’t fire is not a moment to surface mode state.

Source

pub fn lsp_completion_request(&mut self)

5.5.LSP.4: LSP completion request (the :lsp-complete command’s wire side; also fires from the insert-completion engine when lsp-completion-mode is on). Walks back over word characters to determine the prefix replace range, then dispatches textDocument/completion to every attached server with supports_completion. Merged + deduped results flow back as CompletionOutcome. Drain (drain_pending_completion) stays App-side.

Source

pub fn lsp_document_symbol_request(&mut self)

5.5.LSP.5: :lsp-symbols (Phase 4.2.e) – send textDocument/documentSymbol to every attached server; flatten the hierarchy + merge across servers. Drain (drain_pending_symbols, App-side) opens a picker.

Source

pub fn lsp_workspace_symbol_request(&mut self, query: &str)

5.5.LSP.5: :lsp-workspace-symbol [query] (Phase 4.2.f). Workspace-scoped: fans out over every running LSP server, not just servers attached to the current buffer.

Source

pub fn lsp_references_request(&mut self)

5.5.LSP.3: gr (Phase 4.2.d) – send textDocument/references to every attached LSP server with include_declaration: true (vim convention – gr includes the symbol’s own declaration in the list). Spawn the per- server walk on the LSP runtime; the merged + deduped result flows back through pending_references_rx. App-side drain_pending_references renders the list into a buffer- backed *lsp:references* view (drain stays App-resident until its dependencies migrate).

Browse-style, not a tag-intent drill-down: clears pending_tag_origin so <C-t> doesn’t see a stale entry.

Source

pub fn lsp_references_request_to(&mut self, terminus: ReferencesTerminus)

LR.2 (2026-08-11): the same request, with the terminus declared.

The whole async substrate below is shared and untouched — only the drain’s destination differs, which is why the terminus is recorded on the request rather than forked into a second pipeline (§16 rejected mode-side async).

Source

pub fn lsp_request(&mut self, req: LspRequest) -> Vec<RendererSignal>

L7 (lsp-architecture.md §16): host-side dispatcher for the mode-owned LSP navigation chords. lsp-mode’s action_handlers() closures return Effect::Lsp(LspRequest::…); the Effect::Lsp apply arm calls this, which fans each lattice_grammar::LspRequest variant onto the existing (unchanged) async request substrate. Returns the renderer signals the request produced synchronously — empty for the spawn-and-drain requests (hover / nav / references, whose results land off-keystroke via drain_pending_*), non-empty only for FollowLink (opens a buffer / delegates to the OS handler in the same tick). This is the shared host substrate the mode triggers but does not own (the cells-worker analogue); no Editor::do_* is bound to a chord and no host Action variant is involved.

Source

pub fn lsp_nav_request(&mut self, kind: LspNavKind)

5.5.LSP.2: gd / gD / gy / gI – LSP navigation (definition / declaration / typeDefinition / implementation). Send the matching request to every attached server in parallel; the merged + deduped Vec<Location> flows back through pending_definition_rx. Single-result outcomes jump in-place and push the tag stack; multi-result open the locations picker (handled by App’s drain_pending_definitions until that drain migrates host-side).

Captures the pre-jump origin in pending_tag_origin so <C-t> can walk back through chained drill-downs.

Source

pub fn open_lsp_locations_picker( &mut self, title: impl Into<String>, locations: &[Location], )

Source

pub fn drain_pending_definitions(&mut self) -> Vec<RendererSignal>

Drain queued ReferencesOutcomes. Multi-result outcomes open the LSP-locations picker; empty results / NoServers echo via set_message.

Phase 5.8.AA: hoisted from lattice-ui-tui::app::lsp::App::drain_pending_references. All state lives host-side already (picker, set_message); the picker rendering happens in the renderer peer. Drain queued definition / declaration / type-def / impl nav results. Multi-result outcomes open the picker host-side; single-result outcomes return the Location for caller-side do_edit + cursor jump (since do_edit’s file-open chain is still App-resident in the TUI peer).

Phase 5.8.AA: hoists the picker-opening + tag-stack bookkeeping; the single-result jump path stays caller-side (TUI peer applies; GPUI peer logs a warn until the do_edit chain hoists). Drain queued goto-definition / declaration / implementation / type-definition responses and emit renderer signals for the single-result jump. Multi-result opens an in-pane picker; zero-result echoes “no X found”. 5.8.AA.p: signals-shape so run_tick_pending can aggregate without a separate App-side wrapper — GPUI peer reaches the same path.

Source

pub fn drain_pending_references(&mut self)

Source

pub fn lsp_references_refresh(&mut self)

LR.3 (2026-08-11): re-run the references query for the ACTIVE references view and rebuild it in place.

The position comes from the view’s stored origin, never from the live cursor. That distinction is the whole point of the slice: on refresh the cursor is inside the multibuffer, so a cursor-derived query would silently answer a different question.

Not a references view, or no origin recorded ⇒ echo. Never a silent no-op: gr reaching here at all means the user asked.

Source

pub fn do_picker_bulk_accept(&mut self, out: &mut DispatchOutcome)

LR.5 (2026-08-11): <C-q> — send the picker’s FILTERED candidates to the error list, then dismiss.

This is telescope’s send_to_qflist, and the binding means the same thing here. Across the vim ecosystem <C-q> in a fuzzy finder populates the quickfix list; users carry that muscle memory in with them, so it is generic over every picker rather than a per-picker affordance. An earlier cut had it open the references multibuffer — a novel meaning for a chord that already has a well-known one, which the UX-convention rule exists to prevent.

Filtered, not raw: sending the whole result set would discard the query the user just typed, which is the opposite of what “send these” means after narrowing.

Candidates with no location contribute nothing. A picker made entirely of them (registers, marks, commands) echoes rather than swallowing the key — the dead-key failure RV.1 spent a slice removing from gr.

Source

pub fn drain_pending_symbols(&mut self)

Drain queued SymbolsOutcomes — document / workspace symbol search results — and open the picker.

Phase 5.8.AA: hoisted from lattice-ui-tui::app::lsp::App::drain_pending_symbols.

Source

pub fn apply_pull_diagnostics_outcome( cache_slot: &PerBufferCache<LspPullDiagnosticsCache>, diagnostics_layer: &DiagnosticsLayer, outcome: PullDiagnosticsOutcome, )

Slice 3b.5: apply a PullDiagnosticsOutcome to the per-buffer cache + the shared diagnostics layer. Pure with respect to Editor state – the spawned LSP request task and synchronous tests share this code path.

Source

pub fn apply_semantic_tokens_delta_outcome( cache_slot: &PerBufferCache<LspSemanticTokensCache>, buffer_id: BufferId, document_version: u64, previous_result_id: &str, new_result_id: Option<String>, edits: &[SemanticTokensEdit], token_types: &[SemanticTokenType], token_modifiers: &[SemanticTokenModifier], )

Slice 3b.2: apply a SemanticTokensOutcome::Delta to the per-buffer cache. Pure with respect to Editor state – only the passed cache_slot is mutated (via interior ArcSwap), so the spawned LSP request task and synchronous tests can share the exact same code path.

Semantics: read the current cache; verify its result_id matches the delta’s previous_result_id; apply edits to raw_data; redecode; insert_for the new cache. On any mismatch / apply failure / missing cache, remove_for the entry so the next pump re-fetches a Full.

Source

pub fn lsp_inlay_hint_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Gates textDocument/inlayHint issuance + the renderer overlay paint. Phase 5.8.AA.g: hoisted from TUI App.

Source

pub fn lsp_diagnostics_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Phase 5.8.AA.h: remaining mode-gate helpers — one-line delegates to minor_mode_enabled_for. Both renderer peers reach them through Editor::* now.

Source

pub fn lsp_format_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_rename_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_symbols_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_code_action_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_nav_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_progress_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_selection_range_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_semantic_tokens_mode_enabled_for(&self, buffer_id: BufferId) -> bool

Source

pub fn lsp_document_highlight_mode_enabled_for( &self, buffer_id: BufferId, ) -> bool

Gates textDocument/documentHighlight issuance + the renderer overlay paint. Phase 5.8.AA.g: hoisted from TUI App.

Source

pub fn find_document_by_path(&self, path: &Path) -> Option<BufferId>

Look up a buffer by file path. Used by :e FILE to detect “already open”; later by :b NAME for completion. Phase 5.8.AA.j: hoisted from TUI App.

Source

pub fn push_recent_file(&mut self, path: &Path)

MRU push for editor.recent_files. Canonicalises when possible so a path opened twice with different casing / symlink routings still collapses to one entry. Capped at 50 entries (newest first). Phase 5.8.AA.k: hoisted from TUI App.

Source

pub fn lsp_position_to_app_byte( buffer: &Buffer, line: u32, character: u32, ) -> u32

utf-16 column → utf-8 byte for line against buffer.line(line). Returns 0 on out-of-range line. Phase 5.8.AA.l: hoisted from lattice-ui-tui::app::lsp_position_to_app_byte.

Source

pub fn apply_lsp_text_edits( &mut self, edits: Vec<TextEdit>, ) -> Result<(), String>

Apply a batch of LSP TextEdits as one undo unit. Sorts reverse-by-position so each application doesn’t shift the positions of the later ones (LSP convention: edits are non-overlapping and reference the original document). Phase 5.8.AA.l: hoisted from TUI App.

Source

pub fn execute_lsp_command( &mut self, handle: Option<ServerHandle>, cmd: Command, )

Fire workspace/executeCommand for a code-action’s command payload. Server response is opaque; this is fire-and- forget. Phase 5.8.AA.l.6: hoisted from TUI App.

Source

pub fn apply_resolved_code_action( &mut self, handle: Option<ServerHandle>, action: CodeAction, ) -> Vec<RendererSignal>

Apply a fully-resolved code-action: WorkspaceEdit (when present) lands as one undo unit per affected buffer; Command (when present) routes through workspace/executeCommand. Both can fire for the same action — LSP spec allows it. Returns renderer signals from the workspace-edit apply chain. Phase 5.8.AA.l.6: hoisted from TUI App.

Source

pub fn open_picker_from_completion_source( &mut self, source: String, kind: CandidateSourceKind, live: bool, ) -> Vec<RendererSignal>

Seat the picker from a CandidateBatch produced by a first-party source. Wires MRU bonuses (gated by picker.mru.enabled + picker.mru.recency-half-life-days), honours PickerSourceSpec::live, and consumes any stashed initial query for :picker grep TODO-style submissions.

Phase 5.8.AA.n: hoisted from TUI App. Slice 7d.1: open a picker backed by an engine-shape SourceRegistration from CompletionRegistry. Reuses the existing seat path by synthesising (raw, routing) pairs from each candidate’s accept_action. The 7d.0 accept dispatch reads accept_action first, so the synthesised routing is only consulted for MRU bonus computation — candidates whose action doesn’t have a RoutingPayload counterpart (Custom / InsertText) just skip MRU tracking.

Source

pub fn seat_picker_from_pairs( &mut self, source: String, pairs: CandidateBatch, ) -> Vec<RendererSignal>

Source

pub fn open_picker( &mut self, source: String, args: Vec<String>, ) -> Vec<RendererSignal>

:picker <source> [args...] – open a picker by source id. Resolves the generator from the registry, builds a picker context against a fresh document snapshot, calls init, and seats the resulting batch (Inline) or parks the future (Future) for the per-tick drain.

Unknown source ids surface an error echo listing every registered id so the user can recover without :apropos. Phase 5.8.AF.3.

Source

pub fn drain_pending_picker_init(&mut self) -> Vec<RendererSignal>

Drain the pending-picker-init future channel. Pumps the async result the spawned task wrote; once a result arrives the picker is seated. Empty channel = future still pending; closed channel = task dropped without sending. Phase 5.8.AA.n: hoisted from TUI App.

Source

pub fn retire_picker_loading_message(&mut self, source: &str)

Retire the (loading) echo source parked, if it is still standing.

Guarded rather than unconditional. Anything else on the status line was put there by something that happened after this source parked, and clearing it would trade a stale message for a lost one. A picker that seats behind a write’s “wrote 3 lines” must not swallow it.

pub so the lifecycle is testable without standing up a real async plugin source — the message contract is what this guards, and a test that had to boot a WASM picker to check it would be testing something else as well.

Source

pub fn drain_pending_picker_accept(&mut self) -> Vec<RendererSignal>

PH7.4c.2: commit an async picker accept (a WASM plugin source whose accept_async returned a Future). Mirrors [drain_pending_picker_init]: polled off the async-landed wake, non-blocking try_recv; on a resolved outcome it re-installs the open-target override and applies the outcome (the picker was already closed at accept time). A resolution error is echoed; the editor stays live.

Source

pub fn picker_workspace_root_path(&self, snap: &DocumentSnapshot) -> PathBuf

The project root picker sources scan from.

PR.4: this was the active document’s parent directory — a fifth independent root notion, and the same dirname(active file) answer :terminal had. It meant the file finder listed only the directory you happened to be in, which the source’s own comment already called out as behaving “unintuitively for projects spread across many subdirectories”.

Resolved from the snapshot’s path (not the active buffer) because the caller may be building a context for a buffer other than the focused one; a pathless snapshot falls through to the resolver’s working directory.

Source

pub fn build_picker_context<'a>( &'a self, snap: &'a DocumentSnapshot, ) -> PickerContext<'a>

Build the snapshot the picker primitive hands to source generators on each :picker <source> invocation. Caller holds snap for the duration of the synchronous init call – the returned PickerContext borrows from both self and snap.

Phase 5.8.AA.s: hoisted from TUI App. FileTreeRoot / OilDir buffer-locals are read through the canonical domain-crate paths (lattice_listing::file_tree::modes::FileTreeRoot, lattice_listing::oil::modes::OilDir) so the body is renderer- agnostic – both peers can call it.

Source

pub fn do_pane_history_jump(&mut self, index: u32)

PBH.5: accept from the pane-buffer-history picker — move the walk cursor to index and show that buffer.

Deliberately NOT a new visit: this is random access over the trail <C-6> / <C-7> step through. It reuses the walk’s suppression flag, so the switch does not push and the forward tail survives — picking an older stop leaves everything after it reachable, exactly as walking back would.

Source

pub fn refresh_lsp_file_watcher(&mut self)

4.4.l: refresh the workspace/didChangeWatchedFiles watcher subscription set. Per paramount goal #4 this runs entirely in non-blocking primitives — no notify API calls, no event drains. The actual watcher lives on a tokio task on the LSP runtime (see crate::lsp_watcher::spawn_lsp_file_watcher_task).

Per-server fingerprint compare prevents the cmd-send when nothing changed since the last call. The task is spawned lazily on the first registration and torn down (handle dropped → task exits) when no actor advertises the capability.

Phase 5.8.AF.5: rewritten from in-line notify.watch + per-server mutation to a fire-and-forget cmd send.

Source

pub fn apply_selection_chain_step(&mut self)

Step the cursor + selection to the current entry in the cached LSP selection chain. Used by :lsp-expand-region / :lsp-shrink-region. Phase 5.8.AA.m: hoisted from TUI App.

Source

pub fn drain_pending_selection_range(&mut self)

Drain queued selectionRange responses; either seat the chain + apply step 0 (or 1 for Expand), or echo when no provider / empty. Phase 5.8.AA.m: hoisted from TUI App.

Source

pub fn drain_inbound_apply_edits(&mut self) -> Vec<RendererSignal>

Drain server-initiated workspace/applyEdit requests. Each request’s workspace edit is flattened to per-file entries, edits to the active buffer land via apply_lsp_text_edits, cross-file edits route through do_edit. Returns renderer signals from cross-file opens. The server reply (ApplyEditOutcome) ferries back via the per-request oneshot.

Phase 5.8.AA.l.5: hoisted from TUI App.

Source

pub fn drain_pending_format(&mut self)

Drain queued format / onType-format responses; apply edits as one undo unit. Phase 5.8.AA.l.4: hoisted from TUI App.

Source

pub fn drain_pending_rename(&mut self) -> Vec<RendererSignal>

Drain queued :rename responses; apply the WorkspaceEdit returned by the server. Phase 5.8.AA.l.3: hoisted from TUI App. Returns renderer signals from any cross-file do_edit calls.

Source

pub fn apply_rename_workspace_edit( &mut self, per_file: Vec<(Uri, Vec<TextEdit>)>, new_name: String, ) -> Vec<RendererSignal>

Apply a per-file WorkspaceEdit returned by :rename (or a code-action). Active-buffer edits land directly via apply_lsp_text_edits; cross-file edits route through do_edit to open the file then apply.

Returns (summary_text, signals). Caller echoes summary and fans signals through handle_renderer_signal.

Phase 5.8.AA.l.2: hoisted from TUI App.

Source

pub fn move_cursor_to_utf16_column(&mut self, line: u32, col: u32)

BC.8c: move cursor to a UTF-16 code-unit column on line, converting to a byte offset against the now-open line. Best-effort: an out-of-buffer line leaves the cursor put. Host-side because the conversion needs the opened line’s text (generalises the retired move_cursor_to_lsp_position; no lsp_types dependency).

Source

pub fn open_external_uri(&self, uri: &str) -> bool

BC.8c: open uri via the OS handler (open / xdg-open / explorer). Host-applied arm of lattice_grammar::Effect::OpenExternalUri, and the shared helper follow_document_link_target uses for non-file links. Returns whether the spawn was accepted (the documentLink path surfaces a warn on failure; the showDocument effect arm already replied optimistically so it ignores the result — a spawn failure is logged via tracing). No instance-ring logging here: the show-document handler records its own external trail before emitting the effect.

Source

pub fn jump_to_lsp_location(&mut self, loc: &Location) -> Vec<RendererSignal>

Jump to an LSP Location. Same-buffer: move cursor only. Cross-file: route through do_edit so LSP attach + buffer- registry seam handles the open; then move cursor. Pushes the pre-jump cursor onto position history (PluginPush) so <C-o> walks back to where the user started.

Returned Vec<RendererSignal> carries the do_edit signals (Opened/Activated/Reloaded) for the caller to fan through its handle_renderer_signal.

Phase 5.8.AA.k.2: hoisted from lattice-ui-tui::app::lsp::App::jump_to_lsp_location.

Source

pub fn do_edit(&mut self, path: Option<PathBuf>, force: bool) -> DoEditOutcome

Outcome of Editor::do_edit. Renderer peers act on:

  • Directory(path) → open the file-tree (oil) view — caller-side because the oil-view machinery is still App-resident.
  • Activated(signals) / Reloaded(signals) / Opened(signals) → fan signals through the peer’s handle_renderer_signal.
  • Failed / NoFileName → host already echoed via set_message; nothing else to do.
Source

pub fn open_buffer_at( &mut self, path: Option<PathBuf>, position: Position, force: bool, content: Option<&str>, activate_minor: Option<&str>, ) -> DoEditOutcome

CD.2: Effect::OpenBufferAt’s one body, for every peer and the off-renderer drain.

Self::do_edit_seeded, then — when a buffer is showing — the minor and the cursor. The minor is activated before anything is painted, so its chords are live on the first keystroke. Its lifecycle signals ride in the returned outcome, so a caller that fans the outcome’s signals out cannot drop them.

Returns the outcome so a peer still handles Directory its own way.

Source

pub fn do_edit_seeded( &mut self, path: Option<PathBuf>, force: bool, seed: Option<&str>, ) -> DoEditOutcome

Self::do_edit with seed text for a file that does not exist yet.

The seed applies only to a brand-new file — never to one already open, and never to one on disk — so reopening a saved draft keeps what was typed into it.

Source

pub fn maybe_request_pull_diagnostics(&mut self)

4.4.j: per-tick textDocument/diagnostic (pull-based) pump. Phase 5.8.AA.i: hoisted from TUI App.

Source

pub fn maybe_request_folding_range(&mut self)

4.4.f: per-tick foldingRange pump.

Phase 5.8.AF.5 / Slice 3b.1: spawned task writes directly into lsp_folds_cache (PerBufferCache<...>) via insert_for. No channel, no UI-thread drain.

4.5.c: per-tick documentLink pump. Phase 5.8.AA.i: hoisted from TUI App.

Source

pub fn maybe_request_code_lens(&mut self)

4.5.d: per-tick codeLens pump. Phase 5.8.AA.i: hoisted from TUI App.

Source

pub fn maybe_request_document_color(&mut self)

4.5.e: per-tick documentColor pump. Phase 5.8.AA.i: hoisted from TUI App.

Source

pub fn maybe_request_semantic_tokens(&mut self)

4.4.h: per-tick semanticTokens/full pump. Picks delta when prior result_id + server supports delta; full otherwise. Phase 5.8.AA.i: hoisted from TUI App.

Source

pub fn maybe_request_inlay_hint(&mut self)

4.4.g: per-tick inlayHint pump. Fires when the mode is active and the cache is stale for the visible viewport ± overscan. Single-flight; each request cancels its predecessor.

Phase 5.8.AF.5 / Slice 3b.1: spawned task writes directly into lsp_inlay_hints_cache (PerBufferCache<...>) via insert_for. No channel, no UI-thread drain. Renderers read wait-free via rs.lsp.inlay_hints.get_for(buffer_id).

Source

pub fn maybe_request_document_highlight(&mut self)

4.4.e: per-tick documentHighlight pump. Fires when the mode is active and the cursor moved off the previous anchor.

Phase 5.8.AF.5 / Slice 3b.0: rewritten from the old drain-channel shape (tx → Editor.rx → drain on UI thread → cache) to direct ArcSwap publication. The spawned task on the LSP runtime clones the cache’s Arc<ArcSwapOption<...>> and writes results directly when the response arrives. No channel, no drain method, no UI-thread cache write. Per paramount goal #4.

Cancellation semantics unchanged: each call cancels any prior in-flight token; renderers self-validate cache.buffer_id == active_buffer_id so a result that races a buffer switch is naturally ignored at paint time.

Source

pub fn drain_inbound_show_message_requests(&mut self)

Drain server-initiated window/showMessageRequests; open the picker for actionable ones, echo + log informational ones. Phase 5.8.AA.e: hoisted from TUI App.

Source

pub fn open_show_message_request_picker(&mut self, request_id: u32)

Open the actionful showMessageRequest picker. Phase 5.8.AA.e: hoisted from TUI App.

Source

pub fn drain_pending_completion_resolve(&mut self)

4.2.f: drain completionItem/resolve responses; fold the resolved metadata back into editor.insert_completion’s raw entry + refresh the docs popup body when the resolved candidate is the focused one. Phase 5.8.AA.d: hoisted from TUI App.

Source

pub fn drain_lsp_log_events(&mut self)

Drain LspLogPushed events; surface showMessage-sourced records to the minibuffer (transient :echom-style). Other records are owned by the LSP log majors. Phase 5.8.AA.d: hoisted from TUI App.

Source

pub fn drain_pending_insert_completion_lsp(&mut self)

4.2.f: drain the async insert-completion fan-out. Merges incoming candidates into editor.insert_completion’s raw set, refilters via FuzzyInsertMatcher + InsertRanker using per-source priority + frequency bonus, deduplicates, and updates the rendered list. Phase 5.8.AA.d: hoisted from TUI App.

OR.7: every pending outcome is applied, not just the last one. The fan-out has one sender per source now, so “keep the latest” — correct when LSP was the only sender — would have silently dropped whichever source answered first. Each outcome replaces only the rows of the sources it names, which is what lets sources land in any order and at any time.

Source

pub fn drain_modeline_element_updates(&mut self)

ML.3: drain pushed lattice_mode::ModelineElementUpdate events into the shared modeline content store (single-writer, actor thread). Empty content clears the slot (ModelineService::apply). The boot-side forwarder fires async_landed on arrival so a push repaints off-keystroke (§12 wake). A closed/absent channel yields no events — never panics.

Source

pub fn drain_lsp_detach_events(&mut self)

Drain LspBufferDetached events; fire didClose + clear per-buffer URI mapping via lsp_close_buffer (already host). Phase 5.8.AA.d: hoisted from TUI App.

Source

pub fn drain_diagnostic_refresh(&mut self)

4.4.j: drain workspace/diagnostic/refresh events; evict per-buffer result_id caches so the next pump re-pulls. Phase 5.8.AA.c: hoisted from TUI App.

Source

pub fn drain_inlay_hint_refresh(&mut self)

4.4.g: drain workspace/inlayHint/refresh events; clear caches for attached buffers. Phase 5.8.AA.c: hoisted from TUI App.

Source

pub fn drain_semantic_tokens_refresh(&mut self)

4.4.i: drain workspace/semanticTokens/refresh events. Phase 5.8.AA.c: hoisted from TUI App.

Source

pub fn drain_code_lens_refresh(&mut self)

4.5.d: drain workspace/codeLens/refresh events; evict cached lenses for the named servers. Phase 5.8.AA.c: hoisted from TUI App.

Source

pub fn drain_pending_completion(&mut self)

4.2.g: drain LSP completion responses; opens picker. Phase 5.8.AA.c: hoisted from TUI App.

Source

pub fn drain_pending_moniker(&mut self)

4.5.b: drain moniker outcome — echoes the moniker scheme + value. Phase 5.8.AA.b: hoisted from TUI App.

Source

pub fn run_tick_pending(&mut self) -> Vec<RendererSignal>

Source

pub fn idle_gate_deadline(&self) -> Option<Instant>

IDE-protocol I1.1: run every mode-registered per-tick drain closure and apply the Effects they return, surfacing any RendererSignals produced. The single generic host primitive that replaces per-subsystem drain_<x> methods: a mode owns its channel + drain body and registers the closure via the TickCallbackRegistryHandle service; the host just runs them.

Reads the registry from self.services (one Arc load + downcast — cheap, and this runs per tick, not per frame). No registered callbacks → no-op. Effects flow through the same apply_effect_host pipeline mode action handlers use, so renderer-coupled effects land on out and host-side signals are returned for the actor to forward. WK.3: the earliest armed idle-gate deadline, for the actor’s pinned sleep. None when every gate is disarmed (or none is registered), in which case the actor parks the sleep far out and its guard keeps the arm dormant.

Source

pub fn fire_idle_gates(&mut self) -> Vec<RendererSignal>

WK.3: run every idle gate whose deadline has elapsed and apply its Effects through the same apply_effect_host pipeline tick callbacks use. Called from the actor when the pinned sleep fires.

This is the time-domain peer of Self::drain_tick_callbacks, and it exists for the same reason: rather than an Option<Instant> field and a bespoke select! arm per subsystem, subsystems register a gate and the host runs the due ones.

Source

pub fn drain_pending_hover(&mut self) -> Vec<RendererSignal>

Source

pub fn drain_pending_signature_help(&mut self) -> Vec<RendererSignal>

Drain queued SignatureHelpOutcomes and produce renderer signals — same shape as Self::drain_pending_hover. Non-empty body → DisplayBuffer(content); empty body or NoServers → set_message.

Phase 5.8.X: hoisted from lattice-ui-tui::app::lsp::App::drain_pending_signature_help so the GPUI peer reaches the same path.

Source

pub fn drain_pending_code_actions(&mut self) -> Vec<RendererSignal>

Drain queued CodeActionOutcomes. Items open a picker (host-side editor.picker mutation); NoProvider echoes “no server with codeActionProvider”; Resolved outcomes are returned to the caller for renderer-side apply (since the apply path needs apply_lsp_text_edits / execute_lsp_command chains that are still App-resident in the TUI peer; future slices fold those host-side too).

Phase 5.8.Y: hoists the Items + NoProvider arms host-side so the GPUI peer can open the code-action menu. Resolved arm stays as-returned for the App-side apply chain. Drain queued code-action responses. NoProvider / empty Items echo and return no signals; Items opens an in-pane picker (no signals); Resolved applies the workspace-edit + executeCommand chain inline via Self::apply_resolved_code_action and returns its signals. 5.8.AA.r: signal-shaped so run_tick_pending aggregates without an App-side wrapper.

Source

pub fn apply_lsp_code_action( &mut self, row: CodeActionRow, handle: Option<ServerHandle>, ) -> Vec<RendererSignal>

5.8.AA.r: apply a chosen code-action row. Bare Command → executeCommand; action without edit/command → spawn resolve (response lands back through the same channel that drain_pending_code_actions reads); resolved action → inline workspace-edit + executeCommand via Self::apply_resolved_code_action. Returns any RendererSignals the apply chain produced.

Source

pub fn lsp_hover_request(&mut self)

Source

pub fn sync_keymap_overlays(&mut self)

Slice 3c.final.E.5e: reconcile the keymap-layer stack + completion-popup-mode activation against the editor’s current popup / active-snippet state. Hoisted from the renderer (App::sync_keymap_overlays + App::sync_completion_popup_mode_activation) since the body is pure editor-field manipulation – no renderer-side state is touched. The renderer-side wrappers become 1-line mutate_editor delegates routing through the actor seam.

Push order: pop everything, then push snippet (if active), then popup (if active) – popup’s LayerId always sits above snippet, preserving the legacy “popup precedes snippet” gating in input::translate.

Source

pub fn recompute_active_completion_sources_for(&mut self, buffer: BufferId)

5.5.F.5.1: rebuild the buffer-local ActiveCompletionSources snapshot from the buffer’s currently-active major + minors. Called after every mode-lifecycle transition so the completion popup walks an up-to-date contribution list.

Source

pub fn path_for_buffer(&self, buffer_id: BufferId) -> Option<PathBuf>

5.5.F.5.1: best-effort path lookup for buffer_id. Returns the document’s path for Document buffers (the active one reads from self.document, the rest from the buffer registry), None otherwise. Used by the LSP auto-activation hook in F.5.2.

Source

pub fn buffer_capabilities(&self, buffer_id: BufferId) -> CapabilitySet

5.5.F.5.2 (M.5.1): programmatic activation of mode_id on buffer_id. Used by hooks (auto-activation on MajorEntered etc.) and by the auto-generated :<mode-name> toggle command. The registry decides Major-vs-Minor and runs the appropriate activation; for majors the previous major is deactivated first.

On failure, surfaces an EchoLevel::Warn and returns without mutating state.

Returns the RendererSignal list its option-cascade drain enqueued (a typed-option write inside the mode’s on_activate hook can fan out a renderer-coupled tail; e.g. lsp-folding-mode swapping foldmethod=lsp). Callers fan via the renderer signal-pipe. TC.9: what buffer_id can offer a mode, computed from live state.

The gate’s other half. ModeRegistry has always checked required_capabilities() - buffer_caps; every caller passed CapabilitySet::empty(), so any mode declaring any capability was unsatisfiable and said so as a warn — never a failing build or test.

Computed on demand rather than stored. A stored set needs every subsystem that attaches a parser, an LSP server or a path to remember to update it, and the failure mode of forgetting is a mode that never activates — silent, and indistinguishable from the bug this fixes. The reads here are each a map lookup or an ArcSwap load, and activation is a rare event (buffer open, :mode toggle, plugin load), not a per-keystroke one.

Two bits are deliberately NOT granted, because nothing in the editor models them and granting on a proxy would be a lie a plugin author cannot see through:

  • FOLDS — every Document has a rope and fold metadata, so the honest answer is “always”, which makes the bit carry no information. The nearest per-buffer state, foldenable, is vim’s DISPLAY toggle: a user with :set nofoldenable has not lost the ability to fold.
  • DIAGNOSTICS — the overlay provider exists only while lsp-diagnostics-mode is active, which is a mode DEPENDENCY, and the mode system already has implies for that. Spelling it as a capability would give two mechanisms for one relationship.

Both are left ungranted rather than removed: the bits are shipped API, and capability.rs is explicit that a retired capability leaves a hole rather than having its meaning changed underneath a compiled plugin. A mode requiring either is refused today — which is honest, and visible, where a proxy would be neither.

Source

pub fn capabilities_for_proto(&self, proto: BufferId) -> CapabilitySet

Self::buffer_capabilities keyed by the protocol id the activation sites already hold, so each of them reads the same one line rather than converting inline eleven different ways.

Source

pub fn activate_mode_by_id( &mut self, buffer_id: BufferId, mode_id: ModeId, ) -> Vec<RendererSignal>

Source

pub fn deactivate_mode_by_id( &mut self, buffer_id: BufferId, mode_id: ModeId, ) -> Vec<RendererSignal>

5.5.F.5.2 (M.5.1): programmatic deactivation of mode_id on buffer_id. Symmetric to Self::activate_mode_by_id; same signal-return shape.

Source

pub fn maybe_auto_activate_lsp_mode( &mut self, buffer_id: BufferId, ) -> Vec<RendererSignal>

5.5.F.5.2 (M.5.2): language-mode auto-activation hook for lsp-mode. Runs after a major activates; when the active buffer’s path has a server configured in the LSP registry and lsp-mode isn’t already active, activate it.

Asymmetry by design (mode-architecture §M.5): there is no auto-deactivation hook on MajorExited. Active minors stay across major-mode swaps – emacs’s “kill all local variables” footgun is what we’re avoiding.

Source

pub fn activate_major_for_buffer_kind( &mut self, buffer_id: BufferId, kind: BufferKind, ) -> Vec<RendererSignal>

5.5.F.5.3 (M.3.1): activate the resolved major mode for buffer_id based on its kind (and, for Document buffers, the detected language) and refresh the resolved-options cache.

Idempotency / preserve-intent: if the buffer already has any major active, don’t preempt it (covers re-call on same buffer, synthetic Document buffers with a creator-chosen major, and user-driven :toggle-mode <name> swaps). Still runs the auto-LSP hook unconditionally so lsp-mode propagates per- buffer; the hook is itself no-op-when-already-active and no-op-when-no-server-for-path.

Returns Vec<RendererSignal> for the same reason as Self::activate_mode_by_id — mode on_activate hooks can mutate typed options whose cascade emits renderer-coupled signals.

Source

pub fn publish_document_opened_for_active(&mut self)

Boot-time + post-open helper: publish Event::DocumentOpened for the active document buffer.

Path-bearing buffers register their URI eagerly (the URI is a deterministic uri_from_path; LSP attach is async and doesn’t gate the mapping). Path-less scratch buffers publish nothing (no LSP work to drive); the buffer_uris entry stays absent.

Phase 5.7.B.6: migrated from lattice-ui-tui::app::lsp::App::publish_document_opened_for_active so both renderer peers (TUI + GPUI) run the same boot sequence. The body touches only renderer-neutral editor state (document, buffer_uris, event_bus); the TUI peer keeps a thin wrapper for back-compat with the app.publish_document_opened_for_active() call site in App::new.

Source

pub fn fan_out_did_change_configuration(&mut self, server_id: &str)

Phase 5.7.B.7: fan out workspace/didChangeConfiguration to every actor matching server_id with the freshly merged subtree from lsp_config_tree. Emitted in response to RendererSignal::LspConfigChanged from an lsp.<server>.* option-cascade write.

Migrated from lattice-ui-tui::app::lsp::App::fan_out_did_change_configuration so both renderer peers can route the cascade without duplicating the supervisor-walk + log path. TUI keeps a thin wrapper for back-compat with its handle_renderer_signal match arm.

Source

pub fn lookup_lsp_config_section(&self, section: &str) -> Value

Look up a server-supplied section path in the cached TOML tree at lsp.<section>. Returns Value::Null when the path is missing or the TOML value can’t be converted to JSON. Empty section (“all”) returns the whole lsp sub-tree.

Phase 5.7.B.7: migrated from lattice-ui-tui::app::lsp::App::lookup_lsp_config_section alongside the fan-out helper above. Pure data: reads self.lsp_config_tree, converts the matched subtree to JSON via the serde round-trip.

Source

pub fn drain_mode_lifecycle_events(&mut self) -> Vec<RendererSignal>

5.5.F.5.3 (M-async.3): rollback drain for the mode dispatcher’s spawned lifecycle task. Reads ModeEvent variants off pending_mode_lifecycle_rx and acts on ModeActivationFailed only — walk the registry, look up the mode’s kind, then call deactivate_mode_by_id. Idempotent: if the mode wasn’t in active_modes, the deactivate no-ops.

Cheap when no events arrived (single try_recv → Empty). Called once per main-loop tick by runtime.rs. Returns the RendererSignal list every rolled-back deactivation enqueued.

Source

pub fn drain_deferred_activations(&mut self) -> Vec<RendererSignal>

TC.9b: retry activations that were refused only for a capability the buffer has since gained.

Runs on the same pump every other async arrival reaches the screen through, so a mode whose parse landed switches on WITHOUT the user pressing anything — the failure mode boot-composition.md §3 exists to design out, and one this would otherwise have reproduced exactly (the mode would appear on the next keystroke, which reads as flakiness).

Costs a is_empty() check in every ordinary session. When it is not empty, activate_mode_by_id re-checks and either succeeds or re-records the entry, so the list converges rather than growing.

Source

pub fn cancel_deferred_activation( &mut self, buffer_id: BufferId, mode_id: ModeId, )

Forget a pending activation — the user said no, or the buffer is going away. A deferred activation that outlives an explicit “off” would switch the mode back on later, which is the opposite of what was asked.

Source

pub fn deferred_activation_count(&self) -> usize

How many activations are waiting on a capability. Exposed for tests and for :describe-buffer-style introspection; a number that only grows is the symptom of a capability nothing ever grants.

Source

pub fn drain_minor_activation(&mut self) -> Vec<RendererSignal>

Source

pub fn drain_provider_view_refresh(&mut self) -> Vec<RendererSignal>

OA.15a: drain ProviderViewRefreshRequested (a plugin’s refresh-view) — re-open each requested view through its registered opener.

The guest→activator bridge for VIEWS, drain_mode_enablement’s shape one seam over: a guest cannot reach the &mut ModeActivator, so the call is a request and this applies it. The body is deliberately the same as AppEffect::OpenProviderView’s arm — look the name up, hand the opener &mut *self, apply the outcome — because they ARE the same operation reached from two places, and a second opening path that differed would be the divergence ProviderViewRegistry exists to prevent.

Requests are de-duplicated by (provider, args). A mode toggled twice before a tick lands, or an activation event delivered alongside its own re-activation, would otherwise re-scan identically twice — and a scan is the expensive thing at the end of this path (OA.0’s measurements). Distinct args are NOT collapsed: two different arguments are two different questions, and the last one is the one the user asked.

A decline is echoed rather than swallowed. This path has no user gesture behind it — nobody typed anything — so silence would leave a view that did not refresh with nothing to explain it.

Source

pub fn drain_buffer_option_overrides(&mut self) -> Vec<RendererSignal>

Drain Event::BufferOptionOverrideRequested (a plugin’s set-option-in-buffer) into the buffer-local override layer.

The guest→Editor bridge for per-buffer options, and the reason it is a bridge rather than a direct write: the buffer-local layer lives on the Editor, while a guest holds a ConfigRegistry handle — the GLOBAL layer, and the wrong scope for “wrap in org buffers”.

Parsed through parse_for_buffer_local, the same path :setlocal takes, so a guest can express nothing :setlocal could not and an invalid value is refused with the identical message. A refusal is logged rather than echoed: nothing the USER did provoked it, and an error bar over a buffer they just opened would blame them for their config’s bug at the least useful moment. The log names the option.

A request naming a buffer that has since closed is dropped silently — ordinary, not exceptional, since the request crosses a tick and a buffer can close inside one.

Source

pub fn drain_mode_enablement(&mut self) -> Vec<RendererSignal>

CI.4: drain Event::ModeEnablementRequested (a plugin’s enable-mode / disable-mode) — flip the mode registry’s enablement, then re-activate or deactivate the mode across ALL open buffers whose major admits it (the enablement is global, not per-buffer). This is the guest→activator bridge: a guest can’t reach the Editor, so enable-mode publishes and this drains (config-and-init.md §6). Runs per-tick alongside drain_minor_activation.

Source

pub fn activate_buffer_state(&mut self) -> Vec<RendererSignal>

5.5.F.5.5: lifecycle hook fired after a document buffer becomes the active buffer (via Self::activate_document, after :e <path> opens a fresh file, or after :bd / :bn / :bp switches the active pane). Refreshes everything that “lives with the buffer until it closes”: major + auto-LSP mode wiring, the resolved-options cache, the syntax parse + fold seam, and the frame-level highlight caches.

Returns Vec<RendererSignal> because Self::activate_major_for_buffer_kind fans signals from the inner mode-lifecycle cascade (e.g. lsp-folding-mode writing foldmethod=lsp in its on_activate).

Source

pub fn mirror_option_to_modes( &mut self, canonical_name: &str, ) -> Vec<RendererSignal>

5.5.F.5.4 (M.7.1 Phase 1.5): drive the declarative Mode::mirrors_option cascade. Walks every registered mode and, for each that declares it mirrors canonical_name, toggles the mode’s active state on the active document buffer to match the option’s bool value.

Reads through ConfigRegistry::get_bool_by_name (the typed- option layer) rather than the resolved-options view — the user’s explicit :set gesture is the authority for the mode’s active state, not the layered resolution. Non-bool options short-circuit at the get_bool_by_name step.

Returns the RendererSignal list every cascading activate/deactivate enqueued.

Source

pub fn toggle_mode_by_name(&mut self, name: &str) -> Vec<RendererSignal>

5.5.F.5.2 (M.5.1): toggle a mode by name on the active pane’s buffer. Apply-fn target for the auto-generated :<mode-name> ex-commands (mode-architecture §9.6.1).

  • Minor: deactivate if active; activate if inactive.
  • Major: activate if not currently the major; if it’s already the active major, the registry treats this as a reload (deactivate then re-activate, per §9.6).
Source

pub fn foldmethod(&self) -> FoldMethod

:set foldmethod=... – the option-cache hot-path read.

Source

pub fn foldlevel(&self) -> u32

FL.1: :set foldlevel=..., resolved for the active buffer.

Read on the fold-rebuild path rather than per keystroke, so it goes through the resolver instead of earning a slot in option_cache — the cache is for reads on the hot path. Clamped into u32; the validator already bounds it to [0, 1024].

Source

pub fn recompute_folds(&mut self)

Refresh Self::folds from the active FoldMethod.

Manual – no-op (preserves user zf folds). The other providers (Indent / Markdown / Syntax / Lsp) replace folds with the recomputed set, carrying over the closed/open state of any existing fold whose identity matches a recomputed one (so zc survives a reparse).

foldlevel seeds only folds this pass has not seen before, and only when the recompute is a crate::folds::FoldRecomputeCause::Populate one. The edit path goes through Self::recompute_folds_because with Edit instead, so a fold the user’s own keystroke created arrives open.

Syntax runs the language’s tree-sitter folds.scm query against the live parse tree; when the language doesn’t ship a folds.scm (or the parse tree hasn’t been built yet), the syntax provider cascades to the markdown / indent providers based on the file extension. Lsp reads the per-buffer folding-range cache and cascades to Syntax when the cache is empty (request still in-flight, server not attached, or sub-mode disabled).

Source

pub fn recompute_folds_because(&mut self, cause: FoldRecomputeCause)

Self::recompute_folds, told why it is running.

The cause decides one thing and nothing else: whether foldlevel may seed the folds this pass has not seen before. See crate::folds::FoldRecomputeCause — the short version is that a fold the user’s own keystroke just created must not close under their cursor, which is what foldlevel=0 org buffers did on every o.

Source

pub fn maybe_refold_after_async_population(&mut self)

D-fix.5: recompute fold overlays for a buffer that may NOT be the active document, stashing the result into its crate::modes::DocumentFolds buffer-local — the slot inactive panes render their folds from. The active-buffer case delegates to Self::recompute_folds (the live self.folds slot).

This is the substrate gap the both-sides diff fold needs: recompute_folds only ever computes the ACTIVE buffer’s folds, so the baseline pane of a side-by-side diff — inactive while the proposed pane is reviewed — would never fold. Here the context is built from the buffer’s registry snapshot + per-buffer syntax; the overlay providers (gated to buffer_id by the FoldSourceAdapter) run; closed-state + the user’s zf folds carry over from the buffer’s prior DocumentFolds; the result is stashed. Missing handle (buffer mid-close) just skips — no panic.

foldmethod is read from THIS buffer’s resolved option. It used to be the active buffer’s, as a proxy — see the comment at the read for why that stopped being safe once mode-contributed foldmethod became buffer-scoped. OA.4d: recompute folds when the active document has moved past the version they were built from.

Deliberately version-gated rather than folds.is_empty()-gated, which was the tempting one-liner: a gr that REPLACES a view’s rows leaves a non-empty but stale fold list, and an emptiness check would keep it.

Manual is NOT skipped here, and that was a bug on the first cut. recompute_folds has its own, sharper policy: Manual preserves hand-curated zf folds but STILL runs registered overlay sources — and a multibuffer’s folds are exactly such an overlay. Bailing on Manual meant the agenda, which has no foldmethod of its own, never refolded.

Source

pub fn recompute_folds_for_buffer(&mut self, buffer_id: BufferId)

Source

pub fn dispatch_blocking( &self, invocation: CommandInvocation, ) -> Result<Effect, RuntimeError>

5.5.G.23: blocking grammar-dispatch entry. Drives every CommandInvocation through the document actor’s dispatch_with_cancel channel. Today’s TUI parks the thread via block_on; a future tokio-driven runtime can flip the cancellation token on Esc without changing this signature.

Source

pub fn apply_edit_blocking( &mut self, edit: Edit, ) -> Result<AppliedEdit, RuntimeError>

Source

pub fn apply_edit_batch_blocking( &mut self, edits: Vec<Edit>, ) -> Result<Vec<AppliedEdit>, RuntimeError>

5.5.E.7.3: block_on apply_edit_batch. The batch lands as one undo unit on the document’s undo stack. Each edit in the batch is also fed to the LSP supervisor in order via Self::publish_document_changed.

Oil-buffer routing matches Self::apply_edit_blocking: when active_buffer == Oil the batch lands on oil.content edit-by-edit. The “one undo unit” semantics are weaker for oil (its content has no undo stack); v1 oil falls back to :e! reload for “undo all my changes.”

Source

pub fn apply_targeted_edit( &mut self, target: BufferId, edit: Edit, ) -> Result<AppliedEdit, RuntimeError>

CR.0: apply edit to an explicit target buffer — the host counterpart of lattice_grammar::Effect::ApplyEdit. Two routes, distinguished by whether target is the focused buffer:

  • target == active document → Self::apply_edit_blocking, so the edit cascades through the full active-document pipeline (LSP didChange, syntax reparse, highlight byte-shift, publish_document_changed). This is the diff-get / keep-ours / keep-theirs / keep-both direction — conflict resolution always targets the editable local side under the cursor.
  • target is a peer buffer → apply via the registry’s document handle and fan out a DocumentChanged for the peer so its diff session / LSP / syntax recompute through the standard pipeline. This is the diff-put direction (push the current side’s hunk into the other buffer); it mirrors do_diff_put’s peer path exactly so CR.1’s relocation is behaviour-preserving.

Returns the AppliedEdit or a RuntimeError (including Cancelled when the peer target has since closed — a benign race with diff-session auto-drop). The caller owns cursor placement; this method never moves the cursor.

Source

pub fn apply_edit_effect_inline( &mut self, target: BufferId, edit: Edit, cursor: Option<Position>, )

CR.0/CR.1: apply a resolved Effect::ApplyEdit synchronously — the shared body the Action::ApplyEdit arm and the :diffget/:diffput ex-command path both call. Routes through Self::apply_targeted_edit (active-document pipeline vs peer handle) and parks the active cursor when the handler supplied a row. On a recoverable apply error, logs + leaves the cursor put.

Source

pub fn apply_diff_effect_inline( &mut self, eff: Option<Effect>, out: &mut DispatchOutcome, )

CR.1: apply the Option<Effect> a DiffSubsystem::diff_*_effect resolver returned, for the :diffget/:diffput ex-command path. An Effect::ApplyEdit applies synchronously (preserving the pre-CR.1 do_diff_* behaviour without relying on the ex-command effect drain to forward next_actions); any other effect (the error Echo for TargetRequired / NoPeerBuffer) routes through the host’s handle_effect; None is a silent no-op.

Source

pub fn undo_blocking(&mut self) -> Result<Vec<AppliedEdit>, RuntimeError>

5.5.E.7.3: undo one step on the document actor; publishes a DocumentChanged for the inverse edits so the LSP fan-in + syntax worker + highlight shifter stay in sync.

Source

pub fn redo_blocking(&mut self) -> Result<Vec<AppliedEdit>, RuntimeError>

5.5.E.7.3: redo one step on the document actor; symmetric to Self::undo_blocking.

Source

pub fn do_delete_line(&mut self)

5.5.E.7.4: delete the cursor’s whole line including its trailing newline (vim’s :d). The standard delete operator’s CurrentLine range preserves the newline, which leaves an empty line behind – that’s fine for dd (cursor stays put on a now-empty line) but wrong for :d and :g/.../d. Here we explicitly include the newline.

Source

pub fn do_substitute( &mut self, scope: SubstituteScope, pattern: &str, replacement: &str, global: bool, )

5.5.E.7.5: vim’s :s/pattern/replacement/[g] (and :%s/... for whole-buffer scope). Replacement template syntax follows fancy-regex / regex crate: $1, ${name}, $0 (whole match), $$ for a literal $. NOT vim’s \1/& — modern syntax. Reports the replacement count via the echo area.

Source

pub fn handle_edits(&mut self, edits: &[AppliedEdit])

5.5.E.7.7: route grammar-driven edits (operators like >>, dd, c, y) through the same chokepoint as apply_edit_blocking. The actor has already applied the edits to the document; without this routing the LSP didChange fan-out, the pending_syntax_edits accumulation, and the synchronous shift_highlights_for_edit byte-shift all SKIP the edits — which produced the user-reported flicker on >> and dd: spans never shifted on the input thread, so when the worker eventually published the recompute landed as a visible repaint.

After the cursor settles on the start of the deleted range (vim’s behavior after a delete), the edits flow through Self::publish_document_changed so:

  • LSP servers see the didChange.
  • Syntax worker sees the EditDeltas (incremental reparse instead of falling back to full).
  • visible_highlights stays line- and byte-aligned via shift_highlights_for_edit.
Source

pub fn build_global_targets( &mut self, pattern: &str, inverted: bool, ) -> Option<Vec<u32>>

5.5.E.7.6 (planner): vim’s :g/pat/body and :v/pat/body target-list builder. Validates the pattern, scans every addressable line, and collects line numbers where match-vs- pattern equals inverted (so :g keeps matching lines and :v keeps non-matching ones). On empty pattern or zero matches, pushes an echo and returns None so the caller can bail without driving the body loop.

The body-replay loop stays App-side until the Effect router finishes migrating: not every Effect arm is in [handle_effect] yet, and silently dropping a not-yet- migrated body effect (e.g. a :g/foo/p that produces an unhandled echo path) would be a behaviour regression. Once G.x retires App::apply_effect, the loop joins the planner here.

Source

pub fn do_global( &mut self, pattern: &str, inverted: bool, body: &CommandInvocation, out: &mut DispatchOutcome, )

Vim’s :g / :v – execute body on every line matching (or NOT matching, when inverted) the literal pattern. Iterates bottom-up so deletions / edits on later lines don’t shift target line numbers. The body’s per-line Effect is applied immediately via handle_effect because most :g/.../d / :g/.../s effects (DeleteCurrentLine, Substitute) read editor.cursor at apply time — deferring the apply would smash all iterations into whichever line the last dispatch left the cursor at. Any per-effect renderer signals stream back through out so the caller’s RendererSignal drain still fires. Phase 5.8.AF.3.

Source

pub fn publish_document_changed(&mut self, applied: &[AppliedEdit])

5.5.E.7.2: build + publish Event::DocumentChanged from the current snapshot and the edits that were just applied. Called from every path that mutates the buffer (apply_edit / batch / undo / redo). The applied edits ride on the event so downstream subscribers (notably the per-server LSP fan-in) can sync without re-walking the buffer or holding the supervisor lock.

Source

pub fn maybe_reparse_syntax(&mut self)

Request a reparse if the document’s text has changed since the last request. Idempotent and cheap when nothing changed; the actual parse runs on the syntax handle’s worker task off the UI thread (audit slice 3 / paramount goal #1: “UI thread does no … parsing”). Also triggers Self::recompute_folds so foldmethod=indent / =markdown / =syntax stay in lockstep with the latest text.

Source

pub fn do_list_marks(&mut self)

Vim’s :marks – list every set mark’s name + position in the echo area, sorted by mark name. Reads self.marks (host) and surfaces the result via Self::set_message. Moved here from lattice-ui-tui::app::lifecycle in 5.5.E.2 alongside the Effect::EchoMarks arm.

Source

pub fn do_list_registers(&mut self)

Vim’s :reg – list every register’s contents in the echo area. v1 shows the unnamed "", the numbered "0, and the named alphabetic registers in alphabetical order. Moved here from lattice-ui-tui::app::lifecycle in 5.5.E.2 alongside the Effect::EchoRegisters arm.

Source

pub fn store_yank( &mut self, register: Register, content: String, kind: YankKind, explicit_yank: bool, )

Stash a yank / delete payload into the register slots. The dispatcher emits Effect::Yank with the resolved Register selector (either explicit "<a>-style or the Register::Unnamed default); operator semantics:

  • Register::BlackHole -> drop on the floor, no slot touched.
  • Any other explicit register -> store there AND in "" (the unnamed register, vim’s default paste source).
  • Register::Unnamed -> store in "" only.

Yanks (vs deletes) also populate "0. v1 approximates vim’s distinction by treating every grammar Effect::Yank as also writing "0; deletes don’t (they would hit "1+ in vim, which v1 doesn’t model).

Moved here from lattice-ui-tui::app::edit in 5.5.E.3 alongside the Effect::Yank arm. Vim’s append-to-uppercase semantics ("A appends to "a) remains a v1 simplification: A-Z replaces lowercase rather than appending.

CB.1 (clipboard.md §5): explicit_yank gates the system-clipboard mirror. The content is copied to the OS clipboard when this is an explicit yank (explicit_yank == true) AND the clipboard option is on, OR the target is the +/* Register::System (always the clipboard, regardless of the option). Delete / change pass explicit_yank == false, so an incidental delete never clobbers the clipboard (the yank-only rule). The write is fire-and-forget (lattice_core::Clipboard::write) — never blocks the caller.

Source

pub fn set_selections_blocking(&mut self, selections: SelectionSet)

Replace the document actor’s SelectionSet and publish Event::SelectionsChanged so subscribers (LSP fan-in, renderer, plugins) see the new selection state.

Moved here from lattice-ui-tui::app::visual in 5.5.E.4 alongside the Effect::SelectionChange arm. The block_on is renderer-neutral: it parks the input thread on the actor’s selection set channel, which the actor drains synchronously (no other thread can flip the selection set while we wait). After 5.5.E.4 every caller — Visual-mode extension, the dispatcher’s SelectionChange effect, gv reselect, LSP location jumps — invokes self.editor.set_selections_blocking(...).

Source

pub fn publish_selections_changed(&self)

Build + publish Event::SelectionsChanged from the current snapshot. Called whenever the editor’s view of selections rotates (visual extension, dispatcher SelectionChange effect, gv reselect, etc.). Moved here from lattice-ui-tui::app::lifecycle in 5.5.E.4 as the only caller — Self::set_selections_blocking — moved alongside.

Source

pub fn do_set_fold_state_at_cursor(&mut self, state: Option<bool>)

5.5.G.1: vim’s zo / zc / za – toggle, open, or close the fold at the cursor. Some(true) = zc close, Some(false) = zo open, None = za toggle. Selection rules mirror the App-side helper retired in this slice.

Source

pub fn do_set_all_folds(&mut self, closed: bool)

5.5.G.1: vim’s zR (closed = false) / zM (closed = true) – bulk open / close every fold in the buffer.

Source

pub fn do_cycle_fold_at_cursor(&mut self)

org-cycle (z<Tab>): cycle the heading/fold under the cursor through emacs org-mode’s three local states — FOLDED → CHILDREN → SUBTREE → FOLDED:

  • FOLDED: the whole subtree collapsed (just the heading row).
  • CHILDREN: the heading open, its direct children visible but their bodies + deeper descendants folded.
  • SUBTREE: the heading + everything under it fully expanded.

The subtree root is the innermost fold containing the cursor. Nesting is derived by range containment (the markdown / syntax providers emit nested subtree folds); the state is inferred from the current closed flags (stateless, like za). A leaf heading (no descendants) degenerates to a FOLDED ↔ open toggle, matching org.

When the cursor is NOT inside any fold, this falls back to the global cycle (Self::do_cycle_folds_global) — so z<Space> is a single contextual key: cycle the heading you’re on, or the whole buffer when you’re not on one.

Source

pub fn do_cycle_folds_global(&mut self)

org-cycle global (z<S-Tab>): cycle the WHOLE buffer through org’s three global states — OVERVIEW → CONTENTS → SHOW-ALL → OVERVIEW:

  • OVERVIEW: every fold closed (only top-level headings show — a closed parent hides its nested headings).
  • CONTENTS: structural headings open, leaf folds closed — the table-of-contents view (all heading levels, no leaf bodies).
  • SHOW-ALL: every fold open.

A leaf is a fold containing no other fold. With no nesting (a flat fold list) OVERVIEW and CONTENTS coincide, so the cycle degenerates to OVERVIEW ↔ SHOW-ALL. Inferred from the current flags, like the local cycle.

Source

pub fn do_mouse_scroll(&mut self, pane: PaneId, down: bool)

MO.2: the wheel over pane — scroll it WITHOUT taking focus.

Focus staying put is the convention vim (mousescroll), Zed and Helix share, and it is the behaviour that makes a wheel over a reference split usable while you are typing in another. A click is the gesture that moves focus; a wheel is not.

The active pane delegates to Self::do_scroll_line, so mouse scrolling inherits <C-e> / <C-y>’s fold-aware step and its cursor-follows-the-viewport clamp rather than restating either. An inactive pane has no live cursor to clamp — its position is stashed on the leaf — so it moves its own scroll through the same fold walk, against ITS buffer’s folds (crate::modes::DocumentFolds), not the active buffer’s.

Source

pub fn do_mouse_goto( &mut self, pane: PaneId, line: u32, byte: u32, extend: bool, )

MO.2: a press or drag resolved to a buffer position.

Focuses pane first when it is not the active one — clicking into a split is how you move to it, which is the half of the gesture do_mouse_scroll deliberately does not do.

extend is the whole difference between the two gestures, and selection is Visual mode, not a parallel concept: a drag ends with the region live in ModalState::Visual(Charwise), so d, y and every operator work on it with no new machinery. That is the design’s “Visual mode IS the active region” taken literally. A plain press leaves Visual, which is what a click means everywhere else.

Source

pub fn do_goto_fold(&mut self, forward: bool)

5.5.G.1: vim’s zj (forward) / zk (backward) – jump the cursor to the next / previous visible fold edge.

Visible is the load-bearing word, and it is vim’s: “a closed fold is counted as one fold”. The folds nested inside a closed one are not separate stops, because they are not on screen — they render on the closed fold’s own row. Stepping to one moved cursor.line without moving the cursor a single row, so on a collapsed org section with sub-headings zj reported success and visibly did nothing.

So candidates are compared by the row they are DISPLAYED on (crate::folds::FoldIndex::visible_anchor) rather than by their line number, and an edge that shares the cursor’s row is not a destination. The edges themselves are still the line numbers — zk from below a collapsed section lands on its last line, which puts the cursor on the collapsed row, exactly as vim does. What is rejected is a step that cannot be seen, not a step into a fold.

Nearest-first is preserved by picking the min / max LINE among the surviving candidates; anchors decide eligibility, not ordering.

Source

pub fn do_goto_parent_fold(&mut self)

zp / :fold-goto-parent: move the cursor to the parent heading — emacs outline-up-heading (evil-org’s gh). One level up the fold hierarchy, derived by range containment: find the innermost fold containing the cursor (the current section), then jump to the start of the innermost fold that strictly contains it. Distinct from zj/zk, which step to the next/previous fold edge among siblings. No-op message when the cursor isn’t in a fold, or is already under a top-level heading.

Source

pub fn do_delete_fold_at_cursor(&mut self)

5.5.G.1: vim’s zd – delete the innermost fold containing the cursor. E490 when the cursor isn’t inside any fold.

VM.3h: {Visual}zd deletes one level for every selected line (the innermost fold at each) and ends Visual. Vim 9.2 over lines 3–4 of outer 2–7 ⊃ inner 3–4 deletes inner and keeps outer.

Source

pub fn do_set_folds_recursively(&mut self, close: bool)

VM.3h: vim’s zO (close = false) and zC (close = true).

The rules are NOT symmetric, and come from vim 9.2 rather than its help text, which describes both as “folds that don’t contain the cursor line are unchanged” (scratchpad/vimcheck_normal_zOC*.vim):

  • zO opens every fold containing a target line AND every fold nested inside those. On line 3 of outer 2–9 ⊃ mid 3–6 ⊃ inner 4–5, inner opens although it doesn’t contain the cursor.
  • zC closes every fold containing a target line, and nothing else. On line 3 of the same layout, inner stays open.

The target lines are the cursor line in Normal, and every selected line in Visual, where the command also ends Visual. In Visual that makes zC close an enclosing fold that’s only partly selected, which vim does too.

Source

pub fn do_delete_folds_recursively(&mut self)

VM.3h: vim’s zD.

Normal: the innermost fold containing the cursor line, plus every fold nested inside it. Vim 9.2 on line 3 of outer 2–9 ⊃ mid 3–6 ⊃ inner 4–5 deletes mid and inner and keeps outer; on line 4 it deletes inner only, the same as zd.

Visual: every fold lying inside the selected lines, and NOT a fold that merely encloses them (vim 9.2 over 3–4 of outer 2–7 ⊃ inner 3–4 keeps outer). Ends Visual.

Source

pub fn do_start_macro_record(&mut self, register: char)

5.5.G.1: vim’s q{reg} – begin recording subsequent actions into register reg. No-op if a recording is already in flight (matches vim).

Source

pub fn do_stop_macro_record(&mut self)

5.5.G.1: vim’s q (terminate recording) – commit the pending recording into Self::macros keyed by its register, then clear the in-flight slot.

Source§

impl Editor

5.5.G.3: pure-editor edit-cluster helpers (line operations, case toggle, open / append / overwrite, undo/redo glue, backspace). Each body either was already 100% editor.* reads + writes or only used Self::apply_edit_blocking / Self::active_text / host-side primitives.

Source

pub fn do_join_lines(&mut self, with_space: bool)

Vim’s J (with_space = true) and gJ (false) – splice the current line’s newline (+ leading whitespace, for J) with " " (or "" for gJ). Cursor lands on the join point.

Source

pub fn do_toggle_case_at_cursor(&mut self)

Vim’s ~ – toggle the case of the byte at the cursor and advance. Non-letter bytes are unchanged; the cursor still advances. At EOL the cursor stops (no wrap).

Source

pub fn do_enter_append(&mut self)

Vim’s a – step the cursor one byte to the right (clamped to EOL) and switch to Insert. Does NOT route through the canonical enter_mode lifecycle (the App-side EnterMode arm does that with recording_insert plumbing). The semantics here mirror the App-side helper retired in this slice: pure field writes, plus opening the undo group via Self::begin_insert_session.

Source

pub fn do_enter_insert_first_non_blank(&mut self)

Vim’s I – move to the first non-blank column of the current line, then enter Insert mode.

Source

pub fn do_enter_append_end_of_line(&mut self)

Vim’s A – move to end of the current line (past the last byte), then enter Insert mode.

Source

pub fn do_display_line_down(&mut self)

Vim’s gj – move down one display-line segment. When wrapping is off (wrap_width == 0), degrades to j.

Source

pub fn do_display_line_up(&mut self)

Vim’s gk – move up one display-line segment. When wrapping is off (wrap_width == 0), degrades to k.

Source

pub fn do_display_line_start(&mut self)

Vim’s g0 – move to the first byte of the current display segment. Degrades to 0 when wrapping is off.

Source

pub fn do_display_line_end(&mut self)

Vim’s g$ – move to the last byte of the current display segment. Degrades to $ when wrapping is off.

Source

pub fn do_open_line_below(&mut self)

Vim’s o – splice \n at EOL, drop cursor on the new blank line, switch to Insert. Uses Self::active_text so the path works uniformly across Document / Oil / etc.

Source

pub fn do_open_line_above(&mut self)

Vim’s O – mirror of Self::do_open_line_below but inserts \n at BOL and keeps the cursor on the inserted (now upper) row.

Source

pub fn do_overwrite_char(&mut self, c: char)

Vim’s Replace mode (R) overstrike – if the cursor is mid-line, replace [cursor, cursor+1) with c; if past EOL, extend with an insert. Either way the cursor advances by one byte and the original byte (or None for the extend case) is recorded on replace_history so backspace can restore it.

Source

pub fn do_replace_undo_last(&mut self)

Pop the latest replace_history entry and restore. If the entry recorded an original byte, replace; otherwise the extend-case path deletes the byte. Cursor returns to the entry’s position.

Source

pub fn do_delete_char_backward(&mut self)

Vim’s <BS> in Insert / Replace – delete the byte before the cursor (Unicode-aware step via previous_position). No-op at the start of the buffer. Bumps the block-visual I / A live-edit counter so the Esc replay accounts for the deletion.

Source

pub fn do_insert_line_edit(&mut self, kind: InsertLineEdit)

Insert-mode line editing (<C-a>/<C-e>/<C-b>/<C-f>/<C-w>/ <C-u>/<C-k>/<C-t>/<C-d>) — the readline/vim family the built-in Insert keymap binds in every buffer. Cursor moves are char-boundary-safe within the current line and use Insert semantics (<C-e> lands past the last byte). Deletes route through apply_edit_blocking, so the read-only / editable-tail gate still applies (e.g. the agent prompt). Indent/dedent share Self::indent_unit with the > / < operators, so <C-t> and >> cannot disagree about what one indent level is.

Source§

impl Editor

5.5.G.23: fold-aware predicates + cursor snap. Migrated host-side alongside the run_invocation family that consumes them. App-side foldenable / fold_start_at / snap_cursor_past_closed_folds retire to 1-line delegators until every caller migrates.

Source

pub fn foldenable(&self) -> bool

:set foldenable / :set nofoldenable (zi). Default true. Reads through option_cache so the hot-path access is a single bool load with no option-tree traversal.

Source

pub fn conceal_reveal(&self) -> bool

H.4: whether concealed markup renders raw right now.

Insert and Replace reveal; Normal, Visual, Select, Operator-pending, Command, Search and Prompt conceal. The split is “am I editing text” rather than “am I in a modal state”, which is why Visual — a selection state, not an editing one — stays concealed.

This alone is not the gate. It is editor-global; a pane also has to be the one holding the edited buffer before it reveals (build_one_pane_cells_input). Revealing on the mode alone would repaint every visible org buffer in every split on i.

Source

pub fn completion_auto_insert_single(&self) -> bool

5.5.G.23.cmdline: :set completion.auto_insert_single. Default true. When the popup compute yields exactly one candidate and this option is on, the candidate is inlined immediately instead of opening a popup the user has to confirm.

Source

pub fn fold_start_at(&self, line: u32) -> Option<&Fold>

Returns Some(fold) if line is the start of a closed fold; the renderer renders the summary header instead of the line content.

Source

pub fn fold_start_at_any(&self, line: u32) -> Option<&Fold>

Returns Some(fold) if line is the start of any fold (open or closed). Used by the renderer for the gutter glyph.

Source

pub fn line_inside_closed_fold(&self, line: u32) -> bool

True if line is inside (but not the start of) a closed fold. Renderer uses this to skip painting that line. When foldenable = false, always returns false.

Phase 5.8.U: hoisted from lattice-ui-tui::app::folds::App::line_inside_closed_fold so the GPUI peer can also skip lines inside closed folds.

Source

pub fn fold_aware_highlight_end_line(&self) -> Option<u32>

Slice X2.9: compute the fold-aware upper bound for the highlights worker’s parse window. When viewport_height rows are drawn starting at scroll, closed folds collapse multiple buffer lines onto a single row – so the LAST buffer line that actually appears in the viewport can be well past scroll + viewport_height. The worker uses the returned value (when Some) as end_line in its highlight_lines(start, end_line) call so the tail of the viewport (after any collapsed fold) gets coloured spans.

Returns None when the result equals the default scroll + viewport_height (no fold-stretch needed) so the worker can skip the override branch entirely on the common fold-free case. Renderer-agnostic; both peers route through publish_render_state which wires this into SyntaxRenderState::end_line_override.

Source

pub fn snap_cursor_past_closed_folds(&mut self, prev_line: u32)

Move the cursor out of any closed fold’s hidden body to the nearest visible line. Called after every non-jump motion so the cursor’s logical position never lands in a hidden region. foldenable = false suppresses entirely.

Guarded against an infinite loop when a closed fold’s body ends at the last addressable line (fold.end_line == last): the (end + 1).min(last) clamp in the going_down branch would produce last, re-match the same fold, and loop forever. Checking fold.end_line >= last breaks out instead.

Source§

impl Editor

5.5.G.4: pure-editor scroll / page / viewport / bracket / redraw helpers.

Source

pub fn land_cursor_at(&mut self, position: Position)

Land the cursor after an open-and-jump, and REVEAL it.

The pair every cross-file jump wants: position, then open whatever closed folds the target sits inside. Vim spells the second half zv and does it implicitly for jump-class motions; emacs org calls it org-fold-show-context and runs it after org-agenda-goto.

One method rather than the two calls each renderer used to make, because the reveal was missing from BOTH and a fix applied at two call sites is a fix that comes apart at one of them. auto_open_folds_at_cursor already existed and its own doc says it is “called by jump-class motions so the cursor never lands inside a hidden region” — the cross-file jump simply never called it.

Why this is load-bearing rather than polish: org-mode sets foldlevel = 0, so an org file opens fully collapsed. Jumping from the agenda (or through a link) to a headline therefore landed the cursor inside closed folds, and the user arrived looking at a collapsed outline with no sign of where they had gone. Navigation that does not show you the thing you navigated to has not finished.

Source

pub fn auto_open_folds_at_cursor(&mut self)

Open every closed fold whose range contains the current cursor line. Called by jump-class motions so the cursor never lands inside a hidden region.

Source

pub fn do_jump_viewport(&mut self, vpos: ViewportPos)

Source

pub fn do_scroll_cursor_to(&mut self, spos: ScrollPos)

Vim’s zt / zz / zb – adjust scroll so the cursor lands at the requested viewport row; cursor doesn’t move. All three positions are fold-aware: closed fold bodies are skipped when counting visible rows, so the cursor lands at the correct visual position regardless of folded regions between it and the viewport edge.

Source

pub fn do_half_page(&mut self, down: bool, count: u32)

Vim’s <C-f> (down) / <C-b> (up) – step cursor by viewport_height-2 lines with a 1-line overlap; scroll is reconciled by ensure_cursor_visible at the tail of apply. Fold-aware: walks visible (non-fold-hidden) lines so closed folds don’t cause the cursor to skip too far or land inside hidden fold bodies. VM.3j-2: vim’s <C-d> / <C-u> — move the VIEW and the cursor together by scroll lines (half the window when the option is 0, vim’s default).

A scroll command, not a motion. <C-d> was bound to the j MOTION with a baked count of ten, which moved a fixed ten lines whatever the window height and — because a motion composes — made d<C-d> delete eleven lines where vim deletes nothing at all.

Measured in vim 9.2 (vimcheck_ctrl_d.vim): from 3,6 in a 22-row window the view goes 1 → 12 and the cursor 3 → 14, i.e. both by scroll; near the end the cursor stops on the last line; on the last line nothing moves.

Source

pub fn do_page(&mut self, down: bool)

Source

pub fn do_scroll_line(&mut self, down: bool)

Vim’s <C-e> (down) / <C-y> (up) – scroll one display line. Fold-aware: advances scroll past closed fold bodies so it lands on the next visible line, and pulls the cursor along when it would scroll off-screen. scroll always points to a visible (non-fold-hidden) line after the operation.

Source

pub fn do_match_bracket(&mut self)

AppEffect::MatchBracket — the plugin-boundary spelling of %.

VM.3b moved the scan itself into motion:match-pair, so the % chord no longer comes through here: it dispatches the motion like every other motion, which is what gives it d% / v% / y% and a Visual row for free. This arm survives because AppEffect::MatchBracket crosses the WIT boundary (boundary_app_effect.rs) and a guest may still emit it — and it delegates rather than keeping a second copy of the scan, because two implementations of % is exactly the drift VM.1 spent a slice removing from the keymap.

The jump-list entry and the fold-open come from the motion path’s is_jump_motion handling (VM.3a), so they are not repeated here.

Source

pub fn do_redraw_screen(&mut self)

Vim’s <C-l> – force a syntax reparse, drop the highlight cache, recompute folds, and signal the runtime to clear the terminal on next frame. Slice X2.6: legacy visible_* fields retired; per-pane cache is still cleared, and bumping last_parsed_text_version forces the worker to recompute on its next wake.

Source§

impl Editor

5.5.G.12: file-tree dismiss + HelpDismiss arm.

Source

pub fn dismiss_file_tree(&mut self) -> Vec<RendererSignal>

Close the file-tree pane: activate the first listed document buffer as a successor, remove the tree from the registry, and rebind every pane that pointed at it to the new buffer. Returns any signals emitted by activate_buffer_state (mode lifecycle

  • LSP attach cascade).
Source§

impl Editor

Phase 5.8.AD.1: oil + file-tree buffer creation, navigation, and buffer-local seeding. All operations are renderer-neutral — both peers reach them through Editor. The TUI’s app/oil.rs and app/file_tree.rs collapse to delegate shims; the GPUI peer wires Effect::OpenOil / OpenFileTree / CloseFileTree host-side via handle_effect.

Source

pub fn set_oil_dir(&mut self, buffer_id: BufferId, dir: PathBuf)

Write the OilDir buffer-local for buffer_id. Single chokepoint for every oil-buffer dir mutation (M.3.2.c.5).

Also records the generic BufferScopeDir, so :files / :search from an oil buffer resolve against the directory it is listing rather than the process working directory. One chokepoint writes both, which is what stops them drifting apart.

Source

pub fn resolve_path_to_buffer( &mut self, path: &Path, ) -> Result<BufferId, String>

XF.2: resolve path to a buffer, opening it in the background if it is not already open.

The one primitive Effect::WriteToFile needed and the vocabulary did not have: a producer can name a file it has never opened, and everything downstream addresses a BufferId.

§Reuse is the correctness case, not an optimisation

If the file is already open, THAT buffer is returned. Opening a second one over a file the user has unsaved changes in and then editing the copy loses their work silently — the edit lands, the buffer they are looking at never sees it, and whichever saves last wins.

§Background, and listed

The active pane does not move: the user pressed a key to archive a subtree, not to navigate. The buffer is an ordinary listed Document, so :ls shows it, :w saves it and :bd closes it — a plugin’s write leaves something the user can see and act on rather than a hidden mutation.

§Reading here, and why that is acceptable

Document::open reads synchronously. This runs on a user-initiated command, once per target file, never per frame / keystroke / tick — the same bound :e lives with (cross-file-writes.md §9).

A missing file is CREATED (empty, with its path set): capture’s first run is exactly that. A missing parent directory is an error rather than a mkdir -p — creating directories is a larger authority than creating a file, and a typo’d path should not silently build a tree.

A producer that owns the layout can opt out of that refusal — see Self::resolve_path_to_buffer_creating.

Source

pub fn resolve_path_to_buffer_creating( &mut self, path: &Path, create_parents: bool, ) -> Result<BufferId, String>

Self::resolve_path_to_buffer, with the missing-parent refusal made the caller’s choice.

create_parents is the Effect::WriteToFile field of the same name and carries its reasoning: the refusal exists so a typo’d path cannot build a tree, which says nothing about a producer writing into a directory that is part of the layout it owns. org-roam’s daily/YYYY-MM-DD.org is that case — the folder is named by an option with a default, no user ever types it, and refusing means the feature’s first use is the one that fails.

For a plugin the path has already been checked against its fs:write grant at the boundary, so the tree this can build is bounded by the grant rather than by this flag.

Source

pub fn apply_write_to_file( &mut self, path: PathBuf, anchor: FileAnchor, text: String, cut: Option<Range>, create_parents: bool, save: bool, ) -> bool

XF.3: apply an Effect::WriteToFile — insert into the target, then cut from the source, and only in that order.

§Why this applies inline instead of deferring like ApplyEdit

Effect::ApplyEdit pushes an Action::ApplyEdit onto out.next_actions for the renderer to re-dispatch. Composing this effect out of two of those would be tidy and would be wrong: next_actions is a list, the renderer walks it unconditionally, and an effect cannot report failure (pinned by XF.0’s tests). The cut would run whether or not the insert landed — which is the “the subtree is gone” outcome the whole one-effect design exists to make unrepresentable.

Self::apply_targeted_edit returns a Result, so applying inline is what makes “only if it landed” expressible at all. The diff path (apply_diff_effect_inline) already establishes inline application through the same helper.

§Ordering, and the asymmetry in the failure handling

Insert first. A failed insert leaves the source untouched — the user sees an error and still has their text. Cutting first would mean a failed insert had already destroyed the original.

A failed cut after a landed insert is logged and left: the text now exists in both places. Duplicated text is recoverable by hand; lost text is not, so the asymmetry is deliberate rather than an oversight.

§save, and where it sits in that order

OC.9. The target is left MODIFIED by default — cross-file-writes.md §7, matching emacs’s org-refile / org-archive-subtree. A producer whose operation is a commit passes save: true and the target is written to disk, which is what emacs’s org-capture-finalize does unless :no-save.

It runs LAST, so it inherits the ordering the rest of this function establishes: the failed-insert branch returns first, so save can never persist a write that did not land. See Self::save_target_to_disk for why this cannot reuse save_blocking.

Source

pub fn set_buffer_scope_dir(&mut self, buffer_id: BufferId, dir: PathBuf)

Record the directory buffer_id is about — the generic BufferScopeDir.

The single write chokepoint, and the one ModeActivator::set_buffer_scope_dir forwards to, so a provider crate reaches it without a host dependency.

Source

pub fn buffer_scope_dir(&self, buffer_id: BufferId) -> Option<PathBuf>

The directory buffer_id is about, if one was recorded.

Source

pub fn oil_dir_for(&self, buffer_id: BufferId) -> Option<PathBuf>

Read the dir an oil buffer represents from its OilDir buffer-local. None if the buffer isn’t registered or the local hasn’t been seeded.

Source

pub fn oil_with_dir(&self, dir: &Path) -> Option<BufferId>

Find a registered oil buffer whose OilDir matches dir. Used by do_open_oil’s dedup path.

Source

pub fn do_open_oil(&mut self, dir: Option<PathBuf>) -> Vec<RendererSignal>

:Oil [dir] – open an oil buffer rooted at dir (or the current document’s parent / cwd if absent). De-dup: if a buffer at the same dir is already open, switch to it. Phase 5.8.AD.1: hoisted from TUI App.

Source

pub fn apply_oil_navigate( &mut self, view: BufferId, dir: PathBuf, focus: Option<String>, ) -> Vec<RendererSignal>

LM.2: applier for Effect::OilNavigate — re-list oil view to dir in place, landing the cursor on focus (or the top). Shares the reload + write + cursor logic the old do_oil_follow dir branch and do_oil_navigate_up oil branch had; the mode handler (LM.3) now owns the decision (which dir, which focus) and emits the effect, while this owns the apply. Names view so a second oil buffer is untouched. Returns no renderer signals (the re-list is applied in place, like the old dir branch).

Source

pub fn apply_file_tree_toggle( &mut self, view: BufferId, entry_index: u32, ) -> Vec<RendererSignal>

LM.2: applier for Effect::FileTreeToggle — toggle the directory entry at entry_index in file-tree view and re-render its rope. The apply half of the old do_file_tree_follow dir branch; the mode handler (LM.4) decides which row and emits the effect. Names view so a second tree is untouched.

Source

pub fn do_oil_navigate_up(&mut self) -> Vec<RendererSignal>

- – navigate to the parent of the current buffer’s dir. In oil: compute the parent from OilDir. In file- tree: open oil rooted at the parent of the entry under the cursor (or the entry itself when it’s a directory). Anywhere else: open oil rooted at the parent of the active document’s path. Phase 5.8.AD.1.

Source

pub fn oil_snapshot_for(&self, buffer_id: BufferId) -> Option<OilSnapshot>

DL.5: read the oil snapshot for buffer_id.

Source

pub fn set_oil_snapshot(&mut self, buffer_id: BufferId, snapshot: OilSnapshot)

Write the oil snapshot AND publish the listing’s icons. Single chokepoint, so the two cannot drift (the file tree’s set_file_tree_entries is the peer).

Source

pub fn set_file_tree_root(&mut self, buffer_id: BufferId, root: PathBuf)

Write FileTreeRoot. Single chokepoint (M.3.2.c.5).

Also records the generic BufferScopeDir, the oil peer’s reason: a file tree is not a file, so project resolution would otherwise answer for the process working directory rather than the tree the user is looking at.

Source

pub fn set_file_tree_entries( &mut self, buffer_id: BufferId, entries: Vec<FileTreeEntry>, )

Write FileTreeEntries AND re-render the buffer’s rope. Single chokepoint for every entries mutation.

Source

pub fn set_file_tree_nerd_fonts( &mut self, buffer_id: BufferId, nerd_fonts: bool, )

Write FileTreeNerdFonts and re-render the rope.

Source

pub fn file_tree_root_for(&self, buffer_id: BufferId) -> Option<PathBuf>

Source

pub fn file_tree_entries_for( &self, buffer_id: BufferId, ) -> Option<Vec<FileTreeEntry>>

Source

pub fn file_tree_nerd_fonts_for(&self, buffer_id: BufferId) -> Option<bool>

Source

pub fn file_tree_with_root(&self, root: &Path) -> Option<BufferId>

Find a registered file-tree buffer whose FileTreeRoot matches root. Mirrors oil_with_dir.

Source

pub fn do_open_file_tree( &mut self, root: Option<PathBuf>, ) -> Vec<RendererSignal>

:Tree [path] – open a FileTreeBuffer rooted at path (or the current document’s parent / cwd if absent). De-dup: if a tree at the same root is already open, switch to it. Phase 5.8.AD.1: hoisted from TUI App. The nerd-fonts setting reads from the host’s Theme so both peers seed the tree identically.

Source§

impl Editor

5.5.G.17: modal-state transitions + blockwise-Visual I/A. enter_mode is the canonical modal pivot (the Insert-replay recording lifecycle, cursor pull-back on <Esc>, modal-event publish all live here). replicate_block_insert commits a blockwise I/A session as a single batched undo unit. do_enter_block_visual_insert is the Visual-blockwise entry point.

All three were previously App-side; their deps (apply_edit_blocking / apply_edit_batch_blocking / undo_blocking / event_bus.publish) all sit on Editor already, so the migration is a verbatim move.

Source

pub fn enter_mode(&mut self, state: ModalState)

Modal-state pivot. Maintains the Insert-replay recording lifecycle (start capture on entering Insert/Replace; promote captured text into last_insert on exit; commit any pending_block_insert via replicate_block_insert), the Insert->Normal cursor pull-back, and the ModalModeChanged event fan-out. Re-entering the same mode is intentional (the dot-repeat path bounces through Insert for its recording side-effects); we suppress the event publication in that case.

Source

pub fn replicate_block_insert(&mut self, spec: PendingBlockInsert, text: &str)

Commit a blockwise-Visual I / A session as one batched undo unit. Rewinds the live_edits typed on the top row, then builds + applies the multi-row insert batch.

Source

pub fn do_enter_block_visual_insert(&mut self, append: bool)

Vim blockwise-Visual I (append=false) / A (append=true). Captures the block extents, parks them in pending_block_insert, snaps the cursor to the top-row insert column, and switches to Insert. The replication onto rows 2..N happens via Self::replicate_block_insert when Insert exits.

Source§

impl Editor

5.5.G.16: vim zf – create a closed fold over the active Visual selection’s line range. Pure-editor migration; the helper relied only on host-side fold + selection state.

Source

pub fn do_create_fold_from_visual(&mut self)

Vim zf: create a closed fold spanning the active Visual selection’s first..last line, snap the cursor to the fold start, and exit Visual mode. No-op when called outside Visual or when the selection is single-line (a 1-line fold isn’t meaningful in vim).

Source

pub fn do_create_fold(&mut self, start_line: u32, end_line: u32)

VM.3h: zf{motion} / {Visual}zf, with the span the operator resolved. A CLOSED fold over start_line..=end_line, cursor at its first line, as the Visual-only handler above always did; a one-line span creates nothing, as in vim.

Ends Visual itself. The host leaves Visual after an operator only when its effect mutates or yanks (effect_mutates_or_yanks), and a fold does neither, so vjzf would otherwise stay in Visual. Vim ends it.

Source§

impl Editor

5.5.G.15: pure-editor cmdline-completion popup navigation. <S-Tab> walks backward through candidates; <C-y> / <CR> (when popup is open) splices the focused candidate into the cmdline. Migrated from lattice-ui-tui::app::cmdline.

Source

pub fn do_command_line_complete_prev(&mut self)

<S-Tab> – step backward through the cmdline-completion popup candidates with wrap-around on the lower bound. No-op when the popup is closed or empty.

Source

pub fn do_command_line_accept_completion(&mut self)

Splice the cmdline-completion popup’s focused candidate into command_line (replacing the active slot’s prefix) and dismiss the popup. Idempotent when the popup is closed or has no candidates.

Source§

impl Editor

5.5.G.14: pure-editor completion cancel + docs scroll. Migrated from lattice-ui-tui::app::completion; touches only editor-owned insert_completion + completion_in_path_context.

Source

pub fn do_completion_cancel(&mut self)

Tear down the in-flight insert-completion state. Clears the popup and exits any path-context filter mode. Pure- editor; the next Action::CompletionTrigger rebuilds.

Source

pub fn do_completion_docs_scroll_down(&mut self)

Page the docs popup body forward (<C-f> inside the completion-popup minor mode). Half-popup-height jump per press; clamps at the body’s last visible line.

Source

pub fn do_completion_docs_scroll_up(&mut self)

Page the docs popup body backward (<C-b> inside the completion-popup minor mode).

Source§

impl Editor

5.5.G.13: pure-editor command-line history walk. Migrated from lattice-ui-tui::app::cmdline; touches only editor- owned cmdline + history state.

Source

pub fn do_command_history_step(&mut self, back: bool)

Walk through : command history in Command modal. back = true goes to older entries (Up); false goes newer (Down). The first Up snapshots the user’s in-flight line into command_history_pending so the bottom-of-history Down can restore it.

Source

pub fn do_search_history_step(&mut self, back: bool)

MB.5b: walk search_history (peer of Self::do_command_history_step). <C-p> / <Up> walks backward (older); <C-n> / <Down> walks forward (newer). Saves the in-progress pattern on first step so <C-n> can return to it.

Source§

impl Editor

5.5.G.11: simple picker-state helpers + close-hover.

Source

pub fn bump_live_picker_debounce(&mut self)

Slice 2: live-picker debounce. Bump deadline by now + LIVE_PICKER_DEBOUNCE. No-op when no live-query state is in flight.

Source

pub fn reconcile_pane_history(&mut self)

PI.1: set (or replace) the preview projection for pane. The pane’s committed PaneState.buffer_id is untouched; the renderers show override_’s buffer with its preview cursor / scroll until the override is cleared. Bumps the override version so the next build_render_state republishes the pane-tree projection. PBH.1: reap pane_buffer_history entries for panes that no longer exist, and hand back the active pane’s history (creating it, seeded with the pane’s current buffer, if absent).

Reconciles against the tree rather than hooking removals. Panes disappear through more than one path — close_active and collapse_to_active today, and any future one — so an enumeration of removal sites goes stale silently. Retaining only live ids cannot miss a caller. Same shape as refresh_autoread_watcher’s desired-set diff.

O(panes); panes are single-digit in practice and this runs on a pane/buffer transition, never per frame or per char.

Source

pub fn active_pane_history_mut(&mut self) -> &mut PaneBufferHistory

PBH.1: the active pane’s history, created and seeded with the pane’s current buffer when the pane has none.

Seeding matters: without an entry for the buffer the pane is already showing, the first <C-6> after one switch would have a single-entry trail and nothing to go back from.

Reconciles first. There are ~10 close_active / collapse_to_active / split_active call sites in this file and hooking each is the stale-enumeration failure the design rejects; reconciling here instead puts the reap on the one path that actually reads history, so it cannot be missed. A closed pane’s entry lingers until the next history operation, which is bounded and harmless: the map is keyed by PaneId, so a dead pane’s entry can never be read, and any navigation in a surviving pane clears it.

Source

pub fn do_pane_history(&mut self, delta: i32)

PBH.3: walk the active pane’s buffer trail. -1 = back (<C-6>), +1 = forward (<C-7>).

Three things make this correct, and all three are load-bearing:

  1. The outgoing position is captured first. Otherwise the entry being left keeps whatever position it had when it was pushed, and walking back to it a second time lands in the wrong place.
  2. The walk does not record. walking_pane_history gates the push in Self::record_pane_history_visit; without it the step back would push a new entry, truncate its own forward tail, and make <C-7> permanently unreachable.
  3. Dead buffers are pruned before stepping, so a :bd’d entry is skipped rather than failing the switch.

No wrap at either end — a directional key that cycles is a worse key. Echoes instead.

Source

pub fn purge_buffer_from_pane_histories(&mut self, id: BufferId)

PBH.4: drop id from every pane’s trail.

Called when a buffer leaves the registry (:bd). Eager rather than only lazy because the deletion is global: a walk in a different pane would otherwise step onto an entry whose buffer no longer exists, and only the walking pane prunes lazily.

The lazy prune in Self::do_pane_history stays as the safety net for the other paths that drop buffers (:e’s replace of an unreferenced buffer, for one), so neither mechanism is load-bearing alone.

Source

pub fn pane_buffer_history_size(&self) -> usize

PBH.4: the resolved pane.buffer-history-size bound.

Falls back to the compiled default when the registry has not been populated (boot-before-linkme and test fixtures, mirroring command_line_expand_height). Non-positive values clamp to 1 — a zero-length trail has nowhere to hold the buffer the pane is currently showing.

Source

pub fn capture_outgoing_pane_position(&mut self)

PBH.2: stamp the pane’s live cursor/scroll onto the current history entry without moving the trail.

Used before a walk leaves an entry (PBH.3) so the position the user is leaving is preserved on the way out — the same rule the recording path applies, just without a push.

Source

pub fn set_preview_override(&mut self, pane: PaneId, override_: PreviewOverride)

Source

pub fn clear_preview_override( &mut self, pane: PaneId, ) -> Option<PreviewOverride>

PI.1: clear the preview projection for pane (if any) and return it. The pane snaps back to its committed buffer with zero reconstruction — it was never disturbed.

Source

pub fn clear_all_preview_overrides(&mut self) -> bool

PI.1: clear every pane’s preview projection (picker dismissed / accepted). Returns whether anything was cleared.

Source

pub fn preview_override_for(&self, pane: PaneId) -> Option<PreviewOverride>

PI.1: the preview override seated on pane, if it is previewing.

Source

pub fn mount_preview( &mut self, pane: PaneId, buffer: BufferId, cursor: Position, scroll: u32, ) -> Vec<RendererSignal>

PI.2: mount buffer buffer as an isolated read-only preview projection in pane, displayed at (cursor, scroll).

Two steps, both isolated from the committed buffer A:

  1. Read-only options. Activate preview-mode on B’s OWN mode stack — it contributes ReadOnly = true and, by its presence, the ephemeral “previewing” marker. activate_mode_by_id runs recompute_options_for_buffer(B), which writes ONLY resolved_options[B]; it rebuilds the global option_cache only when B == document_buffer_id, which is never true during preview. So document_buffer_id, the global option_cache, and A’s active_modes / resolved_options are untouched.
  2. Display. Seat the pane’s preview override (PI.1) so the renderers show B at the preview viewport while the pane stays committed to A.

Idempotent: activate_mode_by_id no-ops if preview-mode is already on B, and re-seating just replaces the override (moving the selection to B’ is mount_preview(pane, B', …)).

preview-mode deliberately does NOT contribute CursorLine, so B keeps its own cursorline — the renderer draws it at cursor (the target line of an gr / grep location preview stays highlighted).

Source

pub fn unmount_preview(&mut self, pane: PaneId) -> Vec<RendererSignal>

PI.2: unmount the preview seated on pane. Clears the pane’s override (it snaps back to committed A with zero reconstruction) and — if no OTHER pane is still previewing the same buffer — removes preview-mode from that buffer’s stack, restoring its resolved options. GC of an ephemeral preview buffer is the caller’s concern (the dismiss path). No-op if pane isn’t previewing.

Source

pub fn preview_in_active_pane( &mut self, buffer: BufferId, target_line: Option<u32>, ) -> Vec<RendererSignal>

PI.3: preview buffer in the active pane as an isolated read-only projection, optionally centred on target_line. This is the single funnel every preview source (buffer switcher, find-file, grep, gr, LSP location pickers) routes through — it NEVER calls activate_buffer, so the pane’s committed buffer, document_buffer_id, the global option_cache, and the origin’s modes are all untouched.

Moving the selection to a different buffer unmounts the previous preview first (stripping its preview-mode and GC’ing the previous ephemeral preview buffer if there was one). Moving to the same buffer at a new line just re-seats the override (the common gr / grep case: several hits in one file).

Source

pub fn clear_active_preview(&mut self) -> Vec<RendererSignal>

PI.3: tear down the active pane’s preview — unmount (clears the override + strips preview-mode, restoring the previewed buffer) and GC the ephemeral preview buffer if one is live. Used on <Esc> / picker-closed / no-candidate. The pane snaps back to its committed buffer with zero reconstruction; A was never disturbed.

Source

pub fn preview_picker_selection(&mut self) -> Vec<RendererSignal>

If the picker is open and its action is PickerAction::SwitchToBuffer, preview-activate the selected candidate’s buffer in the active pane. No position-history push, no commit. Returns any signals emitted by the underlying activate_buffer_state tail. Slice 3c.unify.preview-accept-driven (7g). Driven by the candidate’s typed accept_action from Design B (slice 7b). Each AcceptAction variant declares its preview semantics:

  • SwitchBuffer — activate the buffer
  • JumpToFileLocation — open file + position cursor
  • JumpInBuffer — activate buffer + position cursor
  • OpenFile — open in active pane
  • OpenLspLog / OpenLspTraceLog — activate log buffer
  • Side-effecting / stateful variants — no preview

Closes the past LSP-references-preview gap (user concern, 2026-05-21). previewing flag gates activate_buffer’s side effects (mode-lifecycle publishes, MRU bumps).

Legacy fallback: if accept_action is unset (LSP picker sources not yet migrated to set the field — slices 9-17), reverts to the pre-7g buffer-switcher- only path keyed on RoutingPayload::Buffer. MG.54: selection-move entry point. A source that declares preview_debounce gets its preview DEFERRED to Self::drain_pending_preview_settle; everything else previews inline, exactly as before.

The deferred path deliberately leaves the pane showing the PREVIOUS preview while the user scrolls rather than clearing it: clearing would snap the pane back to its committed buffer on every arrow key and back out again 150ms later, which is flicker on unedited content — the UX rule vetoes it. A preview one candidate stale for a sixth of a second is the cheaper wrong.

Source

pub fn drain_pending_preview_settle(&mut self) -> Vec<RendererSignal>

MG.54: fire a deferred preview whose settle window has elapsed.

Every armed wake reaches here, including the ones superseded by a later keystroke — those find the deadline still in the future and no-op, so a burst of N moves costs N cheap comparisons and ONE preview.

A picker that closed inside its own window takes its deadline with it (the state is on the picker), so there is nothing here to clear on dismiss and no way for a settle to fire into the picker that replaced the one which armed it.

Source

pub fn do_preview( &mut self, path: PathBuf, target_line: Option<u32>, ) -> Vec<RendererSignal>

Picker live-preview a file WITHOUT the full Self::do_edit open path. do_edit (real open: synchronous parse + LSP attach + a persistent registry buffer) is reserved for the final accept; running it per keystroke during find-file typing froze the UI thread. This instead:

  • activates the REAL buffer if the file is already open (already parsed + attached — richer + still cheap), else
  • loads a BOUNDED slice into the single reusable ephemeral preview buffer ([Self::ensure_preview_buffer]) and activates that — no LSP, no synchronous parse (plain text), no per-candidate leak.

target_line lands + centres the cursor for location previews (gr, grep hits); None starts at the top. The previewing flag suppresses position-history; origin restore + preview-buffer GC are handled by Self::do_picker_dismiss.

Source

pub fn drain_pending_live_picker_query(&mut self) -> Vec<RendererSignal>

5.8.AA.t: per-tick live-picker query drain. Two steps:

  1. Fire on_query_changed if debounce elapsed.
  2. Pump in-flight results.

Both renderer peers reach this through run_tick_pending. Returns any RendererSignals the seat path emits (the buffers source preview activates the alternate buffer, which can fan out a ThemeChanged / mode-lifecycle signal).

Source§

impl Editor

5.5.G.10: pure-editor search-state cluster (/, ?, n, N, *, #). do_find_repeat stays App-side (calls run_invocation); the substitute preview path stays App-side (called from the cmdline flow).

Live-preview the in-progress search pattern from origin. Tolerates compile errors silently while the user is typing.

Commit the search pattern – jump the cursor to the first match, record last_search, populate all_matches for hlsearch. On empty submit, replay last_search (vim <CR> behaviour). MB.5a: run a submitted search on the ACTIVE document. Called by Self::do_search_line_submit AFTER the *search-line* buffer has been unfocused (so active_text() is the real target). Takes the pattern (read from the search buffer), direction, and origin (the cursor when the search started) as params — the old submit_search read these from search_line.pattern/origin, which no longer carries the pattern.

Source

pub fn do_search_line_submit(&mut self)

MB.5a: <CR> on the /·? line — submit the search. Reads the pattern from the focused *search-line* buffer, unfocuses it (restoring the prior document + cursor + Normal modal, like the : line’s submit), then runs the search on that real document. The buffer is unfocused BEFORE the search so active_text() is the target, mirroring do_command_line_submit.

Source

pub fn do_search_line_backspace(&mut self)

<BS> on the /·? line. Deletes the character before the cursor; on an already-empty pattern it CANCELS the search instead — vim’s behaviour, and what the one-row line implies (backspacing off the front of / leaves nothing to edit).

MB.5a lost this: Action::SearchBackspace became a no-op and <BS> fell through to base Insert, which just does nothing on an empty buffer, stranding the user in Search mode with no pattern.

Source

pub fn do_search_line_cancel(&mut self)

MB.5a: <Esc> / <C-c> on the /·? line — cancel the search. Unfocus the *search-line* buffer (which restores the pre-search cursor = prior_cursor = origin + Normal modal) and clear the match decorations. No search runs.

n / N – replay last_search. reverse = false keeps the direction; reverse = true flips it.

Source

pub fn refresh_hlsearch_from_last(&mut self)

Recompute all_matches from the last search pattern without moving the cursor. Called after every document edit while last_search is populated, so hlsearch overlays stay in sync with the edited text (deleting a word that contains a match clears its highlight; retyping restores it if the pattern still matches).

Source

pub fn follow_current_match_to_cursor(&mut self)

VM.3d-1: current_match is the match the cursor is on, or none.

It used to change only on / / n / N / * / #, so after a j or an edit the strong current-match highlight stayed on a match the cursor had left. Decided 2026-09-15: derive it from the cursor after every dispatch, which is also the convention (Neovim’s CurSearch marks the match under the cursor). n / N / * still land the cursor on their match, so they highlight it through this same rule.

Reads all_matches, which is already resolved and sorted by start: by the search itself, by refresh_hlsearch_from_last after an edit, and per buffer on a swap. So this is a binary search, not a scan, on every keystroke. :nohlsearch empties all_matches, so it can’t bring the highlight back. While the /·? line is open, the live preview owns both fields and this leaves them alone.

Source

pub fn resync_hlsearch_to_active_buffer(&mut self)

Re-resolve the hlsearch overlay against whatever buffer just became active. Called from every path that swaps the document under the active pane.

Search state splits into two halves that are owned differently, and conflating them produced two user-visible bugs at once:

  • last_search — the PATTERN — is vim’s / register: session state. It survives :e, :bn, a tab switch, everything. The fresh-open path used to null it, so n in a just-opened file reported E35: no previous regular expression and kept doing so in every buffer thereafter.
  • all_matches / current_match — the resolved BYTE RANGES — are inherently per-buffer. Carrying them across a swap paints one file’s offsets onto another file’s text (“hlsearch bleeds across tabs”), and clearing them without recomputing silently drops the highlight in a buffer that does have matches.

So: keep the pattern, re-derive the ranges. Vim’s hlsearch is on in every window — each one highlights its own occurrences of the one pattern — and that is what this reproduces.

current_match (the primary highlight, vim’s CurSearch) is the match the cursor sits on. It belongs to the buffer being left, so it clears; n in the new buffer repopulates it.

Lives on the swap seam rather than at each call site on purpose: three swap paths (do_edit, activate_document, sync_active_document_to_pane) each hand-maintained this state and each got it wrong in a different way. load_active_pane is the tail of the pane / tab / close paths and of activate_document, so one call there plus one in do_edit covers the set.

Source

pub fn do_search_word_under_cursor(&mut self, direction: SearchDirection)

* / # – extract the word at the cursor, store as last_search, jump to the next (or previous) occurrence.

T-search-1 (2026-05-28): reads from active_text() (the SyntheticDoc rope when terminal-Normal, document rope otherwise) so the word extraction + search both run against the cursor’s actual buffer. Before T-search-1 the call always read self.document.* which gave nonsense (or empty) results on terminal panes.

Source

pub fn search_word_under_cursor( &mut self, direction: SearchDirection, whole: bool, )

VM.3d-3: Self::do_search_word_under_cursor with vim’s g* / g# rule as a parameter — whole false drops the word boundaries, so the search also finds the word inside longer ones.

Source§

impl Editor

5.5.G.9: pure-editor paste cluster (p / P / bracketed-paste).

Source

pub fn do_paste_text(&mut self, text: &str)

Bracketed-paste handler. Routes the payload to cursor / command line / search line based on the current modal. CB.3 follow-up: terminal buffers route through [do_terminal_input] (same path as p/P in run_terminal_invocation) instead of inserting into self.document — the paste belongs on the PTY, not the SyntheticDoc or the active document. This is the external/OS bracketed-paste peer of CB.3’s p/P register paste.

Source

pub fn do_paste_from_clipboard(&mut self)

Paste the system clipboard’s text at the cursor — the GUI peer’s Cmd+V.

The TUI never needs this. A terminal delivers its own paste shortcut as a bracketed-paste burst (Event::Paste), already carrying the text. A GUI toolkit has no such event: the shortcut arrives as an ordinary key and the application is expected to read the clipboard itself. So the two renderers reach Self::do_paste_text by different routes, and GPUI had no route at all — Cmd+V into :, /, a prompt or Insert mode did nothing there.

Distinct from p / P, deliberately. Register paste applies vim’s charwise/linewise placement; a GUI paste inserts literally at the cursor, which is what every other application does with that shortcut and therefore what the muscle memory expects.

ClipboardHandle::read may block and is documented “only called from a spawn_blocking context”. This runs on the editor actor — the same thread read_register already reads it on for "+p — never the renderer’s. That is the whole reason this is a host method rather than a clipboard read in on_key_down.

Deliberately NOT gated on the clipboard option. That option governs whether yank and put reach for the system clipboard implicitly; Cmd+V is the user asking for it explicitly, and refusing an explicit gesture because an implicit-behaviour toggle is off would read as the key being broken.

Source

pub fn do_paste(&mut self, before: bool)

Vim’s p / P – paste from the chosen register (pending_register if set, else unnamed). Charwise splices at cursor; linewise inserts a fresh line; blockwise paints columns down consecutive lines.

Source

pub fn do_paste_blockwise(&mut self, content: &str, before: bool)

Vim’s blockwise paste: each \n-separated row inserted on consecutive lines at the same column. Rows below the buffer extend it with new lines.

Source

pub fn read_register( &self, register: Option<Register>, ) -> Option<UnnamedRegister>

Read the register slot for paste / inspection. Falls back to unnamed_register.

CB.1 (clipboard.md §5): for the unnamed register under clipboard=true, and always for Register::System (+/*), prefers a live clipboard read over the in-memory entry, falling back to it when the clipboard is empty or unavailable — so paste picks up text copied in another app.

CB.2 obligation: read is called synchronously here, and dispatch (including do_paste) is a blocking RPC from the render thread into the actor (input-pipeline.md), so a slow backend read would sit on that path and violate paramount #1. This is correct today because the registered backend is lattice_core::FakeClipboard (in-memory, instant). CB.2, when it installs the real arboard/OSC52 backend, MUST either guarantee this call stays sub-frame (e.g. a bounded-wait cached read) or move it off the synchronous path — do not carry a blocking OS round-trip into this call site unexamined.

Source§

impl Editor

5.5.SNIPPET.1: pure-editor snippet expansion.

SN.3c.1 (2026-06-14): the chord trigger + word-prefix scan moved to snippet-mode (lattice-snippet); the host keeps only the expansion mechanics as Editor::expand_snippet_from_range (language detection + registry lookup + variable render + splice), invoked by the Effect::ExpandSnippet { replace_range } arm. The old do_snippet_expand_at_cursor (which also did the scan) is gone (feedback_mode_owns_its_surface).

SN.2b (2026-06-12): <Tab> / <S-Tab> placeholder navigation (do_snippet_next/prev_placeholder + move_cursor_to_snippet_group) relocated to active-snippet-mode in lattice-snippet — the mode that owns the chords now owns the handler bodies (feedback_mode_owns_its_surface).

Source

pub fn active_language_id(&self) -> String

Active buffer’s snippet language id. Maps the active document’s filename extension to a language string the snippet registry indexes by (e.g. "rs" -> "rust"). Falls back to the empty string when no path is set (the registry’s "*" any-language pack still applies).

Source

pub fn snippet_variable_context(&self) -> VariableContext

Build a VariableContext for snippet expansion from the active buffer / cursor / clipboard / etc. Powers $TM_FILENAME, $TM_CURRENT_LINE, etc.

Source

pub fn expand_snippet(&mut self, body: &SnippetBody, anchor: Position)

Expand a parsed snippet body at the popup’s anchor. Renders the body (variables resolved against the active buffer’s context), splices the resulting text over [anchor, cursor], sets up an ActiveSnippet, and moves the cursor to the first tabstop’s range. Pure-literal snippets (no tabstops) skip the active-snippet step and just leave the cursor at end-of-insert.

Source

pub fn expand_snippet_from_range(&mut self, replace_range: Range)

SN.3c.1: host-owned snippet resolution + expansion for a known trigger range. The mode-owned <C-x><C-s> handler (snippet-mode’s action_handlers()) does the word-prefix scan and emits Effect::ExpandSnippet { replace_range }; this host arm owns the expansion mechanics: prefix = the buffer text inside replace_range; language = active_language_id (host-owned path→language detection — duplicating it in the mode would be worse, so the mode does NOT resolve the snippet); lookup (language, prefix) then the "*" bucket; render with the host-owned VariableContext; reuse expand_snippet for the splice + session + cursor. The range end IS the cursor (the handler emits token-start..cursor and the cursor hasn’t moved), so expand_snippet’s anchor..cursor replace is correct. Graceful: no prefix / no matching snippet → quiet info echo.

Source§

impl Editor

5.5.G.7: pure-editor tag-stack / mark-jump / popup-back-stack / jump-history cluster. With seed_help_metadata_locals and pop_popup_back migrated, the entire jump-history walk runs host-side.

Source

pub fn seed_help_metadata_locals( &mut self, buffer_id: BufferId, metadata: HelpMetadata, )

Seed parsed link / anchor / highlight metadata into the help buffer’s locals. Used by pop_popup_back to restore a snapshot’s metadata; also used at popup-open time so link-follow / search-anchor lookups read the right tables.

Source

pub fn pop_popup_back(&mut self) -> bool

Restore the most recent snapshot from popup_back_stack into the active popup. Returns true if a frame was popped and applied; false when the stack was empty.

Source

pub fn do_tag_stack_pop(&mut self)

<C-t> – pop the tag stack (vim’s :pop). LIFO walk back through the chain of gd / gD / gy / gI drill-downs.

Source

pub fn do_jump_mark(&mut self, name: char, exact: bool)

Jump to a recorded mark ('<letter> / `<letter>). exact = true puts the cursor at the stored byte; exact = false jumps to the line and column = first non-blank.

Source

pub fn do_jump_history(&mut self, delta: i32)

<C-o> / <C-i> – walk the jump-history ring filtered to jump-class entries. In a help-popup that has its own back-stack, the first <C-o> pops the popup back-stack (popup-internal walk); only after the stack is empty does it fall through to the position-history walk.

Source§

impl Editor

5.5.G.6: pure-editor g; / g, mark-history walk. <C-o> / <C-i> jump-history walk stays App-side until pop_popup_back (App) migrates.

Source

pub fn do_mark_history(&mut self, delta: i32)

g; / g, per §5.1.1 – step through NamedMark entries in the position-history ring. No “snapshot current pos” pre-step: mark navigation is exploratory and shouldn’t pollute the jump list with AutoJump entries.

Source

pub fn do_walk_history<F: Fn(&PositionEntry) -> bool>( &mut self, delta: i32, pred: F, empty_label: &str, bound_label: &str, )

Generic walk over the unified position-history ring filtered by pred. Used by both :do_jump_history (App, via <C-o> / <C-i> — filters e.is_jump()) and :do_mark_history (host, via g; / g, — filters e.is_named_mark()).

Source§

impl Editor

5.5.G.5: pure-editor pane navigation. Action::SplitPaneHorizontal / SplitPaneVertical / ClosePane / NavigatePane / NextPane / PrevPane. Bodies were already 100% editor.pane_tree + snapshot_active_pane / load_active_pane (host-side since F.4.1) reads/writes.

Source

pub fn do_split_pane(&mut self, orientation: SplitOrientation)

<C-w>s (horizontal) / <C-w>v (vertical) – split the active pane; the new sibling inherits cursor + scroll.

Source

pub fn do_next_tab(&mut self)

gt — switch to the next tab (wrapping). No-op when only one tab.

Source

pub fn do_prev_tab(&mut self)

gT — switch to the previous tab (wrapping).

Source

pub fn do_goto_tab(&mut self, n: u32)

{N}gt — go to tab N (1-indexed). Clamped to 1..=tabs.len(). n == 0 is treated as “next” per vim’s quirky default.

Source

pub fn do_terminal_spawn( &mut self, cmd_line: Option<String>, env: Vec<(String, String)>, cwd: Option<PathBuf>, )

:tabnew — open a new tab containing a single scratch pane viewing a fresh empty buffer. The new tab is inserted right AFTER the current tab and becomes active. Issue #40 / Terminal-mode T1 (2026-05-22): spawn a PTY-backed shell + activate as a new buffer.

cmd_line = None → user’s $SHELL (or /bin/sh). Some("cargo test") → tokenized on whitespace and exec’d as program + args. T4 will respect a terminal.shell typed option; T1 uses sensible defaults.

PR.3: the spawn cwd is the active buffer’s project root (docs/dev/architecture/project-resolution.md), not the active file’s directory as it was through T1–T4.

Side effects:

  • Insert a new BufferEntry { kind: Terminal, data: Terminal(TerminalBuffer) } into the buffer registry with a fresh id.
  • Activate the new buffer in the active pane (T4 may honor terminal.display).
  • On spawn failure: echo an error; no buffer created. cwd overrides PR.3’s project-root default for this spawn only — PC.2. None is the ordinary :terminal, unchanged.
Source

pub fn do_terminal_input(&mut self, bytes: &[u8])

Terminal-mode T2.a: write encoded bytes to the active Terminal buffer’s PTY stdin. Handler for Action::TerminalInput. No-op when the active buffer isn’t a Terminal (defensive — the translate layer only emits TerminalInput while a Terminal is focused, but the handler stays safe under cross-pane dispatch races).

Source

pub fn do_enter_terminal_insert(&mut self)

Terminal-mode T2.a / T3 (2026-05-25): activate terminal-insert-mode on the active Terminal buffer. Handler for Action::EnterTerminalInsert. No-op if the active buffer is not a Terminal.

T3 addition: snap the scrollback viewport to the live edge before activating the minor mode. Matches vim’s :terminal behaviour — entering Insert while scrolled into history restores live-edge focus instead of leaving the user typing at the PTY prompt while looking at stale rows. Cheap call (one alacritty scroll_display(Bottom) + one snapshot republish); no-op when already at the live edge.

Source

pub fn do_exit_terminal_insert(&mut self)

Terminal-mode T2.a: deactivate terminal-insert-mode on the active Terminal buffer. Handler for Action::ExitTerminalInsert. No-op if the active buffer is not a Terminal or the mode isn’t active.

Source

pub fn do_terminal_arm_exit_chord(&mut self)

T2.c (2026-05-25): handler for Action::TerminalArmExitChord. Sets the buffer’s insert_exit_pending flag so the next keystroke goes through the chord-resolution branch in translate. No-op when the active buffer is not a Terminal.

Source

pub fn do_terminal_scroll(&mut self, kind: TerminalScrollKind)

Terminal-mode T3 (2026-05-25): re-position the scrollback viewport on the active Terminal buffer. Handler for Action::TerminalScroll. Routes through SharedTerm::scroll, which locks the alacritty Term, calls scroll_display, and republishes a fresh snapshot so the renderer paints history without waiting for the next PTY byte. No-op when the active buffer is not a Terminal or the buffer registry doesn’t know it.

Source

pub fn do_new_tab(&mut self)

Source

pub fn do_move_pane_to_new_tab(&mut self)

T4 (2026-05-25): <C-w>T — move the active pane to a fresh tab. Closes the pane in the current tab (only when there’s >1 pane — otherwise the buffer is already alone in a tab and the operation is a no-op echo) and opens a new tab containing the same buffer at the same cursor / scroll position.

Source

pub fn do_new_tab_at(&mut self, path: PathBuf)

:tabnew <path> — open path in a new tab. Creates the new tab first (so the file open lands in it), then delegates to do_edit which handles open-or-create and LSP attach signals.

Source

pub fn do_close_tab(&mut self)

:tabclose — close the active tab. No-op when only one tab is open (the editor is never tab-less, mirroring vim).

Source

pub fn do_only_tab(&mut self)

:tabonly — close every tab except the active one. No-op when only one tab is open.

Source

pub fn do_move_tab(&mut self, n: u32)

:tabmove [N] — move the active tab to position N (1-indexed). n == 0 or n > tabs.len() clamps to the last position. The live pane_tree stays put — only the tabs vec is reordered, and active_tab is updated to the new position so pane_tree keeps tracking it.

Source

pub fn do_close_pane(&mut self)

<C-w>c – close the active pane; the first surviving pane becomes active. No-op when only one pane is open.

Source

pub fn do_only_pane(&mut self)

<C-w>o / :only / emacs C-x 1 – close every pane except the active one, collapsing the tree to the active leaf. No-op (with a warning) when only one pane is open. Mirrors Self::do_only_tab for the pane axis. S3b (2026-06-22).

Source

pub fn do_toggle_zoom_pane(&mut self)

ZP.2: <C-w>z / <C-w><C-z> / :zoom-pane – toggle tmux-style zoom on the active pane.

No snapshot_active_pane / load_active_pane pair, unlike its neighbours here: zoom changes only which rect the pane is painted into. The active pane does not change, no leaf is created or dropped, and no buffer is swapped – so there is nothing to snapshot out of or re-hydrate into. The tail of dispatch publishes the new tree and the renderer re-lays out from it.

Source

pub fn do_quit(&mut self, force: bool, scope: QuitScope)

:q[uit] (scope = Pane) / :qa[ll] (scope = All) – quit.

Pane is vim’s :q: close the active pane when there’s more than one open; else, if other tabs remain, close the current tab; only on the very last pane of the last tab does it run the dirty guard and shut down the editor. All is vim’s :qa: ignore pane / tab count and shut the editor outright. Both share the dirty guard + shutdown; they differ only in the pane/tab close short-circuits, gated on scope.

force (!) bypasses the dirty guard. Publishes Event::BeforeQuit for observability when the editor actually quits. Phase 5.8.AC.1: hoisted from TUI App; S3a (2026-06-22): scope parameter for :qa.

Source

pub fn do_write(&mut self, path: Option<PathBuf>)

:w[rite] [path] – save the active buffer to disk. Oil buffers route through OilBuffer::apply (diff-and-apply filesystem ops); document buffers route through save_blocking / save_as_blocking against the document actor. Phase 5.8.AD.3: hoisted from TUI App.

Source

pub fn save_blocking(&mut self) -> Result<PathBuf, RuntimeError>

Synchronous save of the active document. Phase 5.8.AD.3: hoisted from TUI App with the full LSP fan-out chain (BeforeSave / willSave / willSaveWaitUntil / didSave / didCreateFiles) intact.

Source

pub fn write_copy_blocking(&self, path: &Path) -> Result<(), RuntimeError>

:w <path> – save the active document under a new path. Phase 5.8.AD.3: hoisted from TUI App. Write the active document’s text to path without adopting it.

Vim’s :w {file}: a copy comes out, the buffer keeps its own name, its dirty flag and its undo history. Self::save_as_blocking is the other verb (:saveas) and the one this used to be confused with.

No BeforeSave / DocumentSaved, deliberately. Those say “this document reached disk”, which is what marks it clean and what LSP didSave reports. Neither is true of a copy — the buffer still has unsaved changes against its OWN file, and telling subscribers otherwise is how a dirty buffer starts looking saved.

No actor round-trip either: a copy mutates no document state, so the snapshot the host already holds is the whole input.

Source

pub fn save_as_blocking(&self, path: PathBuf) -> Result<(), RuntimeError>

Source

pub fn do_lsp_code_action_request(&mut self)

:code-actions (Phase 4.3). Run textDocument/codeAction at the cursor (or active Visual selection); the drain opens the merged item list as a vertico picker. Phase 5.8.AD.2.

Source

pub fn do_format_request(&mut self)

:lsp-format / :lsp-format-range. Fires textDocument/formatting or textDocument/rangeFormatting on the first server advertising the matching provider; the drain applies the returned TextEdits as one undo unit. Phase 5.8.AD.2. IN.8b: :format — format the buffer through the availability cascade.

an attached server advertises formatting  → textDocument/formatting
formatprg set, or a default-table entry   → external process
neither                                   → say what was tried

Cascades on availability, not on failure. “Try LSP, fall back if it fails” needs an answer to “how long do we wait before giving up”, and every answer to that is arbitrary — too short and a busy server loses to a formatter the user did not choose, too long and :format hangs. Availability has no timing question: if a server that formats is attached, it is the formatter, and its failures are reported as its own.

The generic name satisfies the dashed-and-namespaced ex-command rule rather than violating it. That rule exists because a generic name implies “works regardless of LSP” while being hard-wired to an LSP-only path; :format is the LSP-independent cascade, and :lsp-format remains the LSP-only command, unchanged.

Source

pub fn drain_pending_external_format(&mut self) -> bool

IN.8b: apply an external formatter’s result once it lands.

Returns whether an outcome was consumed. The bool exists for tests: without it there is no way to distinguish “the result has not arrived yet” from “it arrived and changed nothing”, and a test that cannot tell those apart passes on a version that never delivers.

Source

pub fn do_lsp_format_request(&mut self, is_range: bool)

Source

pub fn do_lsp_format_line_range(&mut self, start_line: u32, end_line: u32)

RF.5b: textDocument/rangeFormatting over an explicit inclusive line span.

The seam g= and a delegating = / gq need: Self::do_lsp_format_request derives its range from the visual selection, which an operator’s {motion} range is not.

Source

pub fn do_lsp_rename_request(&mut self, new_name: &str)

:lsp-rename <new> (Phase 4.3). Pre-cancels any in-flight rename, runs prepareRename when supported, then fires the rename request. The drain applies the returned WorkspaceEdit per-file. Phase 5.8.AD.2.

Source

pub fn do_lsp_call_hierarchy_request(&mut self, outgoing: bool)

4.5.a: :lsp-incoming-calls / :lsp-outgoing-calls. Prepares call-hierarchy items at the cursor, then fans out the chosen direction. Picker rows reuse the SymbolsOutcome plumbing. Phase 5.8.AD.2.

Source

pub fn do_lsp_type_hierarchy_request(&mut self, subtypes: bool)

4.5.b: :lsp-supertypes / :lsp-subtypes. Same shape as the call-hierarchy peer but for type relationships. Phase 5.8.AD.2.

Source

pub fn do_lsp_moniker_request(&mut self)

4.5.g: :lsp-moniker. Fires textDocument/moniker; the response is folded into a one-line summary echoed to the minibuffer. Phase 5.8.AD.2.

Source

pub fn snapshot_current_popup(&self) -> Option<PopupSnapshot>

Snapshot the current popup’s content + cursor + metadata for <C-o> walk-back. Phase 5.8.AE.

Source

pub fn swap_popup_content( &mut self, content: HelpContent, placement: PopupPlacement, )

Swap content into the existing popup buffer in place. Phase 5.8.AE.

Source

pub fn open_help_in_pane( &mut self, content: HelpContent, ) -> (BufferId, Vec<RendererSignal>)

Adopt a freshly-built help buffer as the active view in the active pane. Phase 5.8.AE: hoisted from TUI App. Returns mode-activate signals.

Source

pub fn open_help_in_split( &mut self, content: HelpContent, orientation: SplitOrientation, ) -> (BufferId, Vec<RendererSignal>)

Open content in a fresh split alongside the active pane. Phase 5.8.AE.

Source

pub fn open_popup( &mut self, content: HelpContent, placement: PopupPlacement, ) -> Vec<RendererSignal>

Open a popup with content as its body at placement. Captures pre-popup state for clean dismiss. Phase 5.8.AE: hoisted from TUI App.

Source

pub fn open_popup_buffer( &mut self, buffer: BufferId, placement: PopupPlacement, focus: PopupFocus, ) -> Vec<RendererSignal>

Show an already-registered buffer in a popup overlay at placement with the given focus — the content-agnostic popup primitive (popup-api.md §4.2). It owns no content: the caller registers the buffer (help, permission menu, code-action list) and hands over its id, having stashed the initial view in popup_cursor/popup_scroll.

Steal (State B) moves focus into the buffer — it becomes active, adopts popup_cursor/popup_scroll, and modal resets to Normal, with the prior pane + modal snapshotted so dismiss_popup restores them. Passive (State A) floats it — the underlying buffer keeps focus, caret, and modal, and nothing is captured. PU-A.1b.

Source

pub fn open_popup_named( &mut self, name: &str, mode_id: &str, placement: PopupPlacement, focus: PopupFocus, ) -> Vec<RendererSignal>

PU-B.2: the Effect::OpenPopup { name, mode_id, .. } entry point — idempotently ensure a popup buffer named name under major mode mode_id (stored as BufferData::Help so the popup renderer draws it), then show it via Self::open_popup_buffer. Name-based (not id-based) because the emitters — the :ai-permission ex-command and the async tick callback — have no &mut Editor/services to register a buffer and hand back its id; naming keeps the effect vocabulary the host boundary, exactly like Effect::OpenSyntheticBuffer.

Source

pub fn open_floating_popup( &mut self, content: HelpContent, placement: PopupPlacement, ) -> Vec<RendererSignal>

Open content as a floating popup over the active document. State A semantics (doc keeps focus). Phase 5.8.AE.

Source

pub fn absorb_async_display_signals( &mut self, signals: Vec<RendererSignal>, ) -> Vec<RendererSignal>

Route a HelpContent to the resolved [BufferDisplay] surface for category. Phase 5.8.AE: hoisted from TUI App. AW.4: host-apply the popup-opening (DisplayBuffer) signals produced by an async drain (hover / signature-help / async picker) so popup_buffer (and the pane tree, for pane/split displays) becomes host state — both peers then render the popup from the published RenderState. Returns the remaining renderer-coupled signals to forward on the actor’s signal channel. Called from the editor actor’s async_landed arm, whose signals the TUI peer never consumes; leaving a DisplayBuffer there drops the popup (docs/dev/architecture/lsp-architecture.md §12, AW.4). Idempotent with the renderer-side handler: display_buffer returns only mode-activation / theme cascade signals, never a further DisplayBuffer, so there is no recursion.

Source

pub fn display_buffer( &mut self, content: HelpContent, category: BufferDisplayCategory, ) -> (Option<BufferId>, Vec<RendererSignal>)

Source

pub fn resolve_display(&self, category: BufferDisplayCategory) -> BufferDisplay

Resolve a BufferDisplayCategory to a concrete BufferDisplay. Reads the per-category typed option; falls back to the category’s default. Phase 5.8.AD.6: hoisted from TUI App.

Source

pub fn prepare_open_target_pane( &mut self, target: OpenTarget, ) -> Vec<RendererSignal>

Issue #32 (2026-05-22): set up the destination pane for a picker accept according to the chord-supplied target override:

  • Default → use the user’s PickerResult display preference (prepare_pane_for_picker_result).
  • Split → horizontal split, focus on new pane.
  • VSplit → vertical split, focus on new pane.
  • Tab → new tab containing one pane (current buffer); focus on new tab.

Returns renderer signals from the tab-create path (the split paths don’t emit any). Plugin-callable directly via editor.prepare_open_target_pane(target).

Source

pub fn prepare_pane_for_picker_result(&mut self)

Apply the PickerResult display preference to the active pane before a picker jump / buffer-switch runs. Phase 5.8.AD.6.

Source

pub fn jump_to_file_line_col( &mut self, path: &Path, line: u32, col: u32, ) -> Vec<RendererSignal>

Jump to (path, line, col) opening the file if needed. Pushes the pre-jump cursor onto position history. Phase 5.8.AD.6: hoisted from TUI App.

Source

pub fn apply_picker_outcome( &mut self, outcome: PickerAcceptOutcome, ) -> DispatchOutcome

Translate a picker source’s typed outcome into editor state mutation. Phase 5.8.AD.6: hoisted from TUI App.

Source

pub fn do_open_help_topic(&mut self, topic: Option<&str>) -> Vec<RendererSignal>

:help [topic] direct-call entry. Phase 5.8.AD.5.

Source

pub fn do_describe_command( &mut self, name: &str, anchor: Option<&str>, ) -> Vec<RendererSignal>

:describe-command <name> direct-call. Phase 5.8.AD.5.

Source

pub fn do_describe_key(&mut self, chord: &str) -> Vec<RendererSignal>

:describe-key <chord> direct-call. Phase 5.8.AD.5.

Source

pub fn do_open_hover(&mut self, markdown: &str) -> Vec<RendererSignal>

K (LSP hover) response / :hover [markdown]. Phase 5.8.AD.5.

Source

pub fn do_describe_events(&mut self) -> Vec<RendererSignal>

:describe-events direct-call. Phase 5.8.AD.5.

Source

pub fn do_describe_event(&mut self, name: &str) -> Vec<RendererSignal>

:describe-event <name> direct-call. Phase 5.8.AD.5.

Source

pub fn do_describe_mode(&mut self, name: &str) -> Vec<RendererSignal>

:describe-mode <name> direct-call. Phase 5.8.AD.5.

Source

pub fn do_customize(&mut self, name: Option<&str>) -> Vec<RendererSignal>

:customize [name] direct-call. Phase 5.8.AD.5.

Source

pub fn do_customize_edit(&mut self, name: &str)

:customize-edit <name> – prefill the : command line with set NAME=VALUE and switch to Command modal so the user can edit and accept. Used as the follow-handler for help-buffer [label](customize-edit:NAME) links. Hoisted from the TUI app layer in 2026-05-27 so the GPUI peer reaches the same flow.

<CR> on a help-buffer link. Reads the HelpLinks table from the popup buffer’s locals (falling back to the active pane’s id for in-pane help) and dispatches based on the link’s typed target. Echoes “no link under cursor” when nothing is hit.

Hoisted from lattice-ui-tui::app::help::do_help_follow_link in 2026-05-27 so both renderer peers route <CR> through one host-side dispatcher. The help/dashboard link covering pos, if any.

Extracted from Self::do_help_follow_link so the mouse can ask whether there is a link before deciding a click means “follow” rather than “position”. Calling the follow unconditionally would have worked but echoed “no link under cursor” on every click on ordinary text, which is exactly the noise the debug!-vs-info! rule exists to keep out of the echo area.

It is also what keeps the mouse path free of a BufferKind test. A buffer either has link ranges here or it does not: help and dashboard seed HelpLinks at creation, oil and the file tree never do (their <CR> follow is a different gesture over a different table), and a document has none. So “click a link to follow it” is one rule that lands correctly in all four without naming any of them.

Source

pub fn do_lsp_on_type_formatting_request(&mut self, trigger: char)

Insert-mode auto-trigger: fire textDocument/onTypeFormatting for trigger; apply the returned edits via the format drain. Phase 5.8.AD.4.

Source

pub fn snippet_meta_for( &self, candidate: &RenderedCandidate, ) -> Option<SnippetCandidateMeta>

CSM.5: resolve a candidate’s snippet metadata by decoding the payload (snippet name) and looking up in snippet_registry. Phase 5.8.AD.4.

Source

pub fn do_reload_snippets(&mut self)

:reload-snippets (Phase 4.2.g.4) – rebuild the registry (built-ins + user packs) and echo a summary. The previous registry is replaced atomically. User-initiated, so it echoes; read failures surface so a genuine problem doesn’t look like a silent no-op. Phase 5.8.AF.3 / built-ins 2026-06-12.

Source

pub fn load_snippets_at_startup(&mut self)

Load built-ins + user snippet packs into the live registry at startup. Quiet — the user didn’t ask, so it logs (debug / warn) instead of echoing. Production entry points (TUI runtime, GPUI lib) call this once after load_persistent_config. Test Apps that skip it keep an empty registry, so completion tests stay isolated from the built-in snippet set unless they opt in via :reload-snippets. (built-ins 2026-06-13)

Source

pub fn apply_lsp_completion_accept( &mut self, meta: LspCompletionMeta, anchor: Position, )

Apply an LSP completion accept: main textEdit + any additionalTextEdits as one undo unit, position the cursor at the end of the insert, fire the optional LSP command. Phase 5.8.AD.4.

Source

pub fn expand_snippet_with_lsp_edits( &mut self, body: &SnippetBody, anchor: Position, additional: Vec<TextEdit>, ) -> Result<(), String>

Expand a snippet body alongside LSP additionalTextEdits as one undo unit. Phase 5.8.AD.4.

Source

pub fn completion_ghost_text_suffix(&self) -> Option<String>

Phase 4.2.g.7 ghost-text suffix. Phase 5.8.AD.4.

Source

pub fn do_completion_accept(&mut self)

Accept the focused completion candidate. Three paths: snippet (sync source / LSP-snippet-format) → expand; LSP plain → apply textEdit + extras; sync → replace. Phase 5.8.AD.4.

Source

pub fn do_completion_accept_then_insert( &mut self, ch: char, out: &mut DispatchOutcome, )

Insert-mode character while the popup is open. Accepts the focused candidate first when ch is a commit char; always inserts ch. Phase 5.8.AD.4.

Source

pub fn do_completion_trigger(&mut self)

<C-Space> – manual trigger / refresh of the insert- completion popup. Phase 5.8.AD.4.

Source

pub fn do_async_insert_completion_requests(&mut self)

<C-Space> (or auto-trigger) – run the async completion fan-out for the live Insert-mode popup: every enabled AsyncCompletionSource the buffer’s active modes contribute, each on its own task, each reporting independently as it lands.

OR.7 generalised this from do_async_insert_completion_requests. It used to look up exactly one source — the one whose id equalled gen:lsp-completion — which meant WASM completion sources were dead on arrival: PH7.6 gave them a WIT export, an actor, a carrier mode and an AsyncCompletionSource adapter, the loader registered them, and nothing ever called generate. The seam was complete except for the one end that drives it.

Independent tasks, not a join. Each source sends its own outcome naming itself, so a fast source’s rows appear without waiting on a slow one, and each round replaces only its own sender’s previous rows. One shared [CancellationToken] retires the whole round when the query moves on.

LSP’s preconditions (mode enabled, a buffer URI, an attached server, a convertible position) now drop LSP from the round rather than the round itself — before, no server meant no plugin completion either.

Source

pub fn lsp_completion_meta_for( &self, candidate: &RenderedCandidate, ) -> Option<LspCompletionMeta>

Decode the LSP completion metadata sidecar from a rendered candidate. None for sync-source candidates (no LSP payload). Phase 5.8.AD.4: hoisted from TUI App.

Source

pub fn docs_body_for_selected(&self) -> Option<String>

Build the docs popup body for the focused candidate from cached metadata. None for sync sources or LSP without pre-resolved documentation. Phase 5.8.AD.4.

Source

pub fn selected_needs_resolve(&self) -> bool

True when the focused candidate is LSP-sourced, has no documentation, and the originating server advertises the resolve provider. Phase 5.8.AD.4.

Source

pub fn do_completion_resolve_focused(&mut self)

Fire completionItem/resolve for the focused candidate (Phase 4.2.g.3). The drain seats the resolved fields into the parallel sidecar + the docs popup body. Phase 5.8.AD.4.

Source

pub fn effective_completion_for( &self, language: &str, ) -> EffectiveCompletionConfig

Effective insert-completion config for language – per-language override → global typed option → spec fallback. Phase 5.8.AD.4: hoisted from TUI App.

Source

pub fn refilter_insert_completion(&self, state: &mut InsertCompletionState)

Re-run matcher + ranker over state.raw against the current state.query. Phase 5.8.AD.4.

Source

pub fn populate_insert_completion_sync( &mut self, state: &mut InsertCompletionState, buffer: &Buffer, trigger: &CompletionTrigger, )

Run sync sources against the supplied state, populating state.raw and re-running matcher + ranker so state.rendered reflects the current query. Phase 5.8.AD.4: hoisted from TUI App.

Source

pub fn do_completion_filter_to_source(&mut self, id: String)

CSM.K2: restrict the open completion popup to a single source. id is the SourceId as a raw string. Phase 5.8.AD.4.

Source

pub fn do_completion_filter_clear(&mut self)

CSM.K2: clear the active source filter. Phase 5.8.AD.4.

Source

pub fn do_completion_next(&mut self)

<C-n> – step forward through the insert-completion popup. Refreshes the docs popup when open. Phase 5.8.AD.4.

Source

pub fn do_completion_prev(&mut self)

<C-p> – step backward. Phase 5.8.AD.4.

Source

pub fn do_completion_toggle_docs(&mut self)

<C-d> (or similar) – toggle the docs side panel for the focused candidate. Closes if open; otherwise builds the body from cached metadata (firing completionItem/resolve when documentation is missing and the server advertises the provider). Phase 5.8.AD.4.

4.5.c: gx – follow the document link under the cursor. Phase 5.8.AD.2.

Source

pub fn do_lsp_code_lens_picker(&mut self)

4.5.d: :lsp-code-lens. Open a picker over the active buffer’s cached lenses. Phase 5.8.AD.2.

Source

pub fn accept_lsp_code_lens(&mut self, index: u32)

4.5.d: accept a code lens by index (the routing payload). Resolves lazily, executes on the originating server. Phase 5.8.AD.2.

Source

pub fn do_lsp_color_presentation(&mut self)

4.5.e: :lsp-color-presentation. Phase 5.8.AD.2.

Source

pub fn accept_lsp_color_presentation(&mut self, index: u32)

4.5.e: accept one color presentation by index. Phase 5.8.AD.2.

Source

pub fn resolve_server_id(&self, name: &str) -> Option<String>

Resolve a user-supplied server name to a canonical server id (running actors → configs → binary-name aliases). Phase 5.8.AD.2: hoisted from TUI App.

Source

pub fn running_server_ids(&self) -> Vec<String>

Distinct server ids of every running actor. Phase 5.8.AD.2.

Source

pub fn snapshot_lsp_instances(&self) -> Vec<LspInstanceRow>

Snapshot of every running LSP actor as picker rows. Phase 5.8.AD.2.

Source

pub fn resolve_lsp_instance_for(&self, server_id: &str) -> InstanceKey

Pick an InstanceKey for server_id: prefer a running actor’s instance, then any known-by-logger instance, then synthesise a cwd-based instance as a last resort. Phase 5.8.AD.2.

Source

pub fn open_lsp_log_in_pane(&mut self, server_id: &str)

Activate the per-instance *lsp:<server>:<workspace>* Document buffer in the active pane. Phase 5.8.AD.2.

Source

pub fn snapshot_ai_sessions(&self) -> Vec<AiSessionRow>

Snapshot the AI subsystem’s known sessions into pure-data picker rows. Reads the boot-registered [AiLogger] service (no Editor field — the AI crate stays zero-field); returns empty when the subsystem is absent. AI-1b (T12b).

Source

pub fn open_ai_log_in_pane(&mut self, key: &SessionKey)

Activate the per-session *ai:<provider>:<index>* Document buffer in the active pane. The buffer is created lazily (like the LSP logs) — AiLogMode::on_activate seeds it from the AiLogger ring and live-tails via AiLogPushed. AI-1b (T12b).

Source

pub fn open_synthetic_buffer(&mut self, name: &str, mode_id: &str)

Open (or focus) a named synthetic buffer under mode_id – the generic primitive behind Effect::OpenSyntheticBuffer. The emitting mode’s command supplies the name + mode id; the host only runs the generic open, so no provider-specific host method is needed. The mode must be registered at boot. Pre-OC.7a shape, kept so the dozen native emitters that want nothing seeded read as they did.

Source

pub fn open_synthetic_buffer_seeded( &mut self, name: &str, mode_id: &str, content: Option<&str>, cursor: Option<Position>, activate_minor: Option<&str>, )

OC.7a: Self::open_synthetic_buffer plus the three things a PLUGIN mode cannot do for itself.

A native mode fills its buffer from on_activate. The modes WIT seam is declaration-only, so a plugin mode has no such hook — and Effect::ApplyEdit is not a way round it, because that names a buffer-id the open does not hand back. Without these a guest could create a buffer it was then unable to put a single character into.

Order matters and is the same order Effect::OpenSyntheticBufferAt already argues for: seed and activate the minor BEFORE showing the buffer, so the first painted frame is the finished one rather than an empty buffer that fills a tick later — the content-jump the UX contract vetoes.

Source

pub fn do_open_ai_log(&mut self, provider: Option<&str>)

:ai-log [provider] – open the AI log buffer. 0 known sessions echoes a hint; exactly 1 (after the optional provider prefilter) opens directly; >1 raises a picker so the user disambiguates. Mirrors :lsp-server-log’s open_lsp_picker count logic. AI-1b (T12b).

Source

pub fn open_lsp_trace_log_in_pane(&mut self, server_id: &str)

Activate the per-instance trace-log Document buffer. Phase 5.8.AD.2.

Source

pub fn open_lsp_picker( &mut self, title: &str, prefilter: Option<String>, on_accept: PickerAction, )

Build + open an LSP instance picker. Called by :lsp-log, :lsp-server-log, and :lsp-trace-log. Phase 5.8.AD.2.

Source

pub fn do_next_diagnostic(&mut self)

]d / :diag-next / :cnext – move the cursor to the next diagnostic in the active buffer. Wraps to top. Phase 5.8.AF.3.

Source

pub fn do_prev_diagnostic(&mut self)

[d / :diag-prev / :cprev – move the cursor to the previous diagnostic in the active buffer. Wraps to bottom. Phase 5.8.AF.3.

Source

pub fn set_error_list(&mut self, source: ErrorSource, entries: Vec<ErrorEntry>)

CM.2 (2026-07-22): replace the error list. The producer entry point — CM.3’s parser event-drain (and, later, diagnostics / search) calls this with freshly parsed entries. Resets the index to the first entry. EP.1 (2026-08-10): replace source’s slice of the error list for a new run, leaving every other producer’s entries alone. Resets the navigation index.

Source

pub fn do_lsp_diagnostics_to_error_list(&mut self)

EP.4 (2026-08-10): pull the language server’s currently published diagnostics into the error list on demand.

The manual peer of the feed gated by lsp.diagnostics-to-error-list: the pull path when that option is off, and a forced refresh (e.g. after a server restart) when it is on. Both paths call this one function, so a snapshot and a live tick can never diverge.

Writes as a Refresh, not a new run — pulling must not throw a user walking the list back to entry 1 any more than the live feed may.

Echoes the count deliberately: this surfaces what servers have published, which is not a workspace scan (rust-analyzer publishes workspace-wide after a check; others only for open files). An empty result must not read as a clean tree.

Source

pub fn write_error_list( &mut self, source: ErrorSource, write: ErrorWrite, entries: Vec<ErrorEntry>, )

EP.2/EP.3 (2026-08-10): replace source’s slice, honouring the producer’s declared ErrorWrite — new run resets the index, live refresh re-anchors it.

Source

pub fn error_list(&self) -> &ErrorList

CM.2 (2026-07-22): read-only accessor for the error list.

Source

pub fn do_error_nav(&mut self, target: ErrorTarget)

CM.2 (2026-07-22): generic error navigation — the single host method behind :cnext/:cprev/:cc/:cfirst/:clast and the Builtin ]q/[q chords.

Resolves target against the core error list. When the list is empty, Next/Prev fall back to today’s active-buffer diagnostic hopping (]d/[d semantics), so the repointed :cnext/:cn/:cprev/:cp aliases preserve their old behaviour until a producer populates the list; Jump/ First/Last echo “no error list”. On a resolved entry it calls Self::jump_to_file_line_col (which records the hop in position history) and echoes "(i/n) message" like Self::do_next_diagnostic.

Source

pub fn do_open_lsp_log(&mut self, server_id: Option<&str>)

:lsp-log [server] – open the *lsp* subsystem buffer (no arg) or a per-server picker. Phase 5.8.AD.2.

Source

pub fn do_open_lsp_trace_log(&mut self, server_id: Option<&str>)

:lsp-trace-log [server]. Phase 5.8.AD.2.

Source

pub fn do_lsp_server_log_listing(&mut self)

:lsp-server-log – picker over every running LSP actor. Phase 5.8.AD.2.

Source

pub fn do_toggle_lsp_trace(&mut self, name: &str)

:lsp-trace <name> – toggle JSON-RPC trace for the server. Phase 5.8.AD.2.

Source

pub fn do_lsp_status(&mut self) -> Vec<RendererSignal>

:lsp-status – render every running server in a help- style buffer. Phase 5.8.AD.2: hoisted from TUI App. Returns a RendererSignal::DisplayBuffer for the renderer to surface (popup / pane / split per the user’s typed option).

Source

pub fn do_lsp_progress_cancel(&mut self, server_id: Option<&str>)

:lsp-progress-cancel [server] – send window/workDoneProgress/cancel for every cancellable active progress entry (4.4.c). With server_id == Some, cancel only entries on that server; with None, cancel across every server attached to the current buffer. Phase 5.8.AD.2: hoisted from TUI App.

Source

pub fn do_set_lsp_log_level(&mut self, server_id: Option<&str>, level: &str)

:lsp-log-level [server] <level> – set the subsystem default min level (when no server) or a per-server override. Phase 5.8.AD.2.

Source

pub fn do_lsp_log_clear(&mut self, server_id: Option<&str>)

:lsp-log-clear [server] – drop ring contents. Phase 5.8.AD.2.

Source

pub fn do_lsp_restart(&mut self, server_id: &str)

:lsp-restart <server> – supervisor restart hook. Drives the supervisor mailbox; the call is async and we don’t have an executor on the UI thread, so we spawn into the LSP runtime. Success / error reports flow back through the per-server log ring. Phase 5.8.AD.2: hoisted from TUI App.

Source

pub fn do_lsp_expand_region(&mut self)

4.4.e: :lsp-expand-region – step outward through the cached selection-range chain. If a cached chain still applies (cursor inside its innermost range AND same buffer), step the index outward and apply the new selection. Otherwise fire textDocument/selectionRange and let the drain seat the chain + apply step 0 on completion. Phase 5.8.AD.2: hoisted from TUI App.

Source

pub fn do_lsp_shrink_region(&mut self)

4.4.e: :lsp-shrink-region – step inward inside the cached chain. With no cache (e.g. user invoked shrink first), echo + bail. Phase 5.8.AD.2: hoisted from TUI App.

Source

pub fn apply_lsp_completion_item(&mut self, item: &CompletionItemRow)

Splice a chosen completion item into the buffer at its captured replace range. Plain text only – snippet expansion lands with the buffer-level Insert-mode completion shell. Phase 5.8.AF: hoisted from TUI App.

Source

pub fn finalize_show_message_request( &mut self, request_id: u32, selected_index: Option<u32>, )

Send the LSP response for one in-flight showMessageRequest. Phase 5.8.AF: hoisted from TUI App.

Source

pub fn open_next_queued_show_message_request(&mut self)

Advance the SMR queue. Phase 5.8.AF: hoisted.

Source

pub fn persist_picker_mru_best_effort(&self)

Write the MRU index to its configured path. Best-effort: disabled / fail logs once and continues. Phase 5.8.AF. Slice 3c.final.E.5e: made pub so the renderer’s App::persist_picker_mru_best_effort delegate can route here through read_editor.

Source

pub fn do_picker_dismiss(&mut self) -> Vec<RendererSignal>

Full Action::PickerDismiss. Phase 5.8.AF: full body (SMR queue advance, event-bus publish, preview-origin restore) consolidated host-side.

Source

pub fn transient_open_context(&self, args: Args) -> TransientContext

MG.23h: where a transient is being opened from, for TransientSourceRegistry::build.

A generic host primitive rather than anything magit-shaped: it reports the two mode axes of the buffer that is active right now, which is the question :if-mode and :if-derived ask in Emacs and the question any context-varying menu will ask here. Deliberately not the cursor or the selection — a builder produces rows, and the row’s own action already receives those at fire time, when they are current.

Empty when the active buffer has no ActiveModes entry yet (mid-boot), which degrades to the ungated menu — the same direction keymap_gated_ids degrades in.

Source

pub fn open_named_transient( &mut self, source: String, args: Args, ) -> Vec<RendererSignal>

Open the transient menu registered under source — the whole body of Effect::OpenTransient, hoisted here so both renderer peers are one call and cannot drift.

A native builder answers in this frame and the menu seats immediately. A guest-backed one (TR.2) answers a future: it is spawned on the plugin runtime and parked in pending_transient_build, then seated by drain_pending_transient_build off the async-landed wake — NOT off the next keystroke, which is what the async_landed.notify_one() below buys.

Source

pub fn drain_pending_transient_build(&mut self) -> Vec<RendererSignal>

TR.2: seat a guest-backed menu whose build has landed.

Mirrors drain_pending_picker_init: non-blocking try_recv, polled off the async-landed wake. A guest err is echoed WITH the source name and the menu stays closed — a menu that opens empty is worse than one that says why it did not.

Source

pub fn open_transient(&mut self, spec: TransientSpec) -> Vec<RendererSignal>

PICK.1: open a transient menu. Seats a picker in transient mode with the given spec, state, and preview function.

Source

pub fn close_transient(&mut self) -> Vec<RendererSignal>

PICK.1: close the transient and dismiss the picker.

Source

pub fn do_transient_trigger(&mut self, key: String, out: &mut DispatchOutcome)

PICK.1: trigger a transient item by key. Searches the current transient spec’s groups for an item whose key matches.

Bug fix: the fired handler’s Effect must reach the CALLER’s real DispatchOutcome, not a throwaway local one. apply_effect_host itself already does the right thing — it runs handle_effect (host-side state mutation) AND pushes the non-None effect onto the out: &mut DispatchOutcome it’s given, precisely so renderer-coupled effects (Effect:: OpenSyntheticBuffer — used by EVERY “open the X buffer” magit action — Confirm, OpenTransient, OpenPrompt, …) reach each renderer’s own apply_effect_app_arms, which drains DispatchOutcome.effects after dispatch returns (see that field’s doc comment) — handle_effect alone doesn’t know about them. The previous version of this function built its own short-lived DispatchOutcome::default(), called apply_effect_host on THAT, and only copied out renderer_signals — silently discarding the effects it had just been correctly given, with no path back to the caller. Now this function takes out: &mut DispatchOutcome directly, so apply_effect_host(self, effect, out) populates the real thing. For a host-appliable effect (Effect::Echo) the bug was invisible (handle_effect alone was enough). Symptom: EVERY transient item whose action opens a buffer (log, branch, stash, rebase, commit, status, file-diff) visibly did nothing but dismiss the menu, while Effect::Echo- returning items (pull/push’s optimistic message, discard’s confirm) appeared to work — because they never needed the renderer-side arm in the first place, masking the bug.

Source

pub fn seed_confirm_args( &self, schema: &[ArgSpec], args: &Args, ) -> TransientState

IX.1: spread a confirmed action’s arguments across its schema’s names, ready to seed the confirm dialog’s transient state. Thin wrapper so both renderer peers reach the same projection.

Source

pub fn extend_transient_state(&mut self, seed: TransientState)

IX.1: merge seed into the open transient’s state.

Applied after open_transient, which computes the spec’s own defaults — the seed carries the caller’s real arguments and must win over a default of the same name.

Source

pub fn do_transient_toggle_flag(&mut self, name: String) -> Vec<RendererSignal>

PICK.1: toggle a boolean flag in the transient state by name.

Source

pub fn do_picker_or_transient_accept(&mut self, out: &mut DispatchOutcome)

<CR> in the picker: in transient mode fire whatever <C-n> / <C-p> walked to — by the SELECTED item’s own key, through Self::do_transient_trigger, so submenus / flag toggles / argument prompts behave identically however the item was reached — otherwise the ordinary Self::do_picker_accept.

The single source of truth for both renderers. The GPUI peer’s PickerAccept interceptor used to call do_picker_accept directly, which is a no-op for a transient, so <CR> on a C-n/C-p-selected transient row ran in the TUI (through the dispatch arm) but did nothing in GPUI. Both now route here.

Source

pub fn do_picker_accept(&mut self) -> DispatchOutcome

Full Action::PickerAccept. Phase 5.8.AF: complete body including the trait-driven generator path + MRU recording

  • event-bus publish + legacy routing arms.
Source

pub fn do_open_buffer_picker(&mut self) -> Vec<RendererSignal>

:b no-arg / <leader>b – open the vertico-style buffer switcher. Floats every entry in the registry into the picker; the active buffer is floated to the bottom so the initial preview lands on the alternate buffer. Phase 5.8.AC.1: hoisted from TUI App.

Source

pub fn do_navigate_pane(&mut self, direction: PaneDirection)

Cardinal neighbour walk (<C-w>h/j/k/l).

Source

pub fn buffer_area_rect(&self) -> PaneRect

Build the buffer-area rectangle from terminal_width + viewport_height. Mirrors the App-side helper retired in this slice.

Source

pub fn activate_pane(&mut self, idx: usize)

Make pane idx active, swapping pane stash <-> hot-path cursor / scroll.

Source§

impl Editor

5.5.G.2: pure-editor visual-mode helpers.

Source

pub fn do_enter_visual(&mut self, kind: VisualKind)

v / V / <C-v> from Normal – enter Visual mode at the current cursor, seeding document.selections with a zero-width anchor=head selection so Range::Selection dispatch picks up the cursor immediately.

2026-05-26: Terminal buffers don’t reach this helper — Editor::run_invocation routes Terminal-active invocations through run_terminal_invocation first, and that runner calls do_terminal_enter_visual directly. The earlier if Terminal { ... } branch here is gone; do_enter_visual is now purely the document-side path. [[feedback_buffers_no_special_case]] — dispatch-time branching lives in the per-kind runner pattern, not in generic do_* helpers.

Source

pub fn do_terminal_enter_visual(&mut self, kind: VisualKind)

2026-05-25: terminal-side Visual entry. Mirrors the inline body that previously lived in run_terminal_invocation’s enter_visual_* arm so both the keymap path (Action::Invoke(enter_visual_*) → AppEffect::EnterVisual → Action::EnterVisual) and any future direct callers land on the same t.visual initialiser. Modal stays Normal — the renderer keys off t.visual.is_some() (published as terminal_visual_active) to flip the modeline label and paint per-cell highlights.

Source

pub fn do_exit_visual(&mut self)

<Esc> from Visual (and the post-operator-on-selection path) – capture the current selection as last_visual so gv can restore it, then collapse the selection to a cursor at the current head and drop to Normal.

Source

pub fn do_enter_select(&mut self, kind: VisualKind)

SN.3d Select-mode entry (gh / gH / g<C-h>): anchor a zero-width Select selection of the given kind at the cursor, mirroring Self::do_enter_visual’s fresh-entry path. Typing then overtypes; motions extend. No toggle/switch semantics — the entry chords always fresh-anchor (the Visual↔Select toggle is <C-g> → Self::do_toggle_visual_select); programmatic entry (snippets) sets the selection explicitly and uses EnterMode(Select(k)) instead (select-mode.md §3).

Source

pub fn do_select_overtype(&mut self, c: char)

SN.3d.1 Select-mode overtype: a printable key in Select replaces the whole selection with that char and drops into Insert. The load-bearing new behaviour (select-mode.md §3).

One replace-range edit, NOT delete-then-insert. The char is the replacement text of a single Edit::replace(span → c), so a single u undoes the whole overtype. Two effects (Effect::Many([delete, insert])) would risk two undo units.

Graceful: a degenerate / unresolvable selection range falls back to a plain insert-at-cursor — the keystroke is never dropped and the editor never panics (select-mode.md §9).

Source

pub fn do_exit_select(&mut self)

SN.3d.1 Select-mode <Esc>: capture the selection as last_visual (so gv can restore it), collapse to a cursor at the current head, and drop to Normal. The Select analogue of Self::do_exit_visual; no terminal branch because Select is a document-only state (terminals carry Visual on the buffer).

Source

pub fn do_toggle_visual_select(&mut self)

SN.3d.1 <C-g>: flip Visual(k) ↔ Select(k), preserving the selection geometry. The selection set + visual_anchor are untouched — Select shares Visual’s extent verbatim (select-mode.md §2), so the toggle is a pure modal pivot. One handler serves both directions (the Select <C-g> and the Visual <C-g> entry chord wired in d.2).

Source

pub fn do_reselect_visual(&mut self)

gv – restore the prior selection captured by do_exit_visual, or echo an error if there’s no captured visual to reselect.

Source

pub fn do_swap_visual_ends(&mut self)

Vim’s o in Visual mode: swap the cursor (head) to the other end of the selection. Anchor and head trade places so a following motion / text object grows or shrinks the selection at the end the cursor now sits on.

No-op outside Visual / Select mode, and no-op when visual_anchor is unset – the latter naturally skips buffer kinds that keep their Visual region elsewhere (terminal), so this needs no BufferKind branch. SN.3d: o is bound in Select too (it shares Visual’s selection geometry), so the swap fires for both states.

Source§

impl Editor

Phase 5.5.E.6: host-side option-cascade infrastructure. These methods move the pure-editor half of :set (config write + cache rebuild + cascade dispatch) under Editor; the renderer keeps the renderer-coupled tail wired through RendererSignal (theme rebuild, file-tree rope refresh, mode mirror activate / deactivate, LSP didChangeConfiguration fan-out).

Source

pub fn sync_host_theme_from_config(&mut self)

Sync the host-side renderer-neutral [crate::ui::theme::Theme] (editor.host_theme) from the typed ui.* options in Self::config. Called by the option-cascade for any ui.* key and at App-init time. The renderer half (TUI’s cached Theme mirror rebuild) runs after this returns, driven by RendererSignal::ThemeChanged.

Source

pub fn active_buffer_project(&self) -> Project

Refresh Self::option_cache from the active buffer’s resolved values. Falls back to the registry’s current value when resolved_options doesn’t yet have a cache entry for the active buffer (transient state during boot before the first Self::recompute_options_for_buffer). Cheap: 9 typed reads. PR.2: the project the active buffer belongs to.

The host-side peer of ActionContext::project(), for effect arms and other host code holding an &Editor rather than an action context. A generic primitive, not a per-feature helper: it exposes the registered resolver and branches on nothing.

Total, like the resolver itself. A buffer with no path (scratch, *messages*, a terminal) has no tree to walk, so the working directory stands in; a harness with no resolver registered falls back to the process working directory, which is what every consumer did before PR.2.

Source

pub fn take_pending_structural_section( &mut self, full_path: &str, ) -> Option<Table>

Read a structural section the loader bucketed and remove it from pending_config_structural_sections. Subsequent calls return None. Phase 5.8.AA.u: hoisted from TUI App.

Source

pub fn pending_structural_section_paths(&self, namespace: &str) -> Vec<String>

Iterate the dotted paths of every pending structural section whose path starts with namespace.. Returned as owned strings so the caller can follow up with mutating take_pending_structural_section. Phase 5.8.AA.u.

Source

pub fn apply_persistent_lsp_editor_options(&mut self)

Apply lsp-mode.log-level / legacy lsp.log-level from the cached TOML tree. Phase 5.8.AA.u: hoisted from TUI App (was apply_persistent_lsp_editor_options). The body reads the host-side lsp_config_tree + writes the host-side lsp_logger; only set_message made it App-specific pre-hoist, and that’s host-resident.

Source

pub fn apply_per_language_toml_overrides(&mut self)

Drain every completion.per-language.<lang> structural section the loader bucketed and merge each into self.per_language_completion. Per-key TOML wins over the spec defaults seeded at boot; unset keys leave the default in place. Phase 5.8.AA.u: hoisted from TUI App.

Source

pub fn load_persistent_config( &mut self, workspace_root: Option<&Path>, ) -> Vec<RendererSignal>

Load ~/.editor.config/lattice/lattice.toml (user) and <workspace_root>/.lattice/config.toml (project) in precedence order. Applies scalar overrides to self.config, buckets structural sub-tables into self.pending_config_structural_sections, refreshes the host theme + option cache, applies [lsp] editor options, and emits a single echo summarising loader diagnostics.

Returns the RendererSignals the caller should fan to the renderer (always at least one RendererSignal::ThemeChanged so peers rebuild their typed theme caches).

Phase 5.8.AA.u: hoisted from TUI App. Both renderer peers (TUI + GPUI) reach this through Editor so the persistent config takes effect identically from t=0 in both.

Source

pub fn rebuild_option_cache(&mut self)

Source

pub fn recompute_options_for_buffer(&mut self, buffer: BufferId)

Recompute the resolved-options cache for buffer by stitching every layer of the resolution stack (mode-architecture.md §6.1) and writing the result into Self::resolved_options.

Eager whole-cache recompute (§6.3.1). Called whenever any resolution layer for buffer changes: mode toggle, buffer- local set, modal-state transition, or option write (the cascade in Self::drain_option_changes propagates global :set writes to every buffer’s cache).

Source

pub fn resolved_option<D: OptionDecl>(&self, buffer: BufferId) -> Arc<D::Value> ⓘ
where D::Value: Clone + Send + Sync + 'static,

Read a resolved option’s value for buffer. Returns the option’s bootstrap default if the cache for buffer hasn’t been recomputed yet (transient state during boot before the first Self::recompute_options_for_buffer).

Hot-path read; O(1) TypeId lookup on the cached lattice_config::ResolvedOptions.

Source

pub fn resolved_option_opt<D: OptionDecl>( &self, buffer: BufferId, ) -> Option<Arc<D::Value>>
where D::Value: Clone + Send + Sync + 'static,

Self::resolved_option that degrades to None instead of panicking when the option is not registered.

For the publish path specifically. publish_render_state runs on every keystroke and against the minimal configs Editor::boot builds in tests, and an option that some later slice starts reading there must not turn those into crashes — the same hazard the wrap_reserved_cols comment above avoids by reading the option cache. Command-path callers keep using Self::resolved_option, where an unregistered option is a programming error worth failing loudly on.

Source

pub fn indent_unit(&self, buffer: BufferId) -> IndentUnit

IN.0: one level of indentation for buffer, resolved from shiftwidth / expandtab / tabstop through the buffer-local stack (so :setlocal shiftwidth=2 and a major mode’s Mode::options() contribution both land here).

The single place these three options become one value. Every indent consumer – the > / < operators via GrammarEnv, <C-t> / <C-d>, and the auto-indent surfaces from IN.1 – reads it here rather than resolving the options itself, which is what keeps them from drifting apart.

Source

pub fn native_format_intents(&self, buffer: BufferId) -> NativeFormatIntents

RF.5b: which intents this buffer handles natively, for lattice_grammar::registry::NativeFormatIntents.

Resolved rather than read off the chain’s first rung: a chain of lsp,native on a buffer with no server attached is native, and telling the operator to delegate there would make gq do nothing — the exact silent failure §5 exists to avoid.

Source

pub fn do_format_range( &mut self, intent: FormatIntent, start_line: u32, end_line: u32, )

RF.5b: run intent’s chain over an inclusive line range.

The landing point for AppEffect::FormatRange. Both async paths (LSP, external process) already exist for :format; this gives them a range.

Source

pub fn wrap_width(&self, buffer: BufferId) -> WrapWidth

RF.2: the buffer’s textwidth, resolved through the same buffer-local stack as Self::indent_unit — so :setlocal textwidth=100 moves gq in that buffer and nowhere else.

The option validates to 1..=10_000, so the cast cannot lose a legal value; the max(1) covers a hypothetical out-of-band write rather than trusting the validator from a distance, exactly as indent_unit’s clamp does.

Source

pub fn indent_guide_inputs(&self, buffer: BufferId) -> IndentGuideInputs

IG.2: the indentation-guide inputs for buffer, resolved through the same buffer-local stack as Self::indent_unit.

The version carries only what changes the guides’ geometry – shiftwidth (their spacing) and whether they exist at all. display.indent-guides.char and .active are deliberately absent: the glyph and the active-block highlight are resolved by each renderer at paint, so changing them needs a repaint, not a rebuild of the layer. tabstop is absent because it already bumps the whitespace axis, and one input on two axes is one input that can drift.

Resolved through Self::resolved_option_opt, not Self::resolved_option: this runs on the publish path, where an option a minimal test config never registered must degrade to its default rather than abort the publish. That is the same hazard the wrap_reserved_cols comment in build_pane_cells_inputs names, and the fallbacks here are the values an unconfigured buffer resolves to anyway.

Source

pub fn auto_indent_for_new_line( &self, after_line: u32, moved_tail: Option<&str>, ) -> String

IN.1: the whitespace a line created after after_line should start with, per indentmethod.

moved_tail is the text that will follow the cursor onto the new line (<CR> pressed mid-line); None for o / O, which create an empty line. It only affects the closer check – a } moving down with the cursor should dedent.

The single entry point for predictive indent. o, O and <CR> all route here so they cannot disagree, in the same way Self::indent_unit keeps >> and <C-t> aligned.

Source

pub fn auto_indent_for_split(&self, head: &str, tail: &str) -> String

IN.1: the indent for the lower half of a line split by <CR>.

The upper half is head — the text BEFORE the cursor, which is what actually stays on the line above. Passing the whole line instead is wrong in a way that only shows up mid-line: in foo(a, |b) the whole line is bracket-balanced while the head leaves ( open, so the continuation would fail to indent.

No blank-line walk-back here: when <CR> splits a line, the text above the cursor is the previous line by construction, even when it is empty (cursor at column 0 ⇒ no indent, which is vim’s behaviour — the tail keeps whatever indent it already had).

Source

pub fn strip_pending_auto_indent(&mut self)

Drop auto-inserted indent if the line still holds nothing else.

Called on leaving Insert. Re-checks that the line is genuinely all-whitespace rather than trusting the flag alone: the flag says “we put indent here”, the buffer says whether it is still the only thing there, and the buffer is the authority (a completion, a snippet or a plugin edit may have added content without going through the typing path).

Source

pub fn do_set(&mut self, option: &str) -> Vec<RendererSignal>

Body of :set foo=bar. Parses + applies the spec via the canonical lattice_config::ConfigRegistry cmdline path, drains the cascade so user-visible side effects (recompute folds, theme refresh, …) land before the caller observes the post-set state, and echoes the result. Returns the signal list the cascade enqueued so the renderer can fan out its half (RendererSignal::ThemeChanged etc.).

Source

pub fn do_set_local(&mut self, option: &str) -> Vec<RendererSignal>

:setlocal write path (BL.1). Parses option and writes the result to buffer_local_overrides for the active buffer only, then triggers a per-buffer recompute + cascade.

Handles the full :setlocal surface:

  • name=value / noname / name (bool→true) — write override
  • name? — echo local value or “not set locally (global: X)”
  • name& — clear override for name; bare & clears all
Source

pub fn do_set_global(&mut self, option: &str) -> Vec<RendererSignal>

:setglobal write path (BL.2). Writes only the global config registry; buffer-local override layers are untouched. On name? query, echoes the global registry value (not the resolved per-buffer value).

Source

pub fn drain_option_changes(&mut self) -> Vec<RendererSignal>

Drain queued Event::OptionChanged events from the typed- options bus and apply per-option cascades. Returns the accumulated RendererSignals so renderer-coupled side effects can fan out after every set call.

Cascades re-entrant on themselves: a cascade that writes another typed option (e.g. relativenumber=true implies number=true) queues a new event, and the while let Ok loop picks it up on the next iteration before exiting.

Source§

impl Editor

5.5.F.1: host-side helpers for the :ls / :describe-buffer Effect arms. Each builds a lattice_help::HelpContent from editor.* state and lives next to the cascade so the do_*-style content builders cluster in one module rather than fanning out into per-command files.

Source

pub fn build_list_buffers_content(&self) -> HelpContent

5.5.F.1: build the :ls / :buffers listing content. Mirrors the registry-walk + per-kind formatting that App’s do_list_buffers used; reads lattice_listing::file_tree::modes::FileTreeRoot and lattice_listing::oil::modes::OilDir from buffer_locals so file-tree / oil rows show their root paths.

Source

pub fn build_describe_buffer_content(&self) -> HelpContent

5.5.F.1: build the :describe-buffer content. Mirrors App’s do_describe_buffer; every field read goes through editor.*. The active-mode lines render as [name](mode:name) markdown links so follow-link routes to :describe-mode <name> via the help buffer’s link table.

Source

pub fn build_describe_command_content( &mut self, name: &str, anchor: Option<&str>, ) -> Option<HelpContent>

5.5.F.2: build the :describe-command content.

Two-stage name resolution (crate::excommand::resolve_command_name_or_alias) so both canonical (ex:write) and alias (w, write) spellings resolve to the same spec. Pushes a one-line error message onto the echo ring (and returns None) for unknown names; the caller skips the RendererSignal::DisplayBuffer emit.

Cross-link tail: append See also: [topic](help:topic) rows for every help topic whose related_command_patterns matches this command. Lets a reader of :describe-command operator:fold-create jump to the folding topic via <CR> on the link.

Source

pub fn build_apropos_content(&mut self, pattern: &str) -> Option<HelpContent>

5.5.F.2: build the :apropos <pattern> content. Walks every registered command, matches pattern (case-insensitive) against the canonical name and the doc body, renders a 3-column listing (name kind first-line-of-doc) with command_link wrapping the name for <CR> follow. Empty pattern routes an error to the echo ring and returns None so the renderer skips the display signal.

Source

pub fn build_describe_plugin_api_content( &mut self, seam: Option<&str>, ) -> Option<HelpContent>

PI.2: build the :describe-plugin-api [<seam>] content. With a seam name, render that one interface through the shared Introspectable spine (uniform with :describe-command); without, delegate to the :list-plugin-apis listing. An unknown seam echoes an error + returns None (dispatcher skips the signal). The catalog is derived from wit/ at build time (lattice-plugin-api); the host holds no plugin runtime.

Source

pub fn build_list_plugin_apis_content(&self) -> HelpContent

PI.2: build the :list-plugin-apis content — one row per catalogued interface (name → :describe-plugin-api exec-link, direction, capability, function count, first doc line). Infallible.

Source

pub fn register_plugin_name(&self, id: u32, name: impl Into<String>)

PI.3: record a plugin’s manifest name against its host-issued id, so SourceLayer::Plugin(id) provenance renders as the name (e.g. git-gutter) instead of <plugin:id>. The Phase-8 plugin loader calls this as each plugin loads; today the map starts (and stays) empty.

Source

pub fn register_plugin( &self, id: u32, name: impl Into<String>, doc: impl Into<String>, )

PI.4: record a loaded plugin’s full metadata (name + its own doc). The Phase-8 loader calls this once per plugin at load, resolving doc from the plugin’s embedded WIT world doc-comment (or its manifest doc field) — both immutable at editor runtime, so this is a one-time cache.

Source

pub fn plugin_display_name(&self, id: u32) -> Option<String>

PI.3: the manifest name for a plugin id, if the host knows it. None falls back to <plugin:id> at the display sites.

Source

pub fn plugin_meta(&self, id: u32) -> Option<PluginMeta>

PI.4: the full metadata for a plugin id, if loaded.

id may be any of the plugin’s seam ids, not just its primary one: a binding is stamped with the id of the seam that registered it.

Source

pub fn loaded_plugins(&self) -> Vec<(u32, PluginMeta)>

PI.4: every loaded plugin as (id, meta), sorted by name then id. Empty until the Phase-8 loader populates the registry.

Source

pub fn build_list_commands_content(&self) -> HelpContent

PI.3: build the :list-commands content — every registered command grouped by source layer (built-in / user config / plugin / …), each a :describe-command link. The one introspection enumeration the help family was missing. Infallible.

Source

pub fn build_describe_plugin_content( &mut self, name: &str, ) -> Option<HelpContent>

PI.4: build the :describe-plugin <name> content — one loaded plugin’s own documentation + its contributions, through the shared Introspectable spine (uniform with :describe-command). An unknown / not-loaded name echoes an error + returns None. The loaded-plugin registry is empty until the Phase-8 loader populates it, so today this always echoes.

Source

pub fn build_list_plugins_content(&self) -> HelpContent

PI.4: build the :list-plugins content — every loaded plugin (name → :describe-plugin link + doc summary). Infallible; renders an empty-state line until the Phase-8 loader populates the registry.

Source

pub fn do_export_plugin_api(&mut self, format: Option<&str>)

PI.2b: :export-plugin-api [markdown|json] — dump the whole catalog into a savable text-mode buffer (*plugin-api.md* / *plugin-api.json*) the author saves with :w <path>. Idempotent: re-export overwrites the existing buffer (the OpenSyntheticBuffer open pattern + a full replace). format is pre-validated by the grammar (markdown/md/json).

Source

pub fn build_describe_key_content(&self, chord: &str) -> HelpContent

5.5.F.2: build the :describe-key <chord> content.

Accepts an optional mode prefix (n_j → Normal mode j; i_<C-n> → Insert mode <C-n>; no prefix → all modes). Uses resolve_trace / resolve_trace_all_modes from lattice-keymap to produce a fully-traced, layer-annotated output showing which binding fires now and what is shadowed.

Infallible — an unbound or unparseable chord renders an explanatory message.

Source

pub fn build_list_keymap_content(&self) -> HelpContent

5.5.F.2: build the :list-keymap content. Groups every registered binding (crate::keymap::entries) by mode in a fixed order so the rendered output reads top-down, wraps each chord in a key_link for <CR> follow.

Source

pub fn build_describe_option_content( &mut self, name: &str, ) -> Option<HelpContent>

5.5.F.3: build the :describe-option <name> content. Renders the option’s metadata (canonical name, aliases, type, default, current value, enumerated values) + doc. Unknown name routes a vim-style E518 error to the echo ring and returns None so the dispatcher skips the signal.

Source

pub fn build_describe_element_content( &mut self, name: &str, ) -> Option<HelpContent>

T.9.d: build the :describe-element <name> / :describe-face content. Reads the [ThemeRegistry::describe] snapshot and formats the element’s owner, doc, authoring (reference-form) style spec (palette keys + inherit parent), and the concrete resolved style under the active theme. The introspection counterpart of :describe-option for theme elements (design §8). An unknown element name echoes an error and returns None (the dispatcher then skips the display signal) — never a panic.

Source

pub fn build_list_options_content(&self) -> HelpContent

5.5.F.3: build the :options content. Live reference of every registered customizable option, grouped by lattice_config::OptionGroup. Self-updating: walks the lattice_config::OPTION_DECLS linkme slice, so a new options! { ... } declaration lights up here at the next build with no extra wiring.

Source

pub fn build_describe_option_resolution_content( &mut self, name: &str, ) -> Option<HelpContent>

5.5.F.3: build the :describe-option-resolution <name> content. Walks the §6.1 layer model (modal-state, buffer- local, minors, major, typed-option, default) for the active buffer and marks each layer that contributes the resolved value. Helps debug surprising values where a mode contribution shadows a :set write or vice versa.

Source

pub fn build_describe_events_content(&self) -> HelpContent

5.5.F.3: build the :describe-events content. Walks lattice_protocol::event_registry::EVENT_DESCRIPTORS (the linkme distributed slice every register_event! invocation pushes into); groups rows by source crate so the catalogue is easy to scan.

Source

pub fn snapshot_active_pane(&mut self)

5.5.F.4.1: copy the Editor’s hot-path cursor / scroll into the active pane’s stash. Called before any operation that flips which pane is active.

Unified hot-path: self.cursor and self.scroll are the active buffer’s regardless of kind, so the snapshot reads from there uniformly. Help / file-tree / oil records are also synced into their kind-specific cursor / scroll fields (and the registry copy for help) so the archival state stays current; live state always lives on the hot-path slots.

Source

pub fn snapshot_active_document(&mut self)

5.5.F.4.1: stash the active document’s hot-path mode-state (syntax, last_parsed_text_version, last_synced_syntax_version, folds) into buffer_locals so a subsequent activate_document (same or different id) can restore it. Guarded by active_buffer == Document — when active is file-tree or help, the document’s syntax was already moved into locals on the previous transition; a second snapshot would take() an already-None value and overwrite the entry’s stashed syntax, dropping highlight state.

Source

pub fn load_active_pane(&mut self)

5.5.F.4.1: load the active pane’s stashed cursor / scroll into the hot-path slots. Inverse of Self::snapshot_active_pane. Also restores the help-popup mirror when the active pane is a help buffer pointing at a different popup than the one currently mirrored.

Issue #38 (tab/close axis): re-pointing the pane view and re-pointing self.document at the new pane’s buffer are a PAIR — the active pane renders/edits self.document, so restoring the pane without swapping the document leaves the pane painting the previously-active buffer (“splits/tabs are not independent”). activate_pane originally ran the two as separate calls; do_switch_to_tab / do_close_tab / do_close_pane restored the view but forgot the document swap, so the picker <C-t> / <C-s> / <C-v> targets polluted the original pane on the way back. Folding the sync into the tail here makes the pair atomic: any pane-focus path that restores the view now also follows the document. The sync is a guarded no-op when the pane’s buffer already matches self.document_buffer_id (the common case), so this is free on the hot paths that already agree.

Source

pub fn push_position_history(&mut self, pos: Position, source: PositionSource)

5.5.F.4.2: push a tagged entry onto the position-history ring. If the history cursor is mid-ring (user walking back), truncate forward entries before pushing — standard “modify-from-middle” semantics. Capped at POSITION_HISTORY_CAP; oldest dropped. Adjacent same-position-and-source duplicates are coalesced. Relocated from lattice-ui-tui::app::motions alongside activate_buffer (its only renderer-neutral caller cluster); other position-history call sites stay on App via the delegate until the wider motions.rs migration.

Source

pub fn activate_buffer(&mut self, id: BufferId) -> bool

5.5.F.4.2: switch the active pane to whatever buffer id references, regardless of kind. Document buffers route through Self::activate_document; tree buffers update the active pane + load the tree’s stash; help buffers go through Self::activate_help_in_pane; oil through Self::activate_oil.

Returns true when the activation went through the full activate_document path that the App-side caller needs to follow up with activate_buffer_state() (mode/syntax/option re-init + per-frame highlight-cache clear). Returns false on the early-return paths (unknown id, same-buffer no-op, non-document target) — the caller skips the tail.

Why bool, not void: until F.5 lands mode lifecycle host- side, activate_buffer_state cannot run inside Editor — it calls activate_major_for_buffer_kind + maybe_reparse_syntax, both still on App. The bool is the explicit-coordination signal across the migration window; it deletes when F.5+ brings the tail host-side. Activate id in the active pane and complete its activation — mode wiring, option resolution, option-cache rebuild, syntax, folds.

Returns whether a full activation ran (false for a no-op, e.g. the buffer was already active), which is what :bn / :bp and the buffer-delete successor path report.

There is deliberately no way to do the first half without the second. This used to return “you must now call activate_buffer_state()” and 14 of 25 call sites ignored it. Most were safe by accident — they open synthetic buffers, where mode activation rebuilds the caches anyway — but the ones that switch to an already-built buffer were not, and the symptom is silent: the destination renders with the previous buffer’s resolved options.

Two were confirmed in real use before this was folded in: bury_buffer (q out of a magit view returned the file with magit’s Number = false still in option_cache, so the line numbers were gone) and do_tag_stack_pop (<C-t> across a buffer boundary, same leak). :bn / :bp had it too.

The completion’s renderer signals — the mode-lifecycle and LSP-attach cascade — are enqueued via Editor::enqueue_renderer_signals rather than returned, so the signature stays bool and no caller can drop them either.

Source

pub fn activate_document(&mut self, id: BufferId) -> bool

5.5.F.4.2: switch the active document to id. Snapshots the current active state into buffer_locals, then loads from the destination’s locals. Returns true when the caller should run activate_buffer_state() next (full- activation path); returns false on no-op (already active) or on the same-document fast path (returning to the document buffer that self.document already points at, e.g. from a help-in-pane overlay or a file-tree pane).

Source

pub fn next_listed_buffer_id(&self) -> Option<BufferId>

5.5.F.4.3: next listed buffer id from the active pane’s, in cyclical sorted order. None if there’s only one listed buffer (no other valid target).

Source

pub fn prev_listed_buffer_id(&self) -> Option<BufferId>

5.5.F.4.3: previous listed buffer id from the active pane’s, in cyclical sorted order.

Source

pub fn invoke_command_named(&mut self, id: &str, args: Args) -> DispatchOutcome

5.5.F.4.3: :bnext / :bn — cycle to the next listed buffer. Returns true when the activation went through the full activate_document path; caller must run Self::activate_buffer_state (the handle_effect arm does this inline as of F.5.5). Run the command named id: a registered ACTION is dispatched with its typed args; anything else runs as an ex line built from them.

Shared by the picker’s InvokeCommand outcome and CD.3d’s Effect::InvokeCommand, so the two cannot disagree about how a name resolves. OM.11 records why an action is not sent through the : line: the line cannot reach an action at all, and re-serialising typed args splits a path with a space into two.

Source

pub fn do_focus_buffer(&mut self, id: BufferId)

CD.1: Effect::FocusBuffer — show id in the active pane.

Unlike Self::activate_buffer, an unknown id is silent: the effect comes from a plugin that learnt the id earlier, and the buffer closing in between is an ordinary race rather than something to tell the user about. debug!, per the diagnostic-log rule.

Source

pub fn do_buffer_next(&mut self) -> bool

Source

pub fn do_buffer_prev(&mut self) -> bool

5.5.F.4.3: :bprev / :bp — cycle to the previous listed buffer. Same return-shape as Self::do_buffer_next.

Source

pub fn lsp_close_buffer(&mut self, buffer_id: BufferId)

5.5.F.4.4: detach a buffer from every attached LSP server. Removes the URI→BufferId mapping, then fires the wire-level didClose (fire-and-forget against the supervisor mailbox). No-op when the buffer was never attached.

Source

pub fn do_buffer_delete(&mut self, force: bool) -> bool

5.5.F.4.4: :bd[elete] — close the active buffer. v1 picks any other buffer to activate; if no others remain, the close is rejected. For document buffers ! bypasses the dirty check; tree buffers are always read-only and skip the guard.

Returns true when the successor activation went through the full activate_document path; caller must run Self::activate_buffer_state (the handle_effect arm does this inline as of F.5.5).

Source

pub fn kill_buffer(&mut self) -> bool

Effect::KillBuffer — end the buffer in the active pane: return the pane to where it was before this buffer took it (what Self::bury_buffer does), then delete the buffer.

Bury keeps the buffer; this is for the ones whose life ends at a verb. magit’s commit buffer is the case that needed it: burying on C-c C-c left *magit:commit* alive, and the next commit REUSED it — a reused synthetic buffer is deliberately not re-seeded — so the old message and the old staged diff came back. magit (with-editor) kills the compose buffer on finish and on cancel.

No dirty check. The mode asking has decided the buffer is done — a finished commit message is not “unsaved work” — the same judgement :bd! expresses. With no origin to return to (the buffer was reached some other way), it falls back to exactly :bd!, so the pane still ends up on a real buffer.

Source

pub fn activate_file_tree(&mut self, id: BufferId)

Switch the active pane to the file-tree buffer with id.

DL.4: a thin delegation to Self::activate_document, because a tree is a document now. That is the whole convergence in one line — the swap into the active-document slot is what makes the shared compose path paint it, place its caret, and give it the cursorline, wrap, folds and gutter the bespoke painter never had. activate_document reads the kind back out of the registry (kind_of), so active_buffer still lands on FileTree and every kind-tagged behaviour downstream is unchanged.

The hand-rolled body this replaces stashed and restored the tree’s own cursor / scroll fields — archival duplicates of the pane’s that the hot path never read.

Source

pub fn activate_oil(&mut self, id: BufferId)

Switch the active pane to the oil buffer with id.

DL.5: a delegation to Self::activate_document, for the same reason activate_file_tree became one — oil is a document.

Source

pub fn activate_help_in_pane(&mut self, id: BufferId)

5.5.F.4.2: switch the active pane to an existing help buffer in the registry. Snapshots prior pane state so <C-o> returns the user to the document/cursor they came from. The registry’s HelpBuffer is mirrored into self.popup_buffer so the existing keymap + render paths transparently target it.

Source

pub fn build_describe_event_content( &mut self, name: &str, ) -> Option<HelpContent>

5.5.F.3: build the :describe-event <name> content. Renders the descriptor for one registered event. Mirrors :describe-command / :describe-option’s shape. Unknown name routes an error to the echo ring; dispatcher skips.

Source

pub fn build_list_modes_content(&self) -> HelpContent

5.5.F.6: :list-modes (M.8) content builder — render every registered mode as a help buffer. Groups by kind (Major / Minor); each row shows the mode’s id and * if currently active on the active document buffer. Mode counterpart of :options.

Source

pub fn build_describe_mode_content(&mut self, name: &str) -> Option<HelpContent>

5.5.F.6: :describe-mode <name> (M.8) content builder — render one mode’s metadata: id, kind, contributed option overrides (mapping each TypeId back to the option’s display name via OPTION_DECLS), required capabilities, and current activation state on the active buffer. Mode counterpart of :describe-option. Fallible: pushes echo + returns None on unknown name.

Source

pub fn build_describe_active_modes_content(&self) -> HelpContent

DAM.2: :describe-active-modes (<C-h>m) content builder — the mode stack live on the active buffer, major first, then every minor, each with the chords it contributes.

Deliberately major + minors, not major alone. The minor-mode convention pushes chords shared across majors out into a minor (magit’s gr / q / ]] live on magit-core-mode, not on each magit major), so a major-only view would omit exactly the half that convention centralised.

Infallible — a buffer with no major mode is an ordinary state (synthetic buffers routinely carry minors and no major), so it renders (none) rather than erroring.

Source

pub fn build_describe_active_bindings_content(&self) -> HelpContent

DAM.6: :describe-bindings (<C-h>K) content builder — the chords that can actually fire on the active buffer.

The buffer-scoped peer of Self::build_list_keymap_content (:keymap), which renders the entire static catalog for every binding-mode regardless of what is active. Both views are wanted: :keymap is the reference, this is the answer to “what can I press here”. Two sources are unioned:

  1. builtin catalog entries live in the buffer’s current binding-mode, and
  2. every active mode’s contributions (major + minors), via the same walk Self::build_describe_active_modes_content uses.
Source

pub fn build_customize_picker_content(&self) -> HelpContent

5.5.F.6: :customize (no args) (M.9.0) content builder — picker view: every group + every registered mode that contributes at least one customizable option. Each row is a [label](customize:name) link.

Source

pub fn build_customize_group_content( &mut self, group_name: &str, ) -> Option<HelpContent>

5.5.F.6: :customize <group> content builder — every customizable option in <group>. Each row shows the option’s canonical name + aliases, type, current value, default (when it differs), and the doc string. Fallible: pushes echo + returns None on unknown group.

Source

pub fn build_customize_mode_content( &mut self, mode_name: &str, ) -> Option<HelpContent>

5.5.F.6: :customize <mode-name> content builder — every option the mode contributes via Mode::options(). Each row shows the same metadata as the group view, plus a [mode-shadow] indicator when the contribution is active on the active buffer. Fallible: pushes echo + returns None on unknown mode.

Source

pub fn do_list_diagnostics(&mut self)

5.5.F.7: :diagnostics — open every published diagnostic across every attached server in a vertico-style picker. Severity glyph in the marginalia ([E] / [W] / [I] / [H]) and the diagnostic message as the preview text. Empty snapshot or empty rows route an info echo.

Sets self.picker directly — pickers are a renderer-neutral Editor field, so no RendererSignal is required (the renderer reads the picker each frame).

Source

pub fn do_list_errors(&mut self)

CM.8 (2026-07-22): :clist / :cl — open the error list in a fuzzy picker, the flat browse-and-jump surface parallel to :diagnostics. Complements :cnext (step through) and :copen (the *problems* multibuffer). Reuses the shared LspLocations picker source + JumpToLspLocation accept path, exactly like do_list_diagnostics — the rows come from the core error list.

Source§

impl Editor

5.5.G.23: read-only-motion / oil / help / file-tree dispatch runners. Migrated from lattice-ui-tui::app::dispatch alongside the keystone Action::Invoke slice. Pre- and post-effect work runs entirely on Editor state; effects produced by these runners are scoped to motion / yank / mode-flip and never touch the renderer-coupled Effect::* arms — so they don’t need to thread through DispatchOutcome.effects.

Source

pub fn run_oil_invocation(&mut self, inv: CommandInvocation) -> bool

Dispatch a CommandInvocation against the active oil buffer’s rope. Oil’s content lives in oil.content (a Buffer), separate from self.document (the actor- backed document buffer). The grammar dispatcher only knows about Document, so we synthesise a temporary Document from oil’s rope, dispatch through it, and copy the resulting buffer back. Edits + cursor updates land on the oil rope without touching the document actor. 2026-05-26: returns true when the runner claimed the invocation, false when it should fall through to the caller (the grammar Action gate in [Self::run_invocation]). The per-kind-first dispatch reorder uses this signal to keep buffer-kind-specific behaviour inside each runner instead of leaking branches into do_* helpers downstream.

Source

pub fn run_file_tree_invocation(&mut self, inv: CommandInvocation) -> bool

Resolve a motion against the active file tree’s content. Same shape as Self::run_help_invocation but mutates the tree’s cursor instead of the help buffer’s.

Source

pub fn run_help_invocation(&mut self, inv: CommandInvocation) -> bool

Resolve a motion-class invocation against the active help buffer. Operators / text-objects / ex-commands echo a “read-only” message; the dispatcher in Self::run_document_invocation is the only path that commits buffer mutations.

2026-05-26: returns true when claimed, false for Action-kind commands (: enter-command, / enter-search, LSP nav, …) that should fall through to the grammar Action gate.

Source

pub fn run_editable_tail_invocation(&mut self, inv: CommandInvocation) -> bool

Invocation runner for a read-only buffer that exposes an editable tail (the comint prompt of an owner-written conversation buffer, e.g. ai-conversation-mode). Splits on the cursor:

  • Cursor in the editable tail → return false so the invocation falls through to the normal document path; vim operators (x / dd / dw) edit the prompt like any Document.
  • Cursor in the frozen transcript above the anchor → delegate to Self::run_read_only_motion: motions move the cursor, mutating operators / text objects echo “read-only” and never reach the rope, and Action / ex-command invocations (:, /) still fall through.

AU-3 gap fix: without this runner the mode registered none, so operators routed straight to Self::run_document_invocation and committed edits on the real document, bypassing the editable-tail gate (which only guarded the apply_edit_blocking char path). A whole-line operator started in the tail can still reach a byte above the anchor (dgg); that residual is the cursor-vs-range approximation, harmless here because the transcript re-projects from the conversation store.

Source

pub fn dispatch_invocation( &mut self, inv: CommandInvocation, out: &mut DispatchOutcome, )

5.5.G.23: top-level command-invocation router. Selects the correct runner based on active_buffer. CommandKind::Action invocations bypass the document path and run against a throwaway scratch Document (DESIGN.md §5.2.1 — Action specs return an Effect::AppAction(_) without reading or mutating the buffer).

Effects produced are flushed through [apply_effect_host] (recursive Effect::Many flatten + host migrated-arm pass + push to out.effects for the renderer-coupled tail). UD (unified dispatch): the single command-dispatch entry. Every CommandInvocation — from the keymap (Action::Invoke), the : line (execute_ex_line), macro replay, and (eventually) plugins — flows through here: missing-arg arming, mode-contributed action-handlers, the per-kind buffer runner, the grammar Action gate, and the rich document path (run_document_invocation: jump-history, find/till capture, count + fold expansion, dot-repeat, visual-exit). There is no second, thinner dispatcher — : and plugins get identical treatment to a keystroke.

Source

pub fn run_document_invocation( &mut self, inv: CommandInvocation, out: &mut DispatchOutcome, )

5.5.G.23: mainline document-dispatch runner. Reads pending register / pending count / op count; folds them into the invocation; pushes jump-history for jump-class motions; bakes the find/till target for ; / , repeat; expands dd / yy / cc / >> over a closed fold via the fold-aware count grow. Then dispatches through the document actor (dispatch_blocking) and flushes the resulting effect through [apply_effect_host].

Source

pub fn run_terminal_invocation(&mut self, inv: CommandInvocation) -> bool

Thin dispatcher for Terminal-active panes.

As of 2026-05-28 (T-clean-1 + Slices 1–5 follow-up) this runner no longer carries vim-grammar knowledge. It is a pure dispatcher that hands grammar invocations off to central handlers operating on active_text() (the SyntheticDoc rope):

  • Motion-kind: execute_motion_only against the rope; cursor + visual head sync via sync_terminal_nav_cursor_from_doc.
  • Operator + TextObject: dispatch_synthetic_operator runs the grammar’s full operator path against a transient Document wrapping the rope; non-mutating effects apply (Yank → register), mutating effects echo “read-only”.
  • Visual yank: extracts text from the rope via the doc-space visual_selection_range / visual_block_extents helpers.
  • Visual entry (v/V/<C-v>), page scroll (<C-d>/<C-u>), Insert entry (i/a/I/A): not handled here at all — returned as false so run_invocation’s central Action gate dispatches them uniformly. (Slices 3 + 4.)

Going forward, vim-grammar fixes go in lattice-grammar, not in this runner. The remaining bespoke pieces here are the dispatch wiring + the terminal-specific viewport sync (sync_terminal_nav_cursor_from_doc) which has no document analogue. File-level retirement of this runner requires the dispatch-engine refactor that lets run_document_invocation accept an active-text override (deferred).

Return contract: true claims the invocation; false falls through to the Action gate in run_invocation.

Source

pub fn run_read_only_motion(&mut self, inv: CommandInvocation) -> bool

2026-05-26: returns true when claimed (Motion handled or non-Motion echoed read-only), false for Action-kind commands so the caller can fall through to the grammar Action gate. Action commands (:, /, K, …) are buffer-agnostic and shouldn’t echo “read-only” — they belong on the central dispatch path.

Source§

impl Editor

Source

pub fn drain_pending_renderer_signals(&mut self) -> Vec<RendererSignal>

M.2.b.2 (2026-06-01): drain renderer signals accumulated by impl ModeActivator for Editor calls — extension-crate code (lattice_multibuffer::create_multibuffer_view, future provider triggers) drives activation through the trait surface that returns (), so the host loop must pull queued signals into the active DispatchOutcome after the call frame returns.

Source

pub fn drain_pending_renderer_effects(&mut self) -> Vec<Effect>

OR.16: renderer-owned effects produced on a path with no renderer to hand them to, for the peers to apply on their next frame.

The off-renderer paths (the async picker accept, the fill target, the picker’s delete verb) apply what they can themselves; anything left is queued here instead of being dropped, which is how four features went missing before. Both peers drain this beside the tick’s signals.

Source

pub fn register_invocation_runner( &mut self, id: ModeId, runner: InvocationRunnerFn, )

2026-05-26: register an invocation-runner function under the mode-id its owning lattice_mode::Mode declares via lattice_mode::Mode::invocation_runner. Called from Editor::boot for each built-in runner (run_help_invocation / run_oil_invocation / run_file_tree_invocation / run_terminal_invocation); plugins (post Phase 7) reuse this entry point for the modes they install. Overwrites silently on duplicate registration — boot order is the single writer.

Source

pub fn resolve_invocation_runner( &self, buffer_id: BufferId, ) -> Option<InvocationRunnerFn>

2026-05-26: resolve the invocation runner for buffer_id by walking the active modes (minors most-recently- activated first, then major) and returning the first runner whose mode declared lattice_mode::Mode::invocation_runner and has a registered function on self.invocation_runners. Mirrors crate::pane_render::resolve_pane_render_mode — same walk, different table. Returns None when no active mode owns dispatch (Document panes today).

Source

pub fn is_view_refresh_command(&self, id: CommandId) -> bool

RV.1: is id the generic action:view-refresh the shared gr binds to?

Resolved by name rather than cached in a field so there is no boot-ordering coupling between this and command registration. The cost is one ServiceRegistry lookup plus one name hash on the action-dispatch path — which runs at chord rate (human keypresses), not per frame or per glyph, so it is nowhere near paramount-goal-#1 territory. Memoize if a dispatch bench ever says otherwise.

Source

pub fn resolve_refresh_action(&self, buffer_id: BufferId) -> Option<CommandId>

RV.1 (2026-08-10): resolve the refresh action for buffer_id by walking the active modes (minors most-recently-activated first, then major) and returning the CommandId of the first lattice_mode::Mode::refresh_action declared.

Same walk as Self::resolve_invocation_runner, different table — most-specific-wins, so a provider minor on a multibuffer beats the generic MultibufferMode. Backs the shared gr chord (refreshable-view-mode): the mode declares which of its own actions refreshes, the host walks and dispatches it.

None when no active mode declares one — the caller echoes rather than swallowing the key, so a view without a refresh says so. See docs/dev/architecture/mode-architecture.md §5.5.

Source

pub fn is_view_fold_toggle_command(&self, id: CommandId) -> bool

OA.4b: is id the generic action:view-fold-toggle the shared <Tab> binds to? Peer of Self::is_view_refresh_command, same resolve-by-name reasoning.

Source

pub fn resolve_fold_toggle_action( &self, buffer_id: BufferId, ) -> Option<CommandId>

OA.4b: resolve the fold-toggle action for buffer_id by walking the active modes (minors most-recently-activated first, then major) and returning the CommandId of the first lattice_mode::Mode::fold_toggle_action declared.

Identical walk to Self::resolve_refresh_action, different table, and deliberately so: <Tab> and gr are the same shape of problem — one chord that means the same verb in every grouped view while the body stays each view’s own. Most-specific wins, so a provider minor on a multibuffer beats the generic MultibufferMode.

None when no active mode declares one — the caller leaves <Tab> alone, so an ordinary document keeps jump-list-forward.

Source

pub fn cells_matrix_for(&self, buffer_id: BufferId) -> Arc<ArcSwap<CellMatrix>> ⓘ

D.4.d.0 (2026-05-29): lazy port into the per-document Self::cells_matrices registry. Returns the matrix cell for buffer_id, inserting an empty Arc<ArcSwap<CellMatrix>> on first ask.

Idempotent: every call for the same buffer_id returns the same Arc identity so renderer reads and worker writes stay coherent.

The active document’s entry is seeded at boot to share its Arc with Self::cells_matrix_cell, so callers that resolve the active doc through either surface land on the same cell.

Source

pub fn display_matrix_for( &self, buffer_id: BufferId, ) -> Arc<ArcSwap<DisplayMatrix>> ⓘ

B2.1 (2026-06-04): lazy port into the per-document Self::display_matrices registry. Mirror of Self::cells_matrix_for for the per-line display cache. Returns the matrix cell for buffer_id, inserting an empty Arc<ArcSwap<DisplayMatrix>> on first ask.

Idempotent: every call for the same buffer_id returns the same Arc identity so renderer reads and worker writes stay coherent. The active document’s entry is boot-seeded to share its Arc with Self::display_matrix_cell.

Source

pub fn indent_guides_for( &self, buffer_id: BufferId, ) -> Arc<ArcSwap<IndentGuides>> ⓘ

IG.2 (2026-08-16): lazy port into Self::indent_guides. Peer of Self::display_matrix_for, with the same idempotence contract: every call for the same buffer_id returns the same Arc identity, so renderer reads and worker writes stay coherent.

Source

pub fn sticky_context_for(&self, pane_id: PaneId) -> Arc<ArcSwap<StickyContext>> ⓘ

TC.3b: the sticky-context cell for pane_id, inserting an empty layer on first ask. Keyed by PANE — two panes on one buffer resolve different context and must not share a cell.

Same idempotence contract as the sibling accessors: every call for the same pane_id returns the same Arc identity, so renderer reads and worker writes stay coherent.

Source

pub fn virtual_rows_matrix_for( &self, buffer_id: BufferId, ) -> Arc<ArcSwap<VirtualRowMatrix>> ⓘ

D.4.d.2.0 (2026-05-29): lazy port into the per-document Self::virtual_rows_matrices registry. Mirror of Self::cells_matrix_for for the virtual-row pipeline. Returns the matrix cell for buffer_id, inserting an empty Arc<ArcSwap<VirtualRowMatrix>> on first ask.

Idempotent: every call for the same buffer_id returns the same Arc identity so renderer reads and worker writes stay coherent.

The active document’s entry is seeded at boot to share its Arc with Self::virtual_rows_matrix_cell, so callers that resolve the active doc through either surface land on the same cell.

Source§

impl Editor

Source

pub fn visual_selection_range(&self) -> Option<Range>

Half-open byte range covered by the active Visual selection, or None outside Visual mode. Spans the primary selection’s anchor → head pair, normalised so start <= end.

  • Linewise: covers full lines from start.line to end.line. The end byte is u32::MAX — callers should clamp it to the actual line length when painting (match_overlay_range in TUI; the GPUI peer’s per-line clamp likewise).
  • Charwise (and None — uninitialised selections that default to charwise): includes the HEAD byte (vim semantics). End byte = head.byte + 1.
  • Blockwise: returns the same linear span as Charwise, but renderers ignore this value when the publisher’s visual_block_extents is Some (2026-05-27 — the per-line column band lives there). Kept around so non- renderer consumers (e.g. Range::Selection operator resolution) still see some selection range.

Renderer-neutral; the returned Range is the renderer- agnostic lattice_protocol::position::Range.

Source

pub fn visual_block_extents(&self) -> Option<BlockExtents>

Rectangular block of the active Visual selection when modal == Visual(Blockwise); None otherwise. Normalised so start_line ≤ end_line and start_col ≤ end_col.

2026-05-27: hoisted from lattice-ui-tui::render::visual_block_extents so both renderer peers paint the same block. Charwise / Linewise stay on [visual_selection_range] — Blockwise needs a per-line column band that a linear Range can’t express. T-paint-1 (2026-05-28): also returns the block when the active buffer is a Terminal with t.visual.kind == Block, translating from grid-space to doc-space via synthetic.origin_top_line.

Source§

impl Editor

Source

pub fn boot(document: Document) -> Self

Build a fully-wired renderer-neutral Editor from an initial Document. Phase 5.7.B.1 extraction of the boot body from lattice-ui-tui::app::boot::App::new.

What happens here: event bus + LSP subsystem (with all four server-initiated channels) + every per-feature LSP rx subscription; grammar registry populated with builtins + ex-commands + auto-generated :<mode-name> toggles; mode registry with foundation / syntax / lsp-log / lsp-completion / oil / file-tree / snippet / buffer-kind modes; completion registry with builtins + the seven host-side completion generators; config registry + linkme init + LSP-logger seeding; tree-sitter LangRegistry + initial seeded SyntaxHandle; spawn_document; buffer registry seeded with typed buffer-locals.

What does NOT happen here (each renderer’s App::new runs them afterwards): renderer-specific theme cache rebuild; rebuild_option_cache / sync_host_theme_from_config (host fns but their renderer-signal fan-out is per-renderer); activate_major_for_buffer_kind (returns signals); publish_document_opened_for_active / ensure_named_synthetic_document / ensure_messages_buffer (App-side helpers).

Source§

impl Editor

Source

pub fn maybe_refresh_wasm_context(&mut self)

Per-tick context refresh pump — the off-render-path drive.

Called from run_tick_pending beside maybe_refresh_wasm_decorations. Cheap when nothing changed (registry-epoch + parse-version gated). When a refresh is due it spawns the registered producers on the background runtime (NOT the actor thread), each writing the merged result into the per-buffer cache, bumping the paint generation and waking the render pipeline so the result lands WITHOUT a keypress.

Graceful / no-blanking (§8): a producer whose call errs contributes nothing, and the cache is overwritten only when at least one producer answered — an all-error refresh keeps the prior scopes rather than clearing them. A failed refresh must not read as the feature breaking.

Source§

impl Editor

Source

pub fn resolve_sticky_context_lines( &self, buffer_id: BufferId, cursor_line: u32, scroll: u32, viewport_height: u32, ) -> Arc<[u32]> ⓘ

TC.3b — resolve the source lines this pane pins, for the publish that is about to happen.

Runs at cursor rate (every pane-inputs publish), so it must stay cheap: a cache read plus [resolve_context], which is a linear scan over the buffer’s scopes and a sort of the small enclosing subset. It touches no WASM — the producer that filled the cache ran off-thread on the last reparse.

The host resolving this (rather than each renderer) is what makes the scroll model’s reservation and the painted strip incapable of disagreeing: both read the list this returns.

Empty is the fast path and the overwhelmingly common one — no context plugin loaded means no cached scopes means an empty Arc<[u32]> with no allocation beyond the shared empty slice.

Source§

impl Editor

Source

pub fn maybe_refresh_wasm_decorations(&mut self)

PL8.E per-tick decoration refresh pump — the off-render-path drive.

Called from run_tick_pending next to the maybe_request_* LSP pumps. Cheap when nothing changed (registry-epoch + cache-version gated). When a refresh is due it spawns the registered producers on the background runtime (NOT the actor thread), each writing the merged result into the per-buffer cache via insert_for, bumping the paint generation and waking the render pipeline. NO per-frame WASM — the renderer reads only the cache this fills.

Graceful / no-flicker (§8): a producer whose call errs (trap, quarantine, or a benign “empty buffer” Err) contributes nothing; the cache is overwritten only when at least one producer answered, so an all-error refresh keeps the prior marks painted rather than blanking them. (With a single producer — the common case — this is exactly the WasmDecorationSource doc contract: Err ⇒ keep prior.)

Source§

impl Editor

Source

pub fn maybe_refresh_wasm_media(&mut self)

IM.7 per-tick media refresh pump.

Version- and registry-gated, single-flight, spawns producers off the actor thread, and writes the resolved blocks into the per-buffer cache. No per-frame WASM: the renderer reads only what this fills.

Graceful: a producer that errs contributes nothing and the cache is overwritten only when at least one producer answered, so an all-error refresh keeps the prior blocks. That is what stops every image in a document blinking out on a transient failure mid-edit.

Source§

impl Editor

Source

pub fn ensure_subsystem_buffers(&mut self)

Eagerly create the editor’s standard subsystem buffers (*lsp* + *messages*) so name-based lookups like :b *lsp* and :b *messages* resolve from t=0 instead of waiting for lazy creation on first use.

Phase 5.7.B.9: aggregator added so both renderer peers run identical eager-seeding without each having to name the subsystem-specific constants / mode-ids. The TUI peer historically inlined the two ensure_* calls in App::new; both peers now share this entry.

Source

pub fn drain_message_events(&mut self)

Drain queued MessagePushed events; append each formatted record to the *messages* buffer in one apply_edit_batch so the actor sees a single edit per drain. Phase 5.8.AA.f: hoisted from lattice-ui-tui::app::messages::App::drain_message_events.

Source

pub fn ensure_messages_buffer(&mut self) -> BufferId

Find-or-create the *messages* Document buffer. Idempotent; first creation seeds the buffer with the in-memory ring contents (so :messages after some records have already accumulated shows the backlog). Activates messages-mode (which contributes ReadOnly = true so user keystrokes can’t mutate; the streaming append path goes through Self::append_to_owned_buffer).

Phase 5.7.B.9: migrated from lattice-ui-tui::app::messages::App::ensure_messages_buffer. Both renderer peers now reach the canonical body; the TUI peer’s App::ensure_messages_buffer becomes a thin wrapper.

Source

pub fn do_open_messages(&mut self) -> Vec<RendererSignal>

:messages – activate the *messages* Document buffer. Drains queued events first so the view is up to date. Returns post-activation RendererSignals the renderer must fan through handle_renderer_signal (mode-mirror, theme caches). Phase 5.8.AF.3.

Source§

impl Editor

Source

pub const SYNTHETIC_BUFFER_FLAGS: BufferFlags = SYNTHETIC_BUFFER_FLAGS

Convenience accessor for SYNTHETIC_BUFFER_FLAGS from callers that already have &Editor in scope – saves a use import for the const at every call site (boot, ensure_messages_buffer, the LSP log-buffer creators, the GPUI peer’s finalize_boot, …).

Source

pub fn seed_empty_document_locals(&mut self, buffer_id: BufferId)

M.3.2.c.5: seed an empty set of document mode-locals for a freshly-registered document buffer. Subsequent activation transitions read through these slots; if the slot is missing the accessor returns the type’s natural default. Idempotent (replace-on-collision).

Phase 5.7.B.9: migrated from lattice-ui-tui::app::lifecycle::App::seed_empty_document_locals. The body touches only renderer-neutral editor state (buffer_locals) + host-owned local types (crate::modes::Document*).

Source

pub fn activate_major_by_id(&mut self, buffer_id: BufferId, major_id: ModeId)

Activate major_id on buffer_id directly, bypassing the language-detection path. Used by synthetic-buffer creators that already know which major mode they want (LSP log buffers want lsp-log-mode / lsp-trace-log-mode; *messages* wants messages-mode).

Errors from mode_registry.activate_major surface as an EchoLevel::Warn set_message; activation never panics on per-mode hook failures.

Phase 5.7.B.9: migrated from lattice-ui-tui::app::lsp_log_buffers::App::activate_major_by_id. All host-callable; the set_message + recompute_options_for_buffer deps already live on Editor.

Source

pub fn append_to_owned_buffer(&mut self, buffer_id: BufferId, text: &str)

Append text to the end of the Document at buffer_id. Used by subsystems that own synthetic buffers to feed streamed records without going through the modal-dispatch insert path (which would block on the buffer’s read-only contribution).

Blocking: this calls into the document actor’s apply_edit_batch mailbox via block_on. Cheap when text is small; the actor’s reparse path is a no-op for buffers whose major mode (lsp-log-mode / messages-mode / …) does not attach a syntax handle.

No-op when buffer_id does not resolve to a Document in the registry, or when text is empty.

Phase 5.7.B.9: migrated from lattice-ui-tui::app::lsp_log_buffers::App::append_to_owned_buffer. Uses host’s [last_addressable_line] + Buffer::line_byte_len to compute the EOL insertion point.

Source

pub fn replace_owned_buffer(&mut self, buffer_id: BufferId, text: &str)

Replace the ENTIRE content of an owner-written buffer with text (one full-range edit). The idempotent counterpart to Self::append_to_owned_buffer: a synthetic buffer opened by name is reused across calls, so a re-render (e.g. :export-plugin-api a second time) must overwrite rather than append. Bypasses the read-only dispatcher, exactly like the append path (owner writes).

Source

pub fn ensure_named_synthetic_document( &mut self, name: &str, major_id: ModeId, flags: BufferFlags, ) -> BufferId

Find-or-create a Document buffer named name, register it in the registry with flags, seed empty document locals, and activate major_id directly (skipping the activate_major_for_buffer_kind language-detection path because synthetic buffers have no on-disk path).

Returns the resolved BufferId; idempotent on subsequent calls (re-uses the existing entry).

Activation runs the major’s on_activate synchronously, so any subscription / spawn the mode does is in place by the time this function returns. The major mode is what derives the buffer’s identity (instance key for LSP log variants); the host does not stash any subsystem-shaped buffer-local before activation.

Phase 5.7.B.9: migrated from lattice-ui-tui::app::lsp_log_buffers::App::ensure_named_synthetic_document.

Source

pub fn ensure_named_messages_document( &mut self, name: &str, major_id: ModeId, flags: BufferFlags, ) -> BufferId

Same as Self::ensure_named_synthetic_document but inserts as BufferData::Messages so the kind tag is BufferKind::Messages. Used by ensure_messages_buffer so :ls / modeline / introspection can distinguish the transcript from user-edited documents.

Source

pub fn ensure_named_popup_buffer( &mut self, name: &str, major_id: ModeId, flags: BufferFlags, ) -> BufferId

PU-B.2: idempotently ensure a named popup buffer under major_id, stored as BufferData::Help so the popup renderer draws it (the renderer’s popup path is BufferData::Help-gated) while major_id (e.g. ai-permission-mode) owns the buffer’s behaviour + keymap. The content is empty on creation — the owning mode’s on_activate owner-writes the projection. Returns the existing id when a buffer of this name is already registered (re-open reuses it).

Source

pub fn register_listing_document( &mut self, flags: BufferFlags, kind: ListingKind, ) -> BufferId

DL.4/DL.5: mint an empty actor-backed Document filed under a listing kind.

The listing peer of Self::register_help_document — same PU.1a shape (a DocumentEntry behind a kind discriminator), minus the help metadata. The caller seeds the text through the entries chokepoint, which also publishes the icons, so the rope and the virtual text cannot drift apart.

DL.5 generalised it to oil once that kind converged too.

Source

pub fn register_help_document( &mut self, content: HelpContent, flags: BufferFlags, ) -> BufferId

PU.1a: register a freshly-built lattice_help::HelpContent as an actor-backed synthetic Document (BufferData::Help), seeded with the content’s text + parsed metadata. Returns the new BufferId. This is the single creation path help shares with *messages* and the LSP logs — content lives once in the Document; the title goes to the registry name slot; links / anchors / highlights go to buffer_locals. The caller owns the popup/pane focus wiring + mode activation.

Source

pub fn register_dashboard_document( &mut self, content: HelpContent, flags: BufferFlags, ) -> BufferId

DB.2: create the *dashboard* buffer. Identical to Self::register_help_document except the buffer data is BufferData::Dashboard (so :ls / introspection tell it apart and the follow gates group it with help), and dashboard-mode is the major (assigned by the caller). Reuses the help metadata seed for the markdown SyntaxHandle + link ExtraHighlights, so <CR>-follow works through the shared help mechanism.

Source

pub fn replace_owned_document_text(&mut self, id: BufferId, text: &str)

PU.1a: replace the entire content of an owned synthetic Document at id with text (bypasses the read-only dispatcher, like Self::append_to_owned_buffer). Used by the popup back-stack / link-follow swap paths to re-seed a help Document in place without changing its BufferId. No-op when id is not a Document.

Source§

impl Editor

Source

pub fn do_tutor(&mut self, lesson: Option<u32>) -> Vec<RendererSignal>

:tutor [N] – open the interactive Lattice tutor lesson N. Embeds the lesson via include_str!; copies to a temp path so the user can edit. Phase 5.8.AD.5: returns Vec<RendererSignal> because do_edit may emit signals.

Source

pub fn do_tutor_advance(&mut self) -> Vec<RendererSignal>

<CR> / <C-j> in tutor-mode.

  • GameOver: skip past the current exercise (lose this one; advance without scoring).
  • Lesson complete: open the next lesson, or show AllComplete if on the final lesson.
  • ManualAdvance exercise: always advance (no penalty).
  • Condition met (auto-detected by tick): advance.
  • Condition NOT met: drain one life. If lives hit 0 the session transitions to GameOver and the headerline updates.
Source

pub fn do_tutor_retreat(&mut self) -> Vec<RendererSignal>

<C-k> in tutor-mode.

  • GameOver: reload the current lesson from scratch (retry).
  • Active: retreat to the previous exercise.

Trait Implementations§

Source§

impl Debug for Editor

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Editor

Source§

fn default() -> Editor

Returns the “default value” for a type. Read more
Source§

impl ModeActivator for Editor

Source§

fn active_buffer(&self) -> Option<BufferId>

MR.6: the buffer a provider view is being opened over.

The active document buffer, which is what the trigger was pressed in — the same id transient_open_context reports and the same one the chord dispatcher hands an action handler.

Source§

fn ensure_named_document( &mut self, name: &str, major: ModeId, flags: BufferFlags, ) -> BufferId

The real create-and-activate seam (ensure_named_synthetic_document inserts the buffer + runs the major’s on_activate by id). This is why buffer creation lives here on the &mut-backed ModeActivator and not on the &self BufferStore.

Source§

fn set_buffer_scope_dir(&mut self, buffer: BufferId, dir: PathBuf)

Forward to the host’s single write chokepoint, so a provider crate records what its buffer is about without depending on lattice-host.

Source§

fn register_virtual_row_provider( &mut self, buffer: BufferId, provider: Arc<dyn VirtualRowProvider>, ) -> bool

K.4.6 (2026-06-02): forward to the editor’s VirtualRowProviderRegistry. The virtual-rows worker (crate::virtual_rows_worker) picks up new providers on its next wake; the registry itself is already shared (Arc<VirtualRowProviderRegistry>), so registration is observable to the worker without further plumbing. Returns false if a provider with the same ProviderId already exists in buffer’s scope — matches the underlying registry’s no-replacement contract.

Source§

fn activate_major_for_kind(&mut self, buffer: BufferId, kind: BufferKind)

Activate the major mode bound to kind on buffer. Uses the crate::ModeRegistry::find_major_for_kind lookup (populated by Mode::target_buffer_kind declarations) to resolve the mode id. No-ops if a major is already active on the buffer (idempotency / preserve-intent).
Source§

fn activate_minor_by_id(&mut self, buffer: BufferId, mode: ModeId)

Activate a minor mode by id on buffer. The mode must be registered. Idempotent: re-activating an already-active minor is a no-op.
Source§

fn deactivate_minor_by_id(&mut self, buffer: BufferId, mode: ModeId)

Deactivate a minor mode by id on buffer. Idempotent: a mode that is not active is a no-op. Read more
Source§

fn services(&self) -> Arc<ServiceRegistry> ⓘ

Cheap-clone handle to the App’s ServiceRegistry. Used by extension-crate trigger functions that need to look up service handles (BufferStoreHandle, per-provider services, the per-extension-crate registries) without fighting the borrow checker against the &mut Self activator borrow.

Auto Trait Implementations§

§

impl !Freeze for Editor

§

impl !RefUnwindSafe for Editor

§

impl !UnwindSafe for Editor

§

impl Send for Editor

§

impl Sync for Editor

§

impl Unpin for Editor

§

impl UnsafeUnpin for Editor

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> IntoMaybeUndefined<T> for T

§

fn into_maybe_undefined(self) -> MaybeUndefined<T>

Converts this value into a three-state builder argument.
§

impl<T> IntoOption<T> for T

§

fn into_option(self) -> Option<T>

Converts this value into an optional builder argument.
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

impl<T> Pointee for T

§

type Pointer = u32

§

fn debug( pointer: <T as Pointee>::Pointer, f: &mut Formatter<'_>, ) -> Result<(), Error>

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more