use crate::{
error::Error,
item::{FileTreeItemKind, PathCollapsed},
FileTreeItem,
};
use crate::{error::Result, treeitems_iter::TreeItemsIterator};
use std::{
collections::{BTreeSet, HashMap},
path::{Path, PathBuf},
usize,
};
#[derive(Default)]
pub struct FileTreeItems {
pub tree_items: Vec<FileTreeItem>,
files: usize,
}
impl FileTreeItems {
pub fn new(
list: &[&Path],
collapsed: &BTreeSet<&String>,
) -> Result<Self> {
let (mut items, paths) = Self::create_items(list, collapsed)?;
Self::fold_paths(&mut items, &paths);
Ok(Self {
tree_items: items,
files: list.len(),
})
}
fn create_items<'a>(
list: &'a [&Path],
collapsed: &BTreeSet<&String>,
) -> Result<(Vec<FileTreeItem>, HashMap<&'a Path, usize>)> {
let mut items = Vec::with_capacity(list.len());
let mut paths_added: HashMap<&Path, usize> =
HashMap::with_capacity(list.len());
for e in list {
{
Self::push_dirs(
e,
&mut items,
&mut paths_added,
collapsed,
)?;
}
items.push(FileTreeItem::new_file(e)?);
}
Ok((items, paths_added))
}
pub fn len(&self) -> usize {
self.tree_items.len()
}
pub const fn file_count(&self) -> usize {
self.files
}
pub const fn iterate(
&self,
start: usize,
max_amount: usize,
) -> TreeItemsIterator<'_> {
TreeItemsIterator::new(self, start, max_amount)
}
fn push_dirs<'a>(
item_path: &'a Path,
nodes: &mut Vec<FileTreeItem>,
paths_added: &mut HashMap<&'a Path, usize>,
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_key(c) {
paths_added.insert(c, 0);
if let Some(parent) = c.parent() {
if !parent.as_os_str().is_empty() {
*paths_added.entry(parent).or_insert(0) += 1;
}
}
let path_string = Self::path_to_string(c)?;
let is_collapsed = collapsed.contains(&path_string);
nodes.push(FileTreeItem::new_path(c, is_collapsed)?);
}
}
if let Some(parent) = item_path.parent() {
*paths_added.entry(parent).or_insert(0) += 1;
}
Ok(())
}
fn path_to_string(p: &Path) -> Result<String> {
Ok(p.to_str()
.map_or_else(
|| Err(Error::InvalidPath(p.to_path_buf())),
Ok,
)?
.to_string())
}
pub fn collapse(&mut self, index: usize, recursive: bool) {
if self.tree_items[index].kind().is_path() {
self.tree_items[index].collapse_path();
let path = PathBuf::from(
self.tree_items[index].info().full_path_str(),
);
for i in index + 1..self.tree_items.len() {
let item = &mut self.tree_items[i];
if recursive && item.kind().is_path() {
item.collapse_path();
}
let item_path =
Path::new(item.info().full_path_str());
if item_path.starts_with(&path) {
item.hide();
} else {
break;
}
}
}
}
pub fn expand(&mut self, index: usize, recursive: bool) {
if self.tree_items[index].kind().is_path() {
self.tree_items[index].expand_path();
let full_path = PathBuf::from(
self.tree_items[index].info().full_path_str(),
);
if recursive {
for i in index + 1..self.tree_items.len() {
let item = &mut self.tree_items[i];
if !Path::new(item.info().full_path_str())
.starts_with(&full_path)
{
break;
}
if item.kind().is_path()
&& item.kind().is_path_collapsed()
{
item.expand_path();
}
}
}
self.update_visibility(
&Some(full_path),
index + 1,
false,
);
}
}
pub fn show_element(&mut self, index: usize) -> Option<usize> {
Some(
self.show_element_upward(index)?
+ self.show_element_downward(index)?,
)
}
fn show_element_upward(&mut self, index: usize) -> Option<usize> {
let mut shown = 0_usize;
let item = self.tree_items.get(index)?;
let mut current_folder: (PathBuf, u8) = (
item.info().full_path().parent()?.to_path_buf(),
item.info().indent(),
);
let item_count = self.tree_items.len();
for item in self
.tree_items
.iter_mut()
.rev()
.skip(item_count - index - 1)
{
if item.info().indent() == current_folder.1 {
item.show();
shown += 1;
} else if item.info().indent() == current_folder.1 - 1 {
item.expand_path();
if item.info().is_visible() {
break;
}
item.show();
shown += 1;
current_folder = (
item.info().full_path().parent()?.to_path_buf(),
item.info().indent(),
);
}
}
Some(shown)
}
fn show_element_downward(
&mut self,
index: usize,
) -> Option<usize> {
let mut shown = 0_usize;
let item = self.tree_items.get(index)?;
let mut current_folder: (PathBuf, u8) = (
item.info().full_path().parent()?.to_path_buf(),
item.info().indent(),
);
for item in self.tree_items.iter_mut().skip(index + 1) {
if item.info().indent() == current_folder.1 {
item.show();
shown += 1;
}
if item.info().indent() == current_folder.1 - 1 {
item.show();
shown += 1;
current_folder = (
item.info().full_path().parent()?.to_path_buf(),
item.info().indent(),
);
}
}
Some(shown)
}
fn update_visibility(
&mut self,
prefix: &Option<PathBuf>,
start_idx: usize,
set_defaults: bool,
) {
let mut inner_collapsed: Option<PathBuf> = None;
for i in start_idx..self.tree_items.len() {
if let Some(ref collapsed_path) = inner_collapsed {
let p = Path::new(
self.tree_items[i].info().full_path_str(),
);
if p.starts_with(collapsed_path) {
if set_defaults {
self.tree_items[i]
.info_mut()
.set_visible(false);
}
continue;
}
inner_collapsed = None;
}
let item_kind = self.tree_items[i].kind().clone();
let item_path =
Path::new(self.tree_items[i].info().full_path_str());
if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed)
{
inner_collapsed = Some(item_path.into());
}
if prefix
.as_ref()
.map_or(true, |prefix| item_path.starts_with(prefix))
{
self.tree_items[i].info_mut().set_visible(true);
} else {
if set_defaults {
self.tree_items[i].info_mut().set_visible(false);
} else {
return;
}
}
}
}
fn fold_paths(
items: &mut Vec<FileTreeItem>,
paths: &HashMap<&Path, usize>,
) {
let mut i = 0;
while i < items.len() {
let item = &items[i];
if item.kind().is_path() {
let children = paths
.get(&Path::new(item.info().full_path_str()));
if let Some(children) = children {
if *children == 1 {
if i + 1 >= items.len() {
return;
}
if items
.get(i + 1)
.map(|item| item.kind().is_path())
.unwrap_or_default()
{
let next_item = items.remove(i + 1);
let item_mut = &mut items[i];
item_mut.fold(next_item);
let prefix = item_mut
.info()
.full_path_str()
.to_owned();
Self::unindent(items, &prefix, i + 1);
continue;
}
}
}
}
i += 1;
}
}
fn unindent(
items: &mut [FileTreeItem],
prefix: &str,
start: usize,
) {
for elem in items.iter_mut().skip(start) {
if elem.info().full_path_str().starts_with(prefix) {
elem.info_mut().unindent();
} else {
return;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_simple() {
let items = vec![
Path::new("file.txt"), ];
let res =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
assert!(res.tree_items[0].info().is_visible());
assert_eq!(res.tree_items[0].info().indent(), 0);
assert_eq!(res.tree_items[0].info().path(), items[0]);
assert_eq!(res.tree_items[0].info().full_path(), items[0]);
let items = vec![
Path::new("file.txt"), Path::new("file2.txt"), ];
let res =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
assert_eq!(res.tree_items.len(), 2);
assert_eq!(res.tree_items.len(), res.len());
assert_eq!(res.tree_items[1].info().path(), items[1]);
}
#[test]
fn test_push_path() {
let mut items = Vec::new();
let mut paths: HashMap<&Path, usize> = HashMap::new();
FileTreeItems::push_dirs(
Path::new("a/b/c"),
&mut items,
&mut paths,
&BTreeSet::new(),
)
.unwrap();
assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1);
FileTreeItems::push_dirs(
Path::new("a/b2/c"),
&mut items,
&mut paths,
&BTreeSet::new(),
)
.unwrap();
assert_eq!(*paths.get(&Path::new("a")).unwrap(), 2);
}
#[test]
fn test_push_path2() {
let mut items = Vec::new();
let mut paths: HashMap<&Path, usize> = HashMap::new();
FileTreeItems::push_dirs(
Path::new("a/b/c"),
&mut items,
&mut paths,
&BTreeSet::new(),
)
.unwrap();
assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1);
assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 1);
FileTreeItems::push_dirs(
Path::new("a/b/d"),
&mut items,
&mut paths,
&BTreeSet::new(),
)
.unwrap();
assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1);
assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 2);
}
#[test]
fn test_folder() {
let items = vec![
Path::new("a/file.txt"), ];
let res = FileTreeItems::new(&items, &BTreeSet::new())
.unwrap()
.tree_items
.iter()
.map(|i| i.info().full_path_str().to_string())
.collect::<Vec<_>>();
assert_eq!(
res,
vec![String::from("a"), String::from("a/file.txt"),]
);
}
#[test]
fn test_indent() {
let items = vec![
Path::new("a/b/file.txt"), ];
let list =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
let mut res = list
.tree_items
.iter()
.map(|i| (i.info().indent(), i.info().path()));
assert_eq!(res.next(), Some((0, Path::new("a/b"))));
assert_eq!(res.next(), Some((1, Path::new("file.txt"))));
}
#[test]
fn test_indent_folder_file_name() {
let items = vec![
Path::new("a/b"), Path::new("a.txt"), ];
let list =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
let mut res = list
.tree_items
.iter()
.map(|i| (i.info().indent(), i.info().path_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 = vec![
Path::new("a/file.txt"), Path::new("a/file2.txt"), ];
let tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
assert_eq!(tree.file_count(), 2);
assert_eq!(tree.len(), 3);
let res = tree
.tree_items
.iter()
.map(|i| i.info().full_path_str().to_string())
.collect::<Vec<_>>();
assert_eq!(
res,
vec![
String::from("a"),
String::from("a/file.txt"),
String::from("a/file2.txt"),
]
);
}
#[test]
fn test_collapse() {
let items = vec![
Path::new("a/file1.txt"), Path::new("b/file2.txt"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
assert!(tree.tree_items[1].info().is_visible());
tree.collapse(0, false);
assert!(!tree.tree_items[1].info().is_visible());
}
#[test]
fn test_iterate_collapsed() {
let items = vec![
Path::new("a/file1.txt"), Path::new("b/file2.txt"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(0, false);
let mut it = tree.iterate(0, 10);
assert_eq!(it.next().unwrap().0, 0);
assert_eq!(it.next().unwrap().0, 2);
assert_eq!(it.next().unwrap().0, 3);
assert_eq!(it.next(), None);
}
pub fn get_visibles(tree: &FileTreeItems) -> Vec<bool> {
tree.tree_items
.iter()
.map(|e| e.info().is_visible())
.collect::<Vec<_>>()
}
#[test]
fn test_expand() {
let items = vec![
Path::new("a/b/c"), Path::new("a/d"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(1, false);
let visibles = get_visibles(&tree);
assert_eq!(
visibles,
vec![
true, true, false, true,
]
);
tree.expand(1, false);
let visibles = get_visibles(&tree);
assert_eq!(
visibles,
vec![
true, true, true, true,
]
);
}
#[test]
fn test_expand_bug() {
let items = vec![
Path::new("a/b/c"), Path::new("a/b2/d"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(1, false);
tree.collapse(0, false);
assert_eq!(
get_visibles(&tree),
vec![
true, false, false, false, false,
]
);
tree.expand(0, false);
assert_eq!(
get_visibles(&tree),
vec![
true, true, false, true, true,
]
);
}
#[test]
fn test_collapse_too_much() {
let items = vec![
Path::new("a/b"), Path::new("a2/c"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(0, false);
let visibles = get_visibles(&tree);
assert_eq!(
visibles,
vec![
true, false, true, true,
]
);
}
#[test]
fn test_expand_with_collapsed_sub_parts() {
let items = vec![
Path::new("a/b/c"), Path::new("a/d"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(1, false);
let visibles = get_visibles(&tree);
assert_eq!(
visibles,
vec![
true, true, false, true,
]
);
tree.collapse(0, false);
let visibles = get_visibles(&tree);
assert_eq!(
visibles,
vec![
true, false, false, false,
]
);
tree.expand(0, false);
let visibles = get_visibles(&tree);
assert_eq!(
visibles,
vec![
true, true, false, true,
]
);
}
#[test]
fn test_show_element() {
let items = vec![
Path::new("a/b/c"), Path::new("a/b2/d"), Path::new("a/b2/e"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(0, true);
let res = tree.show_element(5).unwrap();
assert_eq!(res, 4);
assert!(tree.tree_items[3].kind().is_path());
assert!(!tree.tree_items[3].kind().is_path_collapsed());
assert_eq!(
get_visibles(&tree),
vec![
true, true, false, true, true, true,
]
);
}
#[test]
fn test_show_element_later_elements() {
let items = vec![
Path::new("a/b"), Path::new("a/c"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(0, true);
assert_eq!(
get_visibles(&tree),
vec![
true, false, false, ]
);
let res = tree.show_element(1).unwrap();
assert_eq!(res, 2);
assert_eq!(
get_visibles(&tree),
vec![
true, true, true, ]
);
}
#[test]
fn test_show_element_downward_parent() {
let items = vec![
Path::new("a/b/c"), Path::new("a/d"), Path::new("a/e"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(0, true);
let res = tree.show_element(2).unwrap();
assert_eq!(res, 4);
assert_eq!(
get_visibles(&tree),
vec![
true, true, true, true, true, ]
);
}
#[test]
fn test_show_element_expand_visible_parent() {
let items = vec![
Path::new("a/b"), ];
let mut tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
tree.collapse(0, true);
assert_eq!(
get_visibles(&tree),
vec![
true, false, ]
);
let res = tree.show_element(1).unwrap();
assert_eq!(res, 1);
assert!(tree.tree_items[0].kind().is_path());
assert!(!tree.tree_items[0].kind().is_path_collapsed());
assert_eq!(
get_visibles(&tree),
vec![
true, true, ]
);
}
}
#[cfg(test)]
mod test_merging {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_merge_simple() {
let list = vec![Path::new("a/b/c")];
let (mut items, paths) =
FileTreeItems::create_items(&list, &BTreeSet::new())
.unwrap();
assert_eq!(items.len(), 3);
FileTreeItems::fold_paths(&mut items, &paths);
assert_eq!(items.len(), 2);
}
#[test]
fn test_merge_simple2() {
let list = vec![
Path::new("a/b/c"), Path::new("a/b/d"), ];
let (mut items, paths) =
FileTreeItems::create_items(&list, &BTreeSet::new())
.unwrap();
assert_eq!(paths.len(), 2);
assert_eq!(*paths.get(&Path::new("a")).unwrap(), 1);
assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 2);
assert_eq!(items.len(), 4);
FileTreeItems::fold_paths(&mut items, &paths);
assert_eq!(items.len(), 3);
}
#[test]
fn test_merge_indent() {
let list = vec![
Path::new("a/b/c/d"), Path::new("a/e/f"), ];
let (mut items, paths) =
FileTreeItems::create_items(&list, &BTreeSet::new())
.unwrap();
assert_eq!(items.len(), 6);
assert_eq!(paths.len(), 4);
assert_eq!(*paths.get(&Path::new("a")).unwrap(), 2);
assert_eq!(*paths.get(&Path::new("a/b")).unwrap(), 1);
assert_eq!(*paths.get(&Path::new("a/b/c")).unwrap(), 1);
assert_eq!(*paths.get(&Path::new("a/e")).unwrap(), 1);
FileTreeItems::fold_paths(&mut items, &paths);
let indents: Vec<u8> =
items.iter().map(|i| i.info().indent()).collect();
assert_eq!(indents, vec![0, 1, 2, 1, 2]);
}
#[test]
fn test_merge_single_paths() {
let items = vec![
Path::new("a/b/c"), Path::new("a/b/d"), ];
let tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
let mut it = tree
.iterate(0, 10)
.map(|(_, item)| item.info().full_path_str());
assert_eq!(it.next().unwrap(), "a/b");
assert_eq!(it.next().unwrap(), "a/b/c");
assert_eq!(it.next().unwrap(), "a/b/d");
assert_eq!(it.next(), None);
}
#[test]
fn test_merge_nothing() {
let items = vec![
Path::new("a/b/c"), Path::new("a/b2/d"), ];
let tree =
FileTreeItems::new(&items, &BTreeSet::new()).unwrap();
let mut it = tree
.iterate(0, 10)
.map(|(_, item)| item.info().full_path_str());
assert_eq!(it.next().unwrap(), "a");
assert_eq!(it.next().unwrap(), "a/b");
assert_eq!(it.next().unwrap(), "a/b/c");
assert_eq!(it.next().unwrap(), "a/b2");
assert_eq!(it.next().unwrap(), "a/b2/d");
assert_eq!(it.next(), None);
}
}