use iced::Task;
use super::DirectoryTree;
use super::message::{DirectoryTreeEvent, LoadPayload};
use super::node::{LoadedEntry, TreeNode};
use super::walker;
impl DirectoryTree {
pub fn update(&mut self, msg: DirectoryTreeEvent) -> Task<DirectoryTreeEvent> {
match msg {
DirectoryTreeEvent::Toggled(path) => self.on_toggled(path),
DirectoryTreeEvent::Selected(path, is_dir) => {
self.on_selected(path, is_dir);
Task::none()
}
DirectoryTreeEvent::Loaded(payload) => {
self.on_loaded(payload);
Task::none()
}
}
}
fn on_toggled(&mut self, path: std::path::PathBuf) -> Task<DirectoryTreeEvent> {
let depth = depth_of(&self.config.root_path, &path);
let Some(node) = self.root.find_mut(&path) else {
return Task::none();
};
if !node.is_dir {
return Task::none();
}
if node.is_expanded {
node.is_expanded = false;
return Task::none();
}
node.is_expanded = true;
if node.is_loaded {
return Task::none();
}
if let Some(limit) = self.config.max_depth
&& depth > limit
{
node.is_loaded = true; return Task::none();
}
self.generation = self.generation.wrapping_add(1);
walker::scan(path, self.generation, depth)
}
fn on_selected(&mut self, path: std::path::PathBuf, _is_dir: bool) {
if self.root.find_mut(&path).is_none() {
return;
}
self.root.clear_selection();
if let Some(node) = self.root.find_mut(&path) {
node.is_selected = true;
}
}
fn on_loaded(&mut self, payload: LoadPayload) {
let LoadPayload {
path,
generation,
depth: _,
result,
} = payload;
if generation != self.generation {
return;
}
let Some(node) = self.root.find_mut(&path) else {
return;
};
if !node.is_dir {
return;
}
match result.as_ref() {
Ok(entries) => {
node.children = build_children(entries, self.config.filter);
node.error = None;
}
Err(err) => {
node.children.clear();
node.error = Some(err.clone());
}
}
node.is_loaded = true;
if let Ok(entries) = result.as_ref() {
self.cache.put(path, generation, entries.clone());
}
}
}
fn build_children(entries: &[LoadedEntry], filter: crate::DirectoryFilter) -> Vec<TreeNode> {
entries
.iter()
.filter(|e| e.passes(filter))
.map(TreeNode::from_entry)
.collect()
}
fn depth_of(root: &std::path::Path, path: &std::path::Path) -> u32 {
let Ok(rel) = path.strip_prefix(root) else {
return u32::MAX;
};
rel.components().count() as u32
}
impl DirectoryTree {
#[doc(hidden)]
pub fn __test_expand_blocking(&mut self, path: std::path::PathBuf) {
use super::message::LoadPayload;
use std::sync::Arc;
let depth = depth_of(&self.config.root_path, &path);
let result = swdir::scan_dir(&path)
.as_ref()
.map(|e| super::walker::normalize_entries(e))
.map_err(crate::Error::from);
self.generation = self.generation.wrapping_add(1);
let payload = LoadPayload {
path: path.clone(),
generation: self.generation,
depth,
result: Arc::new(result),
};
if let Some(node) = self.root.find_mut(&path)
&& node.is_dir
{
node.is_expanded = true;
}
self.on_loaded(payload);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::DirectoryTree;
use std::path::PathBuf;
#[test]
fn toggled_on_nonexistent_path_is_noop() {
let mut tree = DirectoryTree::new(PathBuf::from("/definitely/not/there"));
let _task = tree.update(DirectoryTreeEvent::Toggled(PathBuf::from(
"/some/unrelated/elsewhere",
)));
}
#[test]
fn selection_is_single() {
let mut tree = DirectoryTree::new(PathBuf::from("/a"));
tree.root
.children
.push(TreeNode::new_root(PathBuf::from("/a/b")));
tree.root
.children
.push(TreeNode::new_root(PathBuf::from("/a/c")));
tree.root.is_loaded = true;
let _ = tree.update(DirectoryTreeEvent::Selected(PathBuf::from("/a/b"), true));
assert!(
tree.root
.find_mut(std::path::Path::new("/a/b"))
.unwrap()
.is_selected
);
let _ = tree.update(DirectoryTreeEvent::Selected(PathBuf::from("/a/c"), true));
assert!(
!tree
.root
.find_mut(std::path::Path::new("/a/b"))
.unwrap()
.is_selected
);
assert!(
tree.root
.find_mut(std::path::Path::new("/a/c"))
.unwrap()
.is_selected
);
}
#[test]
fn collapsing_keeps_children() {
let mut tree = DirectoryTree::new(PathBuf::from("/r"));
tree.root.is_loaded = true;
tree.root.is_expanded = true;
tree.root
.children
.push(TreeNode::new_root(PathBuf::from("/r/x")));
let _ = tree.update(DirectoryTreeEvent::Toggled(PathBuf::from("/r")));
assert!(!tree.root.is_expanded);
assert_eq!(
tree.root.children.len(),
1,
"children must survive collapse"
);
}
#[test]
fn stale_loaded_events_are_dropped() {
let mut tree = DirectoryTree::new(PathBuf::from("/r"));
tree.root.is_dir = true;
tree.root.is_expanded = true;
tree.generation = 5;
let stale = LoadPayload {
path: PathBuf::from("/r"),
generation: 4, depth: 0,
result: std::sync::Arc::new(Ok(vec![LoadedEntry {
path: PathBuf::from("/r/hacked"),
is_dir: false,
is_symlink: false,
is_hidden: false,
}])),
};
let _ = tree.update(DirectoryTreeEvent::Loaded(stale));
assert!(
tree.root.children.is_empty(),
"stale result must be ignored"
);
}
}