Skip to main content

rmux_core/session/
accessors.rs

1use std::collections::BTreeMap;
2use std::path::{Path, PathBuf};
3
4use rmux_proto::{SessionName, TerminalSize};
5
6use super::{current_unix_timestamp, synchronized_active_window, Session, WindowIdAllocator};
7use crate::{AlertFlags, Pane, PaneId, SessionId, Window, WINLINK_ALERTFLAGS};
8
9impl Session {
10    /// Returns the stable validated session name.
11    #[must_use]
12    pub const fn name(&self) -> &SessionName {
13        &self.name
14    }
15
16    /// Returns the named session group when the session is grouped.
17    #[must_use]
18    pub const fn group_name(&self) -> Option<&SessionName> {
19        self.group_name.as_ref()
20    }
21
22    /// Returns the store-assigned session identity used by `$N` targets.
23    #[must_use]
24    pub const fn id(&self) -> SessionId {
25        self.id
26    }
27
28    /// Returns the session creation timestamp as Unix seconds.
29    #[must_use]
30    pub const fn created_at(&self) -> i64 {
31        self.created_at
32    }
33
34    /// Returns the last session activity timestamp as Unix seconds.
35    #[must_use]
36    pub const fn activity_at(&self) -> i64 {
37        self.activity_at
38    }
39
40    /// Returns the last attached timestamp as Unix seconds.
41    #[must_use]
42    pub const fn last_attached_at(&self) -> Option<i64> {
43        self.last_attached_at
44    }
45
46    /// Returns the session working directory when one has been assigned.
47    #[must_use]
48    pub fn cwd(&self) -> Option<&Path> {
49        self.cwd.as_deref()
50    }
51
52    /// Returns the terminal size last applied to the session as a whole.
53    #[must_use]
54    pub const fn terminal_size(&self) -> TerminalSize {
55        self.terminal_size
56    }
57
58    pub(crate) fn set_id(&mut self, id: SessionId) {
59        self.id = id;
60    }
61
62    pub(crate) fn rebind_window_id_allocator(&mut self, allocator: WindowIdAllocator) {
63        let next_after_windows = self
64            .windows
65            .values()
66            .map(|window| window.id().as_u32().saturating_add(1))
67            .max()
68            .unwrap_or_else(|| allocator.peek());
69        self.next_window_id = allocator;
70        self.next_window_id.bump_to(next_after_windows);
71    }
72
73    /// Renames the session without rewriting any other session state.
74    pub fn rename(&mut self, new_name: SessionName) {
75        self.name = new_name;
76    }
77
78    /// Assigns or clears the session group name without mutating any other session state.
79    pub fn set_group_name(&mut self, group_name: Option<SessionName>) {
80        self.group_name = group_name;
81    }
82
83    /// Updates the session working directory.
84    pub fn set_cwd(&mut self, cwd: Option<PathBuf>) {
85        self.cwd = cwd;
86    }
87
88    /// Records session activity at the current time.
89    pub fn touch_activity(&mut self) {
90        self.activity_at = current_unix_timestamp();
91    }
92
93    /// Records that a client attached to the session at the current time.
94    pub fn touch_attached(&mut self) {
95        let now = current_unix_timestamp();
96        self.activity_at = now;
97        self.last_attached_at = Some(now);
98    }
99
100    /// Clones the session as a grouped peer with a fresh identity and timestamps.
101    #[must_use]
102    pub fn clone_as_group_member(
103        &self,
104        name: SessionName,
105        group_name: SessionName,
106        session_id: SessionId,
107    ) -> Self {
108        let now = current_unix_timestamp();
109        let mut cloned = self.clone();
110        cloned.id = session_id;
111        cloned.name = name;
112        cloned.group_name = Some(group_name);
113        cloned.created_at = now;
114        cloned.activity_at = now;
115        cloned.last_attached_at = None;
116        cloned
117    }
118
119    /// Synchronizes shared grouped-session window state from the source session while preserving the local current window when possible.
120    pub fn synchronize_group_from(&mut self, source: &Session) {
121        debug_assert_ne!(self.name, source.name);
122        let previous_active = self.active_window;
123        let previous_last = self.last_window;
124
125        self.windows = source.windows.clone();
126        self.winlink_alert_flags = source.winlink_alert_flags.clone();
127        self.next_pane_id = source.next_pane_id;
128        self.cwd = source.cwd.clone();
129
130        self.active_window =
131            synchronized_active_window(&self.windows, previous_active, previous_last);
132        self.last_window = previous_last
133            .filter(|window_index| {
134                *window_index != self.active_window && self.windows.contains_key(window_index)
135            })
136            .or_else(|| {
137                (previous_active != self.active_window
138                    && self.windows.contains_key(&previous_active))
139                .then_some(previous_active)
140            });
141    }
142
143    /// Returns the session's active window.
144    #[must_use]
145    pub fn window(&self) -> &Window {
146        self.window_at(self.active_window)
147            .expect("active session window must exist")
148    }
149
150    /// Returns the explicitly addressed window when it exists.
151    #[must_use]
152    pub fn window_at(&self, window_index: u32) -> Option<&Window> {
153        self.windows.get(&window_index)
154    }
155
156    /// Returns all windows keyed by window index.
157    #[must_use]
158    pub const fn windows(&self) -> &BTreeMap<u32, Window> {
159        &self.windows
160    }
161
162    pub(crate) fn window_mut(&mut self) -> &mut Window {
163        self.window_at_mut(self.active_window)
164            .expect("active session window must exist")
165    }
166
167    /// Returns the addressed window as a mutable reference when it exists.
168    pub fn window_at_mut(&mut self, window_index: u32) -> Option<&mut Window> {
169        self.windows.get_mut(&window_index)
170    }
171
172    /// Returns the persistent alert flags for the addressed window slot.
173    #[must_use]
174    pub fn winlink_alert_flags(&self, window_index: u32) -> AlertFlags {
175        self.winlink_alert_flags
176            .get(&window_index)
177            .copied()
178            .unwrap_or_else(AlertFlags::empty)
179    }
180
181    /// Returns the combined session alert flags across all alerted windows.
182    #[must_use]
183    pub fn session_alert_flags(&self) -> AlertFlags {
184        self.winlink_alert_flags
185            .values()
186            .copied()
187            .fold(AlertFlags::empty(), |flags, winlink_flags| {
188                flags.union(winlink_flags)
189            })
190    }
191
192    /// Returns the alerted window indexes in display order.
193    #[must_use]
194    pub fn alerted_window_indexes(&self) -> Vec<u32> {
195        self.winlink_alert_flags
196            .iter()
197            .filter_map(|(window_index, flags)| {
198                flags
199                    .intersects(WINLINK_ALERTFLAGS)
200                    .then_some(*window_index)
201            })
202            .collect()
203    }
204
205    /// Returns whether any window in the session currently carries an alert.
206    #[must_use]
207    pub fn has_alerts(&self) -> bool {
208        self.winlink_alert_flags
209            .values()
210            .any(|flags| flags.intersects(WINLINK_ALERTFLAGS))
211    }
212
213    /// Adds persistent alert flags to the addressed session winlink.
214    pub fn add_winlink_alert_flags(&mut self, window_index: u32, flags: AlertFlags) -> bool {
215        if !self.windows.contains_key(&window_index) {
216            return false;
217        }
218
219        let entry = self
220            .winlink_alert_flags
221            .entry(window_index)
222            .or_insert_with(AlertFlags::empty);
223        let changed = !entry.contains(flags);
224        entry.insert(flags);
225        changed
226    }
227
228    /// Clears the selected persistent alert flags from the addressed session winlink.
229    pub fn clear_winlink_alert_flags(&mut self, window_index: u32, flags: AlertFlags) -> bool {
230        let Some(entry) = self.winlink_alert_flags.get_mut(&window_index) else {
231            return false;
232        };
233        if !entry.intersects(flags) {
234            return false;
235        }
236
237        entry.remove(flags);
238        true
239    }
240
241    /// Clears all persistent alert flags from the addressed session winlink.
242    pub fn clear_all_winlink_alert_flags(&mut self, window_index: u32) -> bool {
243        self.clear_winlink_alert_flags(window_index, WINLINK_ALERTFLAGS)
244    }
245
246    /// Returns the active window index.
247    #[must_use]
248    pub const fn active_window_index(&self) -> u32 {
249        self.active_window
250    }
251
252    /// Returns the previously active window index when one exists.
253    #[must_use]
254    pub const fn last_window_index(&self) -> Option<u32> {
255        self.last_window
256    }
257
258    /// Returns the active pane index owned by the session.
259    #[must_use]
260    pub fn active_pane_index(&self) -> u32 {
261        self.window().active_pane_index()
262    }
263
264    /// Returns the stable internal identity for the active pane.
265    #[must_use]
266    pub fn active_pane_id(&self) -> Option<PaneId> {
267        self.active_pane().map(Pane::id)
268    }
269
270    /// Returns the active pane when the session invariant is satisfied.
271    #[must_use]
272    pub fn active_pane(&self) -> Option<&Pane> {
273        self.window().active_pane()
274    }
275
276    /// Returns the stable internal identity for a pane in the active window.
277    #[must_use]
278    pub fn pane_id(&self, pane_index: u32) -> Option<PaneId> {
279        self.pane_id_in_window(self.active_window, pane_index)
280    }
281
282    /// Returns the stable internal identity for a pane in the addressed window.
283    #[must_use]
284    pub fn pane_id_in_window(&self, window_index: u32, pane_index: u32) -> Option<PaneId> {
285        self.window_at(window_index)
286            .and_then(|window| window.pane_id(pane_index))
287    }
288}