Skip to main content

lattice_dashboard/
mode.rs

1//! `dashboard-mode` — the major mode for the `*dashboard*` launch buffer
2//! (DB.2). It is self-contained: it contributes the read-only / gutterless
3//! option set directly (the same set help-mode contributes), so the dashboard
4//! needs no help-mode dependency for behaviour. `<CR>`-follow and Esc reuse
5//! the help machinery via the `BufferKind::Dashboard` gates
6//! (`dashboard.md` §9.2), not via a mode.
7//!
8//! Later slices hang more off this mode: the `dashboard.*` theme elements
9//! (DB.3) and the branding virtual-row provider (DB.4) register in
10//! `on_activate`.
11
12use lattice_config::OptionOverrideSet;
13use lattice_core::BufferKind;
14use lattice_mode::{LifecycleFuture, Mode, ModeContext, ModeId, ModeKind, ModeRegistry};
15use lattice_theme::{ElementOwner, ThemeRegistryHandle};
16
17use crate::theme::register_dashboard_theme_elements;
18
19pub struct DashboardMode;
20
21impl DashboardMode {
22    pub fn mode_id() -> ModeId {
23        ModeId::new("dashboard-mode")
24    }
25}
26
27impl Mode for DashboardMode {
28    type Guard = ();
29
30    fn id(&self) -> ModeId {
31        Self::mode_id()
32    }
33
34    fn kind(&self) -> ModeKind {
35        ModeKind::Major
36    }
37
38    /// Bind this major to `BufferKind::Dashboard` so `find_major_for_kind`
39    /// routes the `*dashboard*` buffer to it.
40    fn target_buffer_kind(&self) -> Option<BufferKind> {
41        Some(BufferKind::Dashboard)
42    }
43
44    /// Read-only, wrapped, no-file, gutterless — the same contribution set
45    /// help-mode carries. `ReadOnly` + `NoFile` mean the dispatcher gates
46    /// keystrokes and `:q` skips the dirty check; `Number = false` +
47    /// `signcolumn = no` render the page gutterless.
48    fn options(&self) -> OptionOverrideSet {
49        lattice_config::overrides! {
50            lattice_config::ReadOnly = true,
51            lattice_config::Wrap = true,
52            lattice_config::NoFile = true,
53            lattice_config::Number = false,
54            lattice_config::SignColumnOption = lattice_config::SignColumn::No,
55            // The dashboard is a splash page, not an editing surface — no
56            // current-line highlight (dashboard-only, via the major mode).
57            lattice_config::CursorLine = false,
58        }
59    }
60
61    fn on_activate(&self, ctx: ModeContext) -> LifecycleFuture<'_, ()> {
62        Box::pin(async move {
63            // DB.3: the mode owns its `dashboard.*` theme element vocabulary
64            // — register it here (idempotent by name). A missing service
65            // (test harness) just skips; the branding provider then renders
66            // with fallback colours.
67            if let Some(theme) = ctx
68                .service::<ThemeRegistryHandle>()
69                .map(|outer| (*outer).clone())
70            {
71                let owner = ElementOwner::Mode(Self::mode_id().as_str().to_string().into());
72                let _ = register_dashboard_theme_elements(theme.as_ref(), owner);
73            }
74            Ok(())
75        })
76    }
77}
78
79/// Register `dashboard-mode` into the mode registry. Called from `install`
80/// via `boot.modes_mut()`.
81pub fn register_dashboard_modes(registry: &mut ModeRegistry) {
82    registry
83        .register(DashboardMode)
84        .expect("dashboard-mode register");
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn id_kind_and_target() {
93        assert_eq!(DashboardMode.id(), DashboardMode::mode_id());
94        assert_eq!(DashboardMode::mode_id().as_str(), "dashboard-mode");
95        assert_eq!(DashboardMode.kind(), ModeKind::Major);
96        assert_eq!(
97            DashboardMode.target_buffer_kind(),
98            Some(BufferKind::Dashboard)
99        );
100    }
101
102    #[test]
103    fn contributes_read_only_gutterless_set() {
104        // ReadOnly + Wrap + NoFile + Number + SignColumn + CursorLine.
105        assert_eq!(DashboardMode.options().iter().count(), 6);
106    }
107}