lattice_host/renderer.rs
1//! The host-side `Renderer` trait.
2//!
3//! Phase 5.B.1 introduces the abstraction that lets `App` be
4//! generic over its renderer. Each frontend crate
5//! (`lattice-ui-tui`, future `lattice-ui-gpui`) defines a
6//! zero-sized marker type and implements [`Renderer`] for it,
7//! specifying the renderer-native types that fill the two
8//! associated slots `App` carries.
9//!
10//! ## Why only two associated types?
11//!
12//! The Phase 5.B.0 field audit
13//! ([`docs/dev/architecture/phase-5b-app-fields.md`]) classified
14//! every field on the current [`crate::App`] (when it lives here
15//! after 5.B.3) against its type's home crate. Of ~200 fields,
16//! **two** carry renderer-specific types:
17//!
18//! - the cached ratatui-typed `Theme` adapter the TUI's render
19//! loop reads on the hot path, and
20//! - the per-mode pane-render dispatch registry whose function
21//! pointers take a renderer-native frame type.
22//!
23//! Every other field is pure data or pulls from already-host-side
24//! crates. That makes the trait's surface deliberately small.
25//! Frame-level types (`Frame`, `InputEvent`, `LayoutConstraints`,
26//! …) live on Phase 5.6's separate `lattice-render::Renderer`
27//! trait — they have a different cardinality (one App-Renderer
28//! pairing ↔ many frame renders) and a different home.
29//!
30//! ## Anchor docs
31//!
32//! - [`docs/dev/architecture/phase-5-extraction.md`] — the
33//! overall Phase 5 plan.
34//! - [`docs/dev/architecture/phase-5b-app-fields.md`] — the
35//! field-by-field audit that confirmed this surface.
36
37/// The host-side renderer abstraction.
38///
39/// Implementors are zero-sized marker types in renderer crates:
40///
41/// ```ignore
42/// // in lattice-ui-tui:
43/// pub struct TuiRenderer;
44/// impl lattice_host::Renderer for TuiRenderer {
45/// type Theme = TuiTheme;
46/// type PaneRenderRegistry = TuiPaneRenderRegistry;
47/// }
48///
49/// // in lattice-ui-gpui (Phase 5.8+):
50/// pub struct GpuiRenderer;
51/// impl lattice_host::Renderer for GpuiRenderer {
52/// type Theme = GpuiTheme;
53/// type PaneRenderRegistry = GpuiPaneRenderRegistry;
54/// }
55/// ```
56///
57/// The trait is intentionally bare — every method goes through
58/// the associated types' native APIs rather than a virtual
59/// dispatch surface. Renderer crates own their hot-path reads.
60///
61/// **Bounds.** `'static + Send + Sync` so `App<R>` can cross
62/// threads (the LSP supervisor, syntax worker, mode dispatcher
63/// all spawn work on shared runtimes that hold references to
64/// `&App<R>`). The renderer marker type itself is ZST so the
65/// bounds are trivially satisfied.
66pub trait Renderer: 'static + Send + Sync {
67 /// Renderer-specific cached theme view. The host owns the
68 /// canonical neutral [`crate::ui::theme::Theme`]; on every
69 /// `:set ui.*` cascade the renderer rebuilds this cache
70 /// from the neutral theme via its own
71 /// `From<&host::ui::theme::Theme>` adapter. Reads on the
72 /// per-frame paint path go straight to this typed field
73 /// without indirection.
74 type Theme: 'static + Send + Sync;
75
76 /// Renderer-specific per-mode pane-render dispatch table.
77 /// The TUI's stores function pointers shaped
78 /// `fn(&mut ratatui::Frame, Rect, &App<TuiRenderer>, ...)`;
79 /// GPUI's stores its analogous shape for the GPUI paint
80 /// context. The host's mode-activation path doesn't care
81 /// about the inside — it just owns the field on App and
82 /// hands `&self.pane_render_registry` back to the renderer
83 /// at paint time.
84 type PaneRenderRegistry: 'static + Send + Sync;
85}
86
87/// Headless renderer marker for host-side tests and any
88/// renderer-neutral integration test that needs a concrete `R`
89/// without pulling in a real renderer's types.
90///
91/// Both associated types are `()`. App fields parametrized
92/// over `R::Theme` / `R::PaneRenderRegistry` collapse to unit
93/// values, costing one byte at most (and zero with niche
94/// optimization). Tests that exercise renderer-agnostic
95/// behaviour use `App<MinimalRenderer>`; tests that exercise
96/// TUI-specific behaviour use `App<TuiRenderer>` (defined in
97/// `lattice-ui-tui`).
98pub struct MinimalRenderer;
99
100impl Renderer for MinimalRenderer {
101 type Theme = ();
102 type PaneRenderRegistry = ();
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 /// Compile-time check: `MinimalRenderer` satisfies the
110 /// trait's `'static + Send + Sync` bounds without explicit
111 /// impls (the ZST trivially does).
112 fn assert_renderer<R: Renderer>() {}
113
114 #[test]
115 fn minimal_renderer_implements_renderer() {
116 assert_renderer::<MinimalRenderer>();
117 }
118
119 #[test]
120 fn minimal_renderer_associated_types_are_unit() {
121 // Exists primarily to document the design intent; the
122 // type system would catch a divergence at compile time.
123 let _: <MinimalRenderer as Renderer>::Theme = ();
124 let _: <MinimalRenderer as Renderer>::PaneRenderRegistry = ();
125 }
126}