1use super::{Tab, TabId};
4use crate::config::Config;
5use anyhow::Result;
6use std::sync::Arc;
7use tokio::runtime::Runtime;
8
9pub struct TabManager {
11 tabs: Vec<Tab>,
13 active_tab_id: Option<TabId>,
15 next_tab_id: TabId,
17}
18
19impl TabManager {
20 pub fn new() -> Self {
22 Self {
23 tabs: Vec::new(),
24 active_tab_id: None,
25 next_tab_id: 1,
26 }
27 }
28
29 pub fn new_tab(
31 &mut self,
32 config: &Config,
33 runtime: Arc<Runtime>,
34 inherit_cwd_from_active: bool,
35 ) -> Result<TabId> {
36 let working_dir = if inherit_cwd_from_active {
38 self.active_tab().and_then(|tab| tab.get_cwd())
39 } else {
40 None
41 };
42
43 let id = self.next_tab_id;
44 self.next_tab_id += 1;
45
46 let tab = Tab::new(id, config, runtime, working_dir)?;
47 self.tabs.push(tab);
48
49 self.active_tab_id = Some(id);
51
52 log::info!("Created new tab {} (total: {})", id, self.tabs.len());
53
54 Ok(id)
55 }
56
57 pub fn close_tab(&mut self, id: TabId) -> bool {
60 let index = self.tabs.iter().position(|t| t.id == id);
61
62 if let Some(idx) = index {
63 log::info!("Closing tab {} (index {})", id, idx);
64
65 self.tabs.remove(idx);
67
68 if self.active_tab_id == Some(id) {
70 self.active_tab_id = if self.tabs.is_empty() {
71 None
72 } else {
73 let new_idx = idx.min(self.tabs.len().saturating_sub(1));
75 Some(self.tabs[new_idx].id)
76 };
77 }
78 }
79
80 self.tabs.is_empty()
81 }
82
83 pub fn active_tab(&self) -> Option<&Tab> {
85 self.active_tab_id
86 .and_then(|id| self.tabs.iter().find(|t| t.id == id))
87 }
88
89 pub fn active_tab_mut(&mut self) -> Option<&mut Tab> {
91 let active_id = self.active_tab_id;
92 active_id.and_then(move |id| self.tabs.iter_mut().find(|t| t.id == id))
93 }
94
95 pub fn switch_to(&mut self, id: TabId) {
97 if self.tabs.iter().any(|t| t.id == id) {
98 if let Some(tab) = self.tabs.iter_mut().find(|t| t.id == id) {
100 tab.has_activity = false;
101 }
102 self.active_tab_id = Some(id);
103 log::debug!("Switched to tab {}", id);
104 }
105 }
106
107 pub fn next_tab(&mut self) {
109 if self.tabs.len() <= 1 {
110 return;
111 }
112
113 if let Some(active_id) = self.active_tab_id {
114 let current_idx = self
115 .tabs
116 .iter()
117 .position(|t| t.id == active_id)
118 .unwrap_or(0);
119 let next_idx = (current_idx + 1) % self.tabs.len();
120 let next_id = self.tabs[next_idx].id;
121 self.switch_to(next_id);
122 }
123 }
124
125 pub fn prev_tab(&mut self) {
127 if self.tabs.len() <= 1 {
128 return;
129 }
130
131 if let Some(active_id) = self.active_tab_id {
132 let current_idx = self
133 .tabs
134 .iter()
135 .position(|t| t.id == active_id)
136 .unwrap_or(0);
137 let prev_idx = if current_idx == 0 {
138 self.tabs.len() - 1
139 } else {
140 current_idx - 1
141 };
142 let prev_id = self.tabs[prev_idx].id;
143 self.switch_to(prev_id);
144 }
145 }
146
147 pub fn switch_to_index(&mut self, index: usize) {
149 if index > 0 && index <= self.tabs.len() {
150 let id = self.tabs[index - 1].id;
151 self.switch_to(id);
152 }
153 }
154
155 pub fn move_tab(&mut self, id: TabId, direction: i32) {
158 if let Some(current_idx) = self.tabs.iter().position(|t| t.id == id) {
159 let new_idx = if direction < 0 {
160 if current_idx == 0 {
161 self.tabs.len() - 1
162 } else {
163 current_idx - 1
164 }
165 } else if current_idx >= self.tabs.len() - 1 {
166 0
167 } else {
168 current_idx + 1
169 };
170
171 if new_idx != current_idx {
172 let tab = self.tabs.remove(current_idx);
173 self.tabs.insert(new_idx, tab);
174 log::debug!("Moved tab {} from index {} to {}", id, current_idx, new_idx);
175 }
176 }
177 }
178
179 pub fn move_active_tab_left(&mut self) {
181 if let Some(id) = self.active_tab_id {
182 self.move_tab(id, -1);
183 }
184 }
185
186 pub fn move_active_tab_right(&mut self) {
188 if let Some(id) = self.active_tab_id {
189 self.move_tab(id, 1);
190 }
191 }
192
193 pub fn tab_count(&self) -> usize {
195 self.tabs.len()
196 }
197
198 pub fn has_multiple_tabs(&self) -> bool {
200 self.tabs.len() > 1
201 }
202
203 pub fn active_tab_id(&self) -> Option<TabId> {
205 self.active_tab_id
206 }
207
208 pub fn tabs(&self) -> &[Tab] {
210 &self.tabs
211 }
212
213 pub fn tabs_mut(&mut self) -> &mut [Tab] {
215 &mut self.tabs
216 }
217
218 #[allow(dead_code)]
220 pub fn get_tab(&self, id: TabId) -> Option<&Tab> {
221 self.tabs.iter().find(|t| t.id == id)
222 }
223
224 #[allow(dead_code)]
226 pub fn get_tab_mut(&mut self, id: TabId) -> Option<&mut Tab> {
227 self.tabs.iter_mut().find(|t| t.id == id)
228 }
229
230 #[allow(dead_code)]
232 pub fn mark_activity(&mut self, tab_id: TabId) {
233 if Some(tab_id) != self.active_tab_id
234 && let Some(tab) = self.get_tab_mut(tab_id)
235 {
236 tab.has_activity = true;
237 }
238 }
239
240 pub fn update_all_titles(&mut self) {
242 for tab in &mut self.tabs {
243 tab.update_title();
244 }
245 }
246
247 pub fn duplicate_active_tab(
249 &mut self,
250 config: &Config,
251 runtime: Arc<Runtime>,
252 ) -> Result<Option<TabId>> {
253 let working_dir = self.active_tab().and_then(|t| t.get_cwd());
254
255 if working_dir.is_some() || self.active_tab_id.is_some() {
256 let id = self.next_tab_id;
257 self.next_tab_id += 1;
258
259 let tab = Tab::new(id, config, runtime, working_dir)?;
260
261 if let Some(active_id) = self.active_tab_id {
263 if let Some(idx) = self.tabs.iter().position(|t| t.id == active_id) {
264 self.tabs.insert(idx + 1, tab);
265 } else {
266 self.tabs.push(tab);
267 }
268 } else {
269 self.tabs.push(tab);
270 }
271
272 self.active_tab_id = Some(id);
273 Ok(Some(id))
274 } else {
275 Ok(None)
276 }
277 }
278
279 #[allow(dead_code)]
281 pub fn active_tab_index(&self) -> Option<usize> {
282 self.active_tab_id
283 .and_then(|id| self.tabs.iter().position(|t| t.id == id))
284 }
285
286 #[allow(dead_code)]
288 pub fn cleanup_dead_tabs(&mut self) {
289 let dead_tabs: Vec<TabId> = self
290 .tabs
291 .iter()
292 .filter(|t| !t.is_running())
293 .map(|t| t.id)
294 .collect();
295
296 for id in dead_tabs {
297 log::info!("Cleaning up dead tab {}", id);
298 self.close_tab(id);
299 }
300 }
301}
302
303impl Default for TabManager {
304 fn default() -> Self {
305 Self::new()
306 }
307}