dioxus_swdir_tree_core/search.rs
1//! Incremental search over the already-loaded node graph.
2//!
3//! Search never triggers I/O (S9.9): it filters the **currently loaded**
4//! tree. [`walk_for_search`] is called every time the query, the filter,
5//! or the node graph changes.
6
7use std::collections::HashSet;
8use std::path::PathBuf;
9
10use crate::node::TreeNode;
11
12/// Active search session held on [`crate::DirectoryTree`].
13///
14/// `None` when no search is active.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct SearchState {
17 /// Query as provided by the application (original casing).
18 pub query: String,
19 /// `query.to_ascii_lowercase()` — used for comparisons (S9.1).
20 pub query_lower: String,
21 /// Paths that are visible in search mode: direct matches ∪ ancestors
22 /// of matches (S9.2). Used by [`crate::DirectoryTree::visible_rows`]
23 /// to gate which rows are drawn.
24 pub visible_paths: HashSet<PathBuf>,
25 /// Count of **direct** matches only; ancestors shown for context are
26 /// not included (S9.8). Applications should use this for "N results"
27 /// displays.
28 pub match_count: usize,
29}
30
31/// Walk `node` and its already-loaded descendants, populating
32/// `visible` with every matching path and every ancestor of a match
33/// (S9.2, S9.3). Returns `true` iff the subtree rooted at `node`
34/// contains at least one match (so the caller knows to include `node`
35/// in the visible set).
36///
37/// The walk ignores `is_expanded` — it descends into any loaded
38/// directory regardless of its expansion state (S9.3).
39pub fn walk_for_search(
40 node: &TreeNode,
41 query_lower: &str,
42 visible: &mut HashSet<PathBuf>,
43 match_count: &mut usize,
44) -> bool {
45 let basename_lower = node.file_name().to_string_lossy().to_ascii_lowercase();
46 let self_matches = basename_lower.contains(query_lower);
47 if self_matches {
48 *match_count += 1;
49 }
50
51 // Recurse into loaded children.
52 let mut descendant_matches = false;
53 for child in &node.children {
54 if walk_for_search(child, query_lower, visible, match_count) {
55 descendant_matches = true;
56 }
57 }
58
59 let has_visible = self_matches || descendant_matches;
60 if has_visible {
61 visible.insert(node.path.clone());
62 }
63 has_visible
64}