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}