use std::collections::HashMap;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use crate::cache::TreeCache;
use crate::config::DisplayFilter;
use crate::drag::{DragMsg, DragOutcome, DragState, is_valid_target};
use crate::entry::LoadedEntry;
use crate::node::TreeNode;
use crate::scan::{LoadPayload, LoadedOutcome, ScanRequest};
use crate::selection::{self, SelectionMode};
use crate::tree::DirectoryTree;
impl DirectoryTree {
pub fn on_toggled(&mut self, path: &Path) -> Option<ScanRequest> {
let depth = self.depth_of(path)?;
let max_depth = self.config.max_depth;
let node = self.root.find_mut(path)?;
if !node.is_dir {
return None;
}
if node.is_expanded {
node.is_expanded = false;
self.prefetching_paths.remove(path);
return None;
}
if node.is_loaded {
node.is_expanded = true;
return None;
}
if let Some(max) = max_depth
&& depth > max
{
node.is_loaded = true;
node.is_expanded = true;
node.children.clear();
return None;
}
self.prefetching_paths.remove(path);
node.is_expanded = true;
self.generation = self.generation.wrapping_add(1);
Some(ScanRequest {
path: path.to_path_buf(),
generation: self.generation,
depth,
})
}
pub fn on_loaded(&mut self, payload: LoadPayload) -> LoadedOutcome {
if payload.generation != self.generation {
return LoadedOutcome::discarded();
}
let filter = self.config.filter;
let Some(node) = self.root.find_mut(&payload.path) else {
return LoadedOutcome::discarded();
};
match payload.result {
Ok(entries) => {
rebuild_children(node, &entries, filter);
node.error = None;
node.is_loaded = true;
self.cache
.insert(payload.path.clone(), payload.generation, entries);
}
Err(issue) => {
node.children.clear();
node.error = Some(issue);
node.is_loaded = true;
}
}
selection::sync_flags(&mut self.root, &self.selected_paths);
crate::tree::recompute_search_if_active(self);
if self.prefetching_paths.remove(&payload.path) {
return LoadedOutcome::accepted();
}
let prefetch_requests = compute_prefetch(self, &payload.path, payload.depth);
LoadedOutcome {
accepted: true,
prefetch_requests,
}
}
}
fn compute_prefetch(
tree: &mut DirectoryTree,
parent_path: &Path,
parent_depth: u32,
) -> Vec<ScanRequest> {
if tree.config.prefetch_per_parent == 0 {
return Vec::new();
}
let max_prefetch = tree.config.prefetch_per_parent as usize;
let max_depth = tree.config.max_depth;
let child_depth = parent_depth + 1;
let candidates: Vec<PathBuf> = tree
.root
.find(parent_path)
.map(|node| {
node.children
.iter()
.filter(|child| {
child.is_dir
&& !child.is_loaded
&& max_depth.is_none_or(|max| child_depth <= max)
&& !is_prefetch_skip(child.file_name(), &tree.config.prefetch_skip)
})
.take(max_prefetch)
.map(|child| child.path.clone())
.collect()
})
.unwrap_or_default();
if candidates.is_empty() {
return Vec::new();
}
tree.generation = tree.generation.wrapping_add(1);
let wave_gen = tree.generation;
let mut requests = Vec::with_capacity(candidates.len());
for path in candidates {
tree.prefetching_paths.insert(path.clone());
requests.push(ScanRequest {
path,
generation: wave_gen,
depth: child_depth,
});
}
requests
}
fn is_prefetch_skip(name: &OsStr, skip: &[String]) -> bool {
let lower = name.to_string_lossy().to_ascii_lowercase();
skip.iter().any(|s| s.to_ascii_lowercase() == lower)
}
pub(crate) fn rebuild_children(
node: &mut TreeNode,
entries: &[LoadedEntry],
filter: DisplayFilter,
) {
let mut previous: HashMap<PathBuf, TreeNode> = std::mem::take(&mut node.children)
.into_iter()
.map(|child| (child.path.clone(), child))
.collect();
node.children = entries
.iter()
.filter(|entry| filter.admits(entry))
.map(|entry| {
previous
.remove(&entry.path)
.unwrap_or_else(|| TreeNode::from_entry(entry))
})
.collect();
}
pub(crate) fn refresh_from_cache(node: &mut TreeNode, cache: &TreeCache, filter: DisplayFilter) {
if node.is_dir
&& node.is_loaded
&& node.error.is_none()
&& let Some(cached) = cache.get(&node.path)
{
rebuild_children(node, &cached.entries, filter);
}
for child in &mut node.children {
refresh_from_cache(child, cache, filter);
}
}
impl DirectoryTree {
pub fn on_drag_msg(&mut self, msg: DragMsg) -> DragOutcome {
match msg {
DragMsg::Pressed { path, is_dir } => {
let sources = if self.selected_paths.contains(&path) {
self.selected_paths.clone()
} else {
vec![path.clone()]
};
self.drag = Some(DragState {
sources,
hovered_target: None,
started_at: path,
started_is_dir: is_dir,
});
DragOutcome::None
}
DragMsg::Entered(path) => {
if self.drag.is_none() {
return DragOutcome::None;
}
let sources = self.drag.as_ref().unwrap().sources.clone();
let is_dir = self.find(&path).map(|n| n.is_dir).unwrap_or(false);
let valid = is_valid_target(&path, &sources, is_dir);
self.drag.as_mut().unwrap().hovered_target = if valid { Some(path) } else { None };
DragOutcome::None
}
DragMsg::Exited(path) => {
if let Some(drag) = &mut self.drag
&& drag.hovered_target.as_deref() == Some(path.as_path())
{
drag.hovered_target = None;
}
DragOutcome::None
}
DragMsg::Released(path) => {
let Some(drag) = self.drag.take() else {
return DragOutcome::None;
};
if path == drag.started_at {
DragOutcome::Clicked {
path,
is_dir: drag.started_is_dir,
}
} else {
DragOutcome::Completed {
sources: drag.sources,
destination: path,
}
}
}
DragMsg::Cancelled => {
self.drag = None;
DragOutcome::None
}
}
}
}
impl DirectoryTree {
pub fn on_selected(&mut self, path: &Path, _is_dir: bool, mode: SelectionMode) {
let path = path.to_path_buf();
self.active_path = Some(path.clone());
match mode {
SelectionMode::Replace => {
self.selected_paths = vec![path.clone()];
self.anchor_path = Some(path);
}
SelectionMode::Toggle => {
if let Some(pos) = self.selected_paths.iter().position(|p| p == &path) {
self.selected_paths.remove(pos);
} else {
self.selected_paths.push(path.clone());
}
self.anchor_path = Some(path);
}
SelectionMode::ExtendRange => {
let Some(anchor) = self.anchor_path.clone() else {
self.selected_paths = vec![path.clone()];
self.anchor_path = Some(path);
selection::sync_flags(&mut self.root, &self.selected_paths);
return;
};
let rows = self.visible_rows();
let anchor_idx = rows.iter().position(|(n, _)| n.path == anchor);
let target_idx = rows.iter().position(|(n, _)| n.path == path);
if let (Some(a), Some(t)) = (anchor_idx, target_idx) {
let (lo, hi) = if a <= t { (a, t) } else { (t, a) };
self.selected_paths =
rows[lo..=hi].iter().map(|(n, _)| n.path.clone()).collect();
}
}
}
selection::sync_flags(&mut self.root, &self.selected_paths);
}
}