pub mod msh;
use crate::core::models::SearchResult;
use crate::core::search::msh::MaxShiftHorspool;
use crate::progress::Bookmark;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SearchType {
Local,
Global,
Bookmark,
Chapter,
}
pub fn search_content(
query: &str,
chapter_index: usize,
chapter_title: &str,
content: &str,
case_sensitive: bool,
) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let (final_query, final_content) = if case_sensitive {
(query.to_string(), content.to_string())
} else {
(query.to_lowercase(), content.to_lowercase())
};
let msh = MaxShiftHorspool::new(&final_query);
let positions = msh.find_all(&final_content);
positions
.into_iter()
.filter(|&pos| content.is_char_boundary(pos) && content.is_char_boundary(pos + query.len()))
.map(|pos| {
let mut start = pos.saturating_sub(30);
while start > 0 && !content.is_char_boundary(start) {
start -= 1;
}
let mut end = (pos + query.len() + 30).min(content.len());
while end < content.len() && !content.is_char_boundary(end) {
end += 1;
}
let context_before = content[start..pos].replace('\n', " ");
let match_text = content[pos..pos + query.len()].replace('\n', " ");
let context_after = content[pos + query.len()..end].replace('\n', " ");
SearchResult::new(
chapter_index,
chapter_title.to_string(),
context_before,
match_text,
context_after,
pos,
)
})
.collect()
}
pub fn search_bookmarks(
query: &str,
bookmarks: &[Bookmark],
case_sensitive: bool,
) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let query_str = if case_sensitive {
query.to_string()
} else {
query.to_lowercase()
};
bookmarks
.iter()
.filter(|b| {
if case_sensitive {
b.name.contains(&query_str) || b.text_preview.contains(&query_str)
} else {
b.name.to_lowercase().contains(&query_str)
|| b.text_preview.to_lowercase().contains(&query_str)
}
})
.map(|b| {
SearchResult::new(
b.chapter_index,
b.chapter_title.clone(),
"".to_string(),
b.name.clone(),
"".to_string(),
b.scroll_position,
)
})
.collect()
}
pub fn search_chapters(
query: &str,
chapter_titles: &[String],
case_sensitive: bool,
) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let query_str = if case_sensitive {
query.to_string()
} else {
query.to_lowercase()
};
chapter_titles
.iter()
.enumerate()
.filter(|(_, title)| {
if case_sensitive {
title.contains(&query_str)
} else {
title.to_lowercase().contains(&query_str)
}
})
.map(|(i, title)| {
SearchResult::new(
i,
title.clone(),
"".to_string(),
title.clone(),
"".to_string(),
0,
)
})
.collect()
}