(
old: &[Line<'a>],
new: &[Line<'a>],
algorithm: Algorithm,
rewrite_shifts: bool,
)
| 186 | } |
| 187 | |
| 188 | fn diff_with_options<'a>( |
| 189 | old: &[Line<'a>], |
| 190 | new: &[Line<'a>], |
| 191 | algorithm: Algorithm, |
| 192 | rewrite_shifts: bool, |
| 193 | ) -> DiffResult { |
| 194 | // Optimization: strip common prefix and suffix. |
| 195 | // |
| 196 | // When `rewrite_shifts == false` (i.e. `diff_raw`, used by the |
| 197 | // record/patch-theory path) we use the **strict** form that never |
| 198 | // reduces the suffix to artificially force Replace detection. This |
| 199 | // preserves pure insertions/deletions, which is what graph |
| 200 | // operations need. |
| 201 | let (prefix_len, suffix_len) = if rewrite_shifts { |
| 202 | common_affixes(old, new) |
| 203 | } else { |
| 204 | common_affixes_strict(old, new) |
| 205 | }; |
| 206 | |
| 207 | let old_mid = &old[prefix_len..old.len().saturating_sub(suffix_len)]; |
| 208 | let new_mid = &new[prefix_len..new.len().saturating_sub(suffix_len)]; |
| 209 | |
| 210 | // If everything is equal, return early |
| 211 | if old_mid.is_empty() && new_mid.is_empty() { |
| 212 | return DiffResult::equal(old.len()); |
| 213 | } |
| 214 | |
| 215 | // Dispatch to the selected algorithm |
| 216 | let mut ops = match algorithm { |
| 217 | Algorithm::Myers => myers::diff(old_mid, new_mid), |
| 218 | Algorithm::Patience => patience::diff(old_mid, new_mid), |
| 219 | }; |
| 220 | |
| 221 | // Adjust offsets for the stripped prefix |
| 222 | ops.adjust_offsets(prefix_len); |
| 223 | |
| 224 | // Add the equal prefix and suffix |
| 225 | if prefix_len > 0 { |
| 226 | ops.prepend_equal(0, prefix_len); |
| 227 | } |
| 228 | if suffix_len > 0 { |
| 229 | let old_start = old.len() - suffix_len; |
| 230 | let new_start = new.len() - suffix_len; |
| 231 | ops.append_equal(old_start, new_start, suffix_len); |
| 232 | } |
| 233 | |
| 234 | // Post-process: detect positional shifts that should be Replace ops. |
| 235 | // |
| 236 | // Myers finds the minimal edit (LCS), which can match identical lines |
| 237 | // at different positions. For example: |
| 238 | // old: [A, B] new: [A, C, B] |
| 239 | // Myers says: Equal(A), Insert(C), Equal(B) — treating B as "moved down". |
| 240 | // |
| 241 | // But if the user modified line 2 (B→C) and added a new line 3 (B), |
| 242 | // the intent is: Equal(A), Replace(B→C), Insert(B). |
| 243 | // |
| 244 | // We detect this pattern: when an Equal op maps old_pos N to new_pos M |
| 245 | // where M > N (line shifted down), AND there's an Insert immediately |
no test coverage detected