1use crate::Differ;
2use crate::differ::{Change, DiffAlgorithm};
3
4use super::{create_patch, handle_empty_files, process_changes_to_chunks};
5
6pub struct MyersDiffer<'a> {
13 differ: &'a Differ,
14}
15
16impl<'a> MyersDiffer<'a> {
17 pub fn new(differ: &'a Differ) -> Self {
19 Self { differ }
20 }
21
22 fn myers_diff(&self, old_lines: &[&str], new_lines: &[&str]) -> Vec<Change> {
25 if old_lines.is_empty() && new_lines.is_empty() {
27 return Vec::new();
28 }
29 if old_lines.is_empty() {
30 return vec![Change::Insert(0, new_lines.len())];
32 }
33 if new_lines.is_empty() {
34 return vec![Change::Delete(0, old_lines.len())];
36 }
37 if old_lines == new_lines {
39 return Vec::new();
40 }
41
42 let n = old_lines.len();
46 let m = new_lines.len();
47 let mut lcs = vec![vec![0; m + 1]; n + 1];
49 for i in 1..=n {
50 for j in 1..=m {
51 if old_lines[i - 1] == new_lines[j - 1] {
52 lcs[i][j] = lcs[i - 1][j - 1] + 1; } else {
54 lcs[i][j] = std::cmp::max(lcs[i - 1][j], lcs[i][j - 1]);
56 }
57 }
58 }
59
60 let mut changes = Vec::new();
62 let mut i = n;
63 let mut j = m;
64 while i > 0 || j > 0 {
65 if i > 0 && j > 0 && old_lines[i - 1] == new_lines[j - 1] {
66 changes.push(Change::Equal(i - 1, j - 1));
68 i -= 1;
69 j -= 1;
70 } else if j > 0 && (i == 0 || lcs[i][j - 1] >= lcs[i - 1][j]) {
71 changes.push(Change::Insert(j - 1, 1));
73 j -= 1;
74 } else if i > 0 {
75 changes.push(Change::Delete(i - 1, 1));
77 i -= 1;
78 } else {
79 break;
81 }
82 }
83
84 changes.reverse();
86
87 changes
91 }
92}
93
94impl DiffAlgorithm for MyersDiffer<'_> {
95 fn generate(&self) -> crate::Patch {
97 let old_lines: Vec<&str> = self.differ.old.lines().collect();
98 let new_lines: Vec<&str> = self.differ.new.lines().collect();
99 if let Some(patch) = handle_empty_files(&old_lines, &new_lines) {
101 return patch;
102 }
103 let changes = self.myers_diff(&old_lines, &new_lines);
105 let chunks =
107 process_changes_to_chunks(&changes, &old_lines, &new_lines, self.differ.context_lines);
108 create_patch(chunks)
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::{PatchAlgorithm, Patcher, test_utils::load_fixture};
117
118 #[test]
119 fn test_simple_myers_diff() {
120 let old = "line1\nline2\nline3";
121 let new = "line1\nline2\nline3";
122 let differ = Differ::new(old, new);
123 let myers = MyersDiffer::new(&differ);
124 let patch = myers.generate();
125 assert!(
126 patch.chunks.is_empty(),
127 "Patch should be empty for identical files"
128 );
129 }
130
131 #[test]
132 fn test_myers_add_line() {
133 let old = "line1\nline2\nline3";
134 let new = "line1\nline2\nline3\nline4";
135 let differ = Differ::new(old, new);
136 let myers = MyersDiffer::new(&differ);
137 let patch = myers.generate();
138 assert_eq!(patch.chunks.len(), 1);
139 let result = Patcher::new(patch).apply(old, false).unwrap();
140 assert_eq!(result, new);
141 }
142
143 #[test]
144 fn test_myers_remove_line() {
145 let old = "line1\nline2\nline3";
146 let new = "line1\nline3";
147 let differ = Differ::new(old, new);
148 let myers = MyersDiffer::new(&differ);
149 let patch = myers.generate();
150 assert_eq!(patch.chunks.len(), 1);
151 let result = Patcher::new(patch).apply(old, false).unwrap();
152 assert_eq!(result, new);
153 }
154
155 #[test]
156 fn test_myers_empty_files() {
157 let old = "";
159 let new = "line1\nline2";
160 let differ = Differ::new(old, new);
161 let myers = MyersDiffer::new(&differ);
162 let patch = myers.generate();
163 assert_eq!(patch.chunks.len(), 1);
164 let result = Patcher::new(patch.clone()).apply(old, false).unwrap();
165 assert_eq!(result, new);
166
167 let old = "line1\nline2";
169 let new = "";
170 let differ = Differ::new(old, new);
171 let myers = MyersDiffer::new(&differ);
172 let patch = myers.generate();
173 assert_eq!(patch.chunks.len(), 1);
174 let result = Patcher::new(patch).apply(old, false).unwrap();
175 assert_eq!(result, new);
176 }
177
178 #[test]
179 fn test_myers_multiple_changes() {
180 let old = "line1\nline2\nline3\nline4\nline5";
181 let new = "line1\nmodified line\nline3\nline4\nnew line";
182 let differ = Differ::new(old, new);
183 let myers = MyersDiffer::new(&differ);
184 let patch = myers.generate();
185 let result = Patcher::new(patch).apply(old, false).unwrap();
186 assert_eq!(result, new);
187 }
188
189 #[test]
190 fn test_myers_complex_diff() {
191 let old = "A\nB\nC\nA\nB\nB\nA";
192 let new = "C\nB\nA\nB\nA\nC";
193 let differ = Differ::new(old, new);
194 let myers = MyersDiffer::new(&differ);
195 let patch = myers.generate();
196 let result = Patcher::new(patch).apply(old, false).unwrap();
197 assert_eq!(result, new);
198 }
199
200 #[test]
202 fn test_myers_fixture_simple() {
203 let old = load_fixture("simple_before.rs");
204 let new = load_fixture("simple_after.rs");
205 let differ = Differ::new(&old, &new); let myers = MyersDiffer::new(&differ);
207 let patch = myers.generate();
208 let result = Patcher::new(patch).apply(&old, false).unwrap();
209 assert_eq!(result, new);
210 }
211
212 #[test]
213 fn test_myers_fixture_complex() {
214 let old = load_fixture("complex_before.rs");
215 let new = load_fixture("complex_after.rs");
216 let differ = Differ::new(&old, &new); let myers = MyersDiffer::new(&differ);
218 let patch = myers.generate();
219 let result = Patcher::new(patch).apply(&old, false).unwrap();
220 assert_eq!(result, new);
221 }
222}