iced_swdir_tree/directory_tree/config.rs
1//! Configuration types: [`DirectoryFilter`] and [`TreeConfig`].
2
3use std::path::PathBuf;
4
5/// The default set of basenames that [`DirectoryTree`]'s v0.5 prefetch
6/// machinery skips, populated into [`TreeConfig::prefetch_skip`] by
7/// default.
8///
9/// These are directories that are commonly present, commonly *enormous*,
10/// and commonly *not* what a user browses for in a tree widget:
11///
12/// * **Version control metadata** — `.git`, `.hg`, `.svn`. A `.git/`
13/// directory alone can contain tens of thousands of tiny files
14/// under `objects/`; speculatively scanning it on every repo-root
15/// expansion is wasteful even on fast SSDs.
16/// * **JavaScript dependencies** — `node_modules`. Frequently the
17/// single largest directory in a project by both file count and
18/// bytes.
19/// * **Python caches and virtual environments** — `__pycache__`,
20/// `.venv`, `venv`.
21/// * **Build artifacts** — `target` (Rust, Java), `build`, `dist`.
22///
23/// The match is **exact-basename, ASCII case-insensitive**. Substring
24/// matches are *not* performed — a folder named `my-target-files/`
25/// is *not* skipped by the entry `"target"`.
26///
27/// # Overriding the default
28///
29/// This list is a starting point, not a contract. Apps that want to
30/// skip additional directories should merge with the default:
31///
32/// ```ignore
33/// use iced_swdir_tree::{DirectoryTree, DEFAULT_PREFETCH_SKIP};
34///
35/// let mut skip: Vec<String> = DEFAULT_PREFETCH_SKIP
36/// .iter()
37/// .map(|&s| s.to_string())
38/// .collect();
39/// skip.push("huge_media_library".into());
40///
41/// let tree = DirectoryTree::new(root)
42/// .with_prefetch_limit(10)
43/// .with_prefetch_skip(skip);
44/// ```
45///
46/// Apps that want to disable skipping entirely — for example a
47/// dedicated `.git/` viewer — can pass an empty list:
48///
49/// ```ignore
50/// let tree = DirectoryTree::new(root).with_prefetch_skip(Vec::<String>::new());
51/// ```
52///
53/// # User clicks are never skipped
54///
55/// This list applies *only* to automatic prefetch. If the user
56/// explicitly clicks to expand a skipped folder, the widget scans
57/// it normally — their click is an explicit request.
58///
59/// [`DirectoryTree`]: crate::DirectoryTree
60pub const DEFAULT_PREFETCH_SKIP: &[&str] = &[
61 ".git",
62 ".hg",
63 ".svn",
64 "node_modules",
65 "__pycache__",
66 ".venv",
67 "venv",
68 "target",
69 "build",
70 "dist",
71];
72
73/// Controls which entries the widget displays.
74///
75/// The widget *always* scans every entry of an expanded directory
76/// (swdir's `scan_dir` makes no filtering decisions); the filter is
77/// applied as we normalize raw entries into [`TreeNode`]s. That means
78/// a filter change takes effect on the next view without needing to
79/// re-scan the filesystem.
80///
81/// [`TreeNode`]: crate::TreeNode
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83pub enum DirectoryFilter {
84 /// Show only directories (convenient for "pick a destination folder"
85 /// pickers).
86 FoldersOnly,
87 /// Show both files and directories, but skip hidden entries (the
88 /// default — matches most OS file pickers).
89 #[default]
90 FilesAndFolders,
91 /// Show everything, including hidden entries.
92 AllIncludingHidden,
93}
94
95impl DirectoryFilter {
96 /// `true` if the filter suppresses hidden entries.
97 pub fn skips_hidden(self) -> bool {
98 !matches!(self, Self::AllIncludingHidden)
99 }
100
101 /// `true` if the filter suppresses regular files.
102 pub fn skips_files(self) -> bool {
103 matches!(self, Self::FoldersOnly)
104 }
105}
106
107/// Per-tree configuration.
108///
109/// Constructed internally by [`DirectoryTree::new`] and its builder
110/// methods; exposed as `pub` so tests and downstream tooling can
111/// introspect the configuration.
112///
113/// [`DirectoryTree::new`]: crate::DirectoryTree::new
114#[derive(Debug, Clone)]
115pub struct TreeConfig {
116 /// The tree's root directory.
117 pub root_path: PathBuf,
118 /// Active display filter.
119 pub filter: DirectoryFilter,
120 /// Maximum depth to descend into. `None` = unbounded.
121 ///
122 /// Depth is measured relative to the root: `Some(0)` means only
123 /// root's direct children load, `Some(1)` allows grandchildren,
124 /// and so on.
125 pub max_depth: Option<u32>,
126 /// **v0.5 — parallel pre-expansion of visible descendants.**
127 ///
128 /// When a user-initiated expansion finishes loading a folder,
129 /// eagerly issue background scans for up to this many of the
130 /// folder's direct children-that-are-folders, in parallel via
131 /// [`ScanExecutor`]. The scans populate the in-memory cache
132 /// (`is_loaded = true`) but do **not** automatically expand the
133 /// children in the UI — the user still controls what's visible.
134 /// When they later click to expand one of those children, the
135 /// data is already there: no I/O, no thread spawn, no delay.
136 ///
137 /// `0` (the default) disables prefetch entirely, matching v0.1–0.4
138 /// behaviour exactly. Higher values improve perceived
139 /// responsiveness at the cost of background I/O on every
140 /// user-initiated expansion. Typical app values: `5`–`25`. A huge
141 /// value just means "prefetch every child folder"; the crate
142 /// doesn't cap it because apps with fast executors may legitimately
143 /// want that.
144 ///
145 /// Prefetch is **one level deep only** — a folder that loaded via
146 /// prefetch does not itself trigger further prefetches of its
147 /// children. This is intentional: cascading prefetch is
148 /// exponential (`per_parent ^ depth`) and would be surprising as
149 /// a default. If you need deeper prefetch, issue further
150 /// [`DirectoryTreeEvent::Toggled`](crate::DirectoryTreeEvent::Toggled)
151 /// events yourself from your app's update handler, or keep an
152 /// eye on a future release — deeper cascade behind an opt-in may
153 /// be added in a patch.
154 ///
155 /// Prefetch respects `max_depth` the same way user-initiated
156 /// scans do: a prefetch target past the depth cap is skipped.
157 ///
158 /// [`ScanExecutor`]: crate::ScanExecutor
159 pub prefetch_per_parent: usize,
160 /// **v0.6.1 — prefetch safety valve.**
161 ///
162 /// A list of basenames that [`DirectoryTree`]'s prefetch
163 /// machinery refuses to scan. Match is **exact-basename, ASCII
164 /// case-insensitive**: the entry `"target"` skips a folder
165 /// named `target/` or `Target/` but not `my-target-files/`.
166 ///
167 /// Defaults to [`DEFAULT_PREFETCH_SKIP`] — a curated list of
168 /// common very-large directories (`.git`, `node_modules`,
169 /// `target`, …) that are rarely the thing a user is browsing
170 /// toward when they click around a tree. Apps can replace the
171 /// list entirely via [`with_prefetch_skip`], or disable
172 /// skipping by passing an empty list.
173 ///
174 /// Applies **only** to automatic prefetch scans. A user-
175 /// initiated expansion (they clicked it) is never filtered —
176 /// their click is an explicit request.
177 ///
178 /// [`DirectoryTree`]: crate::DirectoryTree
179 /// [`with_prefetch_skip`]: crate::DirectoryTree::with_prefetch_skip
180 pub prefetch_skip: Vec<String>,
181}
182
183#[cfg(test)]
184mod tests;