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