async fn collect_names(
project_path: &Path,
include: &Option<String>,
exclude: &Option<String>,
scope: crate::cli::SearchScope,
) -> Result<Vec<(String, String, usize, String)>> {
let mut names = Vec::new();
let files = collect_source_files(project_path, include, exclude).await?;
for file in files {
let content = tokio::fs::read_to_string(&file).await?;
let file_str = file.to_string_lossy().to_string();
let file_names = extract_names(&content, &file_str, scope)?;
names.extend(file_names);
}
Ok(names)
}
async fn collect_source_files(
project_path: &Path,
include: &Option<String>,
exclude: &Option<String>,
) -> Result<Vec<PathBuf>> {
let mut files = Vec::new();
collect_files_recursive(project_path, &mut files, include, exclude).await?;
Ok(files)
}
async fn collect_files_recursive(
dir: &Path,
files: &mut Vec<PathBuf>,
include: &Option<String>,
exclude: &Option<String>,
) -> Result<()> {
let mut entries = tokio::fs::read_dir(dir).await?;
while let Some(entry) = entries.next_entry().await? {
process_entry(entry, files, include, exclude).await?;
}
Ok(())
}
async fn process_entry(
entry: tokio::fs::DirEntry,
files: &mut Vec<PathBuf>,
include: &Option<String>,
exclude: &Option<String>,
) -> Result<()> {
let path = entry.path();
if should_skip(&path, exclude) {
return Ok(());
}
if path.is_dir() {
handle_directory(&path, files, include, exclude).await
} else {
handle_file(path, files, include)
}
}
fn should_skip(path: &Path, exclude: &Option<String>) -> bool {
if let Some(excl) = exclude {
let path_str = path.to_string_lossy();
return path_str.contains(excl);
}
false
}
async fn handle_directory(
path: &Path,
files: &mut Vec<PathBuf>,
include: &Option<String>,
exclude: &Option<String>,
) -> Result<()> {
if should_traverse_directory(path) {
Box::pin(collect_files_recursive(path, files, include, exclude)).await?;
}
Ok(())
}
fn should_traverse_directory(path: &Path) -> bool {
let name = path.file_name().unwrap_or_default().to_string_lossy();
!name.starts_with('.') && name != "node_modules" && name != "target"
}
fn handle_file(path: PathBuf, files: &mut Vec<PathBuf>, include: &Option<String>) -> Result<()> {
if !is_code_file(&path) {
return Ok(());
}
if should_include_file(&path, include) {
files.push(path);
}
Ok(())
}
fn should_include_file(path: &Path, include: &Option<String>) -> bool {
match include {
Some(incl) => {
let path_str = path.to_string_lossy();
path_str.contains(incl)
}
None => true,
}
}
fn is_code_file(path: &Path) -> bool {
matches!(
path.extension().and_then(|s| s.to_str()),
Some("rs" | "js" | "ts" | "py" | "java" | "cpp" | "c" | "go")
)
}