1use std::path::PathBuf;
8
9use crate::app::history::NavigationHistory;
10use crate::app::sort::SortSettings;
11use crate::app::state::Entry;
12
13#[derive(Debug)]
19pub struct TabState {
20 pub cwd: PathBuf,
22 pub entries: Vec<Entry>,
24 pub selected: usize,
26 pub history: NavigationHistory,
28 pub sort: SortSettings,
35}
36
37impl TabState {
38 pub fn new(cwd: PathBuf, sort: SortSettings) -> Self {
44 Self {
45 cwd,
46 entries: Vec::new(),
47 selected: 0,
48 history: NavigationHistory::new(),
49 sort,
50 }
51 }
52}
53
54#[derive(Debug)]
59pub struct TabManager {
60 pub tabs: Vec<TabState>,
62 pub active: usize,
64}
65
66impl TabManager {
67 pub fn new(cwd: PathBuf, sort: SortSettings) -> Self {
70 Self {
71 tabs: vec![TabState::new(cwd, sort)],
72 active: 0,
73 }
74 }
75
76 #[allow(dead_code)]
79 pub fn active_tab(&self) -> &TabState {
80 &self.tabs[self.active]
84 }
85
86 pub fn active_tab_mut(&mut self) -> &mut TabState {
88 &mut self.tabs[self.active]
89 }
90
91 pub fn len(&self) -> usize {
95 self.tabs.len()
96 }
97
98 #[allow(dead_code)]
101 pub fn is_empty(&self) -> bool {
102 self.tabs.is_empty()
103 }
104
105 pub fn is_single(&self) -> bool {
111 self.tabs.len() == 1
112 }
113
114 pub fn open_tab(&mut self, cwd: PathBuf, sort: SortSettings) {
121 let insert_at = self.active + 1;
122 self.tabs.insert(insert_at, TabState::new(cwd, sort));
123 self.active = insert_at;
124 }
125
126 pub fn close_active_tab(&mut self) -> bool {
135 if self.tabs.len() == 1 {
136 return false;
137 }
138 self.tabs.remove(self.active);
139 if self.active >= self.tabs.len() {
141 self.active = self.tabs.len() - 1;
142 }
143 true
144 }
145
146 #[allow(dead_code)]
152 pub fn switch_to(&mut self, index: usize) -> bool {
153 if index >= self.tabs.len() || index == self.active {
154 return false;
155 }
156 self.active = index;
157 true
158 }
159
160 pub fn switch_next(&mut self) -> bool {
164 if self.tabs.len() <= 1 {
165 return false;
166 }
167 self.active = (self.active + 1) % self.tabs.len();
168 true
169 }
170
171 pub fn switch_prev(&mut self) -> bool {
176 if self.tabs.len() <= 1 {
177 return false;
178 }
179 self.active = if self.active == 0 {
180 self.tabs.len() - 1
181 } else {
182 self.active - 1
183 };
184 true
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 fn p(s: &str) -> PathBuf {
193 PathBuf::from(s)
194 }
195
196 #[test]
197 fn new_tab_manager_has_one_tab() {
198 let mgr = TabManager::new(p("/start"), SortSettings::default());
199 assert_eq!(mgr.len(), 1);
200 assert_eq!(mgr.active, 0);
201 assert_eq!(mgr.active_tab().cwd, p("/start"));
202 }
203
204 #[test]
205 fn open_tab_inserts_after_active() {
206 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
207 mgr.open_tab(p("/b"), SortSettings::default());
208 assert_eq!(mgr.len(), 2);
209 assert_eq!(mgr.active, 1);
210 assert_eq!(mgr.active_tab().cwd, p("/b"));
211 }
212
213 #[test]
214 fn close_tab_not_allowed_when_last() {
215 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
216 let closed = mgr.close_active_tab();
217 assert!(!closed);
218 assert_eq!(mgr.len(), 1);
219 }
220
221 #[test]
222 fn close_active_tab_switches_to_remaining() {
223 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
224 mgr.open_tab(p("/b"), SortSettings::default());
225 let closed = mgr.close_active_tab();
227 assert!(closed);
228 assert_eq!(mgr.len(), 1);
229 assert_eq!(mgr.active, 0);
230 assert_eq!(mgr.active_tab().cwd, p("/a"));
231 }
232
233 #[test]
234 fn switch_next_wraps() {
235 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
236 mgr.open_tab(p("/b"), SortSettings::default());
237 let _ = mgr.switch_to(0);
238 mgr.active = 0;
239 assert!(mgr.switch_next());
240 assert_eq!(mgr.active, 1);
241 assert!(mgr.switch_next()); assert_eq!(mgr.active, 0);
243 }
244
245 #[test]
246 fn switch_prev_wraps() {
247 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
248 mgr.open_tab(p("/b"), SortSettings::default());
249 mgr.active = 0;
250 assert!(mgr.switch_prev()); assert_eq!(mgr.active, 1);
252 }
253
254 #[test]
255 fn switch_next_single_tab_returns_false() {
256 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
257 assert!(!mgr.switch_next());
258 }
259
260 #[test]
261 fn switch_to_out_of_bounds_returns_false() {
262 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
263 assert!(!mgr.switch_to(99));
264 }
265
266 #[test]
267 fn switch_to_same_tab_returns_false() {
268 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
269 mgr.open_tab(p("/b"), SortSettings::default());
270 mgr.active = 1;
271 assert!(!mgr.switch_to(1));
272 }
273
274 #[test]
275 fn a_new_tab_inherits_the_order_it_was_opened_from() {
276 use crate::app::sort::SortBy;
277 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
278 let inherited = SortSettings {
279 by: SortBy::Size,
280 reverse: true,
281 dirs_first: false,
282 };
283 mgr.open_tab(p("/b"), inherited);
284 assert_eq!(mgr.active_tab().sort, inherited);
285 assert_eq!(mgr.tabs[0].sort, SortSettings::default());
287 }
288
289 #[test]
290 fn is_single_reflects_tab_count() {
291 let mut mgr = TabManager::new(p("/a"), SortSettings::default());
292 assert!(mgr.is_single());
293 mgr.open_tab(p("/b"), SortSettings::default());
294 assert!(!mgr.is_single());
295 }
296}