summaryrefslogtreecommitdiffstats
path: root/src/components/utils/filetree.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/utils/filetree.rs')
-rw-r--r--src/components/utils/filetree.rs740
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);
+ }
}