1use std::collections::BTreeMap;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12use tear_types::{
13 LayoutNode, PaneId, PaneState, SessionId, SessionState, TearPane, TearSession, TearWindow,
14 WindowId, WindowState,
15};
16
17fn next_counter() -> u64 {
20 use std::sync::atomic::{AtomicU64, Ordering};
21 static COUNTER: AtomicU64 = AtomicU64::new(1);
22 COUNTER.fetch_add(1, Ordering::Relaxed)
23}
24
25fn now_unix() -> u64 {
26 SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .map(|d| d.as_secs())
29 .unwrap_or(0)
30}
31
32#[must_use]
34pub fn mint_session_id(name: &str) -> SessionId {
35 let seed = format!("session:{}:{}:{}", name, now_unix(), next_counter());
36 SessionId::from_seed(&seed)
37}
38#[must_use]
39pub fn mint_window_id(parent: SessionId, name: &str) -> WindowId {
40 let seed = format!("window:{}:{}:{}:{}", parent, name, now_unix(), next_counter());
41 WindowId::from_seed(&seed)
42}
43#[must_use]
44pub fn mint_pane_id(parent: WindowId, shell: &str) -> PaneId {
45 let seed = format!("pane:{}:{}:{}:{}", parent, shell, now_unix(), next_counter());
46 PaneId::from_seed(&seed)
47}
48
49#[derive(Debug, Default)]
54pub struct Registry {
55 pub sessions: BTreeMap<SessionId, TearSession>,
56}
57
58impl Registry {
59 #[must_use]
61 pub fn new() -> Self {
62 Self::default()
63 }
64
65 pub fn sessions_in_order(&self) -> Vec<TearSession> {
67 let mut v: Vec<_> = self.sessions.values().cloned().collect();
68 v.sort_by_key(|s| s.created_at_unix);
69 v
70 }
71
72 pub fn create_session(&mut self, name: &str) -> SessionId {
75 let id = mint_session_id(name);
76 let s = TearSession {
77 id,
78 name: name.into(),
79 windows: BTreeMap::new(),
80 panes: BTreeMap::new(),
81 active_window: WindowId::NULL,
82 state: SessionState::Active,
83 created_at_unix: now_unix(),
84 description: String::new(),
85 source: tear_types::SessionSource::default(),
86 freio: tear_types::Freio::Released,
87 };
88 self.sessions.insert(id, s);
89 id
90 }
91
92 pub fn add_window(
101 &mut self,
102 session_id: SessionId,
103 name: &str,
104 shell: &str,
105 args: &[String],
106 cwd: Option<&str>,
107 env: &[(String, String)],
108 size_cells: (u16, u16),
109 yurai: tear_types::Yurai,
115 ) -> Option<(WindowId, PaneId)> {
116 let s = self.sessions.get_mut(&session_id)?;
117 let win_id = mint_window_id(session_id, name);
118 let pane_id = mint_pane_id(win_id, shell);
119 let pane = TearPane {
120 id: pane_id,
121 shell: shell.into(),
122 args: args.to_vec(),
123 cwd: cwd.map(Into::into),
124 env: env.to_vec(),
125 size_cells,
126 origin_cells: (0, 0),
127 state: PaneState::Running,
128 title: shell.into(),
129 input_policy: tear_types::InputPolicy::default(),
130 yurai,
131 };
132 let win = TearWindow {
133 id: win_id,
134 name: name.into(),
135 layout: LayoutNode::leaf(pane_id),
136 active_pane: pane_id,
137 size_cells,
138 state: WindowState::Active,
139 };
140 s.windows.insert(win_id, win);
141 s.panes.insert(pane_id, pane);
142 if s.active_window == WindowId::NULL {
143 s.active_window = win_id;
144 }
145 Some((win_id, pane_id))
146 }
147
148 pub fn locate_pane(&self, pane: PaneId) -> Option<(SessionId, WindowId)> {
151 for s in self.sessions.values() {
152 if !s.panes.contains_key(&pane) {
153 continue;
154 }
155 for (wid, w) in &s.windows {
156 if w.layout.panes().contains(&pane) {
157 return Some((s.id, *wid));
158 }
159 }
160 }
161 None
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn create_session_lands_in_map() {
171 let mut r = Registry::new();
172 let id = r.create_session("work");
173 assert!(r.sessions.contains_key(&id));
174 assert_eq!(r.sessions[&id].name, "work");
175 }
176
177 #[test]
178 fn add_window_creates_initial_pane_and_focuses_it() {
179 let mut r = Registry::new();
180 let sid = r.create_session("work");
181 let (wid, pid) = r
182 .add_window(sid, "main", "/bin/zsh", &[], None, &[], (80, 24), tear_types::Yurai::Unknown)
183 .unwrap();
184 let s = &r.sessions[&sid];
185 assert!(s.windows.contains_key(&wid));
186 assert!(s.panes.contains_key(&pid));
187 assert_eq!(s.active_window, wid);
188 assert_eq!(s.windows[&wid].active_pane, pid);
189 assert_eq!(s.windows[&wid].layout.pane_count(), 1);
190 }
191
192 #[test]
200 fn add_window_records_the_spawn_args_cwd_and_env_on_the_seed_pane() {
201 let mut r = Registry::new();
202 let sid = r.create_session("work");
203 let args = vec!["-u".to_string(), "NONE".to_string()];
204 let env = vec![("EDITOR".to_string(), "nvim".to_string())];
205 let (_wid, pid) = r
206 .add_window(sid, "main", "/bin/nvim", &args, Some("/code"), &env, (80, 24), tear_types::Yurai::Unknown)
207 .unwrap();
208 let pane = &r.sessions[&sid].panes[&pid];
209 assert_eq!(pane.args, args, "seed pane must record its spawn args");
210 assert_eq!(pane.cwd.as_deref(), Some("/code"));
211 assert_eq!(pane.env, env);
212 }
213
214 #[test]
215 fn locate_pane_finds_its_parent() {
216 let mut r = Registry::new();
217 let sid = r.create_session("work");
218 let (wid, pid) = r
219 .add_window(sid, "main", "/bin/zsh", &[], None, &[], (80, 24), tear_types::Yurai::Unknown)
220 .unwrap();
221 assert_eq!(r.locate_pane(pid), Some((sid, wid)));
222 }
223}