pub struct SearchResult {
pub line_index: usize,
pub byte_offset: usize,
pub length: usize,
}
pub fn search_text(content: &str, query: &str, case_sensitive: bool) -> Vec<SearchResult> {
if query.is_empty() {
return Vec::new();
}
let mut results = Vec::new();
let (search_content, search_query) = if case_sensitive {
(content.to_string(), query.to_string())
} else {
(content.to_lowercase(), query.to_lowercase())
};
for (line_index, line) in search_content.lines().enumerate() {
let mut start = 0;
while let Some(pos) = line[start..].find(&search_query) {
results.push(SearchResult {
line_index,
byte_offset: start + pos,
length: query.len(),
});
start += pos + 1;
}
}
results
}
pub fn matching_lines(content: &str, query: &str) -> Vec<usize> {
if query.is_empty() {
return Vec::new();
}
let query_lower = query.to_lowercase();
content
.lines()
.enumerate()
.filter(|(_, line)| line.to_lowercase().contains(&query_lower))
.map(|(i, _)| i)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn search_empty_query_returns_empty() {
assert!(search_text("hello world", "", false).is_empty());
}
#[test]
fn search_no_match() {
assert!(search_text("hello world", "xyz", false).is_empty());
}
#[test]
fn search_single_match() {
let results = search_text("hello world", "world", false);
assert_eq!(results.len(), 1);
assert_eq!(results[0].line_index, 0);
assert_eq!(results[0].byte_offset, 6);
assert_eq!(results[0].length, 5);
}
#[test]
fn search_multiple_matches_same_line() {
let results = search_text("abcabc", "abc", false);
assert_eq!(results.len(), 2);
}
#[test]
fn search_case_insensitive() {
let results = search_text("Hello World", "hello", false);
assert_eq!(results.len(), 1);
}
#[test]
fn search_case_sensitive() {
let results = search_text("Hello World", "hello", true);
assert!(results.is_empty());
}
#[test]
fn search_multiple_lines() {
let results = search_text("line one\nline two\nline three", "line", false);
assert_eq!(results.len(), 3);
assert_eq!(results[0].line_index, 0);
assert_eq!(results[1].line_index, 1);
assert_eq!(results[2].line_index, 2);
}
#[test]
fn matching_lines_basic() {
let lines = matching_lines("foo\nbar\nfoo bar", "foo");
assert_eq!(lines, vec![0, 2]);
}
#[test]
fn matching_lines_empty_query() {
assert!(matching_lines("foo", "").is_empty());
}
#[test]
fn matching_lines_case_insensitive() {
let lines = matching_lines("FOO\nbar\nFoo", "foo");
assert_eq!(lines, vec![0, 2]);
}
}