1use std::{collections::BTreeSet, path::Path, usize};
2
3use crate::{
4 TreeItemInfo, error::Result, filetreeitems::FileTreeItems,
5 tree_iter::TreeIterator,
6};
7
8#[derive(Copy, Clone, Debug)]
10pub enum MoveSelection {
11 Up,
12 Down,
13 Left,
14 Right,
15 Top,
16 End,
17 PageDown,
18 PageUp,
19}
20
21#[derive(Debug, Clone, Copy)]
22pub struct VisualSelection {
23 pub count: usize,
24 pub index: usize,
25}
26
27#[derive(Default)]
30pub struct FileTree {
31 items: FileTreeItems,
32 selection: Option<usize>,
33 visual_selection: Option<VisualSelection>,
35}
36
37impl FileTree {
38 pub fn new(
40 list: &[&Path],
41 collapsed: &BTreeSet<&String>,
42 ) -> Result<Self> {
43 let mut new_self = Self {
44 items: FileTreeItems::new(list, collapsed)?,
45 selection: if list.is_empty() { None } else { Some(0) },
46 visual_selection: None,
47 };
48 new_self.visual_selection = new_self.calc_visual_selection();
49
50 Ok(new_self)
51 }
52
53 pub const fn is_empty(&self) -> bool {
55 self.items.file_count() == 0
56 }
57
58 pub const fn selection(&self) -> Option<usize> {
60 self.selection
61 }
62
63 pub fn collapse_but_root(&mut self) {
65 if !self.is_empty() {
66 self.items.collapse(0, true);
67 self.items.expand(0, false);
68 }
69 }
70
71 pub fn iterate(
73 &self,
74 start_index_visual: usize,
75 max_amount: usize,
76 ) -> TreeIterator<'_> {
77 let start = self
78 .visual_index_to_absolute(start_index_visual)
79 .unwrap_or_default();
80 TreeIterator::new(
81 self.items.iterate(start, max_amount),
82 self.selection,
83 )
84 }
85
86 pub const fn visual_selection(&self) -> Option<&VisualSelection> {
88 self.visual_selection.as_ref()
89 }
90
91 pub fn selected_file(&self) -> Option<&TreeItemInfo> {
93 self.selection.and_then(|index| {
94 let item = &self.items.tree_items[index];
95 if item.kind().is_path() {
96 None
97 } else {
98 Some(item.info())
99 }
100 })
101 }
102
103 pub fn collapse_recursive(&mut self) {
105 if let Some(selection) = self.selection {
106 self.items.collapse(selection, true);
107 }
108 }
109
110 pub fn expand_recursive(&mut self) {
112 if let Some(selection) = self.selection {
113 self.items.expand(selection, true);
114 }
115 }
116
117 pub fn move_selection(&mut self, dir: MoveSelection) -> bool {
119 self.selection.is_some_and(|selection| {
120 let new_index = match dir {
121 MoveSelection::Up => {
122 self.selection_updown(selection, true)
123 }
124 MoveSelection::Down => {
125 self.selection_updown(selection, false)
126 }
127 MoveSelection::Left => self.selection_left(selection),
128 MoveSelection::Right => {
129 self.selection_right(selection)
130 }
131 MoveSelection::Top => {
132 Self::selection_start(selection)
133 }
134 MoveSelection::End => self.selection_end(selection),
135 MoveSelection::PageDown | MoveSelection::PageUp => {
136 None
137 }
138 };
139
140 let changed_index =
141 new_index.is_some_and(|i| i != selection);
142
143 if changed_index {
144 self.selection = new_index;
145 self.visual_selection = self.calc_visual_selection();
146 }
147
148 changed_index || new_index.is_some()
149 }) }
150
151 pub fn select_file(&mut self, path: &Path) -> bool {
152 let new_selection = self
153 .items
154 .tree_items
155 .iter()
156 .position(|item| item.info().full_path() == path);
157
158 if new_selection == self.selection {
159 return false;
160 }
161
162 self.selection = new_selection;
163 if let Some(selection) = self.selection {
164 self.items.show_element(selection);
165 }
166 self.visual_selection = self.calc_visual_selection();
167 true
168 }
169
170 fn visual_index_to_absolute(
171 &self,
172 visual_index: usize,
173 ) -> Option<usize> {
174 self.items
175 .iterate(0, self.items.len())
176 .enumerate()
177 .find_map(|(i, (abs, _))| {
178 if i == visual_index { Some(abs) } else { None }
179 })
180 }
181
182 fn calc_visual_selection(&self) -> Option<VisualSelection> {
183 self.selection.map(|selection_absolute| {
184 let mut count = 0;
185 let mut visual_index = 0;
186 for (index, _item) in
187 self.items.iterate(0, self.items.len())
188 {
189 if selection_absolute == index {
190 visual_index = count;
191 }
192
193 count += 1;
194 }
195
196 VisualSelection {
197 index: visual_index,
198 count,
199 }
200 })
201 }
202
203 const fn selection_start(current_index: usize) -> Option<usize> {
204 if current_index == 0 { None } else { Some(0) }
205 }
206
207 fn selection_end(&self, current_index: usize) -> Option<usize> {
208 let items_max = self.items.len().saturating_sub(1);
209
210 let mut new_index = items_max;
211
212 loop {
213 if self.is_visible_index(new_index) {
214 break;
215 }
216
217 if new_index == 0 {
218 break;
219 }
220
221 new_index = new_index.saturating_sub(1);
222 new_index = std::cmp::min(new_index, items_max);
223 }
224
225 if new_index == current_index {
226 None
227 } else {
228 Some(new_index)
229 }
230 }
231
232 fn selection_updown(
233 &self,
234 current_index: usize,
235 up: bool,
236 ) -> Option<usize> {
237 let mut index = current_index;
238
239 loop {
240 index = {
241 let new_index = if up {
242 index.saturating_sub(1)
243 } else {
244 index.saturating_add(1)
245 };
246
247 if new_index == index {
249 break;
250 }
251
252 if new_index >= self.items.len() {
253 break;
254 }
255
256 new_index
257 };
258
259 if self.is_visible_index(index) {
260 break;
261 }
262 }
263
264 if index == current_index {
265 None
266 } else {
267 Some(index)
268 }
269 }
270
271 fn select_parent(
272 &mut self,
273 current_index: usize,
274 ) -> Option<usize> {
275 let indent =
276 self.items.tree_items[current_index].info().indent();
277
278 let mut index = current_index;
279
280 while let Some(selection) = self.selection_updown(index, true)
281 {
282 index = selection;
283
284 if self.items.tree_items[index].info().indent() < indent {
285 break;
286 }
287 }
288
289 if index == current_index {
290 None
291 } else {
292 Some(index)
293 }
294 }
295
296 fn selection_left(
297 &mut self,
298 current_index: usize,
299 ) -> Option<usize> {
300 let item = &mut self.items.tree_items[current_index];
301
302 if item.kind().is_path() && !item.kind().is_path_collapsed() {
303 self.items.collapse(current_index, false);
304 return Some(current_index);
305 }
306
307 self.select_parent(current_index)
308 }
309
310 fn selection_right(
311 &mut self,
312 current_selection: usize,
313 ) -> Option<usize> {
314 let item = &mut self.items.tree_items[current_selection];
315
316 if item.kind().is_path() {
317 if item.kind().is_path_collapsed() {
318 self.items.expand(current_selection, false);
319 return Some(current_selection);
320 }
321 return self.selection_updown(current_selection, false);
322 }
323
324 None
325 }
326
327 fn is_visible_index(&self, index: usize) -> bool {
328 self.items
329 .tree_items
330 .get(index)
331 .is_some_and(|item| item.info().is_visible())
332 }
333}
334
335#[cfg(test)]
336mod test {
337 use std::{collections::BTreeSet, path::Path};
338
339 use pretty_assertions::assert_eq;
340
341 use crate::{FileTree, MoveSelection};
342
343 #[test]
344 fn test_selection() {
345 let items = vec![
346 Path::new("a/b"), ];
348
349 let mut tree =
350 FileTree::new(&items, &BTreeSet::new()).unwrap();
351
352 assert!(tree.move_selection(MoveSelection::Down));
353
354 assert_eq!(tree.selection, Some(1));
355
356 assert!(!tree.move_selection(MoveSelection::Down));
357
358 assert_eq!(tree.selection, Some(1));
359 }
360
361 #[test]
362 fn test_selection_skips_collapsed() {
363 let items = vec![
364 Path::new("a/b/c"), Path::new("a/d"), ];
367
368 let mut tree =
374 FileTree::new(&items, &BTreeSet::new()).unwrap();
375
376 tree.items.collapse(1, false);
377 tree.selection = Some(1);
378
379 assert!(tree.move_selection(MoveSelection::Down));
380
381 assert_eq!(tree.selection, Some(3));
382 }
383
384 #[test]
385 fn test_selection_left_collapse() {
386 let items = vec![
387 Path::new("a/b/c"), Path::new("a/d"), ];
390
391 let mut tree =
397 FileTree::new(&items, &BTreeSet::new()).unwrap();
398
399 tree.selection = Some(1);
400
401 assert!(tree.move_selection(MoveSelection::Left));
403 assert_eq!(tree.selection, Some(1));
405
406 assert!(tree.items.tree_items[1].kind().is_path_collapsed());
407 assert!(!tree.items.tree_items[2].info().is_visible());
408 }
409
410 #[test]
411 fn test_selection_left_parent() {
412 let items = vec![
413 Path::new("a/b/c"), Path::new("a/d"), ];
416
417 let mut tree =
423 FileTree::new(&items, &BTreeSet::new()).unwrap();
424
425 tree.selection = Some(2);
426
427 assert!(tree.move_selection(MoveSelection::Left));
428 assert_eq!(tree.selection, Some(1));
429
430 assert!(tree.move_selection(MoveSelection::Left));
431 assert_eq!(tree.selection, Some(1));
432
433 assert!(tree.move_selection(MoveSelection::Left));
434 assert_eq!(tree.selection, Some(0));
435 }
436
437 #[test]
438 fn test_selection_right_expand() {
439 let items = vec![
440 Path::new("a/b/c"), Path::new("a/d"), ];
443
444 let mut tree =
450 FileTree::new(&items, &BTreeSet::new()).unwrap();
451
452 tree.items.collapse(1, false);
453 tree.items.collapse(0, false);
454 tree.selection = Some(0);
455
456 assert!(tree.move_selection(MoveSelection::Right));
457 assert_eq!(tree.selection, Some(0));
458 assert!(!tree.items.tree_items[0].kind().is_path_collapsed());
459
460 assert!(tree.move_selection(MoveSelection::Right));
461 assert_eq!(tree.selection, Some(1));
462 assert!(tree.items.tree_items[1].kind().is_path_collapsed());
463
464 assert!(tree.move_selection(MoveSelection::Right));
465 assert_eq!(tree.selection, Some(1));
466 assert!(!tree.items.tree_items[1].kind().is_path_collapsed());
467 }
468
469 #[test]
470 fn test_selection_top() {
471 let items = vec![
472 Path::new("a/b/c"), Path::new("a/d"), ];
475
476 let mut tree =
482 FileTree::new(&items, &BTreeSet::new()).unwrap();
483
484 tree.selection = Some(3);
485
486 assert!(tree.move_selection(MoveSelection::Top));
487 assert_eq!(tree.selection, Some(0));
488 }
489
490 #[test]
491 fn test_visible_selection() {
492 let items = vec![
493 Path::new("a/b/c"), Path::new("a/b/c2"), Path::new("a/d"), ];
497
498 let mut tree =
505 FileTree::new(&items, &BTreeSet::new()).unwrap();
506
507 tree.selection = Some(1);
508 assert!(tree.move_selection(MoveSelection::Left));
509 assert!(tree.move_selection(MoveSelection::Down));
510 let s = tree.visual_selection().unwrap();
511
512 assert_eq!(s.count, 3);
513 assert_eq!(s.index, 2);
514 }
515}