Skip to main content

rmux_core/session/
accessors.rs

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