diff options
Diffstat (limited to 'src/components/utils/filetree.rs')
-rw-r--r-- | src/components/utils/filetree.rs | 740 |
1 files changed, 370 insertions, 370 deletions
diff --git a/src/components/utils/filetree.rs b/src/components/utils/filetree.rs index 3dbfc78c..4b26825e 100644 --- a/src/components/utils/filetree.rs +++ b/src/components/utils/filetree.rs @@ -3,39 +3,39 @@ use anyhow::{bail, Result}; use asyncgit::StatusItem; use std::{ - collections::BTreeSet, - convert::TryFrom, - ffi::OsStr, - ops::{Index, IndexMut}, - path::Path, + collections::BTreeSet, + convert::TryFrom, + ffi::OsStr, + ops::{Index, IndexMut}, + path::Path, }; /// holds the information shared among all `FileTreeItem` in a `FileTree` #[derive(Debug, Clone)] pub struct TreeItemInfo { - /// indent level - pub indent: u8, - /// currently visible depending on the folder collapse states - pub visible: bool, - /// just the last path element - pub path: String, - /// the full path - pub full_path: String, + /// indent level + pub indent: u8, + /// currently visible depending on the folder collapse states + pub visible: bool, + /// just the last path element + pub path: String, + /// the full path + pub full_path: String, } impl TreeItemInfo { - const fn new( - indent: u8, - path: String, - full_path: String, - ) -> Self { - Self { - indent, - visible: true, - path, - full_path, - } - } + const fn new( + indent: u8, + path: String, + full_path: String, + ) -> Self { + Self { + indent, + visible: true, + path, + full_path, + } + } } /// attribute used to indicate the collapse/expand state of a path item @@ -45,387 +45,387 @@ pub struct PathCollapsed(pub bool); /// `FileTreeItem` can be of two kinds #[derive(PartialEq, Debug, Clone)] pub enum FileTreeItemKind { - Path(PathCollapsed), - File(StatusItem), + Path(PathCollapsed), + File(StatusItem), } /// `FileTreeItem` can be of two kinds: see `FileTreeItem` but shares an info #[derive(Debug, Clone)] pub struct FileTreeItem { - pub info: TreeItemInfo, - pub kind: FileTreeItemKind, + pub info: TreeItemInfo, + pub kind: FileTreeItemKind, } impl FileTreeItem { - fn new_file(item: &StatusItem) -> Result<Self> { - let item_path = Path::new(&item.path); - let indent = u8::try_from( - item_path.ancestors().count().saturating_sub(2), - )?; - - let name = item_path - .file_name() - .map(OsStr::to_string_lossy) - .map(|x| x.to_string()); - - match name { - Some(path) => Ok(Self { - info: TreeItemInfo::new( - indent, - path, - item.path.clone(), - ), - kind: FileTreeItemKind::File(item.clone()), - }), - None => bail!("invalid file name {:?}", item), - } - } - - fn new_path( - path: &Path, - path_string: String, - collapsed: bool, - ) -> Result<Self> { - let indent = - u8::try_from(path.ancestors().count().saturating_sub(2))?; - - match path - .components() - .last() - .map(std::path::Component::as_os_str) - .map(OsStr::to_string_lossy) - .map(String::from) - { - Some(path) => Ok(Self { - info: TreeItemInfo::new(indent, path, path_string), - kind: FileTreeItemKind::Path(PathCollapsed( - collapsed, - )), - }), - None => bail!("failed to create item from path"), - } - } + fn new_file(item: &StatusItem) -> Result<Self> { + let item_path = Path::new(&item.path); + let indent = u8::try_from( + item_path.ancestors().count().saturating_sub(2), + )?; + + let name = item_path + .file_name() + .map(OsStr::to_string_lossy) + .map(|x| x.to_string()); + + match name { + Some(path) => Ok(Self { + info: TreeItemInfo::new( + indent, + path, + item.path.clone(), + ), + kind: FileTreeItemKind::File(item.clone()), + }), + None => bail!("invalid file name {:?}", item), + } + } + + fn new_path( + path: &Path, + path_string: String, + collapsed: bool, + ) -> Result<Self> { + let indent = + u8::try_from(path.ancestors().count().saturating_sub(2))?; + + match path + .components() + .last() + .map(std::path::Component::as_os_str) + .map(OsStr::to_string_lossy) + .map(String::from) + { + Some(path) => Ok(Self { + info: TreeItemInfo::new(indent, path, path_string), + kind: FileTreeItemKind::Path(PathCollapsed( + collapsed, + )), + }), + None => bail!("failed to create item from path"), + } + } } impl Eq for FileTreeItem {} impl PartialEq for FileTreeItem { - fn eq(&self, other: &Self) -> bool { - self.info.full_path.eq(&other.info.full_path) - } + fn eq(&self, other: &Self) -> bool { + self.info.full_path.eq(&other.info.full_path) + } } impl PartialOrd for FileTreeItem { - fn partial_cmp( - &self, - other: &Self, - ) -> Option<std::cmp::Ordering> { - self.info.full_path.partial_cmp(&other.info.full_path) - } + fn partial_cmp( + &self, + other: &Self, + ) -> Option<std::cmp::Ordering> { + self.info.full_path.partial_cmp(&other.info.full_path) + } } impl Ord for FileTreeItem { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.info.path.cmp(&other.info.path) - } + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.info.path.cmp(&other.info.path) + } } /// #[derive(Default)] pub struct FileTreeItems { - items: Vec<FileTreeItem>, - file_count: usize, + items: Vec<FileTreeItem>, + file_count: usize, } impl FileTreeItems { - /// - pub(crate) fn new( - list: &[StatusItem], - collapsed: &BTreeSet<&String>, - ) -> Result<Self> { - let mut items = Vec::with_capacity(list.len()); - let mut paths_added = BTreeSet::new(); - - for e in list { - { - let item_path = Path::new(&e.path); - - Self::push_dirs( - item_path, - &mut items, - &mut paths_added, - collapsed, - )?; - } - - items.push(FileTreeItem::new_file(e)?); - } - - Ok(Self { - items, - file_count: list.len(), - }) - } - - /// - pub(crate) const fn items(&self) -> &Vec<FileTreeItem> { - &self.items - } - - /// - pub(crate) fn len(&self) -> usize { - self.items.len() - } - - /// - pub const fn file_count(&self) -> usize { - self.file_count - } - - /// - pub(crate) fn find_parent_index(&self, index: usize) -> usize { - let item_indent = &self.items[index].info.indent; - let mut parent_index = index; - while item_indent <= &self.items[parent_index].info.indent { - if parent_index == 0 { - return 0; - } - parent_index -= 1; - } - - parent_index - } - - fn push_dirs<'a>( - item_path: &'a Path, - nodes: &mut Vec<FileTreeItem>, - paths_added: &mut BTreeSet<&'a Path>, - collapsed: &BTreeSet<&String>, - ) -> Result<()> { - let mut ancestors = - { item_path.ancestors().skip(1).collect::<Vec<_>>() }; - ancestors.reverse(); - - for c in &ancestors { - if c.parent().is_some() && !paths_added.contains(c) { - paths_added.insert(c); - //TODO: get rid of expect - let path_string = - String::from(c.to_str().expect("invalid path")); - let is_collapsed = collapsed.contains(&path_string); - nodes.push(FileTreeItem::new_path( - c, - path_string, - is_collapsed, - )?); - } - } - - Ok(()) - } - - pub fn multiple_items_at_path(&self, index: usize) -> bool { - let tree_items = self.items(); - let mut idx_temp_inner; - if index + 2 < tree_items.len() { - idx_temp_inner = index + 1; - while idx_temp_inner < tree_items.len().saturating_sub(1) - && tree_items[index].info.indent - < tree_items[idx_temp_inner].info.indent - { - idx_temp_inner += 1; - } - } else { - return false; - } - - tree_items[idx_temp_inner].info.indent - == tree_items[index].info.indent - } + /// + pub(crate) fn new( + list: &[StatusItem], + collapsed: &BTreeSet<&String>, + ) -> Result<Self> { + let mut items = Vec::with_capacity(list.len()); + let mut paths_added = BTreeSet::new(); + + for e in list { + { + let item_path = Path::new(&e.path); + + Self::push_dirs( + item_path, + &mut items, + &mut paths_added, + collapsed, + )?; + } + + items.push(FileTreeItem::new_file(e)?); + } + + Ok(Self { + items, + file_count: list.len(), + }) + } + + /// + pub(crate) const fn items(&self) -> &Vec<FileTreeItem> { + &self.items + } + + /// + pub(crate) fn len(&self) -> usize { + self.items.len() + } + + /// + pub const fn file_count(&self) -> usize { + self.file_count + } + + /// + pub(crate) fn find_parent_index(&self, index: usize) -> usize { + let item_indent = &self.items[index].info.indent; + let mut parent_index = index; + while item_indent <= &self.items[parent_index].info.indent { + if parent_index == 0 { + return 0; + } + parent_index -= 1; + } + + parent_index + } + + fn push_dirs<'a>( + item_path: &'a Path, + nodes: &mut Vec<FileTreeItem>, + paths_added: &mut BTreeSet<&'a Path>, + collapsed: &BTreeSet<&String>, + ) -> Result<()> { + let mut ancestors = + { item_path.ancestors().skip(1).collect::<Vec<_>>() }; + ancestors.reverse(); + + for c in &ancestors { + if c.parent().is_some() && !paths_added.contains(c) { + paths_added.insert(c); + //TODO: get rid of expect + let path_string = + String::from(c.to_str().expect("invalid path")); + let is_collapsed = collapsed.contains(&path_string); + nodes.push(FileTreeItem::new_path( + c, + path_string, + is_collapsed, + )?); + } + } + + Ok(()) + } + + pub fn multiple_items_at_path(&self, index: usize) -> bool { + let tree_items = self.items(); + let mut idx_temp_inner; + if index + 2 < tree_items.len() { + idx_temp_inner = index + 1; + while idx_temp_inner < tree_items.len().saturating_sub(1) + && tree_items[index].info.indent + < tree_items[idx_temp_inner].info.indent + { + idx_temp_inner += 1; + } + } else { + return false; + } + + tree_items[idx_temp_inner].info.indent + == tree_items[index].info.indent + } } impl IndexMut<usize> for FileTreeItems { - fn index_mut(&mut self, idx: usize) -> &mut Self::Output { - &mut self.items[idx] - } + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + &mut self.items[idx] + } } impl Index<usize> for FileTreeItems { - type Output = FileTreeItem; + type Output = FileTreeItem; - fn index(&self, idx: usize) -> &Self::Output { - &self.items[idx] - } + fn index(&self, idx: usize) -> &Self::Output { + &self.items[idx] + } } #[cfg(test)] mod tests { - use super::*; - use asyncgit::StatusItemType; - - fn string_vec_to_status(items: &[&str]) -> Vec<StatusItem> { - items - .iter() - .map(|a| StatusItem { - path: String::from(*a), - status: StatusItemType::Modified, - }) - .collect::<Vec<_>>() - } - - #[test] - fn test_simple() { - let items = string_vec_to_status(&[ - "file.txt", // - ]); - - let res = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - assert_eq!( - res.items, - vec![FileTreeItem { - info: TreeItemInfo { - path: items[0].path.clone(), - full_path: items[0].path.clone(), - indent: 0, - visible: true, - }, - kind: FileTreeItemKind::File(items[0].clone()) - }] - ); - - let items = string_vec_to_status(&[ - "file.txt", // - "file2.txt", // - ]); - - let res = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - - assert_eq!(res.items.len(), 2); - assert_eq!(res.items[1].info.path, items[1].path); - } - - #[test] - fn test_folder() { - let items = string_vec_to_status(&[ - "a/file.txt", // - ]); - - let res = FileTreeItems::new(&items, &BTreeSet::new()) - .unwrap() - .items - .iter() - .map(|i| i.info.full_path.clone()) - .collect::<Vec<_>>(); - - assert_eq!( - res, - vec![String::from("a"), items[0].path.clone(),] - ); - } - - #[test] - fn test_indent() { - let items = string_vec_to_status(&[ - "a/b/file.txt", // - ]); - - let list = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut res = list - .items - .iter() - .map(|i| (i.info.indent, i.info.path.as_str())); - - assert_eq!(res.next(), Some((0, "a"))); - assert_eq!(res.next(), Some((1, "b"))); - assert_eq!(res.next(), Some((2, "file.txt"))); - } - - #[test] - fn test_indent_folder_file_name() { - let items = string_vec_to_status(&[ - "a/b", // - "a.txt", // - ]); - - let list = - FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); - let mut res = list - .items - .iter() - .map(|i| (i.info.indent, i.info.path.as_str())); - - assert_eq!(res.next(), Some((0, "a"))); - assert_eq!(res.next(), Some((1, "b"))); - assert_eq!(res.next(), Some((0, "a.txt"))); - } - - #[test] - fn test_folder_dup() { - let items = string_vec_to_status(&[ - "a/file.txt", // - "a/file2.txt", // - ]); - - let res = FileTreeItems::new(&items, &BTreeSet::new()) - .unwrap() - .items - .iter() - .map(|i| i.info.full_path.clone()) - .collect::<Vec<_>>(); - - assert_eq!( - res, - vec![ - String::from("a"), - items[0].path.clone(), - items[1].path.clone() - ] - ); - } - - #[test] - fn test_multiple_items_at_path() { - //0 a/ - //1 b/ - //2 c/ - //3 d - //4 e/ - //5 f - - let res = FileTreeItems::new( - &string_vec_to_status(&[ - "a/b/c/d", // - "a/b/e/f", // - ]), - &BTreeSet::new(), - ) - .unwrap(); - - assert_eq!(res.multiple_items_at_path(0), false); - assert_eq!(res.multiple_items_at_path(1), false); - assert_eq!(res.multiple_items_at_path(2), true); - } - - #[test] - fn test_find_parent() { - //0 a/ - //1 b/ - //2 c - //3 d - - let res = FileTreeItems::new( - &string_vec_to_status(&[ - "a/b/c", // - "a/b/d", // - ]), - &BTreeSet::new(), - ) - .unwrap(); - - assert_eq!(res.find_parent_index(3), 1); - } + use super::*; + use asyncgit::StatusItemType; + + fn string_vec_to_status(items: &[&str]) -> Vec<StatusItem> { + items + .iter() + .map(|a| StatusItem { + path: String::from(*a), + status: StatusItemType::Modified, + }) + .collect::<Vec<_>>() + } + + #[test] + fn test_simple() { + let items = string_vec_to_status(&[ + "file.txt", // + ]); + + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + assert_eq!( + res.items, + vec![FileTreeItem { + info: TreeItemInfo { + path: items[0].path.clone(), + full_path: items[0].path.clone(), + indent: 0, + visible: true, + }, + kind: FileTreeItemKind::File(items[0].clone()) + }] + ); + + let items = string_vec_to_status(&[ + "file.txt", // + "file2.txt", // + ]); + + let res = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + + assert_eq!(res.items.len(), 2); + assert_eq!(res.items[1].info.path, items[1].path); + } + + #[test] + fn test_folder() { + let items = string_vec_to_status(&[ + "a/file.txt", // + ]); + + let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() + .items + .iter() + .map(|i| i.info.full_path.clone()) + .collect::<Vec<_>>(); + + assert_eq!( + res, + vec![String::from("a"), items[0].path.clone(),] + ); + } + + #[test] + fn test_indent() { + let items = string_vec_to_status(&[ + "a/b/file.txt", // + ]); + + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let mut res = list + .items + .iter() + .map(|i| (i.info.indent, i.info.path.as_str())); + + assert_eq!(res.next(), Some((0, "a"))); + assert_eq!(res.next(), Some((1, "b"))); + assert_eq!(res.next(), Some((2, "file.txt"))); + } + + #[test] + fn test_indent_folder_file_name() { + let items = string_vec_to_status(&[ + "a/b", // + "a.txt", // + ]); + + let list = + FileTreeItems::new(&items, &BTreeSet::new()).unwrap(); + let mut res = list + .items + .iter() + .map(|i| (i.info.indent, i.info.path.as_str())); + + assert_eq!(res.next(), Some((0, "a"))); + assert_eq!(res.next(), Some((1, "b"))); + assert_eq!(res.next(), Some((0, "a.txt"))); + } + + #[test] + fn test_folder_dup() { + let items = string_vec_to_status(&[ + "a/file.txt", // + "a/file2.txt", // + ]); + + let res = FileTreeItems::new(&items, &BTreeSet::new()) + .unwrap() + .items + .iter() + .map(|i| i.info.full_path.clone()) + .collect::<Vec<_>>(); + + assert_eq!( + res, + vec![ + String::from("a"), + items[0].path.clone(), + items[1].path.clone() + ] + ); + } + + #[test] + fn test_multiple_items_at_path() { + //0 a/ + //1 b/ + //2 c/ + //3 d + //4 e/ + //5 f + + let res = FileTreeItems::new( + &string_vec_to_status(&[ + "a/b/c/d", // + "a/b/e/f", // + ]), + &BTreeSet::new(), + ) + .unwrap(); + + assert_eq!(res.multiple_items_at_path(0), false); + assert_eq!(res.multiple_items_at_path(1), false); + assert_eq!(res.multiple_items_at_path(2), true); + } + + #[test] + fn test_find_parent() { + //0 a/ + //1 b/ + //2 c + //3 d + + let res = FileTreeItems::new( + &string_vec_to_status(&[ + "a/b/c", // + "a/b/d", // + ]), + &BTreeSet::new(), + ) + .unwrap(); + + assert_eq!(res.find_parent_index(3), 1); + } } |