Skip to main content

rmux_core/
pane.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3use rmux_proto::{PaneTarget, SessionName};
4
5pub use rmux_proto::PaneId;
6
7/// A pane rectangle within terminal coordinates.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct PaneGeometry {
10    x: u16,
11    y: u16,
12    cols: u16,
13    rows: u16,
14}
15
16impl PaneGeometry {
17    /// Creates a pane rectangle at the given position and size.
18    #[must_use]
19    pub const fn new(x: u16, y: u16, cols: u16, rows: u16) -> Self {
20        Self { x, y, cols, rows }
21    }
22
23    /// Returns the left-most column of the pane.
24    #[must_use]
25    pub const fn x(&self) -> u16 {
26        self.x
27    }
28
29    /// Returns the top-most row of the pane.
30    #[must_use]
31    pub const fn y(&self) -> u16 {
32        self.y
33    }
34
35    /// Returns the pane width in columns.
36    #[must_use]
37    pub const fn cols(&self) -> u16 {
38        self.cols
39    }
40
41    /// Returns the pane height in rows.
42    #[must_use]
43    pub const fn rows(&self) -> u16 {
44        self.rows
45    }
46}
47
48/// Pure in-memory pane state.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct Pane {
51    id: PaneId,
52    index: u32,
53    geometry: PaneGeometry,
54    active_point: u64,
55    created_at: i64,
56    activity_at: i64,
57}
58
59impl Pane {
60    /// Returns the pane's stable internal identity.
61    #[must_use]
62    pub const fn id(&self) -> PaneId {
63        self.id
64    }
65
66    /// Returns the stable pane index used by detached targets.
67    #[must_use]
68    pub const fn index(&self) -> u32 {
69        self.index
70    }
71
72    /// Returns the pane's current geometry.
73    #[must_use]
74    pub const fn geometry(&self) -> PaneGeometry {
75        self.geometry
76    }
77
78    /// Monotonic per-window activation counter (tmux `active_point`):
79    /// bumped each time this pane becomes the window's active pane.
80    pub const fn active_point(&self) -> u64 {
81        self.active_point
82    }
83
84    /// Returns the pane creation timestamp as Unix seconds.
85    #[must_use]
86    pub const fn created_at(&self) -> i64 {
87        self.created_at
88    }
89
90    /// Returns the last pane activity timestamp as Unix seconds.
91    #[must_use]
92    pub const fn activity_at(&self) -> i64 {
93        self.activity_at
94    }
95
96    pub(crate) fn touch_activity(&mut self) {
97        self.activity_at = current_unix_timestamp();
98    }
99
100    pub(crate) fn set_activity_at(&mut self, activity_at: i64) {
101        self.activity_at = activity_at;
102    }
103
104    /// Builds an exact pane target for this pane in the given session.
105    #[must_use]
106    pub fn target(&self, session_name: &SessionName) -> PaneTarget {
107        PaneTarget::new(session_name.clone(), self.index)
108    }
109
110    #[cfg_attr(not(test), allow(dead_code))]
111    pub(crate) fn new(index: u32, geometry: PaneGeometry) -> Self {
112        Self::new_with_id(PaneId::new(index), index, geometry)
113    }
114
115    pub(crate) fn new_with_id(id: PaneId, index: u32, geometry: PaneGeometry) -> Self {
116        let now = current_unix_timestamp();
117        Self {
118            id,
119            index,
120            geometry,
121            active_point: 0,
122            created_at: now,
123            activity_at: now,
124        }
125    }
126
127    pub(crate) fn set_geometry(&mut self, geometry: PaneGeometry) {
128        self.geometry = geometry;
129    }
130
131    pub(crate) fn set_index(&mut self, index: u32) {
132        self.index = index;
133    }
134
135    pub(crate) fn set_active_point(&mut self, active_point: u64) {
136        self.active_point = active_point;
137    }
138}
139
140fn current_unix_timestamp() -> i64 {
141    SystemTime::now()
142        .duration_since(UNIX_EPOCH)
143        .map(|duration| duration.as_secs().min(i64::MAX as u64) as i64)
144        .unwrap_or(0)
145}
146
147#[cfg(test)]
148mod tests {
149    use super::{Pane, PaneGeometry, PaneId};
150    use rmux_proto::SessionName;
151
152    #[test]
153    fn geometry_accessors_match_constructor_values() {
154        let geometry = PaneGeometry::new(4, 7, 80, 24);
155
156        assert_eq!(geometry.x(), 4);
157        assert_eq!(geometry.y(), 7);
158        assert_eq!(geometry.cols(), 80);
159        assert_eq!(geometry.rows(), 24);
160    }
161
162    #[test]
163    fn pane_target_uses_session_name_and_index() {
164        let pane = Pane::new(3, PaneGeometry::new(0, 0, 10, 5));
165        let session_name = SessionName::new("alpha").expect("valid session name");
166
167        assert_eq!(pane.target(&session_name).to_string(), "alpha:0.3");
168    }
169
170    #[test]
171    fn pane_id_is_stable_and_independent_from_display_index() {
172        let pane = Pane::new_with_id(PaneId::new(9), 3, PaneGeometry::new(0, 0, 10, 5));
173
174        assert_eq!(pane.id(), PaneId::new(9));
175        assert_eq!(pane.index(), 3);
176    }
177
178    #[test]
179    fn set_geometry_replaces_the_existing_rectangle() {
180        let mut pane = Pane::new(0, PaneGeometry::new(0, 0, 10, 5));
181        let replacement = PaneGeometry::new(12, 1, 34, 50);
182
183        pane.set_geometry(replacement);
184
185        assert_eq!(pane.geometry(), replacement);
186    }
187}