lattice_host/editor.rs
1// The renderer-agnostic editor state.
2//
3// Phase 5.B.3 introduces [`Editor`] as the destination for
4// the per-cluster field migration from
5// `lattice-ui-tui::App`. See
6// [`docs/dev/architecture/phase-5b-app-design.md`] for the
7// Option-D → Option-E pivot that this struct realises:
8//
9// - The host owns the editor's state and logic in `Editor`.
10// - Each renderer crate composes `Editor` into its own
11// concrete `App` wrapper alongside its renderer-specific
12// caches (`theme`, `pane_render_registry`, ...).
13//
14// Subsequent slices (5.B.4 onwards) relocate field clusters
15// one at a time from `App` into `Editor`, moving the methods
16// that touch only those fields into `impl Editor` here. Each
17// per-cluster commit ships green: methods that still live in
18// `impl App` access migrated fields via `self.editor.foo`;
19// methods that have moved access them via `self.foo` (now an
20// inherent method on `Editor`).
21//
22// The empty-now/grows-later shape is intentional: it lets
23// the wrapper field `editor: Editor` get added to `App`
24// before any field actually moves, giving every subsequent
25// migration a target that already exists in the type
26// system.
27
28use std::collections::HashMap;
29use std::path::PathBuf;
30
31use lattice_grammar::{CommandInvocation, Register};
32use lattice_protocol::position::{Position, Range as ProtoRange};
33
34use std::sync::Arc;
35
36use lattice_config::{ConfigRegistry, OptionOverrideSet, ResolvedOptions};
37use lattice_core::ui::popup::PopupPlacement;
38use lattice_grammar::ModalState;
39use lattice_grammar::builtins::Builtins;
40use lattice_help::topics::HelpTopicRegistryHandle;
41use lattice_lsp::cache::{
42 CodeActionOutcome, CodeActionRow, CompletionItemRow, CompletionOutcome,
43 CompletionResolveOutcome, DocumentHighlightCache, FormatOutcome, HoverOutcome,
44 LspCodeLensCache, LspDocumentColorCache, LspDocumentLinksCache, LspFoldsCache,
45 LspInlayHintCache, LspNavKind, LspPullDiagnosticsCache, LspSelectionChain,
46 LspSemanticTokensCache, ReferencesOutcome, RenameOutcome, SelectionRangeOutcome,
47 SignatureHelpOutcome, SymbolsOutcome,
48};
49// Phase 5.8.AF.5 / Slice 3b.0–3b.5: `CodeLensOutcome`,
50// `DocumentColorOutcome`, `DocumentHighlightOutcome`,
51// `DocumentLinksOutcome`, `FoldingRangeOutcome`,
52// `InlayHintOutcome`, `PullDiagnosticsOutcome`,
53// `SemanticTokensOutcome` no longer imported -- their
54// `pending_*_rx` fields retired (spawned tasks write directly
55// via `PerBufferCache::insert_for` / `ArcSwapOption::store`).
56use lattice_lsp::{DiagnosticsLayer, LspLogger, LspSupervisorHandle};
57use lattice_mode::{
58 ActiveModes, BufferLocals, GuardStoreHandle, ServiceRegistry, TickCallbackRegistration,
59};
60use lattice_picker::{Picker, PickerMruIndex};
61use lattice_protocol::CancellationToken;
62use lattice_protocol::Event;
63use lattice_protocol::edit::EditDelta;
64use lattice_runtime::{EventBus, MessagePushed, MessagesRing, SnapshotCache};
65use lattice_syntax::{LangRegistry, SyntaxHandle};
66
67use crate::action::{Action, EchoMessage};
68use crate::actions::ActionIds;
69use crate::buffer_registry::BufferRegistry;
70use crate::buffers::BufferId;
71use crate::chord::KeyChord;
72use crate::dispatch::RendererSignal;
73use crate::keymap_registry::{KeymapHandle, LayerId};
74use crate::pane::PaneTree;
75use crate::state::{
76 CompletionState, LastFind, LastSearch, LastVisual, LivePickerQueryState, MacroRecording,
77 OptionCache, PendingBlockInsert, PendingPickerAccept, PendingPickerInit, PositionEntry,
78 PrevPaneState, ReplaceEntry, SearchLine, SubstitutePreview, TagStackEntry, UnnamedRegister,
79};
80use crate::versioned::Versioned;
81use lattice_core::BufferKind;
82use lattice_protocol::position::Position as ProtoPosition;
83
84/// Cross-thread wake signal for the overlay worker.
85///
86/// Wraps `Arc<tokio::sync::Notify>` so `Editor` can keep its
87/// `#[derive(Default)]` (Notify itself doesn't impl Default).
88/// Cloning the wrapper clones the inner Arc — same notify
89/// channel. `notify_one()` is fired at the tail of
90/// `publish_render_state` so the worker re-evaluates inputs after
91/// every state change without polling.
92///
93/// Phase 5.8.AF.5 / Slice X2; renamed from `HighlightWake` in
94/// display-line B4.2 (the worker no longer makes highlights).
95#[derive(Clone)]
96pub struct OverlayWake(pub Arc<tokio::sync::Notify>);
97
98/// 2026-05-26: invocation-runner function pointer. The host
99/// registers one per [`lattice_mode::Mode`] whose
100/// EP.6 (2026-08-11): where a references result should land.
101///
102/// One query, three surfaces. Kept as an enum rather than a bool
103/// because a third case arrived within a day of the second — and a
104/// bool that grew a "…or the error list" meaning would have been the
105/// silent-wrong-terminus bug waiting to happen.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
107pub enum ReferencesTerminus {
108 /// `gr` — the locations picker. The historical behaviour.
109 #[default]
110 Picker,
111 /// `:lsp-references` — the editable multibuffer.
112 View,
113 /// `:lsp-references-to-error-list` — the core error list.
114 ErrorList,
115}
116
117/// [`lattice_mode::Mode::invocation_runner`] returns `Some(id)`.
118/// Returns `true` when the runner claimed the invocation,
119/// `false` when [`Editor::run_invocation`] should fall through
120/// to the grammar Action gate / `run_document_invocation` for
121/// central dispatch.
122pub type InvocationRunnerFn = fn(&mut Editor, lattice_grammar::CommandInvocation) -> bool;
123
124impl Default for OverlayWake {
125 fn default() -> Self {
126 Self(Arc::new(tokio::sync::Notify::new()))
127 }
128}
129
130impl std::fmt::Debug for OverlayWake {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("OverlayWake").finish_non_exhaustive()
133 }
134}
135
136/// S2.1 (2026-05-26): wake signal for the cell-builder worker
137/// (S2.2+). Same shape as [`OverlayWake`]: a `Notify` cloned
138/// into [`Editor::cells_wake`] and a sibling clone held by the
139/// worker task. `publish_render_state` fires `notify_one()`
140/// after every dispatch tick so the worker re-evaluates inputs
141/// from the latest published
142/// [`crate::render_state::CellsRenderState`].
143#[derive(Clone)]
144pub struct CellsWake(pub Arc<tokio::sync::Notify>);
145
146/// D.0a.1 (2026-05-29): wake signal for the
147/// `virtual_rows_worker`. Sibling of `CellsWake`. Fired by
148/// `publish_render_state` and by provider-state changes (e.g.,
149/// `DiffSubsystem` after publishing new hunks). The worker
150/// awaits via `notified()` and rebuilds the `VirtualRowMatrix`
151/// off the UI thread.
152#[derive(Clone)]
153pub struct VirtualRowsWake(pub Arc<tokio::sync::Notify>);
154
155impl Default for VirtualRowsWake {
156 fn default() -> Self {
157 Self(Arc::new(tokio::sync::Notify::new()))
158 }
159}
160
161impl std::fmt::Debug for VirtualRowsWake {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.debug_struct("VirtualRowsWake").finish_non_exhaustive()
164 }
165}
166
167impl Default for CellsWake {
168 fn default() -> Self {
169 Self(Arc::new(tokio::sync::Notify::new()))
170 }
171}
172
173impl std::fmt::Debug for CellsWake {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 f.debug_struct("CellsWake").finish_non_exhaustive()
176 }
177}
178
179/// A minor mode whose active/inactive state is driven by a
180/// predicate reading a shared, mode-owned session service —
181/// reconciled on the active buffer each `sync_keymap_overlays`
182/// cycle. Modes contribute these at boot so the generic
183/// overlay-sync carries no subsystem-specific knowledge:
184/// `active-snippet-mode` keys off the shared `SnippetSession`
185/// (`lattice_snippet::snippet_active_predicate`). See
186/// `feedback_mode_owns_its_surface`.
187#[derive(Clone)]
188pub struct SessionBackedMinor {
189 /// `true` ⇒ the mode should be active on the **given buffer**.
190 /// SN.3e: the predicate is buffer-scoped so a session live in one
191 /// buffer never activates the mode in another; `sync_keymap_overlays`
192 /// passes the buffer it is reconciling.
193 pub active: std::sync::Arc<dyn Fn(lattice_core::BufferId) -> bool + Send + Sync>,
194 /// The minor mode toggled by `active`.
195 pub mode_id: lattice_mode::ModeId,
196}
197
198impl std::fmt::Debug for SessionBackedMinor {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 // The predicate closure isn't Debug and is now buffer-scoped
201 // (no buffer to evaluate against here); report the mode it
202 // drives instead.
203 f.debug_struct("SessionBackedMinor")
204 .field("mode_id", &self.mode_id)
205 .finish_non_exhaustive()
206 }
207}
208
209/// I4 (openDiff) D-fix.1: the pane/buffer bookkeeping needed to tear a
210/// programmatic side-by-side diff down cleanly — close the two transient diff
211/// panes and return focus to the pane the `openDiff` was launched from (the
212/// `:claude` terminal). Recorded by
213/// [`Editor::open_programmatic_diff`](crate::dispatch), consumed by
214/// `Editor::finish_programmatic_diff_panes` on `:diff-accept` / `:diff-reject`.
215/// Keyed in [`Editor::programmatic_diff_panes`] by the session's primary
216/// (proposed / right) `BufferId`.
217#[derive(Debug, Clone)]
218pub struct ProgrammaticDiffPanes {
219 /// The pane that was active when `openDiff` fired (the `:claude` terminal
220 /// pane). Focus returns here on resolve — Option A: claude stays put while
221 /// the diff opens in transient panes to its right.
222 pub origin_pane: lattice_core::ui::pane::PaneId,
223 /// The transient baseline + proposed panes opened for the diff; both closed
224 /// on resolve. The `origin_pane` is never in this list.
225 pub diff_panes: Vec<lattice_core::ui::pane::PaneId>,
226 /// The throwaway in-memory baseline + proposed buffers; removed from the
227 /// registry on resolve so they don't linger in `:ls`.
228 pub diff_buffers: Vec<lattice_core::BufferId>,
229 /// D-fix.6: the IDE-peer connection that opened this diff (its
230 /// `ProgrammaticDiffRequest.origin_session`). A session-scoped close
231 /// (`close_tab` / `closeAllDiffTabs` from that same connection) tears
232 /// down only the diffs whose `origin_session` matches — so one agent
233 /// session can never close another's diffs. `0` = no originating session
234 /// (a non-IDE producer), matched by no connection's close.
235 pub origin_session: u64,
236}
237
238/// MG.17b: a transient menu parked while its argument is being typed.
239///
240/// Carries everything needed to put the menu back exactly as it was —
241/// including the parent stack, so an argument inside a submenu returns
242/// you to that submenu rather than to the root.
243#[derive(Debug, Clone)]
244pub struct PendingTransientArgument {
245 /// The menu to re-seat.
246 pub spec: std::sync::Arc<lattice_picker::TransientSpec>,
247 /// Its state, with the typed value written in on submit.
248 pub state: lattice_picker::TransientState,
249 /// Parent menus, so `BS` still walks back out after the round-trip.
250 pub stack: Vec<(
251 std::sync::Arc<lattice_picker::TransientSpec>,
252 lattice_picker::TransientState,
253 usize,
254 )>,
255 /// Which argument's value is being collected.
256 pub name: String,
257}
258
259/// IM.7a — the drawing peer's cell geometry, in pixels.
260///
261/// Two numbers rather than a pane-width channel: combined with a pane's
262/// column count (already published per pane) they give that pane's pixel
263/// width, which is what `lattice_media::block_geometry` needs alongside the
264/// line height.
265#[derive(Debug, Clone, Copy, PartialEq)]
266pub struct CellMetrics {
267 /// One display row's height in pixels — GPUI's `font_size * 1.3`.
268 pub row_px: f32,
269 /// One column's advance in pixels.
270 pub col_px: f32,
271}
272
273/// Renderer-agnostic editor state.
274///
275/// The renderer-agnostic half of every editor App. Each
276/// renderer's `App` struct composes one of these alongside
277/// its renderer-specific caches. Host-level code (mode
278/// lifecycle, dispatch, picker sources, LSP supervisor, ...)
279/// takes `&mut Editor` directly; renderer-side code takes
280/// `&mut App` and reaches the editor via `app.editor`.
281///
282/// **Field set grows per-cluster.** Each 5.B.x slice
283/// migrates a logical cluster of fields here from
284/// `lattice-ui-tui::App`. As clusters land, this struct
285/// accumulates state; in parallel, `App`'s direct field set
286/// shrinks. When the migration completes, every renderer-
287/// agnostic field on App lives here, every renderer-agnostic
288/// method on App lives in this crate's `impl Editor` blocks,
289/// and App becomes a thin wrapper holding `editor: Editor`
290/// plus renderer-specific caches only.
291///
292/// **Clusters landed so far:**
293/// - 5.B.4 -- macro recording state (`macros`,
294/// `macro_recording`, `last_played_macro`).
295/// - 5.B.5 -- marks + registers (`marks`, `registers`,
296/// `pending_register`, `unnamed_register`).
297/// - 5.B.6 -- position history + tag stack
298/// (`position_history`, `position_history_cursor`,
299/// `recent_files`, `tag_stack`, `pending_tag_origin`).
300/// - 5.B.7 -- search state (`search_line`, `last_search`,
301/// `current_match`, `all_matches`, `substitute_preview`).
302/// - 5.B.8 -- vim repeat + visual state (`pending_count`,
303/// `op_count`, `visual_anchor`, `last_change`,
304/// `last_visual`, `last_find`).
305/// - 5.B.9 -- replace + insert state (`replace_history`,
306/// `last_insert`, `recording_insert`,
307/// `pending_block_insert`).
308/// - 5.B.10 -- popup (subset) (`popup_buffer`,
309/// `prev_pane_for_popup`, `popup_placement`). Skipped:
310/// `popup_back_stack` -- holds `PopupSnapshot` which still
311/// lives in `lattice-ui-tui::app::popup`; follow-up slice
312/// moves the snapshot type to host before migrating the
313/// field.
314/// - 5.B.11 -- cmdline + echo (`command_line`,
315/// `last_message`, `messages`,
316/// `pending_message_event_rx`, `pending_redraw`,
317/// `command_history`, `command_history_cursor`,
318/// `command_history_pending`, `auto_submit_after_chord`).
319/// - 5.B.12 -- syntax (`lang_registry`, `syntax`,
320/// `last_parsed_text_version`, `pending_syntax_edits`,
321/// `last_synced_syntax_version`, `visible_highlights`,
322/// `pane_highlights`). Skipped:
323/// `visible_highlights_key` -- its type
324/// `VisibleHighlightsKey` lives in
325/// `lattice-ui-tui::app::highlights` with `pub(super)`
326/// visibility; follow-up slice promotes it to host first.
327/// - 5.B.13 -- picker (`picker`, `picker_registry`,
328/// `picker_mru`, `picker_mru_path`,
329/// `pending_picker_init`, `live_picker_query`,
330/// `previewing`). Picker support types (`PendingPickerInit`,
331/// `LivePickerQueryState`, `InFlightLiveQuery`,
332/// `LIVE_PICKER_DEBOUNCE`) also moved from
333/// `lattice-ui-tui::app` to `lattice_host::state`.
334/// - 5.B.14 -- config + modes (`config`, `option_cache`,
335/// `mode_registry`, `services`, `mode_guards`,
336/// `active_modes`, `buffer_locals`, `resolved_options`,
337/// `buffer_local_overrides`, `option_change_rx`,
338/// `help_topics`, `host_theme`).
339/// - 5.B.15 -- modal + dispatch (`modal`, `partial_chord`,
340/// `registry`, `event_bus`, `builtins`, `action_ids`,
341/// `keymap`, `completion_popup_layer`).
342/// - 5.B.16 -- active-pane state (subset) (`cursor`,
343/// `scroll`, `should_quit`, `viewport_height`,
344/// `terminal_width`, `active_buffer`,
345/// `document_buffer_id`, `buffers`). Skipped:
346/// `document`, `snapshot_cache`, `pane_tree` --
347/// their types have no natural `Default` (actor handles +
348/// tree-with-root invariants). Follow-up slice removes
349/// `#[derive(Default)]` from `Editor` in favour of an
350/// `Editor::new(...)` constructor so these can migrate.
351/// - 5.B.17 -- LSP per-buffer caches (`lsp_progress`,
352/// `lsp_selection_chain`, `lsp_selection_chain_index`,
353/// `lsp_document_highlights`,
354/// `last_document_highlight_issue_cursor`,
355/// `lsp_folds_cache`, `lsp_inlay_hints_cache`,
356/// `lsp_document_links_cache`, `lsp_code_lens_cache`,
357/// `lsp_document_color_cache`,
358/// `lsp_semantic_tokens_cache`,
359/// `lsp_pull_diagnostics_cache`).
360/// - 5.B.18 -- all remaining LSP fields: subsystem handles
361/// (`lsp`, `lsp_diagnostics`, `lsp_logger`, plus
362/// `lsp_log_event_rx`, `lsp_progress_event_rx`,
363/// `lsp_config_tree`, `buffer_uris`), server-initiated
364/// channels (`pending_apply_edit_rx`,
365/// `pending_show_message_request_rx`,
366/// `lsp_pending_show_message_requests`,
367/// `lsp_show_message_request_queue`,
368/// `lsp_next_show_message_request_id`), and all per-
369/// feature request channels (the `pending_*_rx` /
370/// `pending_*_token` pairs for hover, definition,
371/// references, symbols, format, signature-help,
372/// completion, moniker, rename, code-action,
373/// selection-range, document-highlight, folding-range,
374/// document-links, code-lens, document-color, inlay-hint,
375/// semantic-tokens, pull-diagnostics, plus the refresh
376/// channels and the lifecycle / detach channels).
377/// `LspSupervisorHandle` and `DiagnosticsLayer` gained
378/// placeholder `Default` impls (dropped-receiver
379/// channels; production overwrites in `boot.rs`).
380/// `lsp_file_watcher` stays on App for now: its inner
381/// type `LspFileWatcher` lives in `lattice-ui-tui::app::
382/// lsp_watcher` -- migrates with a follow-up that moves
383/// the watcher into a host module.
384/// - 5.B.19 -- call-site migration for all LSP per-feature
385/// request channel fields scaffolded in 5.B.18: updated
386/// all `self.pending_*` / `app.pending_*` accesses in
387/// `app/lsp.rs`, `app/boot.rs`, `app/picker.rs`,
388/// `app/mode.rs`, `app/completion.rs` to
389/// `self.editor.pending_*`; removed the now-redundant
390/// duplicate declarations from `App`. Completion cluster
391/// (`completion_registry`, `completion_state`,
392/// `insert_completion`, etc.) stays on App -- next slice.
393/// - 5.B.20 -- completion cluster tail + popup back-stack +
394/// pending config bucket. `insert_completion`,
395/// `snippet_registry`, `insert_completion_snippet_meta`,
396/// `completion_accept_freq`, `per_language_completion`,
397/// `completion_in_path_context`, `active_snippet`,
398/// `snippet_dirs`, `popup_back_stack` (popup #7 tail), and
399/// `pending_config_structural_sections` move from `App`
400/// to `Editor`. `SnippetCandidateMeta` moved from
401/// `lattice-ui-tui::app` to `lattice-host::state` so
402/// the sidecar type lives next to the field that owns it;
403/// `lattice-ui-tui::app` re-exports the type for
404/// compatibility. After this slice the only fields left on
405/// `App` are the renderer-specific caches (`theme`,
406/// `pane_render_registry`) plus the `LspFileWatcher`
407/// wrapper -- `App` becomes a thin renderer wrapper.
408#[derive(Debug, Default)]
409pub struct Editor {
410 /// Perf plan B.4: identity-preserving sub-state cache for
411 /// `build_render_state`. Cached `Arc<SubState>` slots keyed
412 /// by the `u64` version captured from the corresponding
413 /// `Versioned<T>` field. `std::sync::Mutex` (not `RefCell`)
414 /// because `Editor` is shared across threads as `Arc<Editor>`
415 /// and therefore must be `Sync`; uncontested in practice
416 /// because only `build_render_state` (called on the actor
417 /// thread) takes the lock. See
418 /// [`crate::render_state::PublishCache`] for the slot
419 /// inventory and rebuild contract.
420 pub publish_cache: std::sync::Mutex<crate::render_state::PublishCache>,
421
422 /// Completed macro recordings keyed by register name.
423 /// Replays go through the dispatch layer's `PlayMacro`
424 /// action handler. v1 records `Action` streams; insert-
425 /// mode keystrokes ARE captured (every Action::Insert is
426 /// recorded), but dot-repeat-style replay of insert content
427 /// from `c`/`i`/`a` remains a §15 follow-up.
428 pub macros: HashMap<char, Vec<Action>>,
429 /// In-flight macro recording. `Some` while between
430 /// `q<reg>` start and the matching `q` stop; pushed
431 /// Actions append to `actions`.
432 pub macro_recording: Option<MacroRecording>,
433 /// The most recently played macro register, for `@@`
434 /// repeat.
435 pub last_played_macro: Option<char>,
436 /// Unnamed register -- destination of `y` / `d` / `c`,
437 /// source of `p` / `P`. `None` until something has been
438 /// yanked.
439 pub unnamed_register: Option<UnnamedRegister>,
440 /// User-set marks. v1 stores them flat by name (a-z,
441 /// A-Z, 0-9); uppercase / numbered global marks treat
442 /// all marks as buffer-local since the v1 TUI runs
443 /// against a single document.
444 pub marks: HashMap<char, Position>,
445 /// Named registers `"a-z`, `"A-Z`, numbered `"0-"9`,
446 /// etc. Stores content + kind. `""` (the unnamed
447 /// register) is [`Self::unnamed_register`]; this map
448 /// covers everything else.
449 pub registers: HashMap<Register, UnnamedRegister>,
450 /// YR.1: every yank and every delete, newest first. Distinct from
451 /// `registers`, which is addressed by name — this is addressed by
452 /// recency, and it is what the yank picker lists and what YR.2's
453 /// `"0`–`"9` projection reads.
454 pub yank_ring: crate::state::YankRing,
455 /// Register selected for the next operator / paste
456 /// (`"a` prefix). Consumed-and-cleared by `run_invocation`
457 /// (operators) and `do_paste` (paste). `None` means use
458 /// unnamed.
459 pub pending_register: Option<Register>,
460 /// Unified position-history ring (DESIGN.md §5.1.1).
461 /// Every entry is tagged by source so different keybindings
462 /// walk filtered views of the same data (`Ctrl-O` / `Ctrl-I`
463 /// walk `AutoJump` + `PluginPush`; `g;` / `g,` walk
464 /// `NamedMark`).
465 pub position_history: Vec<PositionEntry>,
466 /// Cursor into [`Self::position_history`] -- the next entry
467 /// the navigation action would visit.
468 pub position_history_cursor: usize,
469 /// CM.2 (2026-07-22): the error list — a persistent,
470 /// cross-file list of navigable locations walked by generic
471 /// `:cnext` / `]q` dispatch. Core/host state shaped like
472 /// [`Self::position_history`], NOT owned by any mode:
473 /// compilation (CM.3), diagnostics, and search are *producers*
474 /// that populate it via [`Self::set_error_list`]. See
475 /// `docs/dev/architecture/compilation-mode.md` §3.
476 pub error_list: crate::error_list::ErrorList,
477 /// CM.3c (2026-07-22): per-buffer severity gutter index for the
478 /// `*compilation*` buffer, keyed by [`lattice_core::BufferId`].
479 /// Written by the `AppEffect::CompilationGutterSet` arm (the
480 /// off-thread compilation drain's host-state seam) and snapshotted
481 /// into `RenderState::compilation_severity` at publish. Shaped like a
482 /// producer-fed cache (compilation is the only producer today); the
483 /// outer `Arc` lets the publish clone be O(1) and the inner
484 /// per-buffer `Arc<Vec<..>>` lets the renderer read wait-free.
485 pub compilation_severity: std::sync::Arc<
486 std::collections::HashMap<
487 lattice_core::BufferId,
488 std::sync::Arc<Vec<(u32, lattice_mode::GutterSeverityLevel)>>,
489 >,
490 >,
491 /// CM.3c (2026-07-22): per-buffer compilation location-line
492 /// index for theme-based highlighting. Twin of
493 /// `compilation_severity` above. Written by the
494 /// `AppEffect::CompilationLocationLines` arm and snapshotted
495 /// into `RenderState::compilation_location_lines` at publish.
496 /// The outer `Arc` lets the publish clone be O(1) and the
497 /// inner per-buffer `Arc<Vec<(u32,u32,u32)>>` lets the renderer read
498 /// wait-free.
499 pub compilation_location_lines: std::sync::Arc<
500 std::collections::HashMap<lattice_core::BufferId, std::sync::Arc<Vec<(u32, u32, u32)>>>,
501 >,
502 /// MC.2b: per-buffer set of source lines inside a fenced/indented code
503 /// block, for the full-width `syntax.code_block` background tint. Recomputed
504 /// from the syntax tree in `recompute_folds_because` (the same reparse-driven
505 /// trigger folds ride) and snapshotted into `RenderState::code_block_lines`.
506 /// Outer `Arc` = O(1) publish clone; inner per-buffer `Arc` = wait-free read.
507 pub code_block_lines:
508 std::sync::Arc<std::collections::HashMap<lattice_core::BufferId, std::sync::Arc<Vec<u32>>>>,
509 /// MG.21a (2026-07-29): per-buffer diff sign maps published by a
510 /// *mode*, for buffers whose content is itself a unified diff.
511 /// Written by the `AppEffect::DiffLineSigns` arm and merged into
512 /// `RenderState::diff.sign_maps` by `diff_sign_maps_by_buffer`, so
513 /// the existing line-tint path in both renderers picks them up
514 /// unchanged.
515 ///
516 /// Kept as a slot separate from the session-derived maps because the
517 /// two have different lifetimes: a session map is recomputed as the
518 /// user edits, while these are rewritten only when the mode
519 /// regenerates the buffer. Sessions win on the (currently
520 /// impossible) key collision — see `diff_sign_maps_by_buffer`.
521 pub provider_diff_signs: std::sync::Arc<
522 std::collections::HashMap<
523 lattice_core::BufferId,
524 std::sync::Arc<crate::diff::overlay::DiffSignMap>,
525 >,
526 >,
527 /// CM.3d (2026-07-22): resolved `compilation.location` theme
528 /// colours — published by the mode during activation so the TUI
529 /// and GPUI renderers read from the theme rather than hardcoding
530 /// RGB. `bg` is the location-line background tint; `fg` is the
531 /// link-like file-path foreground. Defaults: surface2 bg, blue fg.
532 pub compilation_theme_colors: std::sync::Arc<(u32, u32)>,
533 /// MRU list of canonical paths the user has opened via
534 /// `:edit` (or any path flowing through `do_edit`). Newest
535 /// first; deduplicated; capped at `MAX_RECENT_FILES`. Source
536 /// for the `:recent` picker.
537 pub recent_files: Vec<PathBuf>,
538 /// Vim-style tag stack (DESIGN.md §5.1.1 follow-up).
539 /// Distinct from the jump list: each "drill-down" navigation
540 /// (`gd` / `gD` / `gy` / `gI` and their multi-result picker
541 /// accept variants) pushes one entry; `<C-t>` pops the most
542 /// recent entry. `<C-o>` walks all jumps chronologically;
543 /// `<C-t>` pops only the LIFO tag-style drill-downs.
544 pub tag_stack: Vec<TagStackEntry>,
545 /// Pre-jump origin captured when an LSP nav request fires;
546 /// transferred to [`Self::tag_stack`] on the actual jump
547 /// (single-result drain or multi-result picker accept).
548 /// Cleared on picker dismiss / nav cancellation / drain
549 /// with no results.
550 pub pending_tag_origin: Option<TagStackEntry>,
551 /// In-progress `/` or `?` search. `Some` only while
552 /// `modal == ModalState::Search(_)`.
553 pub search_line: Option<SearchLine>,
554 /// The `action:*` name to fire on `<CR>` while a generic
555 /// `Effect::OpenPrompt` prompt is focused (`modal ==
556 /// ModalState::Prompt`). Set by `open_prompt_line`, consumed
557 /// (`.take()`) by `do_prompt_line_submit`.
558 pub pending_prompt_submit_action: Option<String>,
559 /// OC.3a: the synthetic name the prompt was opened with, held so the
560 /// submit can hand it back to the action.
561 ///
562 /// `open-prompt-payload.buffer-name` is documented as the channel a caller
563 /// uses to "smuggle state through a multi-step flow", and a NATIVE handler
564 /// reads it off the prompt buffer it is handed. A plugin action cannot: it
565 /// receives a `buffer-id` over WIT and has no way to turn that into a name.
566 /// So the host carries it back in the fired invocation's args instead —
567 /// same channel, reachable from both sides.
568 pub pending_prompt_buffer_name: Option<String>,
569
570 /// MG.17b: the transient a prompt was opened *from*, held across
571 /// the surface switch.
572 ///
573 /// A transient's `TransientState` already survives flag toggles —
574 /// the `Flag` arm mutates it in place and the menu stays open, and
575 /// the preview closure re-reads it every frame. What it cannot
576 /// survive on its own is an `Argument`: the prompt is a different
577 /// surface that takes the editing buffer and the modal state, so
578 /// the picker is torn down and something has to own the menu until
579 /// it can be re-seated. That is this.
580 ///
581 /// `Some` only between opening an argument's prompt and its submit
582 /// or cancel.
583 pub pending_transient_argument: Option<PendingTransientArgument>,
584 /// Most recent submitted search; consulted by `n` / `N`.
585 pub last_search: Option<LastSearch>,
586 /// Range of the most recent search match, used to draw
587 /// the primary highlight in the buffer view. Cleared on
588 /// Esc and on cursor motion.
589 pub current_match: Option<ProtoRange>,
590 /// Every occurrence of the most recent search pattern,
591 /// used to draw the secondary "hlsearch" overlay.
592 /// Cleared on Esc; persists after submit until the next
593 /// search.
594 pub all_matches: Vec<ProtoRange>,
595 /// In-progress substitute preview. Populated as the user
596 /// types `:s/pat...`; the renderer overlays match ranges
597 /// (and the typed replacement once the second `/` has
598 /// been entered) so the user sees the substitution before
599 /// pressing Enter. Cleared when the cmdline closes or the
600 /// input no longer parses as a substitute (DESIGN.md
601 /// §5.9.10).
602 pub substitute_preview: Option<SubstitutePreview>,
603 /// MB.4: live `:` command-line decorations — syntax spans, a
604 /// validation error, and a parameter hint. Recomputed on every
605 /// command-line edit (`refresh_command_line_decorations`, on the
606 /// actor thread) and published into the modeline render state.
607 /// `None` when the command line is closed / empty.
608 pub command_line_decorations: Option<crate::excommand::CommandLineDecorations>,
609 /// In-progress count prefix being typed (`3` of `3w`,
610 /// `12` of `12dd`). 0 means "no count typed". The next
611 /// `Action::Invoke` consumes this and resets it to 0.
612 pub pending_count: u32,
613 /// Count latched when an operator key was pressed (`2`
614 /// of `2d3w`). Multiplied with the motion's count (`3`)
615 /// to give the final count the operator dispatches with
616 /// (`6`). 0 means "no operator count".
617 pub op_count: u32,
618 /// Anchor position when Visual mode was entered. `None`
619 /// outside Visual; restored on Esc. The `head` of the
620 /// selection follows the cursor; the anchor stays put so
621 /// the selection extends or contracts as the user moves.
622 pub visual_anchor: Option<Position>,
623 /// Last operator-class invocation that mutated the
624 /// buffer. `.` re-dispatches it from the current cursor.
625 /// v1 records operator + motion / operator + range /
626 /// Visual-mode operator; insert-mode text replay remains
627 /// a §5.2.4 gap.
628 pub last_change: Option<CommandInvocation>,
629 /// Last Visual-mode selection extents, captured on exit
630 /// so `gv` can re-enter Visual with the same anchor /
631 /// head / kind.
632 pub last_visual: Option<LastVisual>,
633 /// Last f/F/t/T find on this buffer, for `;` / `,`.
634 pub last_find: Option<LastFind>,
635 /// Per-Replace-session log of overwritten bytes so
636 /// backspace can restore the original (rather than
637 /// deleting). Cleared on entry, pushed on each
638 /// `OverwriteChar`, popped on `ReplaceUndoLast`.
639 pub replace_history: Vec<ReplaceEntry>,
640 /// IN.1: line that was auto-indented and has gained nothing
641 /// since. Vim strips such indent when the line is left, so
642 /// `o<Esc>` leaves no trailing whitespace on a line the user
643 /// never typed into. Set by `note_auto_indent`, cleared by the
644 /// first content typed into the line, consumed by
645 /// `strip_pending_auto_indent` on leaving Insert.
646 pub auto_indent_line: Option<u32>,
647 /// RF.5: whether the `formatprg` deprecation note has been emitted
648 /// this session.
649 ///
650 /// Once, not per format: a note repeated on every `:w` with
651 /// `formatonsave` on is noise the user learns to scroll past, which
652 /// is the opposite of what a deprecation notice is for.
653 pub formatprg_deprecation_noted: bool,
654 /// Text inserted during the most recently completed
655 /// Insert session. Captured on Esc out of Insert;
656 /// replayed by dot-repeat after the operator part. `None`
657 /// if the last change had no insert phase.
658 pub last_insert: Option<String>,
659 /// In-flight blockwise-visual `I` / `A` session. Captured
660 /// at mode-entry time (block extents + per-line insert
661 /// column); consumed when Insert exits, at which point
662 /// the recorded text is replicated to every line in the
663 /// block other than the top row (the top row's insert is
664 /// the recording itself). `None` outside a block-visual
665 /// insert.
666 pub pending_block_insert: Option<PendingBlockInsert>,
667 /// Text being captured during the *current* Insert
668 /// session. Promoted into [`Self::last_insert`] when
669 /// leaving Insert.
670 pub recording_insert: Option<String>,
671 /// Active popup buffer slot, if a popup overlay is open.
672 /// The concrete content lives in the [`crate::buffer_registry::BufferRegistry`]
673 /// keyed by this id, flagged
674 /// `BufferFlags { listed: false, hidden: true }`.
675 pub popup_buffer: Option<BufferId>,
676 /// Pane state captured before activating help -- used by
677 /// `dismiss_popup` to restore the user to whatever buffer
678 /// / cursor / scroll they came from. Set by the in-pane
679 /// help activation path; cleared by dismiss.
680 ///
681 /// **Popup lifetime only.** The bury-back address for an
682 /// in-pane synthetic buffer lives in [`Self::bury_target`]
683 /// and is deliberately a different field -- see there.
684 pub prev_pane_for_popup: Option<PrevPaneState>,
685 /// The pane [`Editor::open_help_in_split`] created for a help buffer, when
686 /// help was opened in its OWN split (not layered over an existing pane).
687 /// `dismiss_popup` reads it to decide between CLOSING that pane (help
688 /// brought it into being, so `<Esc>` removes it) and restoring the buffer
689 /// an active-pane help displaced. `None` for popup / active-pane help and
690 /// once the pane is gone. Cleared on dismiss.
691 pub help_split_pane: Option<lattice_core::ui::pane::PaneId>,
692 /// Where `Effect::BuryBuffer` (magit's `q`) returns the pane
693 /// after an in-pane synthetic buffer is closed. Written by
694 /// `open_synthetic_buffer_seeded`, consumed by
695 /// [`Editor::bury_buffer`](crate::editor::Editor::bury_buffer).
696 ///
697 /// **Its own field because the two mechanisms have different
698 /// lifetimes.** Both used to share `prev_pane_for_popup`, and
699 /// whichever teardown ran first consumed whatever was in the
700 /// slot regardless of who wrote it. Reported against
701 /// org-capture: a transient menu was still up (State A) when
702 /// the `*org-capture*` draft opened, so the draft's bury
703 /// address landed in the shared slot; dismissing the menu
704 /// then hand-restored the pane to the pre-capture buffer
705 /// *without* going through `activate_buffer` -- silently,
706 /// since only that function echoes. `C-c C-c`'s
707 /// `Effect::BufferDelete` resolves its target from
708 /// `active_pane_buffer_id()`, so it deleted the buffer the
709 /// user came from and left the draft alive, unsaved and
710 /// unreachable. MG.47 had patched one call site of this on
711 /// the reasoning that a State-A popup "leaves
712 /// `prev_pane_for_popup` as `None` -- so this guard cannot
713 /// move the pane"; that holds only until something opens a
714 /// synthetic buffer underneath the popup.
715 ///
716 /// See `crates/lattice-host/tests/a_popup_dismiss_must_not_move_the_pane.rs`.
717 pub bury_target: Option<PrevPaneState>,
718 /// Where the popup overlay sits on screen when one is
719 /// open. Lives on the editor (not on the buffer) because
720 /// the popup is a generic rectangular surface inside
721 /// which any buffer kind renders -- placement is a
722 /// property of the popup, not of whatever buffer happens
723 /// to be its content.
724 pub popup_placement: PopupPlacement,
725 /// PU refactor (2026-07-22): decoupled focus-state bool,
726 /// extracted from `active_buffer == BufferKind::Help` which
727 /// was an architectural leak — a focus flag smuggled inside
728 /// a content-identity field. `true` when a Steal popup has
729 /// keyboard focus (State B). Set in `focus_help_popup`,
730 /// `activate_help_in_pane`, `open_popup_buffer` (Steal);
731 /// cleared in `dismiss_popup`. Published in render state so
732 /// both TUI and GPUI renderers read it directly.
733 pub popup_focused: bool,
734 /// Cursor position snapshot at popup-open time, used as
735 /// the anchor for `CursorAnchored` popups. Captured in
736 /// `open_floating_popup` / `open_popup` BEFORE any cursor
737 /// mutation so the renderer paints the popup at the
738 /// symbol the user pressed K on, not the cursor's current
739 /// position. Issue 2026-05-22 (third triage round): without
740 /// this the popup follows the cursor — moving with motions
741 /// rather than staying anchored. `None` when no popup is
742 /// open OR for centered popups (anchor irrelevant).
743 pub popup_anchor: Option<lattice_protocol::Position>,
744 /// Document scroll captured at popup-open time so
745 /// CursorAnchored renderers can convert `popup_anchor.line`
746 /// to a screen row in State B (where `self.scroll` is the
747 /// POPUP's scroll, not the document's).
748 pub popup_doc_scroll_at_anchor: u32,
749 /// PU.1a: the popup's persisted view state when it is NOT the
750 /// focused buffer (State A), and the stash loaded into
751 /// `self.scroll` / `self.cursor` when focus moves into the
752 /// popup (State B). Replaces the old `HelpBuffer.{scroll,cursor}`
753 /// registry fields now that help content is an actor-backed
754 /// Document with no per-view cursor of its own. Reset to 0 /
755 /// ZERO on every popup open; updated by `snapshot_active_pane`
756 /// when an in-pane help buffer is stashed.
757 pub popup_scroll: u32,
758 pub popup_cursor: lattice_protocol::Position,
759 /// PU.1b-3: inner-rect geometry of the floating popup overlay,
760 /// fed back from the renderer each frame (mirrors the per-pane
761 /// `set_pane_viewport` hand-off). The renderer is the single
762 /// authority on the popup's inner rect — it computes it from the
763 /// buffer area + `popup_outer_size` + placement — so it pushes the
764 /// resolved `(height, width)` here. `build_cells_panes` reads them
765 /// to size the synthetic popup-pane `DisplayMatrix` (wrap width =
766 /// `popup_viewport_width`). Both 0 until the first feedback after a
767 /// popup opens; the synthetic pane is gated on
768 /// `popup_viewport_width > 0` so a zero-geometry frame is skipped
769 /// (the renderer's plain-text fallback covers that one frame).
770 pub popup_viewport_height: u32,
771 pub popup_viewport_width: u32,
772 /// PU.5c: the ephemeral registry buffer backing the Insert-mode
773 /// completion-docs side popup. `None` when no docs are shown.
774 /// Reconciled from `insert_completion.doc_popup.body` once per cycle
775 /// (`reconcile_completion_docs_buffer` in `run_tick_pending`):
776 /// created when docs appear, text-replaced when they change, and
777 /// garbage-collected when they vanish — a single chokepoint instead
778 /// of the scattered `insert_completion = None` teardown sites. It is a
779 /// help-flavoured `BufferData::Help` Document with the `ephemeral`
780 /// flag, so it reuses markdown syntax + link styling + the
781 /// `nonu`/`signcolumn=no`/`wrap` help-mode options and renders through
782 /// the shared compose seam (`PaneId::COMPLETION_DOCS`).
783 pub completion_docs_buffer: Option<BufferId>,
784 /// Picker live-preview reusable slot. A single ephemeral, read-only
785 /// buffer the find-file (and other file-opening) picker previews load
786 /// content into as the selection changes — instead of running the full
787 /// `do_edit` open path per keystroke (which synchronously parses +
788 /// attaches LSP + leaks a registry buffer per candidate, freezing the
789 /// UI thread). `do_preview` reuses this one buffer; `do_edit` is
790 /// reserved for the final accept. GC'd on picker dismiss.
791 pub preview_buffer: Option<BufferId>,
792 /// PU.5c: inner-rect geometry of the completion-docs side popup, fed
793 /// back from the renderer each frame (the second synthetic popup, peer
794 /// of `popup_viewport_*`). `build_cells_panes` reads them to size the
795 /// `PaneId::COMPLETION_DOCS` matrix; the synthetic pane is gated on
796 /// `completion_docs_viewport_width > 0`.
797 pub completion_docs_viewport_height: u32,
798 pub completion_docs_viewport_width: u32,
799 /// WK.12: the minibuffer band's buffer — a popup opened with
800 /// `PopupPlacement::MinibufferBand` lands HERE rather than in
801 /// [`Self::popup_buffer`], and the two are independent: opening the band
802 /// never dismisses a popup, which is the bug this slot exists to remove.
803 /// Never focused, so it has no anchor, scroll or focus-stack frame.
804 pub band_buffer: Option<BufferId>,
805 /// Renderer-fed inner geometry for the band, exactly as
806 /// `completion_docs_viewport_*` is fed. The synthetic
807 /// `PaneId::MINIBUFFER_BAND` pane is gated on `band_viewport_width > 0`.
808 pub band_viewport_height: u32,
809 pub band_viewport_width: u32,
810 /// VM.3g-1: vim's `curswant` — the column `j` / `k` aim for, which survives
811 /// a short line in between. `None` until something sets one.
812 pub curswant: Option<lattice_grammar::Curswant>,
813 /// VM.3g-1: set when THIS dispatch was a motion that keeps or pins the goal
814 /// column (`j` / `k` / `$`). Everything else — every other motion, every
815 /// edit, Insert exit and yank — lets the dispatch tail set the goal from
816 /// wherever the cursor ended up, which is vim's rule.
817 pub curswant_claimed: bool,
818 /// VM.3g-3: where a motion reports the goal column it AIMED at, when that
819 /// is not the column it reached. Only `gj` / `gk` do: they aim at a screen
820 /// column the landing display row may be too short to hold, and vim keeps
821 /// the aim (measured: a clamped `gj` landing at column 100 records 160).
822 ///
823 /// A long-lived slot on the Editor rather than one built per dispatch
824 /// because `dispatch_blocking` takes `&self` — it can clone this `Arc`
825 /// into the `DispatchEnv` but cannot hand anything back. The dispatch tail,
826 /// which has `&mut self`, `take()`s it.
827 ///
828 /// Taken rather than read: a motion writes it on every motion dispatch
829 /// (`None` for all but two), but an operator or action never touches it,
830 /// so leaving a value behind would let one `gj`'s aim resurface after an
831 /// unrelated command.
832 pub curswant_report: std::sync::Arc<std::sync::Mutex<Option<lattice_grammar::Curswant>>>,
833 /// FS.1: the focus **stack** — one frame per surface that has taken
834 /// focus away from the pane's own buffer, innermost last.
835 ///
836 /// MB.1 introduced this as a single `Option`, which was right while only
837 /// one surface could hold focus: the `:` line, the `/` line, or a
838 /// prompt. It is wrong the moment focus can nest, and it can — a popup
839 /// holds focus, and `/` inside it takes focus again:
840 ///
841 /// ```text
842 /// [] editing the file
843 /// [popup] the popup has focus
844 /// [popup, search-line] …and `/` inside it
845 /// ```
846 ///
847 /// As an `Option` the second push recorded nothing (it was guarded on
848 /// `is_none()`) and one restore returned to the FILE, skipping the popup
849 /// — which is how `<Esc>` out of a search left the editor focused
850 /// nowhere. See `focused-surface.md` §2.
851 ///
852 /// [`Editor::focused_surface`] reads the top frame; use it rather than
853 /// indexing, so "is something focused" and "what is focused" stay one
854 /// question.
855 pub focus_stack: Vec<crate::state::MinibufferFocus>,
856 /// FS.2: the [`Self::focus_stack`] depth a focused popup sits at, so
857 /// dismissing it unwinds any surface opened INSIDE it (a `/` line, a
858 /// prompt) rather than leaving that focused over a popup that is gone.
859 /// `None` whenever no popup holds focus.
860 pub popup_focus_depth: Option<usize>,
861 /// Most recent transient status / error message,
862 /// displayed in the echo area until replaced.
863 pub last_message: Option<EchoMessage>,
864 /// Append-only chronological ring of every echo
865 /// (`set_message` call). The `:messages` ex-command
866 /// opens a `*messages*` buffer rendered from this ring
867 /// (the emacs `*Messages*` analogue). Bounded by
868 /// `MessagesRing::capacity`. Wrapped in `Arc<Mutex<>>`
869 /// so the boot-installed `MessagesLayer` (a
870 /// `tracing::Layer` running on whatever thread emitted
871 /// the event) can push into the same ring the App reads
872 /// on the main thread for backlog seeding.
873 pub messages: std::sync::Arc<std::sync::Mutex<MessagesRing>>,
874 /// Receiver for [`lattice_runtime::MessagePushed`] events
875 /// published by `set_message`. The runtime's per-tick
876 /// drain coalesces bursts and rebuilds the `*messages*`
877 /// buffer view once per frame.
878 pub pending_message_event_rx: Option<tokio::sync::mpsc::UnboundedReceiver<MessagePushed>>,
879 /// Set by `Action::RedrawScreen` (`<C-l>`); the runtime
880 /// clears this on its next frame after issuing a full
881 /// terminal-clear so any leftover ANSI / stale glyph
882 /// state gets repainted from scratch.
883 pub pending_redraw: bool,
884 /// Submitted `:` command history. Newest at the back.
885 /// Bounded.
886 pub command_history: Vec<String>,
887 /// While in Command modal: index into
888 /// [`Self::command_history`] of the entry currently
889 /// shown (`None` = the user's in-progress text).
890 pub command_history_cursor: Option<usize>,
891 /// Snapshot of the user's typed `command_line` on the
892 /// first Up so Down can return to it after walking
893 /// through history.
894 pub command_history_pending: Option<String>,
895 /// MB.5b: search-line history (peer of `command_history`).
896 /// Each submitted `/` or `?` pattern is pushed here;
897 /// `<C-p>`/`<C-n>` walk this in the search line.
898 pub search_history: Vec<String>,
899 /// MB.5b: index into `search_history` during history walk.
900 pub search_history_cursor: Option<usize>,
901 /// MB.5b: user's typed pattern saved on first `<C-p>` so
902 /// `<C-n>` can return to it.
903 pub search_history_pending: Option<String>,
904 /// Chord-capture overlay flag. Set when the user submitted
905 /// a Chord-arg-required command with no value
906 /// (`:describe-key<CR>` or the K.3.2 `<C-h>k` binding); the
907 /// cmdline pre-fills with the command word + space and
908 /// translation routes every key through
909 /// `translate_command_chord_capture` so plain letters
910 /// appear as chord tokens (`g` → `g`, `<C-c>` → `<C-c>`,
911 /// `<Up>` → `<Up>`, ...). The renderer reads this through
912 /// `auto_submit_hint` to draw the chord-capture cmdline
913 /// hint. Reset on cancel / submit.
914 ///
915 /// K.3.5.fix (2026-06-03): the field's original purpose
916 /// also included auto-submitting on the FIRST captured
917 /// chord token — that auto-submit was dropped because
918 /// chord arguments are sequences (`gg`, `<C-w>v`, `]e`,
919 /// `<leader>fz`), not single chords. The user now types
920 /// the full chord text and submits with `<CR>`. Field name
921 /// kept for backward compat across the renderer / context
922 /// boundaries; behavior is "chord-capture mode active,"
923 /// no longer "auto-submit on chord."
924 ///
925 /// DK.2 (2026-09-07): the explicit `<CR>` is gone again, but not by
926 /// reinstating auto-submit-on-first-chord. The trie decides —
927 /// see [`Self::chord_capture_seq`].
928 pub auto_submit_after_chord: bool,
929 /// DK.2: the chords captured so far in the current chord-capture session.
930 ///
931 /// Capture has to accumulate a SEQUENCE (`gg`, `<C-w>v`, `<leader>fz`), so
932 /// it cannot submit on the first keystroke; but making the user terminate
933 /// with `<CR>` meant `<CR>` — and the `<Esc>` and `<BS>` reserved beside it
934 /// — could never themselves be described. The keymap trie already
935 /// distinguishes "waiting for more" (`Partial`) from "this is the answer"
936 /// (`Bound` / `Unbound`) on every ordinary keystroke, so capture asks it
937 /// instead of asking the user, and no key needs reserving.
938 ///
939 /// Parallel to the command line's TEXT rather than derived from it: the
940 /// line also holds the command word and any earlier args, and re-parsing a
941 /// chord argument back out of it would mean re-deriving the arg span on
942 /// every keystroke to answer a question this vector answers directly.
943 /// Cleared when a prompt arms, and on submit / dismiss.
944 pub chord_capture_seq: Vec<lattice_protocol::chord::KeyChord>,
945 /// Tree-sitter language registry. Services the document
946 /// buffer's `Syntax` and every `HelpBuffer` constructed
947 /// by `:describe-*` / `:apropos` / `:keymap` (help
948 /// bodies render with markdown highlighting + fenced-
949 /// block injections sourced from this same registry).
950 pub lang_registry: Arc<LangRegistry>,
951 /// Per-document tree-sitter state. `None` when the
952 /// document's language is `Plain` (no grammar bundled).
953 /// Reparses run on a worker task; reads against the
954 /// latest snapshot are wait-free via `ArcSwap`.
955 pub syntax: Option<SyntaxHandle>,
956 /// `text_version` last sent to the syntax handle's
957 /// reparse channel. Used to skip republishing identical
958 /// state when no text mutation has happened since the
959 /// previous frame.
960 pub last_parsed_text_version: u64,
961
962 /// Tree-sitter-shaped edit deltas accumulated since the
963 /// last `maybe_reparse_syntax` call. Pushed by
964 /// `publish_document_changed` after each
965 /// `Buffer::apply_edit`; drained by
966 /// `maybe_reparse_syntax` and shipped to the syntax
967 /// worker as `Vec<EditDelta>` for incremental reparse.
968 pub pending_syntax_edits: Vec<EditDelta>,
969 /// `text_version` the syntax worker's tree is known to
970 /// be at. Sent as `from_version` on the next reparse
971 /// request so the worker can verify edits apply to the
972 /// correct tree baseline.
973 pub last_synced_syntax_version: u64,
974 /// OWC: per-buffer text version last seen after a host-issued
975 /// edit. Used to detect owner writes: when the active document's
976 /// `text_version` exceeds this, the host did not issue the edit
977 /// and should adopt the document's primary selection head.
978 pub last_seen_text_version: HashMap<BufferId, u64>,
979 /// LA.2: the `LanguagesRegistered` drain — a plugin whose load changed the
980 /// mode/language catalog lands here, and `run_tick_pending` re-resolves the
981 /// majors and languages of buffers that were opened against the old one.
982 ///
983 /// A channel rather than a flag, and drained here rather than acted on in
984 /// the subscription, because the re-resolution needs `&mut Editor` and the
985 /// bus forwarder runs on the runtime. `EventBus::publish_typed` calls the
986 /// forwarder synchronously, so a publish is visible to the very next tick —
987 /// the paired wake forwarder in `editor_boot` is what makes that tick
988 /// happen without a keypress.
989 ///
990 /// `None` when nothing subscribed it (bare `Editor::default()` in tests);
991 /// the drain is then a no-op.
992 pub pending_catalog_change_rx:
993 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_plugin_loader::LanguagesRegistered>>,
994 /// Pane tree (DESIGN.md §5.9). Always represents the
995 /// ACTIVE tab's panes — when switching tabs we
996 /// `mem::swap` between this field and `tabs[target].panes`.
997 /// Perf plan B.4: wrapped in [`Versioned`] so the panes
998 /// sub-state cache in `build_render_state` can reuse its prior
999 /// `Arc<PanesRenderState>` when the tree hasn't moved since the
1000 /// last publish. `Deref` reads (e.g. `editor.pane_tree.active()`)
1001 /// do NOT bump; `DerefMut` accesses (split/close/set_active)
1002 /// fire one `u64` increment.
1003 pub pane_tree: Versioned<PaneTree>,
1004
1005 /// Issue #29 (2026-05-22): tab pages. Each `TabSlot` carries
1006 /// one tab's pane tree + optional label. The active tab's
1007 /// `panes` field is a default placeholder while live — its
1008 /// real tree sits on `editor.pane_tree`. Inactive tabs hold
1009 /// the full stashed tree. Always non-empty; default boot
1010 /// state is one tab whose pane_tree matches `editor.pane_tree`.
1011 /// Perf plan B.4.b: wrapped in [`Versioned`] so the tabs
1012 /// sub-state cache can detect when the tab list shape changes
1013 /// (push / remove / reorder). The composite cache key for
1014 /// `tabs` also includes `active_tab`, `pane_tree.version()`,
1015 /// and `buffers.version()` because label resolution reads
1016 /// across all four inputs.
1017 pub tabs: Versioned<Vec<lattice_core::ui::tab::TabSlot>>,
1018 /// Index of the active tab in `tabs`. Always valid (clamped
1019 /// on tab close).
1020 pub active_tab: usize,
1021
1022 /// Issue #32 (2026-05-22): override for the next picker
1023 /// accept's open routing. Set by `<C-s>` / `<C-v>` / `<C-t>`
1024 /// chords on picker overlays before dispatching the accept;
1025 /// read + cleared by `apply_picker_outcome` for the file-
1026 /// targeting variants (OpenFile / SwitchBuffer / JumpInBuffer
1027 /// / JumpToLocation). `Default` for `<CR>`.
1028 pub picker_open_target: lattice_picker::OpenTarget,
1029 /// YR.3: where a `FillCaller` accept should put its text, recorded
1030 /// when the picker was **opened**.
1031 ///
1032 /// Not resolved at accept: by then the picker has been dismissed and
1033 /// the modal state that identified the caller is gone, so resolving
1034 /// would read whatever context happens to be current. That is right
1035 /// often enough to pass a single-level test and wrong in exactly the
1036 /// picker-inside-a-prompt case this exists for.
1037 ///
1038 /// `None` means the picker was opened to act, not to answer, and a
1039 /// `FillCaller` arriving against it is a wiring bug the host reports
1040 /// rather than swallows.
1041 /// PC.1: the root the OPEN picker resolves against, overriding the active
1042 /// buffer's project until it closes.
1043 ///
1044 /// Beside its picker-scoped peers rather than threaded through
1045 /// `build_picker_context`, because a `live` source re-queries through
1046 /// `on_query_changed` — which sees the context and NOT the open's args — so
1047 /// the root has to outlive the single `init` call that carried it.
1048 ///
1049 /// `None` is the ordinary case: every picker before PC.1 resolved from the
1050 /// active buffer and still does.
1051 pub picker_root: Option<std::path::PathBuf>,
1052 pub picker_fill_target: Option<lattice_picker::FillTarget>,
1053 /// YR.6: the byte range on the `:` line a `FillCaller` should
1054 /// REPLACE rather than insert before.
1055 ///
1056 /// An argument picker is opened while the user is part-way through
1057 /// typing that argument (`:magit-checkout ma`), so the picked value
1058 /// has to take the place of `ma`. A plain insert — which is right
1059 /// for YR.5's `<C-r><C-r>`, where nothing was being replaced —
1060 /// would produce `mamain`, silently, and only for users who typed a
1061 /// prefix before opening the picker.
1062 ///
1063 /// Captured at open for the same reason the fill target is: by
1064 /// accept time the cursor has moved and the slot that identified
1065 /// the range is gone. `None` = insert at the cursor.
1066 pub picker_fill_replace: Option<(usize, usize)>,
1067 /// YR.5b: the picker the yank picker was opened *over*.
1068 ///
1069 /// `do_picker_accept` takes `self.picker` before applying the
1070 /// outcome, and `open_picker` replaces it — so without stashing, a
1071 /// yank picker opened from `:files` would destroy the `:files`
1072 /// picker on the way in and have nothing to fill on the way out.
1073 /// Restored both on accept (then filled) and on dismiss, because
1074 /// pressing Esc in the yank picker must return you to the list you
1075 /// were filtering rather than closing both.
1076 pub stashed_picker: Option<lattice_picker::Picker>,
1077
1078 /// T.12a: the theme to restore if the colorscheme picker is
1079 /// dismissed (`<Esc>`). Captured on the FIRST live preview as a
1080 /// `(palette, overrides)` snapshot of the theme active when the
1081 /// picker opened. `<Esc>` calls `ThemeRegistry::set_theme` with
1082 /// these to undo the preview; `<CR>` clears it (keeps the
1083 /// previewed theme). `None` when no colorscheme preview is in flight.
1084 pub pending_theme_preview_restore: Option<(
1085 lattice_theme::Palette,
1086 Vec<(lattice_theme::ElementName, lattice_theme::StyleSpec)>,
1087 )>,
1088
1089 /// Handle to the per-document actor (or, in M.1+, a
1090 /// composing multibuffer handle) and its snapshot cache.
1091 /// M.0: typed as the [`ActiveDocument`] newtype around
1092 /// `Arc<dyn Document>` so the slot can hold either a
1093 /// `RopeDocumentHandle` or a `MultibufferDocumentHandle`
1094 /// without kind-branching at the use site. `Default::
1095 /// default()` populates this with a placeholder rope
1096 /// handle whose actor is already gone — production code
1097 /// overwrites the slot before any traffic flows.
1098 pub document: lattice_runtime::ActiveDocument,
1099 pub snapshot_cache: SnapshotCache,
1100
1101 // DR.2 (decoration-retention): the `pane_highlights` /
1102 // `pane_highlight_keys` inactive-pane span cache + its
1103 // `refresh_pane_highlights` producer were retired here. Inactive
1104 // panes now render from their own retained per-pane `DisplayMatrix`
1105 // (the cells worker builds one for every visible pane), the same
1106 // canonical producer the active pane uses — one producer, zero
1107 // decoration recompute on focus change. See
1108 // `docs/dev/architecture/decoration-retention.md`.
1109 /// Active picker overlay. `None` outside picker mode.
1110 pub picker: Option<Picker>,
1111 /// Manual folds. v1 supports non-nested folds defined by line range.
1112 pub folds: Vec<lattice_core::Fold>,
1113 /// D.3.f.0 (2026-05-29): fold-provider registry. Holds the
1114 /// five built-in `Primary` providers (Manual / Indent /
1115 /// Markdown / Syntax / Lsp) and the list of registered
1116 /// `Overlay` providers, which are mode-owned (DX.3-C7):
1117 /// `diff-mode`'s `HunkFoldSource`, multibuffer's excerpt +
1118 /// file-boundary sources, all registered via the
1119 /// `FoldOverlayService` on mode activation).
1120 /// See `docs/dev/architecture/fold-architecture.md`.
1121 /// M.7: shared behind `Arc<Mutex>` so `FoldOverlayServiceImpl`
1122 /// can call `add_overlay`/`remove_overlay` from mode-activation
1123 /// context (outside `&mut Editor`) without blocking the UI thread.
1124 pub fold_registry: std::sync::Arc<std::sync::Mutex<crate::fold_provider::FoldRegistry>>,
1125
1126 /// BC.3b: boot-lifetime tick-callback registration tokens handed off from
1127 /// the `BootContext` via `into_registrations()`. A subsystem `install(boot)`
1128 /// that wires an off-keystroke `boot.inbound::<T>` drain (the first is the
1129 /// Claude Code IDE peer's write bus) produces an RAII token here; holding it
1130 /// for the editor's lifetime keeps the drain registered (dropping it would
1131 /// unregister the drain mid-session). Empty when no subsystem installs an
1132 /// inbound/tick drain at boot. Never read — held purely to keep the drains
1133 /// alive; the leading `_` documents that.
1134 pub _boot_tick_registrations: Vec<TickCallbackRegistration>,
1135 /// D.4.a (2026-05-29): scroll-binding pane groups. Each
1136 /// entry binds a set of `(pane, buffer)` pairs through a
1137 /// pluggable `RowMapper`; propagation runs at
1138 /// `publish_render_state` tail. Membership keyed on the
1139 /// pair so buffer changes within a pane suspend the
1140 /// binding automatically. Subsystems (diff D.4.d, future
1141 /// `:set scrollbind`, zen mode, `:windo`) add/drop their
1142 /// groups around lifecycle. See
1143 /// `docs/dev/architecture/pane-groups.md`.
1144 pub pane_groups: Vec<crate::pane_group::PaneGroup>,
1145 /// D.0b (2026-06-08): id of the singleton identity-mapper
1146 /// pane group that backs `:set scrollbind`. `None` when no
1147 /// panes currently have `scrollbind=true` (the group is
1148 /// dropped when the last member opts out). Rebuilt by
1149 /// `rebuild_scrollbind_group` on every `scrollbind`
1150 /// option-change cascade.
1151 pub scrollbind_group_id: Option<lattice_core::ui::pane::PaneGroupId>,
1152 /// D.8.e (2026-05-31): session key of the **singleton**
1153 /// `:diffthis` group, if any. `:diffthis` toggles per-buffer
1154 /// membership in this one group; other diff sessions
1155 /// (`:diffsplit`, AI-driven openDiff flows, future magit)
1156 /// run as independent `DiffSession`s and **don't** affect
1157 /// this field.
1158 ///
1159 /// State transitions (per
1160 /// `docs/dev/architecture/n-way-diff-membership.md` §6.2):
1161 /// - `None` → user runs `:diffthis` in any pane: create
1162 /// N=1 dormant session keyed under the active buffer;
1163 /// set this to `Some(active_buf)`.
1164 /// - `Some(g)` + active buffer not in g: extend the group
1165 /// via `add_participant`; arity grows.
1166 /// - `Some(g)` + active buffer in g: shrink the group via
1167 /// `remove_participant_buffer`; if arity drops to 0 the
1168 /// subsystem auto-drops the session and this clears
1169 /// back to `None`.
1170 pub diffthis_group: Option<lattice_core::BufferId>,
1171 /// D.8.e (2026-05-31): pane members corresponding to each
1172 /// participant of the diffthis group, in `:diffthis`-call
1173 /// order. The first entry is the pane the FIRST
1174 /// `:diffthis` invocation came from; subsequent entries
1175 /// are appended as the user invokes `:diffthis` in new
1176 /// panes. Used to construct / reshape the pane group when
1177 /// arity transitions across 2 (need scroll-bind +
1178 /// fillers).
1179 ///
1180 /// Cleared in lockstep with `diffthis_group` — both reset
1181 /// to empty / `None` when the group drops to arity 0.
1182 pub diffthis_members: Vec<crate::pane_group::PaneGroupMember>,
1183 /// Picker source registry -- `:picker` source kinds. Held behind
1184 /// `ArcSwap` (`PickerRegistryHandle`) so the plugin loader can register a
1185 /// loaded picker plugin's source at runtime by copy-on-write RCU while the
1186 /// picker-open path reads it wait-free (PL8.B). Shared as a service so
1187 /// `lattice-plugin-loader` reaches it without a host dep.
1188 pub picker_registry: lattice_picker::PickerRegistryHandle,
1189 /// Per-source MRU index that biases the picker's initial
1190 /// candidate ordering toward recently-accepted picks.
1191 pub picker_mru: PickerMruIndex,
1192 /// Optional on-disk persistence path for [`Self::picker_mru`].
1193 /// `None` for ephemeral / test installs.
1194 pub picker_mru_path: Option<PathBuf>,
1195 /// In-flight async picker init, if the active picker
1196 /// source's `init` returned a Future.
1197 pub pending_picker_init: Option<PendingPickerInit>,
1198 /// In-flight async picker accept, if the accepted source's
1199 /// `accept_async` returned a Future (a WASM plugin source).
1200 /// Its resolved outcome commits via
1201 /// `drain_pending_picker_accept`.
1202 pub pending_picker_accept: Option<PendingPickerAccept>,
1203 /// TR.2: in-flight async transient build, if the named menu's
1204 /// builder is guest-backed. Seated by
1205 /// `drain_pending_transient_build` on the async-landed wake — never
1206 /// by a keystroke, which is what makes a plugin menu open on the
1207 /// chord that asked for it.
1208 pub pending_transient_build: Option<crate::state::PendingTransientBuild>,
1209 /// Live-picker query state -- present only when the
1210 /// active picker source has `spec().live == true`.
1211 pub live_picker_query: Option<LivePickerQueryState>,
1212 /// CD.6a: the query the next picker to SEAT starts with, set by
1213 /// `Effect::OpenPicker { query }`. Consumed at seat for any source —
1214 /// unlike a live source's own seed, which only a live source has — and
1215 /// cleared if the open is refused.
1216 pub pending_picker_query: Option<String>,
1217 /// PI.1 (preview isolation): per-pane preview projection. A pane
1218 /// keeps its committed `PaneState.buffer_id`; the entry here (keyed
1219 /// by `PaneId`) records the buffer it currently *displays* plus the
1220 /// preview cursor / scroll. Baked into the published pane-tree
1221 /// leaves at `build_render_state` time so the renderers show the
1222 /// displayed buffer while `:ls` / modeline / dispatch read the
1223 /// committed one. Ephemeral: never persisted / snapshotted; cleared
1224 /// on accept / dismiss / selection-cleared. See
1225 /// `docs/dev/architecture/preview-isolation.md` §5 and
1226 /// [`crate::preview::PreviewOverride`].
1227 pub preview_overrides:
1228 std::collections::HashMap<lattice_core::ui::pane::PaneId, crate::preview::PreviewOverride>,
1229 /// PI.1: monotonic version bumped on every `preview_overrides`
1230 /// mutation. Folded into the panes-substate cache key (`panes_v`)
1231 /// so the published projection rebuilds when an override changes —
1232 /// the override lives outside `pane_tree.version()`.
1233 pub preview_overrides_version: u64,
1234 /// PBH.1: per-pane buffer history — the trail of buffers each pane
1235 /// has shown, walked with `<C-6>` / `<C-7>`.
1236 ///
1237 /// A **side table keyed by `PaneId`**, deliberately not a field on
1238 /// `PaneState`: that type is `Copy` and `PaneTree::split_active`
1239 /// copies it field-wise (`PaneState { id: PaneId::next(),
1240 /// ..new_state }`), so a history field there would be inherited by
1241 /// the split — the one behaviour this feature must not have.
1242 /// `PaneId::next()` is process-monotonic and never reuses ids, so
1243 /// a freshly split pane has no entry here and therefore starts with
1244 /// a fresh trail **by construction**, with nothing to remember to
1245 /// reset. Keeping it out of `PaneState` also keeps that type `Copy`,
1246 /// so split / close / layout stay allocation-free.
1247 ///
1248 /// Reaped by [`crate::dispatch::Editor::reconcile_pane_history`],
1249 /// which retains only ids still present in the tree rather than
1250 /// hooking each pane-removal site. See
1251 /// `docs/dev/architecture/pane-buffer-history.md` §4.
1252 pub pane_buffer_history: std::collections::HashMap<
1253 lattice_core::ui::pane::PaneId,
1254 crate::pane_history::PaneBufferHistory,
1255 >,
1256 /// PBH.3: set for the duration of a `<C-6>` / `<C-7>` walk so the
1257 /// buffer switch it performs is not recorded as a new visit.
1258 ///
1259 /// Without this the step back would push an entry, truncate the
1260 /// forward tail it was moving into, and make `<C-7>` permanently
1261 /// unreachable — the walk would eat its own future.
1262 pub(crate) walking_pane_history: bool,
1263 /// Shared typed-options registry (DESIGN.md §5.12).
1264 /// Every option's *current value* lives in here behind
1265 /// an `ArcSwap<T>`; `:set` parses against it; the
1266 /// customize buffer view (post-1.0) reads + writes
1267 /// through the same surface.
1268 /// User-set working directory (`:cd`). `None` falls back to
1269 /// `std::env::current_dir()` for path resolution.
1270 pub current_dir: Option<PathBuf>,
1271 pub config: Arc<ConfigRegistry>,
1272 /// Hot-path read cache for the option values.
1273 /// Repopulated by `rebuild_option_cache` after every
1274 /// `:set`. Accessor methods on App read this cached
1275 /// primitive directly (~1ns) instead of going through
1276 /// the registry's mutex + ArcSwap + downcast (~33ns).
1277 pub option_cache: OptionCache,
1278 /// Mode registry (M.1). Owns the catalogue of registered
1279 /// modes; activation / deactivation routes through here.
1280 pub mode_registry: lattice_mode::ModeRegistryHandle,
1281 /// SG.4a: the interned ids of the built-in signs (diagnostics + diff),
1282 /// registered into the sign registry at boot. The `builtin_element_ids`
1283 /// shape — a producer emitting a mark per visible line reads a field
1284 /// rather than hashing a name per line.
1285 pub builtin_sign_ids: lattice_mode::BuiltinSignIds,
1286 /// 2026-05-26: per-mode invocation runner table. Boot
1287 /// registers a runner function under each mode-id whose
1288 /// [`lattice_mode::Mode::invocation_runner`] returns
1289 /// `Some(id)`; [`Editor::run_invocation`] looks the runner
1290 /// up by walking the active modes on the active pane's
1291 /// buffer (minors first, then major) and calls the first
1292 /// match. Empty for modes that don't own dispatch
1293 /// (text-mode, completion-mode, semantic-tokens-mode, …).
1294 /// Replaces the hardcoded `match BufferKind` block in
1295 /// `run_invocation`; plugin-installed modes for plugin-
1296 /// installed buffer kinds extend the dispatcher through
1297 /// this map without touching host code.
1298 pub invocation_runners: HashMap<lattice_mode::ModeId, InvocationRunnerFn>,
1299 /// Typed service map subsystems hand off to modes so
1300 /// `Mode::on_activate` can pull subsystem handles via
1301 /// `ctx.service::<T>()`. Populated at boot; read-only
1302 /// after init.
1303 pub services: Arc<ServiceRegistry>,
1304 /// Per-`(buffer, mode)` Guard storage. Modes return an
1305 /// owned `Mode::Guard` from `on_activate`; the
1306 /// dispatcher stashes it here keyed by `(BufferId,
1307 /// ModeId)`. On deactivation the dispatcher drops the
1308 /// Guard, firing its `Drop` impl for synchronous
1309 /// cleanup. Wrapped in `Arc<Mutex<>>` because the
1310 /// spawned lifecycle task inserts from a worker thread.
1311 pub mode_guards: GuardStoreHandle,
1312 /// Per-buffer active modes (major + minors).
1313 ///
1314 /// Perf plan B.4: wrapped in [`Versioned`] so the modes
1315 /// sub-state cache can reuse its prior Arc across publishes
1316 /// when no mode toggle has fired. The `.insert` / `.remove`
1317 /// sites in dispatch autoref `&mut self.active_modes`, which
1318 /// bumps the version once per mutation.
1319 pub active_modes: Versioned<HashMap<BufferId, ActiveModes>>,
1320 /// TC.9b: activations refused only because the buffer did not offer a
1321 /// required capability YET, to be retried when it does.
1322 ///
1323 /// A buffer opens, its modes activate, and its first parse has not run —
1324 /// so a mode requiring `TREE_SITTER` is refused at exactly the moment
1325 /// every mode is activated, and without this nothing ever asks again. The
1326 /// user sees a mode that is simply never on.
1327 ///
1328 /// Only `MissingCapability` refusals land here. A conflict or a wrong kind
1329 /// is a decision, not a race, and retrying it would loop forever.
1330 ///
1331 /// A `Vec` because it is empty in every ordinary session — the retry pass
1332 /// costs a length check — and because the natural operations are "walk all"
1333 /// and "remove one", neither of which wants a map.
1334 pub deferred_mode_activations: Vec<(BufferId, lattice_mode::ModeId)>,
1335 /// Per-buffer mode-owned local state. Modes populate
1336 /// locals via the `BufferLocal` typed-map during
1337 /// `on_activate`; the App routes `&mut BufferLocals`
1338 /// into the registry's activation methods.
1339 ///
1340 /// Perf plan B.4: wrapped in [`Versioned`] for the same reason
1341 /// as `active_modes` — most publishes don't touch
1342 /// `buffer_locals`, so the deep typed-map clone in
1343 /// `build_render_state` can be avoided via Arc reuse.
1344 pub buffer_locals: Versioned<HashMap<BufferId, BufferLocals>>,
1345 /// Per-buffer mode-resolved options cache. Refreshed
1346 /// eagerly on mode toggle and option write.
1347 pub resolved_options: HashMap<BufferId, ResolvedOptions>,
1348 /// AR.0: on-disk fingerprint per file-backed Document buffer, stamped
1349 /// on load and after the buffer's own `:w`. The autoread watcher (AR.2)
1350 /// compares an incoming filesystem event against this to suppress
1351 /// self-writes and skip no-op touches. Non-file buffers (oil, help,
1352 /// synthetic) never get an entry — the map is keyed by the property
1353 /// "has an on-disk backing", not by `BufferKind`. See
1354 /// `docs/dev/architecture/autoread.md`.
1355 pub on_disk_fingerprints: HashMap<BufferId, crate::autoread::OnDiskFingerprint>,
1356 /// PI.4: monotonic version bumped whenever [`Self::resolved_options`]
1357 /// changes. Keys the published `ResolvedOptionsRenderState` cache so
1358 /// both renderer peers read per-buffer resolved options through ONE
1359 /// renderer-agnostic seam (`RenderState::resolved_option_for`) instead
1360 /// of each peer resolving options its own way.
1361 pub resolved_options_version: u64,
1362 /// Buffer-local explicit overrides (`:setlocal foo=bar`)
1363 /// per buffer. Inputs to resolution; the resolver chains
1364 /// these with mode contributions before writing
1365 /// [`Self::resolved_options`].
1366 pub buffer_local_overrides: HashMap<BufferId, OptionOverrideSet>,
1367 /// Receiver for `OptionChanged` events published by the
1368 /// option-cascade pipeline. `Option` only because the
1369 /// field needs to be `take`-able so the drain method can
1370 /// borrow `&mut self` for cascade work while iterating
1371 /// the receiver. Always `Some` between calls.
1372 pub option_change_rx: Option<tokio::sync::mpsc::UnboundedReceiver<Event>>,
1373 /// Free-form help topic registry (DESIGN.md §5.11).
1374 /// `:help` reads from this; built-ins are sourced from
1375 /// `docs/user/*.md` at build time. Plugins / future LSP
1376 /// integrations register additional topics through the
1377 /// same registry.
1378 ///
1379 /// CR.1: a copy-on-write RCU handle, not a fixed `Arc`. Every read
1380 /// site takes one `.load()` snapshot for the duration of its work,
1381 /// so a plugin loading mid-render affects the next `:help`, never
1382 /// half of this one. Also registered as a boot service under
1383 /// `HelpTopicRegistryHandle` so the plugin loader's drain can reach
1384 /// it without a host method.
1385 pub help_topics: HelpTopicRegistryHandle,
1386 /// T.4: builtin element ids interned once at boot from the
1387 /// `ThemeRegistryHandle` (looked up from [`Self::services`]).
1388 /// Snapshotted (Copy) into `RenderState` so a renderer read is
1389 /// `resolved.get(ids.<elem>)`. The registry handle itself lives
1390 /// only in `services` (it is `Arc<dyn ThemeRegistry>`, which has no
1391 /// `Default`, so it cannot be a field on this `derive(Default)`
1392 /// struct); `build_render_state` looks it up to snapshot
1393 /// `resolved()`.
1394 pub builtin_element_ids: crate::ui::theme::BuiltinElementIds,
1395 /// Buffer-level modal state machine (DESIGN.md §5.2).
1396 /// One of Normal / Insert / Visual / Op-pending /
1397 /// Command / Search / Replace.
1398 pub modal: ModalState,
1399 /// In-flight partial-chord stack from the trie. When the
1400 /// trie returns `LookupResult::Partial`, the dispatch
1401 /// layer appends the chord here; the next keystroke
1402 /// runs through the trie with this stack as prefix.
1403 /// Cleared on every non-`AbsorbPartialChord` action.
1404 pub partial_chord: Vec<KeyChord>,
1405 /// Grammar registry shared with the document actor by the
1406 /// `ArcSwap` handle. The actor calls `lattice_grammar::execute`
1407 /// with a wait-free snapshot from inside its own task. The App
1408 /// also reads it directly (`.load()`, or `.load_full()` where an
1409 /// owned snapshot must outlive a `&mut self` borrow) for the
1410 /// parser, completion pipeline, and introspection.
1411 ///
1412 /// PL8.B / B3b: held behind `ArcSwap` (was `Arc<CommandRegistry>`)
1413 /// so the plugin loader can RCU-register a runtime grammar
1414 /// contribution and `store` it; every reader picks it up on its
1415 /// next `.load()`. See [`lattice_grammar::CommandRegistryHandle`].
1416 pub registry: lattice_grammar::CommandRegistryHandle,
1417 /// In-process event bus (DESIGN.md §5.10). The App
1418 /// publishes editor lifecycle events
1419 /// (DocumentChanged, SelectionsChanged,
1420 /// ModalModeChanged, BeforeSave, DocumentSaved,
1421 /// BeforeQuit, OptionChanged) after observing the
1422 /// corresponding state transitions.
1423 pub event_bus: Arc<EventBus>,
1424 /// Built-in command-ids (`d`, `y`, `w`, `j`, …) -- the
1425 /// canonical `CommandId` values keymap registrations
1426 /// resolve against.
1427 pub builtins: Builtins,
1428 /// App-side typed action IDs (`CommandKind::Action`
1429 /// registrations). Each field is a `CommandId`
1430 /// resolving to an `ActionSpec` whose `apply` returns
1431 /// `Effect::AppAction(AppEffect::Foo)`.
1432 pub action_ids: ActionIds,
1433 /// Layered keymap registry (DESIGN.md §5.2.3).
1434 /// Populated at construction; the input dispatcher
1435 /// reads from it on every keystroke. Wait-free reads
1436 /// via internal `ArcSwap`; concurrent writes (mode
1437 /// push/pop, plugin registration, `:bind`) never stall
1438 /// the input path.
1439 pub keymap: KeymapHandle,
1440 /// `LayerId` of the active completion-popup minor-mode
1441 /// layer when the popup is open; `None` otherwise.
1442 /// Pushed / popped in lockstep with `insert_completion`.
1443 pub completion_popup_layer: Option<LayerId>,
1444 /// Pluggable completion pipeline (DESIGN.md §5.11.3). Owned by
1445 /// the host editor.
1446 pub completion_registry: lattice_completion::CompletionRegistry,
1447 /// Active command-line completion popup state (for `:` line).
1448 pub completion_state: Option<CompletionState>,
1449 /// Active **Insert-mode** completion popup (Phase 4.2.g).
1450 /// Distinct from `completion_state` (which drives the `:` line
1451 /// completion popup): this one floats over the buffer, shows
1452 /// candidates from sources (LSP / snippets / buffer-words /
1453 /// path / tree-sitter / plugin), and the host's keystroke
1454 /// dispatcher routes through a "completion-popup minor mode"
1455 /// keymap layer while it's `Some`. Behavioural spec lives in
1456 /// [`docs/dev/architecture/insert-completion.md`].
1457 pub insert_completion: Option<lattice_completion::InsertCompletionState>,
1458 /// Per-language snippet registry (Phase 4.2.g.4). Loaded
1459 /// at startup from bundled / user / project paths via
1460 /// `lattice-snippet::load`; the `gen:snippet` source
1461 /// consults it per-popup-trigger.
1462 /// CSM.5: held as `Arc<ArcSwap<...>>` so the mode-captured
1463 /// handle stays valid across `:reload-snippets`. Source reads
1464 /// load the current snapshot via `.load()` (wait-free); the
1465 /// reload path swaps the inner via `.store()` so the mode's
1466 /// next produce sees the fresh data.
1467 pub snippet_registry: Arc<arc_swap::ArcSwap<lattice_snippet::SnippetRegistry>>,
1468 /// SN.3b: shared cell holding the folded `snippet-mode`
1469 /// [`ActivationPolicy`](lattice_mode::ActivationPolicy).
1470 /// `register_snippet_modes` creates it (default `Global`) and the
1471 /// `snippet-mode` gate reads it on every `MajorEntered`; boot +
1472 /// the `snippet.activation` / `snippet.languages`
1473 /// `apply_option_cascade` arm fold config into it via
1474 /// `lattice_snippet::fold_activation_policy`.
1475 pub snippet_activation_policy: lattice_snippet::SnippetActivationPolicyHandle,
1476 /// SN.3c.0: app-lifetime registration tokens for modes'
1477 /// declarative *global* action handlers (`Mode::action_handlers()`,
1478 /// registered once at boot by
1479 /// [`crate::mode_action_handlers::register_mode_action_handlers`]).
1480 /// Held here so the handlers stay registered for the editor's
1481 /// whole lifetime; dropped at shutdown when `Editor` drops.
1482 pub global_action_handler_regs: Vec<lattice_mode::ActionHandlerRegistration>,
1483 /// Sidecar metadata for snippet candidates in the active
1484 /// insert-completion popup.
1485 /// CSM.5: retired. Snippet candidates now carry their stable
1486 /// `name` in the `Extension::payload` field; the accept path
1487 /// re-resolves the body via `Editor.snippet_registry.by_name`.
1488 /// Field kept as an empty Vec for one slice so callers that
1489 /// haven't migrated still compile; field deletion in a
1490 /// follow-up cleanup slice.
1491 pub insert_completion_snippet_meta: Vec<crate::state::SnippetCandidateMeta>,
1492 /// Per-session accept-count map for the insert-mode
1493 /// completion popup (Phase 4.2.g.5). Each accepted candidate
1494 /// bumps the counter for its `(text, kind)` pair; the ranker
1495 /// reads this map and adds a bounded bonus
1496 /// (`InsertRanker::FREQUENCY_BONUS_CAP`) so recently-accepted
1497 /// items bubble above tied peers next time.
1498 pub completion_accept_freq: HashMap<(String, lattice_completion::CandidateKind), u32>,
1499 /// TOML structural sections collected by the config loader at
1500 /// startup but not yet routed to their owners. Keyed by full
1501 /// dotted path (e.g. `"completion.per-language.markdown"`,
1502 /// `"plugin.rust-analyzer"`); value is the sub-table verbatim.
1503 /// Phase 4.2.g.5 (3b/3) drains the `completion.per-language.*`
1504 /// entries into `per_language_completion`; the plugin host
1505 /// (Phase 7) will drain `plugin.*`.
1506 pub pending_config_structural_sections: std::collections::BTreeMap<String, toml::Table>,
1507 /// Per-language insert-completion overrides (Phase 4.2.g.5
1508 /// (3b/3); spec at `docs/dev/architecture/insert-completion.md` §9).
1509 pub per_language_completion: HashMap<String, lattice_completion::PerLanguageOverrides>,
1510 /// `true` while the active insert-completion popup is in
1511 /// path-completion mode (Phase 4.2.g.6 (2/2)).
1512 pub completion_in_path_context: bool,
1513 /// Live snippet expansion (SN.2: relocated to a shared
1514 /// `SnippetSession` service so the `SnippetActiveMode`-owned
1515 /// `<Tab>` / `<S-Tab>` handlers can reach it). Active while a
1516 /// snippet is expanding; the session ends on `$0` consumption /
1517 /// `<Esc>` / cursor leaving the tabstop ranges. The same `Arc` is
1518 /// registered in `ServiceRegistry` under `SnippetSessionHandle`.
1519 pub snippet_session: lattice_snippet::SnippetSessionHandle,
1520 /// Session-backed minor modes reconciled on the active buffer
1521 /// each `sync_keymap_overlays` cycle (one entry per
1522 /// service-driven minor). Each pairs a predicate — reading a
1523 /// shared, mode-owned session service — with the minor's
1524 /// `ModeId`; the mode is active iff its predicate is true. Modes
1525 /// contribute these at boot (`active-snippet-mode` keys off the
1526 /// shared `SnippetSession`), so the generic overlay-sync carries
1527 /// no subsystem-specific `is_active()` literal
1528 /// (`feedback_mode_owns_its_surface`).
1529 pub session_backed_minors: Vec<SessionBackedMinor>,
1530 /// Per-language directories from which snippet packs are
1531 /// loaded on startup / `:reload-snippets` (Phase 4.2.g.4).
1532 pub snippet_dirs: Vec<PathBuf>,
1533 /// LIFO stack of snapshots taken every time the popup's content
1534 /// gets swapped in place by a help -> help link follow (e.g.
1535 /// `:describe-buffer` -> click `[text-mode](mode:text-mode)` ->
1536 /// `:describe-mode text-mode`). One popup buffer is reused
1537 /// across the navigation so jump-list / marks / search /
1538 /// register state stay coherent; this stack records what was
1539 /// in the buffer before each swap so `<C-o>` from inside the
1540 /// popup can restore the prior frame without leaving Help.
1541 pub popup_back_stack: Vec<crate::popup::PopupSnapshot>,
1542 /// Active buffer's cursor (DESIGN.md §5.1.1). Updated
1543 /// in lockstep with the active pane's stash so cross-
1544 /// pane jumps restore the right position.
1545 pub cursor: ProtoPosition,
1546 /// Sticky display-column target for `gj`/`gk` (vim's `w_curswant`).
1547 /// Stores the byte offset within the current wrap segment so
1548 /// consecutive display-line moves try to land at the same column.
1549 /// `None` between any non-display-line motion.
1550 /// First visible line in the viewport (0-based).
1551 pub scroll: u32,
1552 /// First visible display column in the viewport (0-based) —
1553 /// horizontal scroll for the active pane. Mirrors the active
1554 /// `PaneState::leftcol`; maintained by
1555 /// `ensure_cursor_horizontally_visible`. Always 0 when `wrap`
1556 /// is on (the body reflows, nothing is off-screen-right).
1557 pub leftcol: u32,
1558 /// Quit flag. The main loop reads this and tears down
1559 /// after the next paint. Set by `:q` / `:qa` / `Ctrl-C`
1560 /// / SIGINT.
1561 pub should_quit: bool,
1562 /// Last height we were drawn at; used by motion
1563 /// clamping and viewport scrolling. Updated by the
1564 /// renderer before each frame.
1565 ///
1566 /// IM.1: this is a **budget in line-heights**, not a count of rows. The
1567 /// two are the same number whenever rows are uniform — which is always,
1568 /// for the TUI — but they diverge once a row is taller than one line
1569 /// (a scaled heading, an IM.3 media block), because how many rows fit
1570 /// then depends on which rows. Spend it through
1571 /// [`Editor::row_weights`]; do not count rows against it.
1572 pub viewport_height: u32,
1573
1574 /// IM.1: per-source-line vertical cost overrides, in line-heights.
1575 ///
1576 /// Empty for every TUI buffer and for any GPUI buffer with no scaled or
1577 /// media rows, and the scroll walks short-circuit on that — see
1578 /// [`lattice_cells::RowWeights::is_uniform`]. Published by the renderer
1579 /// peer, which is the only layer that knows how tall it draws things.
1580 pub row_weights: std::sync::Arc<lattice_cells::RowWeights>,
1581 /// Last terminal width we were drawn at. Used by pane
1582 /// geometry (DESIGN.md §5.9 navigation needs to know
1583 /// which pane is horizontally adjacent). `None` until
1584 /// the renderer first records it.
1585 pub terminal_width: Option<u16>,
1586 /// IM.7a: the drawing peer's cell geometry in pixels, published through
1587 /// [`crate::action::Action::SetCellMetrics`].
1588 ///
1589 /// `None` until a peer that draws images reports it, and forever on one
1590 /// that does not — which is how a media block stays at its provisional
1591 /// reservation in the TUI without the host reading any image header.
1592 pub cell_metrics: Option<CellMetrics>,
1593 /// Which buffer the input pipeline currently routes to.
1594 /// When a help overlay is open this is `Help`; otherwise
1595 /// `Document`. Denormalized from
1596 /// `pane_tree.active().buffer` -- updated in lockstep
1597 /// with the active pane.
1598 pub active_buffer: BufferKind,
1599 /// Stable id for the *active* document buffer. Mirrors
1600 /// the active pane's `buffer_id` whenever that pane
1601 /// holds a Document leaf.
1602 pub document_buffer_id: BufferId,
1603 /// Unified buffer registry (DESIGN.md §5.9). Holds
1604 /// every open buffer regardless of kind -- documents,
1605 /// file trees, future outline / diagnostics views.
1606 pub buffers: BufferRegistry,
1607 /// ML.0b-2: shared modeline element service (descriptor registry +
1608 /// content store, ArcSwap-backed). The SAME `Arc` is registered into
1609 /// `services` at boot so modes reach it via
1610 /// `ctx.service::<ModelineServiceHandle>()`; the host reads
1611 /// `modeline.snapshot()` each `build_render_state` into
1612 /// `RenderState.modeline_elements`. `Arc<ModelineService>` is
1613 /// `Default`, so `#[derive(Default)]` on `Editor` still holds (the
1614 /// boot literal overrides it with the registered instance).
1615 pub modeline: lattice_mode::ModelineServiceHandle,
1616 /// ML.3: actor-thread drain channel for [`lattice_mode::ModelineElementUpdate`]
1617 /// events pushed by modes/plugins over the event bus. Boot subscribes
1618 /// a sender; `drain_modeline_element_updates` (in `run_tick_pending`)
1619 /// applies each into `modeline`'s content store (single-writer). A
1620 /// separate boot subscription fires `async_landed` so a pushed update
1621 /// repaints off-keystroke (§12 wake). `Option` is `Default` (None), so
1622 /// `#[derive(Default)]` on `Editor` still holds.
1623 pub modeline_update_rx:
1624 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_mode::ModelineElementUpdate>>,
1625 /// Cached `textDocument/selectionRange` chain for the
1626 /// smart-expansion operator.
1627 pub lsp_selection_chain: Option<LspSelectionChain>,
1628 /// Current step inside `lsp_selection_chain.ranges`.
1629 /// 0 = innermost; `chain.ranges.len() - 1` = outermost.
1630 pub lsp_selection_chain_index: usize,
1631 /// Cached `textDocument/documentHighlight` for the
1632 /// active buffer + symbol position.
1633 ///
1634 /// Phase 5.8.AF.5 / Slice 3b.0: the cache lives behind
1635 /// `Arc<ArcSwapOption<...>>` so the spawned task on the LSP
1636 /// runtime can store results directly when the response
1637 /// arrives — no channel, no UI-thread drain. Renderers read
1638 /// wait-free via `editor.render_state.load().lsp.document_highlights.load()`.
1639 pub lsp_document_highlights: std::sync::Arc<arc_swap::ArcSwapOption<DocumentHighlightCache>>,
1640 /// Cursor position at which the most recent
1641 /// `documentHighlight` request was issued.
1642 pub last_document_highlight_issue_cursor: Option<ProtoPosition>,
1643 /// Per-buffer cache of the last
1644 /// `textDocument/foldingRange` response.
1645 ///
1646 /// Phase 5.8.AF.5 / Slice 3b.1: `PerBufferCache<T>` so the
1647 /// spawned LSP request task can write results directly when
1648 /// the response arrives -- no channel, no UI-thread drain.
1649 /// Renderers read wait-free via
1650 /// `rs.lsp.folds.get_for(buffer_id)`.
1651 pub lsp_folds_cache: crate::per_buffer_cache::PerBufferCache<LspFoldsCache>,
1652 /// Phase 5.8.AF.5 / Slice 3b.1: the old drain
1653 /// (`drain_pending_folding_range`) called `recompute_folds()`
1654 /// inline after writing the cache so `self.folds` reflected
1655 /// the latest LSP response. The new shape has the task
1656 /// writing the cache off-thread; this tuple lets
1657 /// `maybe_request_folding_range` detect when the cache
1658 /// version has changed and trigger `recompute_folds()` on
1659 /// the renderer thread (where `&mut self.folds` is safe).
1660 /// `Some((buffer_id, document_version))` records the cache
1661 /// state last reflected into `self.folds`.
1662 pub last_recomputed_lsp_fold_version: Option<(BufferId, u64)>,
1663 /// OA.4d: `(buffer, text_version)` the current [`Editor::folds`] were
1664 /// computed from, so the tick can notice when they no longer match.
1665 ///
1666 /// Folds are otherwise seeded in two places, and neither covers a view
1667 /// that fills in ASYNCHRONOUSLY: `activate_buffer` seeds them when the
1668 /// buffer is activated — which for a provider view is while it is still
1669 /// EMPTY, before the scan lands — and `maybe_reparse_syntax` recomputes
1670 /// on the edit path, which a view nobody typed into never takes. So the
1671 /// agenda had no folds at all until something forced a redraw, and
1672 /// `<Tab>` was a no-op because there was nothing under the cursor to
1673 /// cycle.
1674 pub last_folded_text_version: Option<(BufferId, u64)>,
1675 /// Per-buffer `inlayHint` cache.
1676 ///
1677 /// Phase 5.8.AF.5 / Slice 3b.1: see `lsp_folds_cache` note.
1678 /// Renderers read wait-free via
1679 /// `rs.lsp.inlay_hints.get_for(buffer_id)`.
1680 pub lsp_inlay_hints_cache: crate::per_buffer_cache::PerBufferCache<LspInlayHintCache>,
1681 /// PL8.E: WASM gutter-decoration wiring (per-buffer cache + producer
1682 /// registry handle + off-keystroke paint generation + single-flight
1683 /// bookkeeping), bundled so this struct grows by one field. The per-tick
1684 /// `maybe_refresh_wasm_decorations` drives the producers off the render
1685 /// path; renderers read `rs.wasm_gutter_decorations`. Inert in
1686 /// `Editor::default()` (no registry wired).
1687 pub wasm_decorations: crate::wasm_decorations::WasmDecorationState,
1688 /// IM.7: inline-media producer wiring — the cache the virtual-row builder
1689 /// reads, plus the registry the loader registers into.
1690 pub wasm_media: crate::wasm_media::WasmMediaState,
1691 /// TC.3a: WASM sticky-context wiring (per-buffer scope cache + producer
1692 /// registry handle + off-keystroke paint generation + single-flight
1693 /// bookkeeping). The per-tick `maybe_refresh_wasm_context` drives the
1694 /// producers off the render path; per-pane resolution reads the cache.
1695 /// Inert in `Editor::default()` (no registry wired).
1696 pub wasm_context: crate::wasm_context::WasmContextState,
1697 /// Per-buffer `documentLink` cache.
1698 /// Per-buffer `textDocument/documentLink` cache. Phase
1699 /// 5.8.AF.5 / Slice 3b.4: `PerBufferCache<T>` so the spawned
1700 /// LSP request task writes results directly. Renderers read
1701 /// via `rs.lsp.document_links.get_for(buffer_id)`.
1702 pub lsp_document_links_cache: crate::per_buffer_cache::PerBufferCache<LspDocumentLinksCache>,
1703 /// Per-buffer code-lens cache.
1704 /// Per-buffer `textDocument/codeLens` cache. Phase 5.8.AF.5
1705 /// / Slice 3b.3: `PerBufferCache<T>` so the spawned LSP
1706 /// request task writes results directly. Renderers read
1707 /// via `rs.lsp.code_lens.get_for(buffer_id)`.
1708 pub lsp_code_lens_cache: crate::per_buffer_cache::PerBufferCache<LspCodeLensCache>,
1709 /// Per-buffer `documentColor` cache.
1710 /// Per-buffer `textDocument/documentColor` cache. Phase
1711 /// 5.8.AF.5 / Slice 3b.4: `PerBufferCache<T>`.
1712 pub lsp_document_color_cache: crate::per_buffer_cache::PerBufferCache<LspDocumentColorCache>,
1713 /// Per-buffer semantic-tokens cache.
1714 /// Per-buffer cache of the last `textDocument/semanticTokens/*`
1715 /// response. Phase 5.8.AF.5 / Slice 3b.2: `PerBufferCache<T>`
1716 /// so the spawned LSP request task writes results (Items /
1717 /// Delta-applied / Empty) directly when the response arrives.
1718 /// Renderers read wait-free via
1719 /// `rs.lsp.semantic_tokens.get_for(buffer_id)`.
1720 pub lsp_semantic_tokens_cache: crate::per_buffer_cache::PerBufferCache<LspSemanticTokensCache>,
1721 /// Per-buffer pull-diagnostics cache (keyed
1722 /// `result_id`s for `Unchanged` short-circuit).
1723 /// Per-buffer `textDocument/diagnostic` (pull) cache.
1724 /// Phase 5.8.AF.5 / Slice 3b.5: `PerBufferCache<T>`.
1725 pub lsp_pull_diagnostics_cache:
1726 crate::per_buffer_cache::PerBufferCache<LspPullDiagnosticsCache>,
1727 // ---- LSP subsystem handles + log/progress channels ----
1728 pub lsp: LspSupervisorHandle,
1729 /// ML.3c: handle to the `lattice-lsp`-owned progress/status store
1730 /// (decision A — the accumulator relocated out of the host). The
1731 /// modeline forwarder writes it; the host reads it here only for
1732 /// `:lsp-progress-cancel` (in-flight cancellable tokens). `Arc<…>` is
1733 /// `Default`, so `#[derive(Default)]` on `Editor` still holds.
1734 pub lsp_progress_store: lattice_lsp::modeline::LspProgressStoreHandle,
1735 pub lsp_diagnostics: DiagnosticsLayer,
1736 /// L4a.2 (lsp-architecture.md §15): inline cursor-line
1737 /// diagnostic-summary idle gate. `inline_diag_line` is the line
1738 /// the gate is currently timing (the cursor line at arm time);
1739 /// `inline_diag_deadline` is the [`tokio::time::Instant`] at which
1740 /// its summary becomes visible (the actor's pinned sleep targets
1741 /// it); `inline_diag_visible` flips true when that deadline passes
1742 /// and back to false on re-arm (new cursor line) / Insert mode /
1743 /// `ui.diagnostics.inline = off`. The published summary in
1744 /// `DiagnosticsRenderState::inline_summary` is recomputed each
1745 /// `build_render_state` while visible, so diagnostics landing on
1746 /// the line after the gate fires refresh it for free. See
1747 /// `update_inline_diag_gate` / `fire_inline_diag_gate`.
1748 pub inline_diag_line: Option<u32>,
1749 pub inline_diag_deadline: Option<tokio::time::Instant>,
1750 pub inline_diag_visible: bool,
1751 /// WK.4: the last `PartialChordPending` published, so the publisher
1752 /// can skip republishing an unchanged tuple. This is the publisher's
1753 /// own dedup cache — not subsystem state — and it is what keeps an
1754 /// ORDINARY keystroke's cost at a tuple compare that short-circuits
1755 /// on two empty slices. See `publish_partial_chord_pending`.
1756 pub last_partial_chord_event: Option<lattice_keymap::PartialChordPending>,
1757 pub lsp_logger: LspLogger,
1758 /// 4.4.l.2 / 5.8.AA.o / 5.8.AF.5: file-watcher service handle.
1759 /// `None` until the first actor with `workspace/didChangeWatchedFiles`
1760 /// capability is observed; at that point the actual watcher +
1761 /// notify event loop is spawned on the LSP runtime (see
1762 /// `crate::lsp_watcher::spawn_lsp_file_watcher_task`). Editor
1763 /// only sends `SyncSubscriptions` commands through this
1764 /// handle — no notify API calls, no event drains, ever run
1765 /// on the renderer's per-tick loop. Per paramount goal #4.
1766 pub lsp_watcher: Option<crate::lsp_watcher::LspFileWatcherHandle>,
1767 /// Editor-side memo of `server_id → CachedSubscription`. Used
1768 /// to detect whether the actor roster or its compiled
1769 /// subscriptions changed since the last `sync` call; only
1770 /// non-trivial diffs are pushed to the task. Mirrors the
1771 /// per-server map the task itself holds, but lives here so the
1772 /// "did anything change?" check stays a cheap fingerprint
1773 /// compare on the renderer's `refresh_lsp_file_watcher` path.
1774 pub lsp_watcher_subscriptions:
1775 std::collections::HashMap<String, crate::lsp_watcher::CachedSubscription>,
1776 /// Editor-side memo of currently-watched roots. Mirrors the
1777 /// task's set so we can skip sending `SyncSubscriptions` when
1778 /// nothing changed.
1779 pub lsp_watcher_watched_roots: std::collections::HashSet<std::path::PathBuf>,
1780 /// AR.3: handle to the autoread watcher task (spawned lazily on the
1781 /// first file-backed buffer with `autoread` on; dropped → task exits
1782 /// when the last such buffer closes). `Editor` only sends `Sync`
1783 /// commands through it — no notify calls on the renderer thread. See
1784 /// `docs/dev/architecture/autoread.md`.
1785 pub autoread_watcher: Option<crate::autoread::AutoreadWatcherHandle>,
1786 /// AR.3: the change stream from the watcher task, drained host-side by
1787 /// AR.4's reload policy. `None` until the watcher is spawned.
1788 pub autoread_changes:
1789 Option<tokio::sync::mpsc::UnboundedReceiver<crate::autoread::AutoreadChange>>,
1790 /// AR.3: order-independent hash of the last-synced desired watch set.
1791 /// The cheap "did the watch set change?" gate on `refresh_autoread_watcher`
1792 /// so buffer-switches that don't change the set skip the cmd-send.
1793 pub autoread_watch_fingerprint: u64,
1794 /// AR.4: the latest un-applied external change per buffer. The tick drain
1795 /// records watcher changes here (keyed by `BufferId`) and applies the
1796 /// *active* buffer's entry immediately; a background buffer's entry is
1797 /// applied when it next becomes active (vim's checktime-on-`BufEnter`).
1798 pub autoread_pending: std::collections::HashMap<BufferId, crate::autoread::AutoreadChange>,
1799 /// AR.5: buffers with an open autoread **conflict diff** (on-disk change +
1800 /// unsaved edits). While a buffer is in this set autoread stays hands-off
1801 /// for it — no re-opened resolver, no reload — so a resolve-then-save
1802 /// can't loop. Cleared when the buffer is reloaded (`:e!` → new id) or
1803 /// closed.
1804 pub autoread_conflict_open: std::collections::HashSet<BufferId>,
1805 /// Phase 5.8.AF.5 / Slice 3a: renderer's wait-free read
1806 /// contract. Published by `Editor::publish_render_state` at
1807 /// the end of every `dispatch()` tick. Renderers load via
1808 /// `editor.render_state.load_full()` once per frame and read
1809 /// every per-frame field through the returned snapshot.
1810 pub render_state: std::sync::Arc<arc_swap::ArcSwap<crate::render_state::RenderState>>,
1811 /// Phase 5.8.AF.5 / Slice X2: wake signal for the overlay
1812 /// worker. `publish_render_state` fires
1813 /// `overlay_wake.0.notify_one()` at its tail so the worker
1814 /// re-evaluates the syntax inputs published into
1815 /// `RenderState.syntax` and re-buckets overlay quads on a cache
1816 /// miss. `Notify` coalesces — a burst of publishes wakes the
1817 /// worker once, which is what we want (it always reads the
1818 /// latest published inputs anyway).
1819 ///
1820 /// display-line B4.2: renamed from `highlight_wake`; the dead
1821 /// span/row prepaint cache the worker also fed was deleted.
1822 pub overlay_wake: OverlayWake,
1823 /// Perf plan B.2 slice B.2.a: parallel cell carrying the
1824 /// worker's per-row pre-bucketed static-overlay quads
1825 /// (doc_highlight / all_matches / substitute) for the active
1826 /// pane's visible window. The Arc identity lives on `Editor` so
1827 /// `build_render_state` clones it into every snapshot; the
1828 /// overlay worker writes directly and renderer peers read the
1829 /// latest write via the published Arc without a republish
1830 /// round-trip.
1831 pub syntax_static_overlay_quads_cell:
1832 std::sync::Arc<arc_swap::ArcSwap<crate::render_state::StaticOverlayQuads>>,
1833 /// S2.1 (2026-05-26): cell-grid renderer output cell. Same
1834 /// stability pattern as `syntax_static_overlay_quads_cell`: the Arc
1835 /// identity lives on `Editor` so `build_render_state` clones
1836 /// it into every snapshot; the cell-builder worker (S2.2+)
1837 /// holds a sibling clone and writes directly via
1838 /// `cell.store(new_matrix)`. Empty `CellMatrix` until the
1839 /// worker lands.
1840 pub cells_matrix_cell: std::sync::Arc<arc_swap::ArcSwap<lattice_cells::CellMatrix>>,
1841 /// D.4.d.0 (2026-05-29): per-document cells-matrix
1842 /// registry. Each visible buffer gets its own
1843 /// `Arc<ArcSwap<CellMatrix>>` so the cells worker can
1844 /// rebuild per buffer, and the renderer can pull the
1845 /// matrix matching each pane's buffer at paint time
1846 /// (load-bearing for side-by-side diff — D.4 — where
1847 /// two panes show different buffers simultaneously).
1848 ///
1849 /// The active-document entry is stored under
1850 /// `document_buffer_id` and **shares its Arc identity
1851 /// with [`Self::cells_matrix_cell`]** so the existing
1852 /// hot path (cells_worker writing through the field,
1853 /// renderer reading through `RenderState.cells.matrix`)
1854 /// stays bit-identical until the worker iteration
1855 /// upgrade lands in D.4.d.1.
1856 ///
1857 /// Inserts are lazy via
1858 /// [`Self::cells_matrix_for`] — a buffer's entry shows
1859 /// up the first time anything asks for its matrix.
1860 /// Pruning of stale entries is deferred until the
1861 /// worker actually consumes the registry; for now,
1862 /// entries accumulate without harm because
1863 /// [`arc_swap::ArcSwap`] over an empty `CellMatrix` is
1864 /// a cheap idle resource.
1865 pub cells_matrices: std::sync::Arc<
1866 std::sync::Mutex<
1867 std::collections::HashMap<
1868 lattice_core::BufferId,
1869 std::sync::Arc<arc_swap::ArcSwap<lattice_cells::CellMatrix>>,
1870 >,
1871 >,
1872 >,
1873 /// B2.1 (2026-06-04): per-line display-cache output cell — the
1874 /// substrate that retires `cells_matrix_cell`. Same stability
1875 /// pattern: the Arc identity lives on `Editor` so the publisher
1876 /// clones it into every snapshot; the worker (B2.2) holds a
1877 /// sibling clone and writes via `cell.store(new_matrix)`. Empty
1878 /// `DisplayMatrix` until the worker build path lands.
1879 /// See `docs/dev/architecture/display-line.md`.
1880 pub display_matrix_cell:
1881 std::sync::Arc<arc_swap::ArcSwap<crate::display_matrix::DisplayMatrix>>,
1882 /// B2.1 (2026-06-04): per-document display-matrix registry.
1883 /// Mirror of [`Self::cells_matrices`] for the per-line cache.
1884 /// Each visible buffer gets its own `Arc<ArcSwap<DisplayMatrix>>`
1885 /// so the worker can rebuild per buffer and the renderer can pull
1886 /// the matrix matching each pane's buffer at paint time. The
1887 /// active-document entry is boot-seeded to share its Arc identity
1888 /// with [`Self::display_matrix_cell`]; other entries are inserted
1889 /// lazily via [`Self::display_matrix_for`].
1890 pub display_matrices: std::sync::Arc<
1891 std::sync::Mutex<
1892 std::collections::HashMap<
1893 lattice_core::BufferId,
1894 std::sync::Arc<arc_swap::ArcSwap<crate::display_matrix::DisplayMatrix>>,
1895 >,
1896 >,
1897 >,
1898 /// IG.2 (2026-08-16): per-document indentation-guide registry.
1899 /// Exact peer of [`Self::display_matrices`] — the guide layer is
1900 /// built in the same worker pass, from the same snapshot, and
1901 /// carries the same `MatrixVersion`, so it is keyed and reached
1902 /// the same way. Inserted lazily via [`Self::indent_guides_for`].
1903 pub indent_guides: std::sync::Arc<
1904 std::sync::Mutex<
1905 std::collections::HashMap<
1906 lattice_core::BufferId,
1907 std::sync::Arc<arc_swap::ArcSwap<crate::indent_guides::IndentGuides>>,
1908 >,
1909 >,
1910 >,
1911 /// TC.3b: per-PANE sticky-context registry. The one per-pane layer keyed
1912 /// by `PaneId` rather than `BufferId` — see
1913 /// [`crate::sticky_context`] for why the usual buffer keying is wrong
1914 /// here. Inserted lazily via [`Self::sticky_context_for`].
1915 pub sticky_contexts: std::sync::Arc<
1916 std::sync::Mutex<
1917 std::collections::HashMap<
1918 lattice_core::ui::pane::PaneId,
1919 std::sync::Arc<arc_swap::ArcSwap<crate::sticky_context::StickyContext>>,
1920 >,
1921 >,
1922 >,
1923 /// D.2.d (2026-05-29): diff subsystem instance. Holds the
1924 /// per-buffer `DiffSession` registry, the routing inverse
1925 /// index, and the per-session lazy debouncer. Reads through
1926 /// the host's `BufferTextProvider` impl (wired post-D.2.c
1927 /// when the first consumer slice lands) for live-rope
1928 /// baselines / current sources. `:describe-diff` reads
1929 /// `diff_subsystem.build_describe_diff_content()` directly.
1930 /// See `docs/dev/architecture/diff-system.md` §3.4.
1931 pub diff_subsystem: std::sync::Arc<crate::diff::subsystem::DiffSubsystem>,
1932 /// D.0a.1 (2026-05-29): virtual-rows worker output cell.
1933 /// Same stability pattern as `cells_matrix_cell`: the Arc
1934 /// identity lives on `Editor` so `build_render_state`
1935 /// clones it into every snapshot; the
1936 /// `virtual_rows_worker` holds a sibling clone and writes
1937 /// directly via `cell.store(new_matrix)`. Empty
1938 /// `VirtualRowMatrix` until the first provider registers
1939 /// and the worker rebuilds.
1940 pub virtual_rows_matrix_cell:
1941 std::sync::Arc<arc_swap::ArcSwap<lattice_cells::VirtualRowMatrix>>,
1942 /// D.4.d.2.0 (2026-05-29): per-document virtual-rows
1943 /// matrix registry. Mirror of [`Self::cells_matrices`]
1944 /// for the displacing-virtual-row primitive. Each
1945 /// visible buffer that takes the virtual-rows path gets
1946 /// its own `Arc<ArcSwap<VirtualRowMatrix>>` so the
1947 /// worker (after D.4.d.2.1.b) can rebuild per buffer,
1948 /// and the renderer can pull the right matrix per pane
1949 /// at paint time (load-bearing for side-by-side diff
1950 /// fillers — D.4 — where two panes show different
1951 /// hunks' filler rows simultaneously).
1952 ///
1953 /// The active-document entry is stored under
1954 /// `document_buffer_id` and **shares its Arc identity
1955 /// with [`Self::virtual_rows_matrix_cell`]** so the
1956 /// existing hot path (virtual_rows_worker writing
1957 /// through the field, renderer reading through
1958 /// `RenderState.virtual_rows.matrix`) stays bit-identical
1959 /// until the worker iteration upgrade lands in
1960 /// D.4.d.2.1.b.
1961 ///
1962 /// Inserts are lazy via
1963 /// [`Self::virtual_rows_matrix_for`] — a buffer's entry
1964 /// shows up the first time anything asks for its matrix.
1965 /// Pruning of stale entries is deferred until the worker
1966 /// actually consumes the registry.
1967 pub virtual_rows_matrices: std::sync::Arc<
1968 std::sync::Mutex<
1969 std::collections::HashMap<
1970 lattice_core::BufferId,
1971 std::sync::Arc<arc_swap::ArcSwap<lattice_cells::VirtualRowMatrix>>,
1972 >,
1973 >,
1974 >,
1975 /// D.0a.1 (2026-05-29): wake signal for the virtual-rows
1976 /// worker. `publish_render_state` fires `notify_one()`
1977 /// after every dispatch tick (permit-style coalescing
1978 /// mirrors `cells_wake`). Provider state changes fire the
1979 /// same signal directly to wake the worker between
1980 /// dispatch ticks.
1981 pub virtual_rows_wake: VirtualRowsWake,
1982 /// D.0a.1 (2026-05-29): the provider registry the
1983 /// virtual-rows worker iterates on every wake. Consumers
1984 /// (D.3 inline diff deletion-block provider, M.2
1985 /// multibuffer excerpt-header provider) register their
1986 /// providers here at slice-mount time and unregister at
1987 /// teardown.
1988 pub virtual_row_providers:
1989 std::sync::Arc<crate::virtual_rows_worker::VirtualRowProviderRegistry>,
1990 /// D.3.a.1 (2026-05-29): the bus-subscription guard from
1991 /// `DiffSubsystem::bind`. Held for the editor's lifetime;
1992 /// its `Drop` unsubscribes the bus + aborts the drainer
1993 /// task on editor teardown. Stored behind `Option` so the
1994 /// `Editor::default()` path (used by tests that don't
1995 /// boot through `editor_boot`) can leave it unset without
1996 /// the bind machinery firing.
1997 pub diff_subscription_guard: Option<crate::diff::subsystem::DiffSubscriptionGuard>,
1998 /// VCS.2 (2026-07-25): the bus-subscription guard from
1999 /// `VcsSubsystem::bind`. Held for the editor's lifetime;
2000 /// its `Drop` unsubscribes the bus + aborts the drainer
2001 /// task on editor teardown. Mirrors `diff_subscription_guard`.
2002 pub vcs_subscription_guard: Option<crate::vcs::VcsSubscriptionGuard>,
2003 /// D.3.a.1 (2026-05-29): per-session wake-forwarder
2004 /// `JoinHandle`s. `:diff` spawns a tokio task that awaits
2005 /// `DiffSession::publish_notify().notified()` and fires
2006 /// `VirtualRowsWake` on each publish; `:diffoff` aborts
2007 /// the task by `BufferId` and unregisters the provider.
2008 /// `tokio::sync::Mutex` is overkill here — mutation is
2009 /// `:diff`/`:diffoff` frequency, never per-frame.
2010 pub diff_forwarders: std::sync::Arc<
2011 std::sync::Mutex<
2012 std::collections::HashMap<lattice_core::BufferId, tokio::task::JoinHandle<()>>,
2013 >,
2014 >,
2015 /// I4 (Claude Code IDE peer, `openDiff`): host-drained inbound receiver for
2016 /// programmatic side-by-side diff requests. An off-thread producer (the IDE
2017 /// peer) `send`s a [`lattice_diff::ProgrammaticDiffRequest`] on the matching
2018 /// [`lattice_diff::ProgrammaticDiffBus`] (registered as a boot service); the
2019 /// `send` wakes the editor, and [`Self::drain_inbound_programmatic_diffs`]
2020 /// drains this receiver per tick, opening each diff on the actor thread. The
2021 /// open is irreducibly `&mut Editor` + lattice-diff types, so — like LSP
2022 /// `workspace/applyEdit` (`pending_apply_edit_rx`) — it is host-drained, not
2023 /// a mode-owned `Effect` handler.
2024 pub pending_programmatic_diff_rx:
2025 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_diff::ProgrammaticDiffRequest>>,
2026 /// I4: per-session "save the current (right) side to this path on Accept"
2027 /// map, keyed by the session's primary `BufferId` (the proposed/right
2028 /// buffer). Set when [`Self::open_programmatic_diff`] registers a session;
2029 /// honored in `tear_down_single_diff_session` (a `DiffOutcome::Accept` writes
2030 /// the buffer here before firing the bound oneshot — the openDiff
2031 /// `FILE_SAVED` contract: the review *is* the save). Removed on teardown.
2032 pub programmatic_diff_accept_paths:
2033 std::collections::HashMap<lattice_core::BufferId, std::path::PathBuf>,
2034 /// I4 (openDiff) D-fix.1: per programmatic-diff-session pane teardown info,
2035 /// keyed by the session's primary (proposed) `BufferId`. Recorded by
2036 /// `open_programmatic_diff`; consumed by `finish_programmatic_diff_panes`
2037 /// on `:diff-accept` / `:diff-reject` to close the transient diff panes and
2038 /// return focus to the originating (`:claude`) pane. Populated/cleared in
2039 /// lockstep with `programmatic_diff_accept_paths`.
2040 pub programmatic_diff_panes:
2041 std::collections::HashMap<lattice_core::BufferId, ProgrammaticDiffPanes>,
2042 /// D-fix.5: per diff-participant buffer, the last `HunkIndex`
2043 /// revision its folds were recomputed against. `refresh_diff_folds`
2044 /// (run each tick on the diff-publish wake) consults this to skip
2045 /// buffers whose hunks haven't moved — so a diff session's unchanged +
2046 /// hunk folds refresh off-keystroke when the async recompute
2047 /// publishes, without re-folding on every unrelated wake. Keyed by
2048 /// the participant `BufferId` (each side tracked independently, since
2049 /// each folds its own slot). Stale entries are harmless; the buffer's
2050 /// `diff-mode` deactivation drops its fold sources, and the next
2051 /// recompute simply finds none.
2052 pub diff_fold_seen_revisions: std::collections::HashMap<lattice_core::BufferId, u64>,
2053 /// Document version each buffer's overlay folds were last computed
2054 /// at — see `Editor::refresh_overlay_folds`. Peer of
2055 /// `diff_fold_seen_revisions`, keyed on the document rather than a
2056 /// diff session so it covers overlays whose buffer is edited out of
2057 /// band (magit-status's inline diff toggle).
2058 pub overlay_fold_seen_versions: std::collections::HashMap<lattice_core::BufferId, u64>,
2059 /// S2.1 (2026-05-26): wake signal for the cell-builder worker.
2060 /// `publish_render_state` fires `notify_one()` after every
2061 /// dispatch tick. The worker `notified().await`s; permit-style
2062 /// coalescing handles bursts.
2063 pub cells_wake: CellsWake,
2064 /// S2.4.b (2026-05-26): single-edit tracker for the
2065 /// cell-builder's incremental rebuild path. `Some(delta)`
2066 /// iff exactly one `apply_edit_blocking` (or LSP-applied
2067 /// edit) call has happened since the last
2068 /// `build_render_state` AND the previous publish cycle had no
2069 /// pending delta. Any second edit, batch, undo, redo, or
2070 /// other multi-edit path clears it back to `None` —
2071 /// conservatively forcing the worker to full-rebuild rather
2072 /// than risk applying a stale single-edit shift.
2073 /// `build_render_state` `take()`s and hands it to the cells
2074 /// substate.
2075 pub last_edit_for_cells: Option<lattice_cells::EditDelta>,
2076 /// Phase 5.8.AF.6 / Slice X1b: paint-request signal. The
2077 /// highlights worker fires `paint_request.notify_one()` after
2078 /// every `WorkerDecision::Recomputed` so renderer peers can
2079 /// schedule a paint even when no user input was in flight.
2080 /// `Notify` coalesces; bursts of recomputes wake the bridge
2081 /// once and a single paint covers the latest spans. The TUI
2082 /// peer's 100ms event-poll picks up the new cell naturally
2083 /// (no bridge needed); the GPUI peer spawns a foreground-
2084 /// executor future that awaits this Notify and calls
2085 /// `cx.notify()` to schedule a render.
2086 pub paint_request: std::sync::Arc<tokio::sync::Notify>,
2087 /// Slice B.1 (2026-06-03): "async work landed" wake. Fired by
2088 /// async completions that produce render-relevant state with no
2089 /// keystroke in flight — today the syntax reparse worker (via the
2090 /// `on_publish` Notify handed to each `SyntaxHandle`). The editor
2091 /// actor's loop `select!`s on this and runs `run_tick_pending` +
2092 /// `publish_render_state`, so an idle reparse repaints without
2093 /// waiting for the next key (closes the X1b idle-arrival gap for
2094 /// syntax; LSP-response tasks can fire the same Notify as a
2095 /// follow-up). Distinct from `paint_request`, which is the
2096 /// downstream UI-redraw signal fired after a worker publishes.
2097 pub async_landed: std::sync::Arc<tokio::sync::Notify>,
2098 pub lsp_log_event_rx: Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::LspLogPushed>>,
2099 /// Merged user+project `lsp.*` config tree. BC.8b: shared
2100 /// (`Arc<ArcSwap<…>>`) so the mode-owned `workspace/configuration` inbound
2101 /// handler (`lattice_lsp::configuration::make_handler`) reads the *current*
2102 /// tree; the host re-`store`s it on config reload.
2103 pub lsp_config_tree: std::sync::Arc<arc_swap::ArcSwap<toml::Table>>,
2104 /// Perf plan B.4.b: wrapped in [`Versioned`] so the buffers
2105 /// sub-state cache can elide the per-publish HashMap clone.
2106 /// Mutators (`buffer_uris.insert/remove`) autoref `&mut`,
2107 /// fire `DerefMut`, and bump.
2108 pub buffer_uris: Versioned<HashMap<BufferId, lattice_lsp::Uri>>,
2109 // ---- LSP server-initiated channels ----
2110 pub pending_apply_edit_rx:
2111 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::InboundApplyEdit>>,
2112 // BC.8b/BC.8c: `pending_configuration_rx` + `pending_show_document_rx`
2113 // removed — the `workspace/configuration` and `window/showDocument` buses
2114 // are now the generic `InboundBus`, drained per-tick through their mode-
2115 // owned handlers (`boot.inbound`), not host `Editor` receiver fields.
2116 pub pending_show_message_request_rx:
2117 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::InboundShowMessageRequest>>,
2118 pub lsp_pending_show_message_requests: HashMap<u32, lattice_lsp::InboundShowMessageRequest>,
2119 pub lsp_show_message_request_queue: std::collections::VecDeque<u32>,
2120 pub lsp_next_show_message_request_id: u32,
2121 /// CG.1/CG.2: the **foreground** cancellation slot — work the user
2122 /// explicitly triggered that may hold up the next interaction
2123 /// (project search, an LSP command, a WASM plugin call).
2124 /// Background work (indexing, file watchers, the LSP boot
2125 /// handshake) owns its own long-lived tokens and is deliberately
2126 /// out of scope — see `docs/dev/architecture/cancellation.md` §3.
2127 ///
2128 /// CG.2 moved this from an owned `Option<CancellationToken>` to a
2129 /// shared handle, and registered the same `Arc` as a service. The
2130 /// `Editor` is only one of its users: providers arm through
2131 /// `services.get::<ForegroundCancelHandle>()`, because the places
2132 /// that spawn cancellable work — action-handler closures, event
2133 /// subscriptions — hold `&self` services and never `&mut Editor`.
2134 ///
2135 /// `Arc<T: Default>` is `Default`, so `#[derive(Default)]` on
2136 /// `Editor` still holds.
2137 pub foreground_cancel: lattice_mode::ForegroundCancelHandle,
2138 // ---- LSP per-feature request channels (rx + token pairs) ----
2139 pub pending_hover_rx: Option<tokio::sync::mpsc::UnboundedReceiver<HoverOutcome>>,
2140 pub pending_hover_token: Option<CancellationToken>,
2141 /// 2026-05-27: cursor + scroll captured at K-press time, consumed
2142 /// by `open_floating_popup` when the LSP response arrives so the
2143 /// hover popup anchors to the invocation site rather than wherever
2144 /// the cursor has drifted to. One-shot — cleared after the popup
2145 /// opens (or when the request is cancelled).
2146 pub pending_hover_anchor: Option<(lattice_protocol::position::Position, u32)>,
2147 pub pending_definition_rx:
2148 Option<tokio::sync::mpsc::UnboundedReceiver<Vec<lattice_lsp::lsp_types::Location>>>,
2149 pub pending_definition_token: Option<CancellationToken>,
2150 pub pending_nav_kind: Option<LspNavKind>,
2151 /// LR.2 / EP.6: which terminus the in-flight references request is
2152 /// for.
2153 ///
2154 /// Recorded when the request is issued so the drain routes without
2155 /// guessing. Defaults to `Picker` because that is what `gr` has
2156 /// always done and what every existing caller means.
2157 pub pending_references_terminus: ReferencesTerminus,
2158 /// LR.3: when the in-flight references request is a *refresh*, the
2159 /// view to rebuild in place. `None` means "open a new view".
2160 ///
2161 /// Without this a refresh would open a SECOND `*references*` buffer
2162 /// beside the one the user pressed `gr` in — the same mistake
2163 /// `*problems*` refresh had to avoid, since `create_multibuffer_view`
2164 /// mints a fresh `BufferId` every call.
2165 pub refreshing_references_view: Option<lattice_core::BufferId>,
2166 pub pending_references_rx: Option<tokio::sync::mpsc::UnboundedReceiver<ReferencesOutcome>>,
2167 pub pending_references_token: Option<CancellationToken>,
2168 pub pending_symbols_rx: Option<tokio::sync::mpsc::UnboundedReceiver<SymbolsOutcome>>,
2169 pub pending_symbols_token: Option<CancellationToken>,
2170 pub pending_format_rx: Option<tokio::sync::mpsc::UnboundedReceiver<FormatOutcome>>,
2171 /// IN.8b: results from an external formatter run. Separate channel
2172 /// from the LSP one because the payloads differ -- see
2173 /// `ExternalFormatOutcome`.
2174 pub pending_external_format_rx:
2175 Option<tokio::sync::mpsc::UnboundedReceiver<crate::dispatch::ExternalFormatOutcome>>,
2176 pub pending_format_token: Option<CancellationToken>,
2177 pub pending_signature_help_rx:
2178 Option<tokio::sync::mpsc::UnboundedReceiver<SignatureHelpOutcome>>,
2179 pub pending_signature_help_token: Option<CancellationToken>,
2180 pub pending_completion_rx: Option<tokio::sync::mpsc::UnboundedReceiver<CompletionOutcome>>,
2181 pub pending_completion_token: Option<CancellationToken>,
2182 pub pending_completion_items: Option<Vec<CompletionItemRow>>,
2183 pub pending_moniker_rx: Option<tokio::sync::mpsc::UnboundedReceiver<String>>,
2184 pub pending_rename_rx: Option<tokio::sync::mpsc::UnboundedReceiver<RenameOutcome>>,
2185 pub pending_rename_token: Option<CancellationToken>,
2186 pub pending_code_action_rx: Option<tokio::sync::mpsc::UnboundedReceiver<CodeActionOutcome>>,
2187 pub pending_code_action_token: Option<CancellationToken>,
2188 pub pending_code_action_items: Option<Vec<CodeActionRow>>,
2189 pub pending_code_action_handle: Option<lattice_lsp::ServerHandle>,
2190 pub pending_selection_range_rx:
2191 Option<tokio::sync::mpsc::UnboundedReceiver<SelectionRangeOutcome>>,
2192 pub pending_selection_range_token: Option<CancellationToken>,
2193 pub pending_document_highlight_token: Option<CancellationToken>,
2194 // Phase 5.8.AF.5 / Slice 3b.0: `pending_document_highlight_rx`
2195 // retired -- the spawned task now writes directly into
2196 // `lsp_document_highlights` (`ArcSwapOption`) when the
2197 // response arrives. No channel, no drain.
2198 pub pending_folding_range_token: Option<CancellationToken>,
2199 // Phase 5.8.AF.5 / Slice 3b.1: `pending_folding_range_rx`
2200 // retired -- the spawned task writes directly into
2201 // `lsp_folds_cache` via `PerBufferCacheExt::insert_for`.
2202 pub pending_document_links_token: Option<CancellationToken>,
2203 // Phase 5.8.AF.5 / Slice 3b.4: `pending_document_links_rx`
2204 // retired -- spawned task writes directly into
2205 // `lsp_document_links_cache` via `PerBufferCacheExt::insert_for`.
2206 pub pending_code_lens_token: Option<CancellationToken>,
2207 // Phase 5.8.AF.5 / Slice 3b.3: `pending_code_lens_rx` retired
2208 // -- spawned task writes directly into `lsp_code_lens_cache`
2209 // via `PerBufferCacheExt::insert_for`.
2210 pub pending_code_lens_refresh_rx:
2211 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::LspCodeLensRefresh>>,
2212 pub pending_code_lens_items: Option<Vec<lattice_lsp::lsp_types::CodeLens>>,
2213 pub pending_code_lens_server: Option<Arc<str>>,
2214 pub pending_document_color_token: Option<CancellationToken>,
2215 // Phase 5.8.AF.5 / Slice 3b.4: `pending_document_color_rx`
2216 // retired -- spawned task writes directly into
2217 // `lsp_document_color_cache` via `PerBufferCacheExt::insert_for`.
2218 pub pending_color_presentations: Option<Vec<lattice_lsp::lsp_types::ColorPresentation>>,
2219 pub pending_color_range: Option<lattice_lsp::lsp_types::Range>,
2220 pub pending_inlay_hint_token: Option<CancellationToken>,
2221 // Phase 5.8.AF.5 / Slice 3b.1: `pending_inlay_hint_rx`
2222 // retired -- the spawned task writes directly into
2223 // `lsp_inlay_hints_cache` via `PerBufferCacheExt::insert_for`.
2224 pub pending_semantic_tokens_token: Option<CancellationToken>,
2225 // Phase 5.8.AF.5 / Slice 3b.2: `pending_semantic_tokens_rx`
2226 // retired -- the spawned task writes directly into
2227 // `lsp_semantic_tokens_cache` via `PerBufferCacheExt::insert_for`
2228 // (or `remove_for` on result_id mismatch in the Delta path).
2229 pub pending_pull_diagnostics_token: Option<CancellationToken>,
2230 // Phase 5.8.AF.5 / Slice 3b.5: `pending_pull_diagnostics_rx`
2231 // retired -- spawned task writes directly into
2232 // `lsp_pull_diagnostics_cache` + `lsp_diagnostics` layer.
2233 pub pending_diagnostic_refresh_rx:
2234 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::LspDiagnosticRefresh>>,
2235 pub pending_inlay_hint_refresh_rx:
2236 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::LspInlayHintRefresh>>,
2237 /// 2026-06-03: buffers whose server sent
2238 /// `workspace/inlayHint/refresh` since their hints were last
2239 /// requested. `drain_inlay_hint_refresh` marks here instead of
2240 /// wiping `lsp_inlay_hints_cache`, so the previously-resolved
2241 /// hints stay rendered until the refetch lands (no
2242 /// disappear-then-reappear flicker — `feedback_decorations_update_in_place`).
2243 /// `maybe_request_inlay_hint` consults this to force a refetch
2244 /// even when the document version is unchanged, and clears the
2245 /// entry once it issues the request.
2246 pub inlay_refresh_pending: std::collections::HashSet<lattice_core::BufferId>,
2247 /// 2026-06-03: same shape as [`Self::inlay_refresh_pending`] for
2248 /// `workspace/semanticTokens/refresh`. The semantic-token colour
2249 /// overlay renders directly from `lsp_semantic_tokens_cache` every
2250 /// frame, so wiping the cache on refresh blanked all LSP colouring
2251 /// until the refetch landed (whole-viewport flicker per keystroke).
2252 /// `drain_semantic_tokens_refresh` marks here instead; the prior
2253 /// tokens keep rendering and `maybe_request_semantic_tokens` forces
2254 /// a refetch (delta from the retained `result_id`) that swaps them
2255 /// in place. (Pull diagnostics render from the persistent
2256 /// `DiagnosticsLayer` and code lenses are picker-only, so neither
2257 /// needs this — audited 2026-06-03.)
2258 pub semantic_tokens_refresh_pending: std::collections::HashSet<lattice_core::BufferId>,
2259 pub pending_semantic_tokens_refresh_rx:
2260 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::LspSemanticTokensRefresh>>,
2261 pub pending_lsp_detach_rx:
2262 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_lsp::events::LspBufferDetached>>,
2263 pub pending_mode_lifecycle_rx:
2264 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_mode::ModeEvent>>,
2265 /// MA.2: receives `Event::MajorEntered` so the per-tick
2266 /// minor-activation resolver (`drain_minor_activation`) can
2267 /// auto-activate minors whose `ActivationPolicy` admits the
2268 /// just-entered major on this buffer's kind.
2269 pub pending_major_entered_rx:
2270 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_protocol::Event>>,
2271 /// CI.4: receives `Event::ModeEnablementRequested` (a plugin's `enable-mode`)
2272 /// so the per-tick `drain_mode_enablement` flips the mode registry and
2273 /// re-activates open buffers.
2274 pub pending_mode_enablement_rx:
2275 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_protocol::Event>>,
2276 /// Receives `Event::BufferOptionOverrideRequested` (a plugin's
2277 /// `set-option-in-buffer`) so the per-tick drain writes the buffer-local
2278 /// layer. `pending_mode_enablement_rx`'s shape and its reason: the layer
2279 /// lives here, on the Editor, and the guest holds only a `ConfigRegistry`
2280 /// handle — which is the global layer and the wrong scope.
2281 pub pending_buffer_option_override_rx:
2282 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_protocol::Event>>,
2283 /// OA.15a: receives `ProviderViewRefreshRequested` (a plugin's
2284 /// `refresh-view`) so the per-tick `drain_provider_view_refresh` re-opens
2285 /// the view. The guest cannot reach the activator, so the call is a request
2286 /// and this is where it is applied — `pending_mode_enablement_rx`'s shape,
2287 /// with a typed event so the wake comes for free.
2288 pub pending_provider_view_refresh_rx: Option<
2289 tokio::sync::mpsc::UnboundedReceiver<
2290 lattice_mode::provider_view::ProviderViewRefreshRequested,
2291 >,
2292 >,
2293 pub pending_insert_completion_async_rx:
2294 Option<tokio::sync::mpsc::UnboundedReceiver<lattice_completion::AsyncCompletionOutcome>>,
2295 pub pending_insert_completion_async_token: Option<CancellationToken>,
2296 pub pending_completion_resolve_rx:
2297 Option<tokio::sync::mpsc::UnboundedReceiver<CompletionResolveOutcome>>,
2298 pub pending_completion_resolve_token: Option<CancellationToken>,
2299 /// M.2.b.2 (2026-06-01): `RendererSignal`s accumulated by
2300 /// `impl ModeActivator for Editor` calls made through
2301 /// extension-crate code paths (`create_multibuffer_view` and
2302 /// future provider triggers). The trait surface returns `()`
2303 /// — keeping `RendererSignal` out of `lattice-mode` — so
2304 /// signals are stashed here until the App's dispatch loop
2305 /// drains them via
2306 /// [`Editor::drain_pending_renderer_signals`].
2307 pub pending_renderer_signals: Vec<RendererSignal>,
2308 /// OR.16: see [`Editor::drain_pending_renderer_effects`].
2309 pub pending_renderer_effects: Vec<lattice_grammar::Effect>,
2310}
2311
2312impl Editor {
2313 /// M.2.b.2 (2026-06-01): drain renderer signals accumulated
2314 /// by `impl ModeActivator for Editor` calls — extension-crate
2315 /// code (`lattice_multibuffer::create_multibuffer_view`,
2316 /// future provider triggers) drives activation through the
2317 /// trait surface that returns `()`, so the host loop must
2318 /// pull queued signals into the active `DispatchOutcome`
2319 /// after the call frame returns.
2320 #[must_use]
2321 pub fn drain_pending_renderer_signals(&mut self) -> Vec<RendererSignal> {
2322 std::mem::take(&mut self.pending_renderer_signals)
2323 }
2324
2325 /// OR.16: renderer-owned effects produced on a path with no renderer to
2326 /// hand them to, for the peers to apply on their next frame.
2327 ///
2328 /// The off-renderer paths (the async picker accept, the fill target, the
2329 /// picker's delete verb) apply what they can themselves; anything left is
2330 /// queued here instead of being dropped, which is how four features went
2331 /// missing before. Both peers drain this beside the tick's signals.
2332 #[must_use]
2333 pub fn drain_pending_renderer_effects(&mut self) -> Vec<lattice_grammar::Effect> {
2334 std::mem::take(&mut self.pending_renderer_effects)
2335 }
2336
2337 /// M.2.b.2 (2026-06-01): push a renderer-signal batch onto
2338 /// the trait-activator's pending queue. Called by
2339 /// [`crate::activator`]'s impl after each cascade returns.
2340 pub(crate) fn enqueue_renderer_signals(&mut self, mut signals: Vec<RendererSignal>) {
2341 self.pending_renderer_signals.append(&mut signals);
2342 }
2343
2344 /// 2026-05-26: register an invocation-runner function under
2345 /// the mode-id its owning [`lattice_mode::Mode`] declares via
2346 /// [`lattice_mode::Mode::invocation_runner`]. Called from
2347 /// `Editor::boot` for each built-in runner
2348 /// (`run_help_invocation` / `run_oil_invocation` /
2349 /// `run_file_tree_invocation` / `run_terminal_invocation`);
2350 /// plugins (post Phase 7) reuse this entry point for the
2351 /// modes they install. Overwrites silently on duplicate
2352 /// registration — boot order is the single writer.
2353 pub fn register_invocation_runner(
2354 &mut self,
2355 id: lattice_mode::ModeId,
2356 runner: InvocationRunnerFn,
2357 ) {
2358 self.invocation_runners.insert(id, runner);
2359 }
2360
2361 /// 2026-05-26: resolve the invocation runner for `buffer_id`
2362 /// by walking the active modes (minors most-recently-
2363 /// activated first, then major) and returning the first
2364 /// runner whose mode declared
2365 /// [`lattice_mode::Mode::invocation_runner`] and has a
2366 /// registered function on `self.invocation_runners`.
2367 /// Mirrors [`crate::pane_render::resolve_pane_render_mode`]
2368 /// — same walk, different table. Returns `None` when no
2369 /// active mode owns dispatch (Document panes today).
2370 pub fn resolve_invocation_runner(
2371 &self,
2372 buffer_id: lattice_core::BufferId,
2373 ) -> Option<InvocationRunnerFn> {
2374 let modes = self.active_modes.get(&buffer_id)?;
2375 for &minor_id in modes.minors().iter().rev() {
2376 let mode = self.mode_registry.load().get(minor_id)?;
2377 if let Some(runner_id) = mode.invocation_runner()
2378 && let Some(runner) = self.invocation_runners.get(&runner_id)
2379 {
2380 return Some(*runner);
2381 }
2382 }
2383 let major_id = modes.major()?;
2384 let mode = self.mode_registry.load().get(major_id)?;
2385 let runner_id = mode.invocation_runner()?;
2386 self.invocation_runners.get(&runner_id).copied()
2387 }
2388
2389 /// RV.1: is `id` the generic `action:view-refresh` the shared `gr`
2390 /// binds to?
2391 ///
2392 /// Resolved by name rather than cached in a field so there is no
2393 /// boot-ordering coupling between this and command registration.
2394 /// The cost is one `ServiceRegistry` lookup plus one name hash on
2395 /// the action-dispatch path — which runs at chord rate (human
2396 /// keypresses), not per frame or per glyph, so it is nowhere near
2397 /// paramount-goal-#1 territory. Memoize if a dispatch bench ever
2398 /// says otherwise.
2399 pub fn is_view_refresh_command(&self, id: lattice_protocol::ids::CommandId) -> bool {
2400 self.services
2401 .get::<lattice_grammar::CommandRegistryHandle>()
2402 .and_then(|reg| reg.load().id_by_name(lattice_mode::VIEW_REFRESH_ACTION))
2403 == Some(id)
2404 }
2405
2406 /// RV.1 (2026-08-10): resolve the refresh action for `buffer_id` by
2407 /// walking the active modes (minors most-recently-activated first,
2408 /// then major) and returning the `CommandId` of the first
2409 /// [`lattice_mode::Mode::refresh_action`] declared.
2410 ///
2411 /// Same walk as [`Self::resolve_invocation_runner`], different
2412 /// table — most-specific-wins, so a provider minor on a multibuffer
2413 /// beats the generic `MultibufferMode`. Backs the shared `gr` chord
2414 /// (`refreshable-view-mode`): the mode declares which of its own
2415 /// actions refreshes, the host walks and dispatches it.
2416 ///
2417 /// `None` when no active mode declares one — the caller echoes
2418 /// rather than swallowing the key, so a view without a refresh says
2419 /// so. See `docs/dev/architecture/mode-architecture.md` §5.5.
2420 pub fn resolve_refresh_action(
2421 &self,
2422 buffer_id: lattice_core::BufferId,
2423 ) -> Option<lattice_protocol::ids::CommandId> {
2424 let modes = self.active_modes.get(&buffer_id)?;
2425 let registry = self.mode_registry.load();
2426 let mut declared: Option<&'static str> = None;
2427 // Walk minors (most-recently-activated first), then the major.
2428 // The walk does NOT stop at the first hit: continuing costs a
2429 // handful of `Option` reads over the buffer's active modes and
2430 // buys a `debug!` naming every shadowed declaration. Two modes
2431 // both claiming the refresh is a wiring bug in one of them, and
2432 // silently picking one is how it would stay invisible.
2433 //
2434 // Unlike `resolve_invocation_runner`, a minor missing from the
2435 // registry skips rather than aborting the walk — one absent
2436 // minor must not mask a major's declaration.
2437 let candidates = modes
2438 .minors()
2439 .iter()
2440 .rev()
2441 .copied()
2442 .chain(modes.major())
2443 .filter_map(|id| registry.get(id).map(|m| (id, m)));
2444 for (mode_id, mode) in candidates {
2445 let Some(action) = mode.refresh_action() else {
2446 continue;
2447 };
2448 match declared {
2449 None => declared = Some(action),
2450 Some(winner) => tracing::debug!(
2451 %mode_id,
2452 shadowed = action,
2453 winner,
2454 "several active modes declare a refresh action; the most specific wins"
2455 ),
2456 }
2457 }
2458 let action = declared?;
2459 let cmd_reg = self
2460 .services
2461 .get::<lattice_grammar::CommandRegistryHandle>()?;
2462 let id = cmd_reg.load().id_by_name(action);
2463 if id.is_none() {
2464 // A mode declared an action name the command registry does
2465 // not know. Loud at debug rather than a silent dead key —
2466 // this is a wiring bug in the declaring crate.
2467 tracing::debug!(
2468 action,
2469 "refresh_action names an unregistered command; `gr` will report nothing to refresh"
2470 );
2471 }
2472 id
2473 }
2474
2475 /// OA.4b: is `id` the generic `action:view-fold-toggle` the shared
2476 /// `<Tab>` binds to? Peer of [`Self::is_view_refresh_command`], same
2477 /// resolve-by-name reasoning.
2478 pub fn is_view_fold_toggle_command(&self, id: lattice_protocol::ids::CommandId) -> bool {
2479 self.services
2480 .get::<lattice_grammar::CommandRegistryHandle>()
2481 .and_then(|reg| reg.load().id_by_name(lattice_mode::VIEW_FOLD_TOGGLE_ACTION))
2482 == Some(id)
2483 }
2484
2485 /// OA.4b: resolve the fold-toggle action for `buffer_id` by walking the
2486 /// active modes (minors most-recently-activated first, then major) and
2487 /// returning the `CommandId` of the first
2488 /// [`lattice_mode::Mode::fold_toggle_action`] declared.
2489 ///
2490 /// Identical walk to [`Self::resolve_refresh_action`], different table,
2491 /// and deliberately so: `<Tab>` and `gr` are the same shape of problem —
2492 /// one chord that means the same *verb* in every grouped view while the
2493 /// *body* stays each view's own. Most-specific wins, so a provider minor
2494 /// on a multibuffer beats the generic `MultibufferMode`.
2495 ///
2496 /// `None` when no active mode declares one — the caller leaves `<Tab>`
2497 /// alone, so an ordinary document keeps jump-list-forward.
2498 pub fn resolve_fold_toggle_action(
2499 &self,
2500 buffer_id: lattice_core::BufferId,
2501 ) -> Option<lattice_protocol::ids::CommandId> {
2502 let modes = self.active_modes.get(&buffer_id)?;
2503 let registry = self.mode_registry.load();
2504 let mut declared: Option<&'static str> = None;
2505 let candidates = modes
2506 .minors()
2507 .iter()
2508 .rev()
2509 .copied()
2510 .chain(modes.major())
2511 .filter_map(|id| registry.get(id).map(|m| (id, m)));
2512 for (mode_id, mode) in candidates {
2513 let Some(action) = mode.fold_toggle_action() else {
2514 continue;
2515 };
2516 match declared {
2517 None => declared = Some(action),
2518 Some(winner) => tracing::debug!(
2519 %mode_id,
2520 shadowed = action,
2521 winner,
2522 "several active modes declare a fold toggle; the most specific wins"
2523 ),
2524 }
2525 }
2526 let action = declared?;
2527 let cmd_reg = self
2528 .services
2529 .get::<lattice_grammar::CommandRegistryHandle>()?;
2530 let id = cmd_reg.load().id_by_name(action);
2531 if id.is_none() {
2532 tracing::debug!(
2533 action,
2534 "fold_toggle_action names an unregistered command; `<Tab>` will do nothing"
2535 );
2536 }
2537 id
2538 }
2539
2540 /// D.4.d.0 (2026-05-29): lazy port into the per-document
2541 /// [`Self::cells_matrices`] registry. Returns the matrix
2542 /// cell for `buffer_id`, inserting an empty
2543 /// `Arc<ArcSwap<CellMatrix>>` on first ask.
2544 ///
2545 /// Idempotent: every call for the same `buffer_id`
2546 /// returns the same `Arc` identity so renderer reads and
2547 /// worker writes stay coherent.
2548 ///
2549 /// The active document's entry is seeded at boot to
2550 /// share its `Arc` with [`Self::cells_matrix_cell`], so
2551 /// callers that resolve the active doc through either
2552 /// surface land on the same cell.
2553 pub fn cells_matrix_for(
2554 &self,
2555 buffer_id: lattice_core::BufferId,
2556 ) -> std::sync::Arc<arc_swap::ArcSwap<lattice_cells::CellMatrix>> {
2557 let mut map = self
2558 .cells_matrices
2559 .lock()
2560 .expect("cells_matrices mutex poisoned");
2561 map.entry(buffer_id).or_default().clone()
2562 }
2563
2564 /// B2.1 (2026-06-04): lazy port into the per-document
2565 /// [`Self::display_matrices`] registry. Mirror of
2566 /// [`Self::cells_matrix_for`] for the per-line display cache.
2567 /// Returns the matrix cell for `buffer_id`, inserting an empty
2568 /// `Arc<ArcSwap<DisplayMatrix>>` on first ask.
2569 ///
2570 /// Idempotent: every call for the same `buffer_id` returns the
2571 /// same `Arc` identity so renderer reads and worker writes stay
2572 /// coherent. The active document's entry is boot-seeded to share
2573 /// its `Arc` with [`Self::display_matrix_cell`].
2574 pub fn display_matrix_for(
2575 &self,
2576 buffer_id: lattice_core::BufferId,
2577 ) -> std::sync::Arc<arc_swap::ArcSwap<crate::display_matrix::DisplayMatrix>> {
2578 let mut map = self
2579 .display_matrices
2580 .lock()
2581 .expect("display_matrices mutex poisoned");
2582 map.entry(buffer_id).or_default().clone()
2583 }
2584
2585 /// IG.2 (2026-08-16): lazy port into [`Self::indent_guides`].
2586 /// Peer of [`Self::display_matrix_for`], with the same idempotence
2587 /// contract: every call for the same `buffer_id` returns the same
2588 /// `Arc` identity, so renderer reads and worker writes stay
2589 /// coherent.
2590 pub fn indent_guides_for(
2591 &self,
2592 buffer_id: lattice_core::BufferId,
2593 ) -> std::sync::Arc<arc_swap::ArcSwap<crate::indent_guides::IndentGuides>> {
2594 let mut map = self
2595 .indent_guides
2596 .lock()
2597 .expect("indent_guides mutex poisoned");
2598 map.entry(buffer_id).or_default().clone()
2599 }
2600
2601 /// TC.3b: the sticky-context cell for `pane_id`, inserting an empty layer
2602 /// on first ask. Keyed by PANE — two panes on one buffer resolve different
2603 /// context and must not share a cell.
2604 ///
2605 /// Same idempotence contract as the sibling accessors: every call for the
2606 /// same `pane_id` returns the same `Arc` identity, so renderer reads and
2607 /// worker writes stay coherent.
2608 pub fn sticky_context_for(
2609 &self,
2610 pane_id: lattice_core::ui::pane::PaneId,
2611 ) -> std::sync::Arc<arc_swap::ArcSwap<crate::sticky_context::StickyContext>> {
2612 let mut map = self
2613 .sticky_contexts
2614 .lock()
2615 .expect("sticky_contexts mutex poisoned");
2616 map.entry(pane_id).or_default().clone()
2617 }
2618
2619 /// D.4.d.2.0 (2026-05-29): lazy port into the per-document
2620 /// [`Self::virtual_rows_matrices`] registry. Mirror of
2621 /// [`Self::cells_matrix_for`] for the virtual-row pipeline.
2622 /// Returns the matrix cell for `buffer_id`, inserting an
2623 /// empty `Arc<ArcSwap<VirtualRowMatrix>>` on first ask.
2624 ///
2625 /// Idempotent: every call for the same `buffer_id`
2626 /// returns the same `Arc` identity so renderer reads and
2627 /// worker writes stay coherent.
2628 ///
2629 /// The active document's entry is seeded at boot to
2630 /// share its `Arc` with
2631 /// [`Self::virtual_rows_matrix_cell`], so callers that
2632 /// resolve the active doc through either surface land
2633 /// on the same cell.
2634 pub fn virtual_rows_matrix_for(
2635 &self,
2636 buffer_id: lattice_core::BufferId,
2637 ) -> std::sync::Arc<arc_swap::ArcSwap<lattice_cells::VirtualRowMatrix>> {
2638 let mut map = self
2639 .virtual_rows_matrices
2640 .lock()
2641 .expect("virtual_rows_matrices mutex poisoned");
2642 map.entry(buffer_id).or_default().clone()
2643 }
2644}