Skip to main content

vtcode_commons/
walk.rs

1#![expect(
2    unused_results,
3    reason = "WalkBuilder configuration uses fluent setters only for their mutation side effects."
4)]
5
6//! Shared directory walker helpers built on the `ignore` crate.
7//!
8//! All file traversal in vtcode should go through these builders so that
9//! `.gitignore`, `.ignore`, `.git/exclude`, and the centralized exclusion
10//! constants are applied consistently.
11
12use ignore::{DirEntry, WalkBuilder};
13use std::path::Path;
14
15use crate::exclusions::DEFAULT_EXCLUDED_DIRS;
16
17/// Build a multi-threaded [`WalkBuilder`] with sensible defaults.
18///
19/// - Respects `.gitignore`, `.ignore`, `.git/exclude`, and parent ignore files
20/// - Does not follow symlinks
21/// - Uses the `ignore` crate's default thread pool
22///
23/// Callers that need to prune additional directories should use
24/// [`filter_entry`](WalkBuilder::filter_entry) with [`is_excluded_dir`].
25pub fn build_default_walker(root: &Path) -> WalkBuilder {
26    let mut builder = WalkBuilder::new(root);
27    apply_defaults(&mut builder);
28    builder
29}
30
31/// Build a single-threaded [`WalkBuilder`] with the same defaults as
32/// [`build_default_walker`].
33///
34/// Use this in synchronous contexts where spawning the `ignore` crate's
35/// thread pool would be wasteful (e.g., inside `spawn_blocking` closures
36/// that already run on a dedicated thread).
37pub fn build_walker_single_threaded(root: &Path) -> WalkBuilder {
38    let mut builder = WalkBuilder::new(root);
39    builder.threads(1);
40    apply_defaults(&mut builder);
41    builder
42}
43
44/// Apply standard walker defaults to an existing [`WalkBuilder`].
45///
46/// Sets gitignore support, hidden file visibility, and symlink policy.
47/// Callers that need additional customization (e.g., parallel walkers,
48/// symlink following) can call this then override specific settings.
49pub fn apply_defaults(builder: &mut WalkBuilder) {
50    // Respect all standard ignore-file mechanisms.
51    builder.git_ignore(true);
52    builder.git_global(true);
53    builder.git_exclude(true);
54    builder.ignore(true);
55    builder.parents(true);
56
57    // Do not follow symlinks by default.
58    builder.follow_links(false);
59
60    // Do not skip hidden files by default.  The `ignore` crate skips them
61    // by default, but the previous traversal code did not.  Callers that
62    // want to hide dotfiles should filter them explicitly.
63    builder.hidden(false);
64}
65
66/// Returns `true` if `entry` is a directory whose name appears in
67/// [`DEFAULT_EXCLUDED_DIRS`].
68///
69/// Intended for use inside [`WalkBuilder::filter_entry`] closures:
70///
71/// ```ignore
72/// builder.filter_entry(|entry| !vtcode_commons::walk::is_excluded_dir(entry));
73/// ```
74pub fn is_excluded_dir(entry: &DirEntry) -> bool {
75    if !entry.file_type().is_some_and(|ft| ft.is_dir()) {
76        return false;
77    }
78
79    entry
80        .file_name()
81        .to_str()
82        .is_some_and(|name| DEFAULT_EXCLUDED_DIRS.contains(&name))
83}