1 2 /// a displayable representation of where 3 /// the needle was found, with some text around 4 #[derive(Debug, Clone)] 5 pub struct ContentMatch { 6 pub extract: String, 7 pub needle_start: usize, // position in the extract, in bytes 8 pub needle_end: usize, // length in bytes 9 } 10 11 impl ContentMatch { build( hay: &[u8], pos: usize, needle: &str, desired_len: usize, ) -> Self12 pub fn build( 13 hay: &[u8], 14 pos: usize, // position in the hay 15 needle: &str, 16 desired_len: usize, // max length of the extract in bytes 17 ) -> Self { 18 if hay.is_empty() { 19 // this happens if you search `cr/.*` and a file starts with an empty line 20 return Self { 21 extract: "".to_string(), 22 needle_start: 0, 23 needle_end: 0, 24 }; 25 } 26 let mut extract_start = pos; 27 let mut extract_end = pos + needle.len(); // not included 28 loop { 29 if extract_start == 0 || extract_end - extract_start >= desired_len / 2 { 30 break; 31 } 32 let c = hay[extract_start - 1]; 33 if c < 32 { 34 break; 35 } 36 extract_start -= 1; 37 } 38 // left trimming 39 while (hay[extract_start] == 32) && extract_start < pos { 40 extract_start += 1; 41 } 42 loop { 43 if extract_end == hay.len() || extract_end - extract_start >= desired_len { 44 break; 45 } 46 let c = hay[extract_end]; 47 if c < 32 { 48 break; 49 } 50 extract_end += 1; 51 } 52 // at this point we're unsure whether we start at a correct char boundary, hence 53 // the from_utf8_lossy 54 let extract = String::from_utf8_lossy(&hay[extract_start..extract_end]).to_string(); 55 let needle_start = extract.find(needle).unwrap_or(0); 56 Self { 57 extract, 58 needle_start, 59 needle_end: needle_start + needle.len(), 60 } 61 } 62 } 63