gnostr_filetreelist/
item.rs1use std::path::{Path, PathBuf};
2
3use crate::error::Result;
4
5#[derive(Debug, Clone)]
8pub struct TreeItemInfo {
9 indent: u8,
11 visible: bool,
13 folded: Option<PathBuf>,
16 full_path: PathBuf,
18}
19
20impl TreeItemInfo {
21 pub const fn new(indent: u8, full_path: PathBuf) -> Self {
23 Self {
24 indent,
25 visible: true,
26 folded: None,
27 full_path,
28 }
29 }
30
31 pub const fn is_visible(&self) -> bool {
33 self.visible
34 }
35
36 pub fn full_path_str(&self) -> &str {
39 self.full_path.to_str().unwrap_or_default()
40 }
41
42 pub fn full_path(&self) -> &Path {
44 self.full_path.as_path()
45 }
46
47 pub fn path_str(&self) -> &str {
49 self.path().as_os_str().to_str().unwrap_or_default()
50 }
51
52 pub fn path(&self) -> &Path {
55 self.folded.as_ref().map_or_else(
56 || {
57 Path::new(
58 self.full_path
59 .components()
60 .next_back()
61 .and_then(|c| c.as_os_str().to_str())
62 .unwrap_or_default(), )
63 },
64 PathBuf::as_path,
65 )
66 }
67
68 pub const fn indent(&self) -> u8 {
70 self.indent
71 }
72
73 pub const fn unindent(&mut self) { self.indent = self.indent.saturating_sub(1);
75 }
76
77 pub const fn set_visible(&mut self, visible: bool) { self.visible = visible;
78 }
79}
80
81#[derive(PartialEq, Eq, Debug, Copy, Clone)]
84pub struct PathCollapsed(pub bool);
85
86#[derive(PartialEq, Eq, Debug, Clone)]
88pub enum FileTreeItemKind {
89 Path(PathCollapsed),
90 File,
91}
92
93impl FileTreeItemKind {
94 pub const fn is_path(&self) -> bool {
95 matches!(self, Self::Path(_))
96 }
97
98 pub const fn is_path_collapsed(&self) -> bool {
99 match self {
100 Self::Path(collapsed) => collapsed.0,
101 Self::File => false,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
109pub struct FileTreeItem {
110 info: TreeItemInfo,
111 kind: FileTreeItemKind,
112}
113
114impl FileTreeItem {
115 pub fn new_file(path: &Path) -> Result<Self> {
116 let item_path = PathBuf::from(path);
117
118 let indent = u8::try_from(
119 item_path.ancestors().count().saturating_sub(2),
120 )?;
121
122 Ok(Self {
123 info: TreeItemInfo::new(indent, item_path),
124 kind: FileTreeItemKind::File,
125 })
126 }
127
128 pub fn new_path(path: &Path, collapsed: bool) -> Result<Self> {
129 let indent =
130 u8::try_from(path.ancestors().count().saturating_sub(2))?;
131
132 Ok(Self {
133 info: TreeItemInfo::new(indent, path.to_owned()),
134 kind: FileTreeItemKind::Path(PathCollapsed(collapsed)),
135 })
136 }
137
138 pub fn fold(&mut self, next: Self) {
140 if let Some(folded) = self.info.folded.as_mut() {
141 *folded = folded.join(next.info.path());
142 } else {
143 self.info.folded =
144 Some(self.info.path().join(next.info.path()));
145 }
146
147 self.info.full_path = next.info.full_path;
148 }
149
150 pub const fn info(&self) -> &TreeItemInfo {
152 &self.info
153 }
154
155 pub const fn info_mut(&mut self) -> &mut TreeItemInfo { &mut self.info
157 }
158
159 pub const fn kind(&self) -> &FileTreeItemKind {
161 &self.kind
162 }
163
164 pub fn collapse_path(&mut self) {
167 assert!(self.kind.is_path());
168 self.kind = FileTreeItemKind::Path(PathCollapsed(true));
169 }
170
171 pub fn expand_path(&mut self) {
174 assert!(self.kind.is_path());
175 self.kind = FileTreeItemKind::Path(PathCollapsed(false));
176 }
177
178 pub const fn hide(&mut self) { self.info.visible = false;
180 }
181
182 pub const fn show(&mut self) { self.info.visible = true;
184 }
185}
186
187impl Eq for FileTreeItem {}
188
189impl PartialEq for FileTreeItem {
190 fn eq(&self, other: &Self) -> bool {
191 self.info.full_path.eq(&other.info.full_path)
192 }
193}
194
195impl PartialOrd for FileTreeItem {
196 fn partial_cmp(
197 &self,
198 other: &Self,
199 ) -> Option<std::cmp::Ordering> {
200 Some(self.cmp(other))
201 }
202}
203
204impl Ord for FileTreeItem {
205 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
206 self.info.path().cmp(other.info.path())
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use pretty_assertions::assert_eq;
213
214 use super::*;
215
216 #[test]
217 fn test_smoke() {
218 let mut a =
219 FileTreeItem::new_path(Path::new("a"), false).unwrap();
220
221 assert_eq!(a.info.full_path_str(), "a");
222 assert_eq!(a.info.path_str(), "a");
223
224 let b =
225 FileTreeItem::new_path(Path::new("a/b"), false).unwrap();
226 a.fold(b);
227
228 assert_eq!(a.info.full_path_str(), "a/b");
229 assert_eq!(
230 &a.info.folded.as_ref().unwrap(),
231 &Path::new("a/b")
232 );
233 assert_eq!(a.info.path(), Path::new("a/b"));
234 }
235}