objects/util/
line_diff.rs1use similar::{Algorithm, DiffOp};
5
6pub fn split_text_lines(bytes: &[u8]) -> Option<Vec<String>> {
8 let content = std::str::from_utf8(bytes).ok()?;
9 Some(content.lines().map(str::to_string).collect())
10}
11
12pub fn lcs_line_matches(old_lines: &[String], new_lines: &[String]) -> Vec<(usize, usize)> {
18 let mut matches = Vec::new();
19 for op in similar::capture_diff_slices(Algorithm::Myers, old_lines, new_lines) {
20 if let DiffOp::Equal {
21 old_index,
22 new_index,
23 len,
24 } = op
25 {
26 matches.extend((0..len).map(|offset| (old_index + offset, new_index + offset)));
27 }
28 }
29 matches
30}
31
32#[cfg(test)]
33mod tests {
34 use super::lcs_line_matches;
35
36 #[test]
37 fn line_matches_preserve_simple_alignment() {
38 let old = ["a", "b", "c"].map(str::to_string);
39 let new = ["a", "x", "c"].map(str::to_string);
40 assert_eq!(lcs_line_matches(&old, &new), vec![(0, 0), (2, 2)]);
41 }
42
43 #[test]
44 fn line_matches_are_bounded_for_large_files() {
45 let old = (0..50_000)
49 .map(|index| format!("line {index}"))
50 .collect::<Vec<_>>();
51 let mut new = old.clone();
52 new[25_000] = "replacement".to_string();
53
54 let matches = lcs_line_matches(&old, &new);
55 assert_eq!(matches.len(), 49_999);
56 assert_eq!(matches.first(), Some(&(0, 0)));
57 assert_eq!(matches.last(), Some(&(49_999, 49_999)));
58 assert!(!matches.contains(&(25_000, 25_000)));
59 }
60}