Rust · 4738 bytes Raw Blame History
1 use crate::types::{FileEntry, FileStatus, SelectableItem, TreeNode};
2 use std::path::{Path, PathBuf};
3
4 /// Build a tree from a list of file entries
5 pub fn build_tree(files: &[FileEntry], hide_dotfiles: bool) -> TreeNode {
6 let mut root = TreeNode::root();
7
8 for file in files {
9 // Skip dotfiles if requested
10 if hide_dotfiles && is_dotfile(&file.path) {
11 continue;
12 }
13
14 add_to_tree(&mut root, &file.path, &file.status);
15 }
16
17 root.sort_children();
18 root
19 }
20
21 /// Check if a path is a dotfile/dotdir
22 fn is_dotfile(path: &Path) -> bool {
23 path.components().any(|c| {
24 c.as_os_str()
25 .to_str()
26 .map(|s| s.starts_with('.'))
27 .unwrap_or(false)
28 })
29 }
30
31 /// Add a file path to the tree, creating intermediate directories
32 fn add_to_tree(node: &mut TreeNode, path: &Path, status: &FileStatus) {
33 let components: Vec<_> = path.components().collect();
34 add_to_tree_recursive(node, &components, 0, status, path.to_path_buf());
35 }
36
37 fn add_to_tree_recursive(
38 node: &mut TreeNode,
39 components: &[std::path::Component],
40 depth: usize,
41 status: &FileStatus,
42 full_path: PathBuf,
43 ) {
44 if depth >= components.len() {
45 return;
46 }
47
48 let name = components[depth]
49 .as_os_str()
50 .to_str()
51 .unwrap_or("")
52 .to_string();
53
54 let is_last_component = depth == components.len() - 1;
55
56 // Build path up to this component
57 let component_path: PathBuf = components[..=depth]
58 .iter()
59 .map(|c| c.as_os_str())
60 .collect();
61
62 // Find or create child node
63 let child_idx = node.children.iter().position(|c| c.name == name);
64
65 match child_idx {
66 Some(idx) => {
67 // Node exists, merge status if it's the final component
68 if is_last_component {
69 node.children[idx].status.merge(status);
70 } else {
71 // Continue recursing
72 add_to_tree_recursive(
73 &mut node.children[idx],
74 components,
75 depth + 1,
76 status,
77 full_path,
78 );
79 }
80 }
81 None => {
82 // Create new node
83 let new_node = if is_last_component {
84 TreeNode::new_file(name, full_path, status.clone())
85 } else {
86 let mut dir = TreeNode::new_directory(name, component_path.clone());
87 add_to_tree_recursive(&mut dir, components, depth + 1, status, full_path);
88 dir
89 };
90 node.children.push(new_node);
91 }
92 }
93 }
94
95 /// Flatten tree into a list of selectable items (respecting expanded state)
96 pub fn flatten_tree(root: &TreeNode, hide_dotfiles: bool) -> Vec<SelectableItem> {
97 let mut items = Vec::new();
98 flatten_node(root, 0, true, Vec::new(), &mut items, hide_dotfiles);
99 items
100 }
101
102 fn flatten_node(
103 node: &TreeNode,
104 depth: usize,
105 is_last: bool,
106 ancestors: Vec<bool>,
107 items: &mut Vec<SelectableItem>,
108 hide_dotfiles: bool,
109 ) {
110 // Skip root node itself, but process its children
111 if node.name != "." {
112 // Skip dotfiles if requested
113 if hide_dotfiles && node.name.starts_with('.') {
114 return;
115 }
116
117 items.push(SelectableItem::from_node(node, depth, is_last, ancestors.clone()));
118 }
119
120 // Only process children if expanded (or if this is root)
121 if node.is_expanded || node.name == "." {
122 let child_count = node.children.len();
123 for (i, child) in node.children.iter().enumerate() {
124 let child_is_last = i == child_count - 1;
125 let mut child_ancestors = ancestors.clone();
126 if node.name != "." {
127 child_ancestors.push(is_last);
128 }
129 flatten_node(
130 child,
131 if node.name == "." { depth } else { depth + 1 },
132 child_is_last,
133 child_ancestors,
134 items,
135 hide_dotfiles,
136 );
137 }
138 }
139 }
140
141 /// Find a node in the tree by path
142 pub fn find_node_mut<'a>(root: &'a mut TreeNode, path: &Path) -> Option<&'a mut TreeNode> {
143 if root.full_path == path {
144 return Some(root);
145 }
146
147 for child in &mut root.children {
148 if let Some(found) = find_node_mut(child, path) {
149 return Some(found);
150 }
151 }
152
153 None
154 }
155
156 /// Toggle expanded state for a node at the given path
157 pub fn toggle_expanded(root: &mut TreeNode, path: &Path) -> bool {
158 if let Some(node) = find_node_mut(root, path) {
159 if !node.is_file {
160 node.toggle_expanded();
161 return true;
162 }
163 }
164 false
165 }
166