Skip to main content

lattice_host/
preview.rs

1//! Preview isolation (PI series).
2//!
3//! In-pane picker preview renders buffer B as an **isolated read-only
4//! projection** in a pane — never mutating the committed buffer A, the
5//! global active-buffer hot state, or A's resolved options / mode stack.
6//! See `docs/dev/architecture/preview-isolation.md` for the contract and
7//! `docs/dev/operations/slice-plans/preview-isolation.md` (PI series) for
8//! sequencing.
9//!
10//! This module owns the two host-side pieces the design calls out:
11//!
12//! - [`PreviewOverride`] — the ephemeral per-pane sidecar value
13//!   (`Editor::preview_overrides`, keyed by `PaneId`). It records the
14//!   *displayed* buffer + the preview viewport (cursor / scroll) while a
15//!   pane's *committed* `buffer_id` stays put. Baked into the published
16//!   pane-tree leaves at render-publish time (`build_render_state`) so the
17//!   renderers show the displayed buffer while `:ls` / modeline / dispatch
18//!   keep reading the committed one.
19//! - [`PreviewMode`] — the `preview-mode` minor (§10.2 resolution: option
20//!   (a)) that owns `ReadOnly = true` and, by its presence on B's own mode
21//!   stack, the ephemeral "this buffer is being previewed" marker. It
22//!   deliberately does **not** touch `CursorLine`, so a preview keeps the
23//!   buffer's cursorline (the target line stays highlighted — e.g. an LSP
24//!   reference preview).
25
26use lattice_core::{BufferId, BufferKind};
27use lattice_mode::{CapabilitySet, LifecycleFuture, Mode, ModeContext, ModeId, ModeKind};
28use lattice_protocol::position::Position;
29
30/// PI.1: a pane's ephemeral preview projection. Stored host-side in
31/// `Editor::preview_overrides` (keyed by `PaneId`) so the live
32/// `Editor::pane_tree` stays committed + geometry-only; the override is
33/// baked into the *published* pane-tree leaves each frame.
34///
35/// The preview cursor / scroll live here (not on `Editor::cursor` /
36/// `Editor::scroll`) so entering / leaving preview never disturbs the
37/// committed buffer's viewport — exit is dropping this value, not
38/// restoring anything.
39#[derive(Debug, Clone, Copy, PartialEq)]
40pub struct PreviewOverride {
41    /// The buffer the pane currently *displays*. A real registry buffer
42    /// with its own resolved options (computed read-only via
43    /// `mount_preview`); the pane renders it exactly like an inactive
44    /// split, plus a cursorline at [`Self::cursor`].
45    pub buffer_id: BufferId,
46    /// The displayed buffer's kind (drives the renderer's per-kind pane
47    /// dispatch). Preview is Document-only for now (design §10.4).
48    pub buffer: BufferKind,
49    /// Preview cursor inside the displayed buffer. Location previews
50    /// (`gr` / grep) seat it on the target line; file previews start at
51    /// the top.
52    pub cursor: Position,
53    /// First visible line of the displayed buffer in the pane.
54    pub scroll: u32,
55}
56
57/// PI.2 (§10.2 option (a)): the `preview-mode` minor. Contributes
58/// `ReadOnly = true` to the buffer it is active on; its *presence* on a
59/// buffer's mode stack is the ephemeral "previewing" marker (introspect
60/// via `:describe-mode`). Activated only on the previewed buffer B's own
61/// stack (never A's), so B's `resolved_options` reflect read-only while
62/// the committed buffer is untouched.
63pub struct PreviewMode;
64
65impl PreviewMode {
66    pub fn mode_id() -> ModeId {
67        ModeId::new("preview-mode")
68    }
69}
70
71impl Mode for PreviewMode {
72    type Guard = ();
73    fn id(&self) -> ModeId {
74        Self::mode_id()
75    }
76    fn kind(&self) -> ModeKind {
77        ModeKind::Minor
78    }
79    fn options(&self) -> lattice_config::OptionOverrideSet {
80        // ReadOnly only — CursorLine is intentionally left to resolve
81        // from the buffer's own layers so preview keeps the cursorline
82        // that marks the target line.
83        lattice_config::overrides! {
84            lattice_config::ReadOnly = true,
85        }
86    }
87    fn required_capabilities(&self) -> CapabilitySet {
88        CapabilitySet::empty()
89    }
90    fn on_activate(&self, _ctx: ModeContext) -> LifecycleFuture<'_, ()> {
91        Box::pin(async { Ok(()) })
92    }
93}