use crate::state::SessionState; use ignore::WalkBuilder; use serde::Serialize; use std::fs; use std::path::PathBuf; #[derive(Serialize, Debug, poem_openapi::Object)] pub struct SearchResult { pub path: String, pub matches: usize, } fn get_project_root(state: &SessionState) -> Result { state.get_project_root() } pub async fn search_files( query: String, state: &SessionState, ) -> Result, String> { let root = get_project_root(state)?; search_files_impl(query, root).await } pub async fn search_files_impl(query: String, root: PathBuf) -> Result, String> { let root_clone = root.clone(); let results = tokio::task::spawn_blocking(move || { let mut matches = Vec::new(); let walker = WalkBuilder::new(&root_clone).git_ignore(true).build(); for result in walker { match result { Ok(entry) => { if !entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) { continue; } let path = entry.path(); if let Ok(content) = fs::read_to_string(path) && content.contains(&query) { let relative = path .strip_prefix(&root_clone) .unwrap_or(path) .to_string_lossy() .to_string(); matches.push(SearchResult { path: relative, matches: 1, }); } } Err(err) => eprintln!("Error walking dir: {}", err), } } matches }) .await .map_err(|e| format!("Search task failed: {e}"))?; Ok(results) }