#[derive(Debug, Clone)]
pub struct Interval {
pub start: usize,
pub end: usize,
}
pub fn fuzzy_match(content: &str, target: &str) -> bool {
content.to_lowercase().contains(&target.to_lowercase())
}
pub fn get_match_intervals(content: &str, target: &str) -> Vec<Interval> {
let mut intervals = Vec::new();
if target.is_empty() {
return intervals;
}
let content_lower = content.to_lowercase();
let target_lower = target.to_lowercase();
let content_chars: Vec<(usize, char)> = content.char_indices().collect();
let lower_chars: Vec<(usize, char)> = content_lower.char_indices().collect();
let mut search_from = 0;
while let Some(pos) = content_lower[search_from..].find(&target_lower) {
let lower_start = search_from + pos;
let lower_end = lower_start + target_lower.len();
let char_start_idx = lower_chars
.iter()
.position(|(byte_idx, _)| *byte_idx == lower_start);
let char_end_idx = lower_chars
.iter()
.position(|(byte_idx, _)| *byte_idx == lower_end)
.unwrap_or(lower_chars.len());
if let Some(char_start_idx) = char_start_idx {
let orig_start = content_chars[char_start_idx].0;
let orig_end = if char_end_idx < content_chars.len() {
content_chars[char_end_idx].0
} else {
content.len()
};
intervals.push(Interval {
start: orig_start,
end: orig_end,
});
}
if let Some(char_start_idx) = char_start_idx {
if char_start_idx + 1 < lower_chars.len() {
search_from = lower_chars[char_start_idx + 1].0;
} else {
break;
}
} else {
break;
}
}
intervals
}
pub fn highlight_matches(content: &str, target: &str, fuzzy: bool) -> String {
if !fuzzy {
return content.replace(target, &format!("\x1b[32m{}\x1b[0m", target));
}
let intervals = get_match_intervals(content, target);
if intervals.is_empty() {
return content.to_string();
}
let mut result = String::new();
let mut last_end = 0;
for interval in &intervals {
result.push_str(&content[last_end..interval.start]);
result.push_str(&format!(
"\x1b[32m{}\x1b[0m",
&content[interval.start..interval.end]
));
last_end = interval.end;
}
result.push_str(&content[last_end..]);
result
}