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
11pub 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}