1use super::node::{NodeId, NodeState, TreeNode};
2use crate::model::filesystem::DirEntry;
3use crate::services::fs::FsManager;
4use std::collections::HashMap;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9#[derive(Debug)]
15pub struct FileTree {
16 root_path: PathBuf,
18 nodes: HashMap<NodeId, TreeNode>,
20 path_to_node: HashMap<PathBuf, NodeId>,
22 root_id: NodeId,
24 next_id: usize,
26 fs_manager: Arc<FsManager>,
28}
29
30impl FileTree {
31 pub async fn new(root_path: PathBuf, fs_manager: Arc<FsManager>) -> io::Result<Self> {
37 if !fs_manager.exists(&root_path).await {
39 return Err(io::Error::new(
40 io::ErrorKind::NotFound,
41 format!("Path does not exist: {:?}", root_path),
42 ));
43 }
44
45 if !fs_manager.is_dir(&root_path).await? {
46 return Err(io::Error::new(
47 io::ErrorKind::InvalidInput,
48 format!("Path is not a directory: {:?}", root_path),
49 ));
50 }
51
52 let root_entry = fs_manager.get_entry(&root_path).await?;
54
55 let root_id = NodeId(0);
57 let root_node = TreeNode::new(root_id, root_entry.clone(), None);
58
59 let mut nodes = HashMap::new();
60 nodes.insert(root_id, root_node);
61
62 let mut path_to_node = HashMap::new();
63 path_to_node.insert(root_path.clone(), root_id);
64
65 Ok(Self {
66 root_path,
67 nodes,
68 path_to_node,
69 root_id,
70 next_id: 1,
71 fs_manager,
72 })
73 }
74
75 pub fn root_id(&self) -> NodeId {
77 self.root_id
78 }
79
80 pub fn root_path(&self) -> &Path {
82 &self.root_path
83 }
84
85 pub fn get_node(&self, id: NodeId) -> Option<&TreeNode> {
87 self.nodes.get(&id)
88 }
89
90 fn get_node_mut(&mut self, id: NodeId) -> Option<&mut TreeNode> {
92 self.nodes.get_mut(&id)
93 }
94
95 pub fn get_node_by_path(&self, path: &Path) -> Option<&TreeNode> {
97 self.path_to_node
98 .get(path)
99 .and_then(|id| self.get_node(*id))
100 }
101
102 pub fn all_nodes(&self) -> impl Iterator<Item = &TreeNode> {
104 self.nodes.values()
105 }
106
107 pub async fn expand_node(&mut self, id: NodeId) -> io::Result<()> {
116 let node = self
118 .get_node(id)
119 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Node not found"))?;
120
121 if !node.is_dir() {
122 return Err(io::Error::new(
123 io::ErrorKind::InvalidInput,
124 "Cannot expand a file node",
125 ));
126 }
127
128 if node.is_expanded() {
130 return Ok(());
131 }
132
133 if let Some(node) = self.get_node_mut(id) {
135 node.state = NodeState::Loading;
136 }
137
138 let path = self.get_node(id).unwrap().entry.path.clone();
140 let result = self.fs_manager.list_dir_with_metadata(path.clone()).await;
141
142 match result {
143 Ok(entries) => {
144 let mut sorted_entries = entries;
148 sorted_entries.sort_by(|a, b| match (a.is_dir(), b.is_dir()) {
149 (true, false) => std::cmp::Ordering::Less,
150 (false, true) => std::cmp::Ordering::Greater,
151 _ => natural_cmp(&a.name, &b.name),
152 });
153
154 let mut child_ids = Vec::new();
156 for entry in sorted_entries {
157 let child_id = self.add_node(entry, Some(id));
158 child_ids.push(child_id);
159 }
160
161 if let Some(node) = self.get_node_mut(id) {
163 node.children = child_ids;
164 node.state = NodeState::Expanded;
165 }
166
167 Ok(())
168 }
169 Err(e) => {
170 if let Some(node) = self.get_node_mut(id) {
172 node.state = NodeState::Error(e.to_string());
173 }
174 Err(e)
175 }
176 }
177 }
178
179 pub fn collapse_node(&mut self, id: NodeId) {
184 if let Some(node) = self.get_node(id) {
185 if !node.is_dir() {
186 return;
187 }
188
189 let children_to_remove: Vec<NodeId> = node.children.clone();
191
192 for child_id in children_to_remove {
194 self.remove_node_recursive(child_id);
195 }
196 }
197
198 if let Some(node) = self.get_node_mut(id) {
200 node.children.clear();
201 node.state = NodeState::Collapsed;
202 }
203 }
204
205 pub async fn toggle_node(&mut self, id: NodeId) -> io::Result<()> {
207 let node = self
208 .get_node(id)
209 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Node not found"))?;
210
211 if !node.is_dir() {
212 return Ok(());
213 }
214
215 if node.is_expanded() {
216 self.collapse_node(id);
217 Ok(())
218 } else {
219 self.expand_node(id).await
220 }
221 }
222
223 pub async fn refresh_node(&mut self, id: NodeId) -> io::Result<()> {
227 self.collapse_node(id);
229 self.expand_node(id).await
230 }
231
232 pub async fn reload_expanded_node(&mut self, id: NodeId) -> io::Result<()> {
246 let expanded_paths = self.collect_expanded_descendant_paths(id);
247 self.refresh_node(id).await?;
248 for path in expanded_paths {
254 if let Some(target_id) = self.expand_to_path(&path).await {
255 if let Err(e) = self.expand_node(target_id).await {
256 tracing::warn!("Failed to re-expand {:?} after tree reload: {}", path, e);
257 }
258 }
259 }
260 Ok(())
261 }
262
263 fn collect_expanded_descendant_paths(&self, id: NodeId) -> Vec<PathBuf> {
267 let mut out = Vec::new();
268 if let Some(node) = self.get_node(id) {
269 for &child in &node.children {
270 self.collect_expanded_recursive(child, &mut out);
271 }
272 }
273 out
274 }
275
276 fn collect_expanded_recursive(&self, id: NodeId, out: &mut Vec<PathBuf>) {
277 if let Some(node) = self.get_node(id) {
278 if node.is_expanded() {
279 out.push(node.entry.path.clone());
280 for &child in &node.children {
281 self.collect_expanded_recursive(child, out);
282 }
283 }
284 }
285 }
286
287 pub fn get_visible_nodes(&self) -> Vec<NodeId> {
292 let mut visible = Vec::new();
293 self.collect_visible_recursive(self.root_id, &mut visible);
294 visible
295 }
296
297 fn collect_visible_recursive(&self, id: NodeId, visible: &mut Vec<NodeId>) {
299 visible.push(id);
300
301 if let Some(node) = self.get_node(id) {
302 if node.is_expanded() {
303 for &child_id in &node.children {
304 self.collect_visible_recursive(child_id, visible);
305 }
306 }
307 }
308 }
309
310 pub fn get_ancestors(&self, id: NodeId) -> Vec<NodeId> {
312 let mut ancestors = Vec::new();
313 let mut current = Some(id);
314
315 while let Some(node_id) = current {
316 ancestors.push(node_id);
317 current = self.get_node(node_id).and_then(|n| n.parent);
318 }
319
320 ancestors.reverse();
321 ancestors
322 }
323
324 pub fn get_depth(&self, id: NodeId) -> usize {
326 self.get_ancestors(id).len() - 1
327 }
328
329 pub fn find_by_relative_path(&self, relative_path: &Path) -> Option<NodeId> {
331 let full_path = self.root_path.join(relative_path);
332 self.path_to_node.get(&full_path).copied()
333 }
334
335 fn add_node(&mut self, entry: DirEntry, parent: Option<NodeId>) -> NodeId {
337 let id = NodeId(self.next_id);
338 self.next_id += 1;
339
340 let node = TreeNode::new(id, entry.clone(), parent);
341 self.path_to_node.insert(entry.path.clone(), id);
342 self.nodes.insert(id, node);
343
344 id
345 }
346
347 fn remove_node_recursive(&mut self, id: NodeId) {
349 if let Some(node) = self.get_node(id) {
350 let children = node.children.clone();
351 let path = node.entry.path.clone();
352
353 for child_id in children {
355 self.remove_node_recursive(child_id);
356 }
357
358 self.path_to_node.remove(&path);
360
361 self.nodes.remove(&id);
363 }
364 }
365
366 pub fn node_count(&self) -> usize {
368 self.nodes.len()
369 }
370
371 pub async fn expand_to_path(&mut self, path: &Path) -> Option<NodeId> {
398 let relative_path = path.strip_prefix(&self.root_path).ok()?;
400
401 let mut current_id = self.root_id;
403
404 for component in relative_path.components() {
406 let component_str = component.as_os_str().to_str()?;
407
408 let node = self.get_node(current_id)?;
410 if node.is_dir() && !node.is_expanded() {
411 if let Err(e) = self.expand_node(current_id).await {
413 tracing::warn!("Failed to expand node during path traversal: {}", e);
414 return None;
415 }
416 }
417
418 let node = self.get_node(current_id)?;
420 let child_id = node
421 .children
422 .iter()
423 .find(|&&child_id| {
424 if let Some(child_node) = self.get_node(child_id) {
425 child_node.entry.name == component_str
426 } else {
427 false
428 }
429 })
430 .copied();
431
432 match child_id {
433 Some(id) => current_id = id,
434 None => {
435 tracing::warn!("Component '{}' not found in tree", component_str);
437 return None;
438 }
439 }
440 }
441
442 Some(current_id)
443 }
444}
445
446fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
457 use std::cmp::Ordering;
458 let mut ai = a.char_indices().peekable();
459 let mut bi = b.char_indices().peekable();
460 loop {
461 match (ai.peek().copied(), bi.peek().copied()) {
462 (None, None) => return Ordering::Equal,
463 (None, Some(_)) => return Ordering::Less,
464 (Some(_), None) => return Ordering::Greater,
465 (Some((_, ca)), Some((_, cb))) => {
466 if ca.is_ascii_digit() && cb.is_ascii_digit() {
467 let (a_start, a_end) = take_digit_run(&mut ai);
472 let (b_start, b_end) = take_digit_run(&mut bi);
473 let a_digits = &a[a_start..a_end];
474 let b_digits = &b[b_start..b_end];
475 let a_trim = a_digits.trim_start_matches('0');
476 let b_trim = b_digits.trim_start_matches('0');
477 match a_trim.len().cmp(&b_trim.len()) {
478 Ordering::Equal => match a_trim.cmp(b_trim) {
479 Ordering::Equal => {
480 if a_digits.len() != b_digits.len() {
481 return a_digits.len().cmp(&b_digits.len());
482 }
483 }
484 other => return other,
485 },
486 other => return other,
487 }
488 } else {
489 let (_, ca) = ai.next().unwrap();
490 let (_, cb) = bi.next().unwrap();
491 let la = ca.to_ascii_lowercase();
492 let lb = cb.to_ascii_lowercase();
493 if la != lb {
494 return la.cmp(&lb);
495 }
496 }
497 }
498 }
499 }
500}
501
502fn take_digit_run<I>(iter: &mut std::iter::Peekable<I>) -> (usize, usize)
506where
507 I: Iterator<Item = (usize, char)>,
508{
509 let (start, _) = *iter.peek().expect("caller checked at least one digit");
510 let mut end = start;
511 while let Some(&(idx, c)) = iter.peek() {
512 if c.is_ascii_digit() {
513 end = idx + c.len_utf8();
514 iter.next();
515 } else {
516 break;
517 }
518 }
519 (start, end)
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525 use crate::model::filesystem::StdFileSystem;
526 use std::fs as std_fs;
527 use tempfile::TempDir;
528
529 #[test]
534 fn natural_cmp_orders_digit_runs_by_magnitude() {
535 let mut names = vec![
536 "chapter-1.md",
537 "chapter-10.md",
538 "chapter-2.md",
539 "chapter-21.md",
540 "chapter-3.md",
541 ];
542 names.sort_by(|a, b| natural_cmp(a, b));
543 assert_eq!(
544 names,
545 vec![
546 "chapter-1.md",
547 "chapter-2.md",
548 "chapter-3.md",
549 "chapter-10.md",
550 "chapter-21.md",
551 ]
552 );
553 }
554
555 #[test]
560 fn natural_cmp_is_case_insensitive_for_text() {
561 use std::cmp::Ordering;
562 assert_eq!(natural_cmp("Foo.txt", "foo.txt"), Ordering::Equal);
563 assert_eq!(natural_cmp("bar.txt", "Foo.txt"), Ordering::Less);
564 }
565
566 #[test]
570 fn natural_cmp_leading_zeros_break_ties_after_magnitude() {
571 use std::cmp::Ordering;
572 assert_eq!(natural_cmp("v1", "v01"), Ordering::Less);
573 assert_eq!(natural_cmp("v01", "v1"), Ordering::Greater);
574 assert_eq!(natural_cmp("v002.txt", "v10.txt"), Ordering::Less);
576 }
577
578 #[test]
581 fn natural_cmp_handles_mixed_runs() {
582 use std::cmp::Ordering;
583 assert_eq!(natural_cmp("img2a.png", "img10a.png"), Ordering::Less);
584 assert_eq!(natural_cmp("img2b.png", "img2a.png"), Ordering::Greater);
585 assert_eq!(natural_cmp("img.png", "img2.png"), Ordering::Less);
586 }
587
588 async fn create_test_tree() -> (TempDir, FileTree) {
589 let temp_dir = TempDir::new().unwrap();
590 let temp_path = temp_dir.path();
591
592 std_fs::create_dir(temp_path.join("dir1")).unwrap();
603 std_fs::write(temp_path.join("dir1/file1.txt"), "content1").unwrap();
604 std_fs::write(temp_path.join("dir1/file2.txt"), "content2").unwrap();
605
606 std_fs::create_dir(temp_path.join("dir2")).unwrap();
607 std_fs::create_dir(temp_path.join("dir2/subdir")).unwrap();
608 std_fs::write(temp_path.join("dir2/subdir/file3.txt"), "content3").unwrap();
609
610 std_fs::write(temp_path.join("file4.txt"), "content4").unwrap();
611
612 let backend = Arc::new(StdFileSystem);
613 let manager = Arc::new(FsManager::new(backend));
614 let tree = FileTree::new(temp_path.to_path_buf(), manager)
615 .await
616 .unwrap();
617
618 (temp_dir, tree)
619 }
620
621 #[tokio::test]
622 async fn test_tree_creation() {
623 let (_temp_dir, tree) = create_test_tree().await;
624
625 assert_eq!(tree.node_count(), 1); let root = tree.get_node(tree.root_id()).unwrap();
627 assert!(root.is_collapsed());
628 assert_eq!(root.children.len(), 0);
629 }
630
631 #[tokio::test]
632 async fn test_expand_root() {
633 let (_temp_dir, mut tree) = create_test_tree().await;
634
635 tree.expand_node(tree.root_id()).await.unwrap();
636
637 let root = tree.get_node(tree.root_id()).unwrap();
638 assert!(root.is_expanded());
639 assert_eq!(root.children.len(), 3); assert_eq!(tree.node_count(), 4);
643 }
644
645 #[tokio::test]
646 async fn test_expand_nested() {
647 let (_temp_dir, mut tree) = create_test_tree().await;
648
649 tree.expand_node(tree.root_id()).await.unwrap();
651
652 let root = tree.get_node(tree.root_id()).unwrap();
654 let dir1_id = root.children[0]; tree.expand_node(dir1_id).await.unwrap();
657
658 let dir1 = tree.get_node(dir1_id).unwrap();
659 assert!(dir1.is_expanded());
660 assert_eq!(dir1.children.len(), 2); assert_eq!(tree.node_count(), 6);
664 }
665
666 #[tokio::test]
667 async fn test_collapse_node() {
668 let (_temp_dir, mut tree) = create_test_tree().await;
669
670 tree.expand_node(tree.root_id()).await.unwrap();
672 let root = tree.get_node(tree.root_id()).unwrap();
673 let dir1_id = root.children[0];
674 tree.expand_node(dir1_id).await.unwrap();
675
676 assert_eq!(tree.node_count(), 6);
677
678 tree.collapse_node(dir1_id);
680
681 let dir1 = tree.get_node(dir1_id).unwrap();
682 assert!(dir1.is_collapsed());
683 assert_eq!(dir1.children.len(), 0);
684
685 assert_eq!(tree.node_count(), 4);
687 }
688
689 #[tokio::test]
690 async fn test_toggle_node() {
691 let (_temp_dir, mut tree) = create_test_tree().await;
692
693 tree.expand_node(tree.root_id()).await.unwrap();
694 let root = tree.get_node(tree.root_id()).unwrap();
695 let dir1_id = root.children[0];
696
697 tree.toggle_node(dir1_id).await.unwrap();
699 assert!(tree.get_node(dir1_id).unwrap().is_expanded());
700
701 tree.toggle_node(dir1_id).await.unwrap();
703 assert!(tree.get_node(dir1_id).unwrap().is_collapsed());
704 }
705
706 #[tokio::test]
707 async fn test_get_visible_nodes() {
708 let (_temp_dir, mut tree) = create_test_tree().await;
709
710 let visible = tree.get_visible_nodes();
712 assert_eq!(visible.len(), 1);
713
714 tree.expand_node(tree.root_id()).await.unwrap();
716 let visible = tree.get_visible_nodes();
717 assert_eq!(visible.len(), 4); let root = tree.get_node(tree.root_id()).unwrap();
721 let dir1_id = root.children[0];
722 tree.expand_node(dir1_id).await.unwrap();
723
724 let visible = tree.get_visible_nodes();
725 assert_eq!(visible.len(), 6); }
727
728 #[tokio::test]
729 async fn test_get_ancestors() {
730 let (_temp_dir, mut tree) = create_test_tree().await;
731
732 tree.expand_node(tree.root_id()).await.unwrap();
733 let root = tree.get_node(tree.root_id()).unwrap();
734 let dir1_id = root.children[0];
735 tree.expand_node(dir1_id).await.unwrap();
736
737 let dir1 = tree.get_node(dir1_id).unwrap();
738 let file1_id = dir1.children[0];
739
740 let ancestors = tree.get_ancestors(file1_id);
741 assert_eq!(ancestors.len(), 3); assert_eq!(ancestors[0], tree.root_id());
743 assert_eq!(ancestors[1], dir1_id);
744 assert_eq!(ancestors[2], file1_id);
745 }
746
747 #[tokio::test]
748 async fn test_get_depth() {
749 let (_temp_dir, mut tree) = create_test_tree().await;
750
751 tree.expand_node(tree.root_id()).await.unwrap();
752 let root = tree.get_node(tree.root_id()).unwrap();
753 let dir1_id = root.children[0];
754 tree.expand_node(dir1_id).await.unwrap();
755
756 assert_eq!(tree.get_depth(tree.root_id()), 0);
757 assert_eq!(tree.get_depth(dir1_id), 1);
758
759 let dir1 = tree.get_node(dir1_id).unwrap();
760 let file1_id = dir1.children[0];
761 assert_eq!(tree.get_depth(file1_id), 2);
762 }
763
764 #[tokio::test]
765 async fn test_sorted_entries() {
766 let (_temp_dir, mut tree) = create_test_tree().await;
767
768 tree.expand_node(tree.root_id()).await.unwrap();
769
770 let root = tree.get_node(tree.root_id()).unwrap();
771 let children: Vec<_> = root
772 .children
773 .iter()
774 .map(|&id| tree.get_node(id).unwrap())
775 .collect();
776
777 assert!(children[0].is_dir());
779 assert!(children[1].is_dir());
780 assert!(children[2].is_file());
781
782 assert_eq!(children[0].entry.name, "dir1");
784 assert_eq!(children[1].entry.name, "dir2");
785 }
786
787 #[tokio::test]
788 async fn test_refresh_node() {
789 let temp_dir = TempDir::new().unwrap();
790 let temp_path = temp_dir.path();
791
792 std_fs::create_dir(temp_path.join("dir1")).unwrap();
793 std_fs::write(temp_path.join("dir1/file1.txt"), "content").unwrap();
794
795 let backend = Arc::new(StdFileSystem);
796 let manager = Arc::new(FsManager::new(backend));
797 let mut tree = FileTree::new(temp_path.to_path_buf(), manager)
798 .await
799 .unwrap();
800
801 tree.expand_node(tree.root_id()).await.unwrap();
803 let root = tree.get_node(tree.root_id()).unwrap();
804 let dir1_id = root.children[0];
805 tree.expand_node(dir1_id).await.unwrap();
806
807 let dir1 = tree.get_node(dir1_id).unwrap();
809 assert_eq!(dir1.children.len(), 1);
810
811 std_fs::write(temp_path.join("dir1/file2.txt"), "content2").unwrap();
813
814 tree.refresh_node(dir1_id).await.unwrap();
816
817 let dir1 = tree.get_node(dir1_id).unwrap();
819 assert_eq!(dir1.children.len(), 2);
820 }
821
822 #[tokio::test]
823 async fn test_find_by_relative_path() {
824 let (_temp_dir, mut tree) = create_test_tree().await;
825
826 tree.expand_node(tree.root_id()).await.unwrap();
827 let root = tree.get_node(tree.root_id()).unwrap();
828 let dir1_id = root.children[0];
829
830 let found_id = tree.find_by_relative_path(Path::new("dir1"));
831 assert_eq!(found_id, Some(dir1_id));
832
833 let not_found = tree.find_by_relative_path(Path::new("nonexistent"));
834 assert_eq!(not_found, None);
835 }
836
837 #[tokio::test]
838 async fn test_expand_to_path() {
839 let (_temp_dir, mut tree) = create_test_tree().await;
840 let root_path = tree.root_path().to_path_buf();
841
842 assert_eq!(tree.node_count(), 1);
844
845 let target_path = root_path.join("dir2/subdir/file3.txt");
847 let node_id = tree.expand_to_path(&target_path).await;
848
849 assert!(node_id.is_some(), "Should find the nested file");
850
851 let root = tree.get_node(tree.root_id()).unwrap();
853 assert!(root.is_expanded(), "Root should be expanded");
854
855 let dir2_node = root
857 .children
858 .iter()
859 .find_map(|&id| {
860 let node = tree.get_node(id)?;
861 if node.entry.name == "dir2" {
862 Some(node)
863 } else {
864 None
865 }
866 })
867 .expect("dir2 should exist");
868
869 assert!(dir2_node.is_expanded(), "dir2 should be expanded");
870
871 let subdir_node = dir2_node
873 .children
874 .iter()
875 .find_map(|&id| {
876 let node = tree.get_node(id)?;
877 if node.entry.name == "subdir" {
878 Some(node)
879 } else {
880 None
881 }
882 })
883 .expect("subdir should exist");
884
885 assert!(subdir_node.is_expanded(), "subdir should be expanded");
886
887 let target_node = tree.get_node(node_id.unwrap()).unwrap();
889 assert_eq!(target_node.entry.name, "file3.txt");
890 assert!(target_node.is_file());
891 }
892
893 #[tokio::test]
894 async fn test_expand_to_path_not_under_root() {
895 let (_temp_dir, mut tree) = create_test_tree().await;
896
897 let outside_path = PathBuf::from("/tmp/somefile.txt");
899 let result = tree.expand_to_path(&outside_path).await;
900
901 assert!(
902 result.is_none(),
903 "Should return None for paths outside root"
904 );
905 }
906
907 #[tokio::test]
908 async fn test_expand_to_path_nonexistent() {
909 let (_temp_dir, mut tree) = create_test_tree().await;
910 let root_path = tree.root_path().to_path_buf();
911
912 let nonexistent_path = root_path.join("dir1/nonexistent.txt");
914 let result = tree.expand_to_path(&nonexistent_path).await;
915
916 assert!(result.is_none(), "Should return None for nonexistent paths");
917 }
918
919 }