Skip to main content

par_term/tab/
manager_nav.rs

1//! Tab navigation methods for [`TabManager`].
2//!
3//! Split from `tab/manager.rs` to keep that file under 500 lines.
4//! Contains all tab-switching and tab-reordering operations.
5
6use super::TabId;
7use super::manager::TabManager;
8// Note: TabManager fields (tabs, active_tab_id) are pub(super) — visible to crate::tab,
9// which includes this sibling module.
10
11impl TabManager {
12    /// Switch to a tab by ID
13    pub fn switch_to(&mut self, id: TabId) {
14        if self.tabs.iter().any(|t| t.id == id) {
15            // Clear activity indicator when switching to tab
16            if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == id) {
17                tab.activity.has_activity = false;
18            }
19            self.set_active_tab(Some(id));
20            log::debug!("Switched to tab {}", id);
21        }
22    }
23
24    /// Switch to the next tab (wraps around, skips hidden tabs)
25    pub fn next_tab(&mut self) {
26        let visible: Vec<TabId> = self
27            .tabs
28            .iter()
29            .filter(|t| !t.is_hidden)
30            .map(|t| t.id)
31            .collect();
32        if visible.len() <= 1 {
33            return;
34        }
35        if let Some(active_id) = self.active_tab_id {
36            let current_pos = visible.iter().position(|&id| id == active_id).unwrap_or(0);
37            let next_id = visible[(current_pos + 1) % visible.len()];
38            self.switch_to(next_id);
39        }
40    }
41
42    /// Switch to the previous tab (wraps around, skips hidden tabs)
43    pub fn prev_tab(&mut self) {
44        let visible: Vec<TabId> = self
45            .tabs
46            .iter()
47            .filter(|t| !t.is_hidden)
48            .map(|t| t.id)
49            .collect();
50        if visible.len() <= 1 {
51            return;
52        }
53        if let Some(active_id) = self.active_tab_id {
54            let current_pos = visible.iter().position(|&id| id == active_id).unwrap_or(0);
55            let prev_pos = if current_pos == 0 {
56                visible.len() - 1
57            } else {
58                current_pos - 1
59            };
60            let prev_id = visible[prev_pos];
61            self.switch_to(prev_id);
62        }
63    }
64
65    /// Switch to tab by index (1-based for Cmd+1-9, skips hidden tabs)
66    pub fn switch_to_index(&mut self, index: usize) {
67        let visible: Vec<TabId> = self
68            .tabs
69            .iter()
70            .filter(|t| !t.is_hidden)
71            .map(|t| t.id)
72            .collect();
73        if index > 0 && index <= visible.len() {
74            let id = visible[index - 1];
75            self.switch_to(id);
76        }
77    }
78
79    /// Move a tab left or right
80    /// direction: -1 for left, 1 for right
81    pub fn move_tab(&mut self, id: TabId, direction: i32) {
82        if let Some(current_idx) = self.tabs.iter().position(|t| t.id == id) {
83            let new_idx = if direction < 0 {
84                if current_idx == 0 {
85                    self.tabs.len() - 1
86                } else {
87                    current_idx - 1
88                }
89            } else if current_idx >= self.tabs.len() - 1 {
90                0
91            } else {
92                current_idx + 1
93            };
94
95            if new_idx != current_idx {
96                let tab = self.tabs.remove(current_idx);
97                self.tabs.insert(new_idx, tab);
98                log::debug!("Moved tab {} from index {} to {}", id, current_idx, new_idx);
99                // Renumber tabs that still have default titles
100                self.renumber_default_tabs();
101            }
102        }
103    }
104
105    /// Move a tab to a specific index (used by drag-and-drop reordering)
106    /// Returns true if the tab was actually moved, false if not found or already at target
107    pub fn move_tab_to_index(&mut self, id: TabId, target_index: usize) -> bool {
108        let current_idx = match self.tabs.iter().position(|t| t.id == id) {
109            Some(idx) => idx,
110            None => return false,
111        };
112
113        let clamped_target = target_index.min(self.tabs.len().saturating_sub(1));
114        if clamped_target == current_idx {
115            return false;
116        }
117
118        let tab = self.tabs.remove(current_idx);
119        self.tabs.insert(clamped_target, tab);
120        log::debug!(
121            "Moved tab {} from index {} to {}",
122            id,
123            current_idx,
124            clamped_target
125        );
126        self.renumber_default_tabs();
127        true
128    }
129
130    /// Move active tab left
131    pub fn move_active_tab_left(&mut self) {
132        if let Some(id) = self.active_tab_id {
133            self.move_tab(id, -1);
134        }
135    }
136
137    /// Move active tab right
138    pub fn move_active_tab_right(&mut self) {
139        if let Some(id) = self.active_tab_id {
140            self.move_tab(id, 1);
141        }
142    }
143}