use crate::Differ;
use crate::differ::{Change, DiffAlgorithm};
use super::{create_patch, handle_empty_files, process_changes_to_chunks};
pub struct MyersDiffer<'a> {
differ: &'a Differ,
}
impl<'a> MyersDiffer<'a> {
pub fn new(differ: &'a Differ) -> Self {
Self { differ }
}
fn myers_diff(&self, old_lines: &[&str], new_lines: &[&str]) -> Vec<Change> {
if old_lines.is_empty() && new_lines.is_empty() {
return Vec::new();
}
if old_lines.is_empty() {
return vec![Change::Insert(0, new_lines.len())];
}
if new_lines.is_empty() {
return vec![Change::Delete(0, old_lines.len())];
}
if old_lines == new_lines {
return Vec::new();
}
let n = old_lines.len();
let m = new_lines.len();
let mut lcs = vec![vec![0; m + 1]; n + 1];
for i in 1..=n {
for j in 1..=m {
if old_lines[i - 1] == new_lines[j - 1] {
lcs[i][j] = lcs[i - 1][j - 1] + 1; } else {
lcs[i][j] = std::cmp::max(lcs[i - 1][j], lcs[i][j - 1]);
}
}
}
let mut changes = Vec::new();
let mut i = n;
let mut j = m;
while i > 0 || j > 0 {
if i > 0 && j > 0 && old_lines[i - 1] == new_lines[j - 1] {
changes.push(Change::Equal(i - 1, j - 1));
i -= 1;
j -= 1;
} else if j > 0 && (i == 0 || lcs[i][j - 1] >= lcs[i - 1][j]) {
changes.push(Change::Insert(j - 1, 1));
j -= 1;
} else if i > 0 {
changes.push(Change::Delete(i - 1, 1));
i -= 1;
} else {
break;
}
}
changes.reverse();
changes
}
}
impl DiffAlgorithm for MyersDiffer<'_> {
fn generate(&self) -> crate::Patch {
let old_lines: Vec<&str> = self.differ.old.lines().collect();
let new_lines: Vec<&str> = self.differ.new.lines().collect();
if let Some(patch) = handle_empty_files(&old_lines, &new_lines) {
return patch;
}
let changes = self.myers_diff(&old_lines, &new_lines);
let chunks =
process_changes_to_chunks(&changes, &old_lines, &new_lines, self.differ.context_lines);
create_patch(chunks)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{PatchAlgorithm, Patcher, test_utils::load_fixture};
#[test]
fn test_simple_myers_diff() {
let old = "line1\nline2\nline3";
let new = "line1\nline2\nline3";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
assert!(
patch.chunks.is_empty(),
"Patch should be empty for identical files"
);
}
#[test]
fn test_myers_add_line() {
let old = "line1\nline2\nline3";
let new = "line1\nline2\nline3\nline4";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
assert_eq!(patch.chunks.len(), 1);
let result = Patcher::new(patch).apply(old, false).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_myers_remove_line() {
let old = "line1\nline2\nline3";
let new = "line1\nline3";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
assert_eq!(patch.chunks.len(), 1);
let result = Patcher::new(patch).apply(old, false).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_myers_empty_files() {
let old = "";
let new = "line1\nline2";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
assert_eq!(patch.chunks.len(), 1);
let result = Patcher::new(patch.clone()).apply(old, false).unwrap();
assert_eq!(result, new);
let old = "line1\nline2";
let new = "";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
assert_eq!(patch.chunks.len(), 1);
let result = Patcher::new(patch).apply(old, false).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_myers_multiple_changes() {
let old = "line1\nline2\nline3\nline4\nline5";
let new = "line1\nmodified line\nline3\nline4\nnew line";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
let result = Patcher::new(patch).apply(old, false).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_myers_complex_diff() {
let old = "A\nB\nC\nA\nB\nB\nA";
let new = "C\nB\nA\nB\nA\nC";
let differ = Differ::new(old, new);
let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
let result = Patcher::new(patch).apply(old, false).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_myers_fixture_simple() {
let old = load_fixture("simple_before.rs");
let new = load_fixture("simple_after.rs");
let differ = Differ::new(&old, &new); let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
let result = Patcher::new(patch).apply(&old, false).unwrap();
assert_eq!(result, new);
}
#[test]
fn test_myers_fixture_complex() {
let old = load_fixture("complex_before.rs");
let new = load_fixture("complex_after.rs");
let differ = Differ::new(&old, &new); let myers = MyersDiffer::new(&differ);
let patch = myers.generate();
let result = Patcher::new(patch).apply(&old, false).unwrap();
assert_eq!(result, new);
}
}