Skip to main content

rmux_core/session/window_ops/
navigation.rs

1use rmux_proto::RmuxError;
2
3use super::super::target_error::invalid_window_target;
4use super::super::Session;
5
6impl Session {
7    /// Selects the active window by index.
8    pub fn select_window(&mut self, window_index: u32) -> Result<(), RmuxError> {
9        if !self.windows.contains_key(&window_index) {
10            return Err(invalid_window_target(&self.name, window_index));
11        }
12
13        if self.active_window != window_index {
14            self.last_window = Some(self.active_window);
15            self.active_window = window_index;
16            let _ = self.clear_all_winlink_alert_flags(window_index);
17        }
18
19        Ok(())
20    }
21
22    /// Selects the next window in sparse index order, wrapping to the lowest index.
23    pub fn next_window(&mut self) -> Result<u32, RmuxError> {
24        self.next_window_alert_mode(false)
25    }
26
27    /// Selects the next alerted window in sparse index order.
28    pub fn next_window_with_alerts(&mut self) -> Result<u32, RmuxError> {
29        self.next_window_alert_mode(true)
30    }
31
32    fn next_window_alert_mode(&mut self, alerts_only: bool) -> Result<u32, RmuxError> {
33        if self.windows.len() <= 1 {
34            return Err(RmuxError::Message("no next window".to_owned()));
35        }
36
37        let next_window = self
38            .ordered_window_indexes_after(self.active_window, true)
39            .find(|window_index| {
40                !alerts_only
41                    || self
42                        .winlink_alert_flags(*window_index)
43                        .intersects(crate::WINLINK_ALERTFLAGS)
44            })
45            .ok_or_else(|| RmuxError::Message("no next window".to_owned()))?;
46        self.select_window(next_window)?;
47        Ok(next_window)
48    }
49
50    /// Selects the previous window in sparse index order, wrapping to the highest index.
51    pub fn previous_window(&mut self) -> Result<u32, RmuxError> {
52        self.previous_window_alert_mode(false)
53    }
54
55    /// Selects the previous alerted window in sparse index order.
56    pub fn previous_window_with_alerts(&mut self) -> Result<u32, RmuxError> {
57        self.previous_window_alert_mode(true)
58    }
59
60    fn previous_window_alert_mode(&mut self, alerts_only: bool) -> Result<u32, RmuxError> {
61        if self.windows.len() <= 1 {
62            return Err(RmuxError::Message("no previous window".to_owned()));
63        }
64
65        let previous_window = self
66            .ordered_window_indexes_after(self.active_window, false)
67            .find(|window_index| {
68                !alerts_only
69                    || self
70                        .winlink_alert_flags(*window_index)
71                        .intersects(crate::WINLINK_ALERTFLAGS)
72            })
73            .ok_or_else(|| RmuxError::Message("no previous window".to_owned()))?;
74        self.select_window(previous_window)?;
75        Ok(previous_window)
76    }
77
78    /// Selects the most recently active window.
79    pub fn last_window(&mut self) -> Result<u32, RmuxError> {
80        let last_window = self
81            .last_window
82            .ok_or_else(|| RmuxError::Message("no last window".to_owned()))?;
83        self.select_window(last_window)?;
84        Ok(last_window)
85    }
86
87    /// Restores tmux's winlink stack fallback after unlinking an active linked slot.
88    pub fn restore_last_window_after_active_unlink(&mut self) {
89        if self.last_window.is_some() {
90            return;
91        }
92        let next_last = self
93            .ordered_window_indexes_after(self.active_window, true)
94            .next();
95        self.last_window = next_last;
96    }
97
98    fn ordered_window_indexes_after(
99        &self,
100        start_window: u32,
101        forward: bool,
102    ) -> impl Iterator<Item = u32> + '_ {
103        let ordered = self.windows.keys().copied().collect::<Vec<_>>();
104        let Some(start_index) = ordered
105            .iter()
106            .position(|window_index| *window_index == start_window)
107        else {
108            return Vec::new().into_iter();
109        };
110
111        let len = ordered.len();
112        let mut next = Vec::with_capacity(len.saturating_sub(1));
113        for offset in 1..len {
114            let index = if forward {
115                (start_index + offset) % len
116            } else {
117                (start_index + len - offset) % len
118            };
119            next.push(ordered[index]);
120        }
121        next.into_iter()
122    }
123}