Skip to main content

fresh/view/file_tree/
node.rs

1use crate::model::filesystem::DirEntry;
2use std::fmt;
3
4/// Unique identifier for a tree node
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub struct NodeId(pub usize);
7
8impl fmt::Display for NodeId {
9    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10        write!(f, "Node({})", self.0)
11    }
12}
13
14/// Represents a node in the file tree
15#[derive(Debug, Clone)]
16pub struct TreeNode {
17    /// Unique identifier
18    pub id: NodeId,
19    /// Filesystem entry information
20    pub entry: DirEntry,
21    /// Parent node ID (None for root)
22    pub parent: Option<NodeId>,
23    /// Child node IDs (for directories)
24    pub children: Vec<NodeId>,
25    /// Current state of the node
26    pub state: NodeState,
27}
28
29impl TreeNode {
30    /// Create a new tree node
31    pub fn new(id: NodeId, entry: DirEntry, parent: Option<NodeId>) -> Self {
32        let state = if entry.is_dir() {
33            NodeState::Collapsed
34        } else {
35            NodeState::Leaf
36        };
37
38        Self {
39            id,
40            entry,
41            parent,
42            children: Vec::new(),
43            state,
44        }
45    }
46
47    /// Check if this node is a directory
48    pub fn is_dir(&self) -> bool {
49        self.entry.is_dir()
50    }
51
52    /// Check if this node is a file
53    pub fn is_file(&self) -> bool {
54        self.entry.is_file()
55    }
56
57    /// Check if this node is expanded
58    pub fn is_expanded(&self) -> bool {
59        self.state == NodeState::Expanded
60    }
61
62    /// Check if this node is collapsed
63    pub fn is_collapsed(&self) -> bool {
64        self.state == NodeState::Collapsed
65    }
66
67    /// Check if this node is loading
68    pub fn is_loading(&self) -> bool {
69        self.state == NodeState::Loading
70    }
71
72    /// Check if this node has an error
73    pub fn is_error(&self) -> bool {
74        matches!(self.state, NodeState::Error(_))
75    }
76
77    /// Check if this node is a leaf (file, not a directory)
78    pub fn is_leaf(&self) -> bool {
79        self.state == NodeState::Leaf
80    }
81
82    /// Get the depth of this node in the tree
83    pub fn depth(&self, get_parent: impl Fn(NodeId) -> Option<NodeId>) -> usize {
84        let mut depth = 0;
85        let mut current = self.parent;
86
87        while let Some(parent_id) = current {
88            depth += 1;
89            current = get_parent(parent_id);
90        }
91
92        depth
93    }
94}
95
96/// State of a tree node
97#[derive(Debug, Clone, PartialEq)]
98pub enum NodeState {
99    /// Directory not yet expanded
100    Collapsed,
101    /// Directory expanded, loading children
102    Loading,
103    /// Directory expanded, children loaded
104    Expanded,
105    /// Failed to load (with error message)
106    Error(String),
107    /// File (leaf node, cannot be expanded)
108    Leaf,
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use crate::model::filesystem::{DirEntry, EntryType};
115    use std::path::PathBuf;
116
117    #[test]
118    fn test_node_creation() {
119        let entry = DirEntry::new(
120            PathBuf::from("/test/file.txt"),
121            "file.txt".to_string(),
122            EntryType::File,
123        );
124
125        let node = TreeNode::new(NodeId(0), entry, None);
126
127        assert_eq!(node.id, NodeId(0));
128        assert_eq!(node.parent, None);
129        assert!(node.is_file());
130        assert!(node.is_leaf());
131        assert_eq!(node.children.len(), 0);
132    }
133
134    #[test]
135    fn test_directory_node() {
136        let entry = DirEntry::new(
137            PathBuf::from("/test/dir"),
138            "dir".to_string(),
139            EntryType::Directory,
140        );
141
142        let node = TreeNode::new(NodeId(1), entry, Some(NodeId(0)));
143
144        assert!(node.is_dir());
145        assert!(node.is_collapsed());
146        assert!(!node.is_expanded());
147        assert_eq!(node.parent, Some(NodeId(0)));
148    }
149
150    #[test]
151    fn test_node_states() {
152        let entry = DirEntry::new(
153            PathBuf::from("/test/dir"),
154            "dir".to_string(),
155            EntryType::Directory,
156        );
157
158        let mut node = TreeNode::new(NodeId(0), entry, None);
159
160        assert!(node.is_collapsed());
161        assert!(!node.is_loading());
162        assert!(!node.is_error());
163
164        node.state = NodeState::Loading;
165        assert!(node.is_loading());
166        assert!(!node.is_collapsed());
167
168        node.state = NodeState::Expanded;
169        assert!(node.is_expanded());
170        assert!(!node.is_loading());
171
172        node.state = NodeState::Error("Failed to read".to_string());
173        assert!(node.is_error());
174        assert!(!node.is_expanded());
175    }
176
177    #[test]
178    fn test_node_depth() {
179        // Create a simple tree structure
180        let root = TreeNode::new(
181            NodeId(0),
182            DirEntry::new(PathBuf::from("/"), "/".to_string(), EntryType::Directory),
183            None,
184        );
185
186        let child1 = TreeNode::new(
187            NodeId(1),
188            DirEntry::new(
189                PathBuf::from("/dir1"),
190                "dir1".to_string(),
191                EntryType::Directory,
192            ),
193            Some(NodeId(0)),
194        );
195
196        let child2 = TreeNode::new(
197            NodeId(2),
198            DirEntry::new(
199                PathBuf::from("/dir1/dir2"),
200                "dir2".to_string(),
201                EntryType::Directory,
202            ),
203            Some(NodeId(1)),
204        );
205
206        // Helper function to get parent
207        let get_parent = |id: NodeId| match id.0 {
208            0 => None,
209            1 => Some(NodeId(0)),
210            2 => Some(NodeId(1)),
211            _ => None,
212        };
213
214        assert_eq!(root.depth(get_parent), 0);
215        assert_eq!(child1.depth(get_parent), 1);
216        assert_eq!(child2.depth(get_parent), 2);
217    }
218}