147 lines
3.7 KiB
Rust
147 lines
3.7 KiB
Rust
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<Mutex<bool>>,
|
|
pub progress: Arc<Mutex<f32>>,
|
|
pub results: Arc<Mutex<Option<Vec<FilteredLine>>>>,
|
|
}
|
|
|
|
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<Vec<FilteredLine>> {
|
|
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<Regex> {
|
|
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<Regex>) -> 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<LineIndex>,
|
|
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<Mutex<f32>>,
|
|
) -> Vec<FilteredLine> {
|
|
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<String>, 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);
|
|
}
|
|
}
|