Skip to main content

lattice_core/ui/
tab.rs

1//! Tabs (issue #29, 2026-05-22).
2//!
3//! In vim a "tab page" is a container of windows — each tab owns
4//! its own pane tree. Buffers stay globally shared so the same
5//! buffer can appear in multiple tabs.
6//!
7//! Lattice mirrors that model:
8//! - `TabSlot` is a stash of one tab's pane tree + optional label.
9//! - `Editor.pane_tree` remains the live `PaneTree` for the
10//!   active tab (zero-churn for the hundreds of `editor.pane_tree`
11//!   call sites).
12//! - `Editor.tabs: Vec<TabSlot>` holds one entry per tab,
13//!   including the active one. The active tab's `panes` field
14//!   in that vec is a default placeholder while it's "live";
15//!   on tab switch we `mem::swap` between `editor.pane_tree`
16//!   and `editor.tabs[target].panes`.
17//!
18//! ## Tab IDs
19//!
20//! Tabs carry a process-monotonic `TabId` (parallels `PaneId` /
21//! `BufferId`). Useful for stable references in render state and
22//! events — index-based addressing changes when tabs are reordered
23//! or closed.
24
25use std::sync::atomic::{AtomicU32, Ordering};
26
27use crate::ui::pane::PaneTree;
28
29crate::labeled_enum! {
30    /// `:set tabline.show=...` (issue #29, 2026-05-22). Controls
31    /// when the tabline row is visible at the top of the screen.
32    ///
33    /// Mirrors vim's `:set showtabline` (`0` / `1` / `2`) but
34    /// uses readable labels.
35    pub enum TablineShow {
36        /// Never paint the tabline (no row reserved).
37        Never = "never" => "Never show the tabline",
38        /// Auto: show only when more than one tab is open.
39        #[default]
40        Auto = "auto" => "Show only when multiple tabs are open",
41        /// Always paint the tabline, even for a single tab.
42        Always = "always" => "Always show the tabline",
43    }
44}
45
46/// Process-monotonic tab id. Stable across reorder / close.
47#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
48pub struct TabId(pub u32);
49
50impl TabId {
51    /// Mint the next id. Like `PaneId::next` — process-wide
52    /// atomic counter starting at 1 so `TabId::default()` (0)
53    /// is unambiguously "no tab".
54    pub fn next() -> Self {
55        static NEXT: AtomicU32 = AtomicU32::new(1);
56        Self(NEXT.fetch_add(1, Ordering::Relaxed))
57    }
58}
59
60/// One tab's stashed state. The active tab's `panes` is a
61/// default-empty placeholder while that tab is live (its real
62/// panes live on `editor.pane_tree`). Inactive tabs hold the
63/// full pane tree here.
64#[derive(Debug, Clone, Default)]
65pub struct TabSlot {
66    /// This tab's stable id, minted by [`TabId::next`] in [`Self::new`].
67    /// `TabSlot::default()` leaves it at `TabId(0)`, the "no tab" value.
68    pub id: TabId,
69    /// Stashed pane tree. Read AS-IS for inactive tabs. For
70    /// the active tab this is a default placeholder; the live
71    /// tree is on `editor.pane_tree`.
72    pub panes: PaneTree,
73    /// Optional custom label. `None` ⇒ render derives the
74    /// label from the active pane's buffer name (basename of
75    /// the path, or `[scratch]`).
76    pub label: Option<String>,
77}
78
79impl TabSlot {
80    /// Construct a fresh tab with a new id and empty pane tree.
81    /// Caller is expected to populate `panes` (e.g. via
82    /// `PaneTree::single(initial_pane)`) before stashing or
83    /// to immediately `mem::swap` with `editor.pane_tree`.
84    pub fn new() -> Self {
85        Self {
86            id: TabId::next(),
87            panes: PaneTree::default(),
88            label: None,
89        }
90    }
91}