1#[derive(Debug, Clone)]
3pub struct Interval {
4 pub start: usize,
5 pub end: usize,
6}
7
8pub fn fuzzy_match(content: &str, target: &str) -> bool {
10 content.to_lowercase().contains(&target.to_lowercase())
11}
12
13pub fn get_match_intervals(content: &str, target: &str) -> Vec<Interval> {
16 let mut intervals = Vec::new();
17
18 if target.is_empty() {
19 return intervals;
20 }
21
22 let content_lower = content.to_lowercase();
23 let target_lower = target.to_lowercase();
24
25 let content_chars: Vec<(usize, char)> = content.char_indices().collect();
28 let lower_chars: Vec<(usize, char)> = content_lower.char_indices().collect();
29
30 let mut search_from = 0;
32 while let Some(pos) = content_lower[search_from..].find(&target_lower) {
33 let lower_start = search_from + pos;
34 let lower_end = lower_start + target_lower.len();
35
36 let char_start_idx = lower_chars
38 .iter()
39 .position(|(byte_idx, _)| *byte_idx == lower_start);
40 let char_end_idx = lower_chars
41 .iter()
42 .position(|(byte_idx, _)| *byte_idx == lower_end)
43 .unwrap_or(lower_chars.len());
44
45 if let Some(char_start_idx) = char_start_idx {
46 let orig_start = content_chars[char_start_idx].0;
48 let orig_end = if char_end_idx < content_chars.len() {
49 content_chars[char_end_idx].0
50 } else {
51 content.len()
52 };
53
54 intervals.push(Interval {
55 start: orig_start,
56 end: orig_end,
57 });
58 }
59
60 if let Some(char_start_idx) = char_start_idx {
62 if char_start_idx + 1 < lower_chars.len() {
63 search_from = lower_chars[char_start_idx + 1].0;
64 } else {
65 break;
66 }
67 } else {
68 break;
69 }
70 }
71
72 intervals
73}
74
75pub fn highlight_matches(content: &str, target: &str, fuzzy: bool) -> String {
78 if !fuzzy {
79 return content.replace(target, &format!("\x1b[32m{}\x1b[0m", target));
81 }
82
83 let intervals = get_match_intervals(content, target);
84 if intervals.is_empty() {
85 return content.to_string();
86 }
87
88 let mut result = String::new();
89 let mut last_end = 0;
90
91 for interval in &intervals {
92 result.push_str(&content[last_end..interval.start]);
94 result.push_str(&format!(
96 "\x1b[32m{}\x1b[0m",
97 &content[interval.start..interval.end]
98 ));
99 last_end = interval.end;
100 }
101
102 result.push_str(&content[last_end..]);
104 result
105}