gnostr_filetreelist/
item.rs

1use std::path::{Path, PathBuf};
2
3use crate::error::Result;
4
5/// holds the information shared among all `FileTreeItem` in a
6/// `FileTree`
7#[derive(Debug, Clone)]
8pub struct TreeItemInfo {
9	/// indent level
10	indent: u8,
11	/// currently visible depending on the folder collapse states
12	visible: bool,
13	/// contains this paths last component and folded up paths added
14	/// to it if this is `None` nothing was folding into here
15	folded: Option<PathBuf>,
16	/// the full path
17	full_path: PathBuf,
18}
19
20impl TreeItemInfo {
21	///
22	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	///
32	pub const fn is_visible(&self) -> bool {
33		self.visible
34	}
35
36	///
37	//TODO: remove
38	pub fn full_path_str(&self) -> &str {
39		self.full_path.to_str().unwrap_or_default()
40	}
41
42	///
43	pub fn full_path(&self) -> &Path {
44		self.full_path.as_path()
45	}
46
47	/// like `path` but as `&str`
48	pub fn path_str(&self) -> &str {
49		self.path().as_os_str().to_str().unwrap_or_default()
50	}
51
52	/// returns the last component of `full_path`
53	/// or the last components plus folded up children paths
54	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	///
69	pub const fn indent(&self) -> u8 {
70		self.indent
71	}
72
73	///
74	    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/// attribute used to indicate the collapse/expand state of a path
82/// item
83#[derive(PartialEq, Eq, Debug, Copy, Clone)]
84pub struct PathCollapsed(pub bool);
85
86/// `FileTreeItem` can be of two kinds
87#[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/// `FileTreeItem` can be of two kinds: see `FileTreeItem` but shares
107/// an info
108#[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	///
139	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	///
151	pub const fn info(&self) -> &TreeItemInfo {
152		&self.info
153	}
154
155	///
156	    pub const fn info_mut(&mut self) -> &mut TreeItemInfo {		&mut self.info
157	}
158
159	///
160	pub const fn kind(&self) -> &FileTreeItemKind {
161		&self.kind
162	}
163
164	/// # Panics
165	/// panics if self is not a path
166	pub fn collapse_path(&mut self) {
167		assert!(self.kind.is_path());
168		self.kind = FileTreeItemKind::Path(PathCollapsed(true));
169	}
170
171	/// # Panics
172	/// panics if self is not a path
173	pub fn expand_path(&mut self) {
174		assert!(self.kind.is_path());
175		self.kind = FileTreeItemKind::Path(PathCollapsed(false));
176	}
177
178	///
179	    pub const fn hide(&mut self) {		self.info.visible = false;
180	}
181
182	///
183	    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}