use regex::Regex; use std::fs::File; use std::io::BufReader; use std::sync::{Arc, Mutex}; use std::thread; use crate::line_index::LineIndex; use crate::types::FilteredLine; pub const MAX_SEARCH_HISTORY: usize = 50; pub struct SearchState { pub searching: Arc>, pub progress: Arc>, pub results: Arc>>>, } impl SearchState { pub fn new() -> Self { Self { searching: Arc::new(Mutex::new(false)), progress: Arc::new(Mutex::new(0.0)), results: Arc::new(Mutex::new(None)), } } pub fn is_searching(&self) -> bool { *self.searching.lock().unwrap() } pub fn get_progress(&self) -> f32 { *self.progress.lock().unwrap() } pub fn take_results(&self) -> Option> { self.results.lock().unwrap().take() } } pub struct SearchParams { pub query: String, pub case_sensitive: bool, pub use_regex: bool, } impl SearchParams { fn build_regex_matcher(&self) -> Option { if !self.use_regex { return None; } let pattern = if self.case_sensitive { self.query.clone() } else { format!("(?i){}", self.query) }; Regex::new(&pattern).ok() } fn matches_line(&self, content: &str, regex_matcher: &Option) -> bool { if let Some(regex) = regex_matcher { regex.is_match(content) } else if self.use_regex { false } else if self.case_sensitive { content.contains(&self.query) } else { content.to_lowercase().contains(&self.query.to_lowercase()) } } } pub fn start_search( search_state: &SearchState, params: SearchParams, line_index: Arc, file_path: std::path::PathBuf, ) { if search_state.is_searching() { return; } let searching = Arc::clone(&search_state.searching); let progress = Arc::clone(&search_state.progress); let results = Arc::clone(&search_state.results); *searching.lock().unwrap() = true; *progress.lock().unwrap() = 0.0; *results.lock().unwrap() = None; thread::spawn(move || { let filtered = search_lines(¶ms, &line_index, &file_path, &progress); *results.lock().unwrap() = Some(filtered); *progress.lock().unwrap() = 1.0; *searching.lock().unwrap() = false; }); } fn search_lines( params: &SearchParams, line_index: &LineIndex, file_path: &std::path::Path, progress: &Arc>, ) -> Vec { let mut filtered = Vec::new(); let regex_matcher = params.build_regex_matcher(); if let Ok(file) = File::open(file_path) { let mut file_handle = BufReader::new(file); let total_lines = line_index.total_lines; for line_num in 0..total_lines { if let Some(content) = line_index.read_line(&mut file_handle, line_num) { if params.matches_line(&content, ®ex_matcher) { filtered.push(FilteredLine { line_number: line_num, content, }); } } if line_num % 1000 == 0 { *progress.lock().unwrap() = line_num as f32 / total_lines as f32; } } } filtered } pub fn add_to_history(history: &mut Vec, query: &str) { if query.is_empty() { return; } if let Some(pos) = history.iter().position(|x| x == query) { history.remove(pos); } history.insert(0, query.to_string()); if history.len() > MAX_SEARCH_HISTORY { history.truncate(MAX_SEARCH_HISTORY); } }