use similar::{Algorithm, DiffOp};
pub fn split_text_lines(bytes: &[u8]) -> Option<Vec<String>> {
let content = std::str::from_utf8(bytes).ok()?;
Some(content.lines().map(str::to_string).collect())
}
pub fn lcs_line_matches(old_lines: &[String], new_lines: &[String]) -> Vec<(usize, usize)> {
let mut matches = Vec::new();
for op in similar::capture_diff_slices(Algorithm::Myers, old_lines, new_lines) {
if let DiffOp::Equal {
old_index,
new_index,
len,
} = op
{
matches.extend((0..len).map(|offset| (old_index + offset, new_index + offset)));
}
}
matches
}
#[cfg(test)]
mod tests {
use super::lcs_line_matches;
#[test]
fn line_matches_preserve_simple_alignment() {
let old = ["a", "b", "c"].map(str::to_string);
let new = ["a", "x", "c"].map(str::to_string);
assert_eq!(lcs_line_matches(&old, &new), vec![(0, 0), (2, 2)]);
}
#[test]
fn line_matches_are_bounded_for_large_files() {
let old = (0..50_000)
.map(|index| format!("line {index}"))
.collect::<Vec<_>>();
let mut new = old.clone();
new[25_000] = "replacement".to_string();
let matches = lcs_line_matches(&old, &new);
assert_eq!(matches.len(), 49_999);
assert_eq!(matches.first(), Some(&(0, 0)));
assert_eq!(matches.last(), Some(&(49_999, 49_999)));
assert!(!matches.contains(&(25_000, 25_000)));
}
}