mod file_node;
pub use file_node::FileNode;
use anyhow::Result;
use ignore::WalkBuilder;
use std::path::{Path, PathBuf};
pub struct FileTree {
root: PathBuf,
nodes: Vec<FileNode>,
pub show_hidden: bool,
max_depth: usize,
offset: usize,
}
impl FileTree {
pub fn new(root: &Path, show_hidden: bool, max_depth: usize) -> Result<Self> {
let mut tree = Self {
root: root.to_path_buf(),
nodes: Vec::new(),
show_hidden,
max_depth,
offset: 0,
};
tree.rebuild_visible_nodes()?;
Ok(tree)
}
pub fn root_path(&self) -> &Path {
&self.root
}
pub fn nodes(&self) -> &[FileNode] {
&self.nodes
}
pub fn offset(&self) -> usize {
self.offset
}
pub fn set_offset(&mut self, offset: usize) {
self.offset = offset;
}
fn rebuild_visible_nodes(&mut self) -> Result<()> {
self.nodes.clear();
self.build_tree(&self.root.clone(), 0)?;
Ok(())
}
fn build_tree(&mut self, path: &Path, depth: usize) -> Result<()> {
if depth > self.max_depth {
return Ok(());
}
let walker = WalkBuilder::new(path)
.hidden(!self.show_hidden)
.git_ignore(true)
.git_global(true)
.git_exclude(true)
.max_depth(Some(1))
.sort_by_file_name(|a, b| {
a.cmp(b)
})
.build();
for entry in walker.flatten() {
let entry_path = entry.path();
if entry_path == path && depth > 0 {
continue;
}
if depth == 0 && entry_path != path {
continue;
}
let is_dir = entry_path.is_dir();
let name = entry_path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| entry_path.to_string_lossy().to_string());
if !self.show_hidden && name.starts_with('.') && depth > 0 {
continue;
}
let node = FileNode::new(entry_path.to_path_buf(), name, depth, is_dir);
self.nodes.push(node);
if is_dir {
self.build_tree(entry_path, depth + 1)?;
}
}
Ok(())
}
pub fn refresh(&mut self) {
let _ = self.rebuild_visible_nodes();
}
}