Skip to main content

wisp/
file_index.rs

1use std::path::{Path, PathBuf};
2
3pub(crate) const MAX_INDEXED_FILES: usize = 50_000;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct FileEntry {
7    pub path: PathBuf,
8    pub display_name: String,
9}
10
11/// Walks the working tree for the files the `@` picker can offer.
12pub fn index_files(root: &Path) -> Vec<FileEntry> {
13    index_files_up_to(root, MAX_INDEXED_FILES)
14}
15
16#[cfg(feature = "testing")]
17pub fn index_files_with_limit(root: &Path, limit: usize) -> Vec<FileEntry> {
18    index_files_up_to(root, limit)
19}
20
21pub(crate) fn file_entries(
22    root: &Path,
23    paths: impl IntoIterator<Item = PathBuf>,
24    limit: usize,
25) -> Vec<FileEntry> {
26    let mut entries: Vec<_> = paths
27        .into_iter()
28        .filter(|path| !excluded(path))
29        .take(limit)
30        .map(|path| FileEntry {
31            display_name: path.strip_prefix(root).unwrap_or(&path).to_string_lossy().replace('\\', "/"),
32            path,
33        })
34        .collect();
35    entries.sort_by(|left, right| left.display_name.cmp(&right.display_name));
36    entries
37}
38
39fn index_files_up_to(root: &Path, limit: usize) -> Vec<FileEntry> {
40    let paths = ignore::WalkBuilder::new(root)
41        .git_ignore(true)
42        .git_global(true)
43        .git_exclude(true)
44        .hidden(false)
45        .parents(true)
46        .build()
47        .flatten()
48        .filter(|entry| entry.file_type().is_some_and(|kind| kind.is_file()))
49        .map(ignore::DirEntry::into_path);
50    file_entries(root, paths, limit)
51}
52
53fn excluded(path: &Path) -> bool {
54    path.components().any(|component| {
55        matches!(component.as_os_str().to_string_lossy().as_ref(), ".git" | ".hg" | ".svn" | "node_modules" | "target")
56    })
57}