66 lines
1.9 KiB
Rust
66 lines
1.9 KiB
Rust
|
|
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<PathBuf, String> {
|
||
|
|
state.get_project_root()
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn search_files(
|
||
|
|
query: String,
|
||
|
|
state: &SessionState,
|
||
|
|
) -> Result<Vec<SearchResult>, String> {
|
||
|
|
let root = get_project_root(state)?;
|
||
|
|
search_files_impl(query, root).await
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn search_files_impl(query: String, root: PathBuf) -> Result<Vec<SearchResult>, 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)
|
||
|
|
}
|