use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::search::{self, SearchState};
use crate::cache::TreeCache;
use crate::config::{DisplayFilter, TreeConfig};
use crate::drag::DragState;
use crate::node::TreeNode;
use crate::scan::{self, LoadedOutcome};
use crate::selection;
pub(crate) mod transitions;
#[derive(Debug, Clone, PartialEq)]
pub struct DirectoryTree {
pub(crate) root: TreeNode,
pub(crate) config: TreeConfig,
pub(crate) cache: TreeCache,
pub(crate) generation: u32,
pub(crate) selected_paths: Vec<PathBuf>,
pub(crate) active_path: Option<PathBuf>,
pub(crate) anchor_path: Option<PathBuf>,
pub(crate) drag: Option<DragState>,
pub(crate) prefetching_paths: HashSet<PathBuf>,
pub(crate) search: Option<crate::search::SearchState>,
}
impl DirectoryTree {
pub fn new(root_path: impl Into<PathBuf>) -> Self {
let config = TreeConfig::new(root_path);
let root = TreeNode::new_root(config.root_path.clone());
Self {
root,
config,
cache: TreeCache::default(),
generation: 0,
selected_paths: Vec::new(),
active_path: None,
anchor_path: None,
drag: None,
prefetching_paths: HashSet::new(),
search: None,
}
}
pub fn with_filter(mut self, filter: DisplayFilter) -> Self {
self.config.filter = filter;
self
}
pub fn with_max_depth(mut self, max_depth: u32) -> Self {
self.config.max_depth = Some(max_depth);
self
}
pub fn root(&self) -> &TreeNode {
&self.root
}
pub fn config(&self) -> &TreeConfig {
&self.config
}
pub fn filter(&self) -> DisplayFilter {
self.config.filter
}
pub fn generation(&self) -> u32 {
self.generation
}
pub fn cache(&self) -> &TreeCache {
&self.cache
}
pub fn find(&self, path: &Path) -> Option<&TreeNode> {
self.root.find(path)
}
pub fn depth_of(&self, path: &Path) -> Option<u32> {
let rel = path.strip_prefix(&self.config.root_path).ok()?;
Some(rel.components().count() as u32)
}
pub fn set_filter(&mut self, filter: DisplayFilter) {
if filter == self.config.filter {
return;
}
self.config.filter = filter;
transitions::refresh_from_cache(&mut self.root, &self.cache, filter);
selection::sync_flags(&mut self.root, &self.selected_paths);
recompute_search_if_active(self); }
pub fn visible_rows(&self) -> Vec<(&TreeNode, u32)> {
let mut rows = Vec::new();
if let Some(search) = &self.search {
collect_rows_search(&self.root, 0, &mut rows, &search.visible_paths);
} else {
collect_rows(&self.root, 0, &mut rows);
}
rows
}
pub fn expand_blocking(&mut self, path: &Path) -> Option<LoadedOutcome> {
let request = self.on_toggled(path)?;
let payload = scan::run(&request);
Some(self.on_loaded(payload))
}
pub fn selected_paths(&self) -> &[PathBuf] {
&self.selected_paths
}
pub fn selected_path(&self) -> Option<&Path> {
self.active_path.as_deref()
}
pub fn is_selected(&self, path: &Path) -> bool {
self.selected_paths.iter().any(|p| p == path)
}
pub fn with_prefetch_limit(mut self, n: u32) -> Self {
self.config.prefetch_per_parent = n;
self
}
pub fn with_prefetch_skip(mut self, skip: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.config.prefetch_skip = skip.into_iter().map(Into::into).collect();
self
}
pub fn prefetching_paths(&self) -> &HashSet<PathBuf> {
&self.prefetching_paths
}
pub fn search_query(&self) -> Option<&str> {
self.search.as_ref().map(|s| s.query.as_str())
}
pub fn search_state(&self) -> Option<&SearchState> {
self.search.as_ref()
}
pub fn search_match_count(&self) -> usize {
self.search.as_ref().map_or(0, |s| s.match_count)
}
pub fn set_search_query(&mut self, query: &str) {
if query.is_empty() {
self.search = None;
return;
}
let query_lower = query.to_ascii_lowercase();
let mut visible = HashSet::new();
let mut match_count = 0;
search::walk_for_search(&self.root, &query_lower, &mut visible, &mut match_count);
self.search = Some(SearchState {
query: query.to_string(),
query_lower,
visible_paths: visible,
match_count,
});
}
pub fn clear_search(&mut self) {
self.search = None;
}
pub fn drag_state(&self) -> Option<&DragState> {
self.drag.as_ref()
}
}
fn collect_rows<'a>(node: &'a TreeNode, depth: u32, rows: &mut Vec<(&'a TreeNode, u32)>) {
rows.push((node, depth));
if node.is_dir && node.is_expanded && node.is_loaded {
for child in &node.children {
collect_rows(child, depth + 1, rows);
}
}
}
fn collect_rows_search<'a>(
node: &'a TreeNode,
depth: u32,
rows: &mut Vec<(&'a TreeNode, u32)>,
visible_paths: &std::collections::HashSet<std::path::PathBuf>,
) {
if !visible_paths.contains(&node.path) {
return;
}
rows.push((node, depth));
if node.is_dir && node.is_loaded {
for child in &node.children {
collect_rows_search(child, depth + 1, rows, visible_paths);
}
}
}
pub(crate) fn recompute_search_if_active(tree: &mut DirectoryTree) {
let query_lower = match &tree.search {
Some(s) => s.query_lower.clone(),
None => return,
};
let mut visible = HashSet::new();
let mut match_count = 0;
search::walk_for_search(&tree.root, &query_lower, &mut visible, &mut match_count);
if let Some(s) = &mut tree.search {
s.visible_paths = visible;
s.match_count = match_count;
}
}