1use std::collections::{BTreeMap, BTreeSet};
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, WindowId, WINLINK_ALERTFLAGS};
8
9fn window_index_for_id(
10 windows: &BTreeMap<u32, Window>,
11 window_id: WindowId,
12 preferred_index: u32,
13) -> Option<u32> {
14 if windows
15 .get(&preferred_index)
16 .is_some_and(|window| window.id() == window_id)
17 {
18 return Some(preferred_index);
19 }
20 let mut matches = windows
21 .iter()
22 .filter_map(|(window_index, window)| (window.id() == window_id).then_some(*window_index));
23 let only_match = matches.next()?;
24 matches.next().is_none().then_some(only_match)
25}
26
27fn remap_local_winlink_alert_flags(
28 previous_windows: &BTreeMap<u32, Window>,
29 previous_flags: &BTreeMap<u32, AlertFlags>,
30 synchronized_windows: &BTreeMap<u32, Window>,
31 source_flags: &BTreeMap<u32, AlertFlags>,
32 winlink_alert_map: Option<&BTreeMap<u32, u32>>,
33) -> BTreeMap<u32, AlertFlags> {
34 let mut synchronized_indices = BTreeMap::new();
35 let mut claimed = BTreeSet::new();
36
37 if let Some(winlink_alert_map) = winlink_alert_map {
38 for (&previous_index, previous_window) in previous_windows {
39 let Some(&next_index) = winlink_alert_map.get(&previous_index) else {
40 continue;
41 };
42 if synchronized_windows
43 .get(&next_index)
44 .is_some_and(|window| window.id() == previous_window.id())
45 && claimed.insert(next_index)
46 {
47 synchronized_indices.insert(previous_index, next_index);
48 }
49 }
50 }
51
52 for (&previous_index, previous_window) in previous_windows {
56 if synchronized_indices.contains_key(&previous_index) {
57 continue;
58 }
59 if synchronized_windows
60 .get(&previous_index)
61 .is_some_and(|window| window.id() == previous_window.id())
62 && claimed.insert(previous_index)
63 {
64 synchronized_indices.insert(previous_index, previous_index);
65 }
66 }
67
68 for (&previous_index, previous_window) in previous_windows {
69 if synchronized_indices.contains_key(&previous_index) {
70 continue;
71 }
72 let next_index = synchronized_windows.iter().find_map(|(&index, window)| {
73 (window.id() == previous_window.id() && !claimed.contains(&index)).then_some(index)
74 });
75 if let Some(next_index) = next_index {
76 claimed.insert(next_index);
77 synchronized_indices.insert(previous_index, next_index);
78 }
79 }
80
81 let mut remapped = previous_flags
82 .iter()
83 .filter_map(|(&previous_index, &flags)| {
84 synchronized_indices
85 .get(&previous_index)
86 .copied()
87 .map(|next_index| (next_index, flags))
88 })
89 .collect::<BTreeMap<_, _>>();
90 for &window_index in synchronized_windows.keys() {
91 remapped.entry(window_index).or_insert_with(|| {
92 source_flags
93 .get(&window_index)
94 .copied()
95 .unwrap_or_else(AlertFlags::empty)
96 });
97 }
98 remapped
99}
100
101impl Session {
102 #[must_use]
104 pub const fn name(&self) -> &SessionName {
105 &self.name
106 }
107
108 #[must_use]
110 pub const fn group_name(&self) -> Option<&SessionName> {
111 self.group_name.as_ref()
112 }
113
114 #[must_use]
116 pub const fn id(&self) -> SessionId {
117 self.id
118 }
119
120 #[must_use]
122 pub const fn created_at(&self) -> i64 {
123 self.created_at
124 }
125
126 #[must_use]
128 pub const fn activity_at(&self) -> i64 {
129 self.activity_at
130 }
131
132 #[must_use]
134 pub const fn last_attached_at(&self) -> Option<i64> {
135 self.last_attached_at
136 }
137
138 #[must_use]
140 pub fn cwd(&self) -> Option<&Path> {
141 self.cwd.as_deref()
142 }
143
144 #[must_use]
146 pub const fn terminal_size(&self) -> TerminalSize {
147 self.terminal_size
148 }
149
150 pub(crate) fn set_id(&mut self, id: SessionId) {
151 self.id = id;
152 }
153
154 pub(crate) fn rebind_window_id_allocator(&mut self, allocator: WindowIdAllocator) {
155 let next_after_windows = self
156 .windows
157 .values()
158 .map(|window| window.id().as_u32().saturating_add(1))
159 .max()
160 .unwrap_or_else(|| allocator.peek());
161 self.next_window_id = allocator;
162 self.next_window_id.bump_to(next_after_windows);
163 }
164
165 pub fn rename(&mut self, new_name: SessionName) {
167 self.name = new_name;
168 }
169
170 pub fn set_group_name(&mut self, group_name: Option<SessionName>) {
172 self.group_name = group_name;
173 }
174
175 pub fn set_cwd(&mut self, cwd: Option<PathBuf>) {
177 self.cwd = cwd;
178 }
179
180 pub fn touch_attached(&mut self) {
182 let now = current_unix_timestamp();
183 self.activity_at = now;
184 self.last_attached_at = Some(now);
185 }
186
187 #[must_use]
189 pub fn clone_as_group_member(
190 &self,
191 name: SessionName,
192 group_name: SessionName,
193 session_id: SessionId,
194 ) -> Self {
195 let now = current_unix_timestamp();
196 let mut cloned = self.clone();
197 cloned.id = session_id;
198 cloned.name = name;
199 cloned.group_name = Some(group_name);
200 cloned.created_at = now;
201 cloned.activity_at = now;
202 cloned.last_attached_at = None;
203 cloned
204 }
205
206 pub fn synchronize_group_from(&mut self, source: &Session) {
208 self.synchronize_group_from_with_optional_winlink_alert_map(source, None);
209 }
210
211 pub fn synchronize_group_from_with_winlink_alert_map(
214 &mut self,
215 source: &Session,
216 winlink_alert_map: &BTreeMap<u32, u32>,
217 ) {
218 self.synchronize_group_from_with_optional_winlink_alert_map(
219 source,
220 Some(winlink_alert_map),
221 );
222 }
223
224 fn synchronize_group_from_with_optional_winlink_alert_map(
225 &mut self,
226 source: &Session,
227 winlink_alert_map: Option<&BTreeMap<u32, u32>>,
228 ) {
229 debug_assert_ne!(self.name, source.name);
230 let previous_active = self.active_window;
231 let previous_last = self.last_window;
232 let previous_active_window_id = self.window_at(previous_active).map(Window::id);
233 let previous_last_window_id = previous_last
234 .and_then(|window_index| self.window_at(window_index))
235 .map(Window::id);
236
237 let synchronized_active = previous_active_window_id
238 .and_then(|window_id| window_index_for_id(&source.windows, window_id, previous_active))
239 .unwrap_or_else(|| {
240 synchronized_active_window(&source.windows, previous_active, previous_last)
241 });
242 let synchronized_last = previous_last_window_id
243 .and_then(|window_id| {
244 window_index_for_id(
245 &source.windows,
246 window_id,
247 previous_last.expect("last window id requires an index"),
248 )
249 })
250 .filter(|window_index| *window_index != synchronized_active)
251 .or_else(|| {
252 (previous_active != synchronized_active
253 && source.windows.contains_key(&previous_active))
254 .then_some(previous_active)
255 });
256 self.synchronize_group_windows(
257 source,
258 synchronized_active,
259 synchronized_last,
260 winlink_alert_map,
261 );
262 }
263
264 pub fn synchronize_group_from_with_window_selection_map(
266 &mut self,
267 source: &Session,
268 index_map: &BTreeMap<u32, u32>,
269 ) {
270 self.synchronize_group_from_with_window_selection_and_winlink_alert_maps(
271 source, index_map, index_map,
272 );
273 }
274
275 pub fn synchronize_group_from_with_window_selection_and_winlink_alert_maps(
278 &mut self,
279 source: &Session,
280 window_selection_map: &BTreeMap<u32, u32>,
281 winlink_alert_map: &BTreeMap<u32, u32>,
282 ) {
283 debug_assert_ne!(self.name, source.name);
284 let previous_active = self.active_window;
285 let previous_last = self.last_window;
286 let synchronized_active = window_selection_map
287 .get(&previous_active)
288 .copied()
289 .unwrap_or(previous_active);
290 let synchronized_active = if source.windows.contains_key(&synchronized_active) {
291 synchronized_active
292 } else {
293 synchronized_active_window(&source.windows, previous_active, previous_last)
294 };
295 let synchronized_last = previous_last
296 .map(|window_index| {
297 window_selection_map
298 .get(&window_index)
299 .copied()
300 .unwrap_or(window_index)
301 })
302 .filter(|window_index| {
303 *window_index != synchronized_active && source.windows.contains_key(window_index)
304 });
305 self.synchronize_group_windows(
306 source,
307 synchronized_active,
308 synchronized_last,
309 Some(winlink_alert_map),
310 );
311 }
312
313 fn synchronize_group_windows(
314 &mut self,
315 source: &Session,
316 active_window: u32,
317 last_window: Option<u32>,
318 winlink_alert_map: Option<&BTreeMap<u32, u32>>,
319 ) {
320 let synchronized_alert_flags = remap_local_winlink_alert_flags(
321 &self.windows,
322 &self.winlink_alert_flags,
323 &source.windows,
324 &source.winlink_alert_flags,
325 winlink_alert_map,
326 );
327 self.windows = source.windows.clone();
328 self.winlink_alert_flags = synchronized_alert_flags;
329 self.next_pane_id = source.next_pane_id;
330 self.cwd = source.cwd.clone();
331 self.active_window = active_window;
332 self.last_window = last_window;
333 }
334
335 #[must_use]
337 pub fn window(&self) -> &Window {
338 self.window_at(self.active_window)
339 .expect("active session window must exist")
340 }
341
342 #[must_use]
344 pub fn window_at(&self, window_index: u32) -> Option<&Window> {
345 self.windows.get(&window_index)
346 }
347
348 #[must_use]
350 pub const fn windows(&self) -> &BTreeMap<u32, Window> {
351 &self.windows
352 }
353
354 pub(crate) fn window_mut(&mut self) -> &mut Window {
355 self.window_at_mut(self.active_window)
356 .expect("active session window must exist")
357 }
358
359 pub fn window_at_mut(&mut self, window_index: u32) -> Option<&mut Window> {
361 self.windows.get_mut(&window_index)
362 }
363
364 #[must_use]
366 pub fn winlink_alert_flags(&self, window_index: u32) -> AlertFlags {
367 self.winlink_alert_flags
368 .get(&window_index)
369 .copied()
370 .unwrap_or_else(AlertFlags::empty)
371 }
372
373 #[must_use]
375 pub fn session_alert_flags(&self) -> AlertFlags {
376 self.winlink_alert_flags
377 .values()
378 .copied()
379 .fold(AlertFlags::empty(), |flags, winlink_flags| {
380 flags.union(winlink_flags)
381 })
382 }
383
384 #[must_use]
386 pub fn alerted_window_indexes(&self) -> Vec<u32> {
387 self.winlink_alert_flags
388 .iter()
389 .filter_map(|(window_index, flags)| {
390 flags
391 .intersects(WINLINK_ALERTFLAGS)
392 .then_some(*window_index)
393 })
394 .collect()
395 }
396
397 #[must_use]
399 pub fn has_alerts(&self) -> bool {
400 self.winlink_alert_flags
401 .values()
402 .any(|flags| flags.intersects(WINLINK_ALERTFLAGS))
403 }
404
405 pub fn add_winlink_alert_flags(&mut self, window_index: u32, flags: AlertFlags) -> bool {
407 if !self.windows.contains_key(&window_index) {
408 return false;
409 }
410
411 let entry = self
412 .winlink_alert_flags
413 .entry(window_index)
414 .or_insert_with(AlertFlags::empty);
415 let changed = !entry.contains(flags);
416 entry.insert(flags);
417 changed
418 }
419
420 pub fn clear_winlink_alert_flags(&mut self, window_index: u32, flags: AlertFlags) -> bool {
422 let Some(entry) = self.winlink_alert_flags.get_mut(&window_index) else {
423 return false;
424 };
425 if !entry.intersects(flags) {
426 return false;
427 }
428
429 entry.remove(flags);
430 true
431 }
432
433 pub fn clear_all_winlink_alert_flags(&mut self, window_index: u32) -> bool {
435 self.clear_winlink_alert_flags(window_index, WINLINK_ALERTFLAGS)
436 }
437
438 #[must_use]
440 pub const fn active_window_index(&self) -> u32 {
441 self.active_window
442 }
443
444 #[must_use]
446 pub const fn last_window_index(&self) -> Option<u32> {
447 self.last_window
448 }
449
450 #[must_use]
452 pub fn active_pane_index(&self) -> u32 {
453 self.window().active_pane_index()
454 }
455
456 #[must_use]
458 pub fn active_pane_id(&self) -> Option<PaneId> {
459 self.active_pane().map(Pane::id)
460 }
461
462 #[must_use]
464 pub fn active_pane(&self) -> Option<&Pane> {
465 self.window().active_pane()
466 }
467
468 #[must_use]
470 pub fn pane_id(&self, pane_index: u32) -> Option<PaneId> {
471 self.pane_id_in_window(self.active_window, pane_index)
472 }
473
474 #[must_use]
476 pub fn pane_id_in_window(&self, window_index: u32, pane_index: u32) -> Option<PaneId> {
477 self.window_at(window_index)
478 .and_then(|window| window.pane_id(pane_index))
479 }
480}