Skip to main content

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;