Diff two strings, producing TextInsert/TextDelete for simple edits or Modified for complex changes. Finds the longest common prefix and suffix, then examines the middle. If only text was inserted (old middle is empty), emits TextInsert. If only text was deleted (new middle is empty), emits TextDelete. Otherwise, falls back to Modified.
(diffs: &mut Vec<FieldDiff>, prefix: &str, old_s: &str, new_s: &str)
| 282 | /// If only text was deleted (new middle is empty), emits TextDelete. |
| 283 | /// Otherwise, falls back to Modified. |
| 284 | fn diff_strings(diffs: &mut Vec<FieldDiff>, prefix: &str, old_s: &str, new_s: &str) { |
| 285 | let old_chars: Vec<char> = old_s.chars().collect(); |
| 286 | let new_chars: Vec<char> = new_s.chars().collect(); |
| 287 | |
| 288 | // Common prefix length. |
| 289 | let common_prefix = old_chars |
| 290 | .iter() |
| 291 | .zip(new_chars.iter()) |
| 292 | .take_while(|(a, b)| a == b) |
| 293 | .count(); |
| 294 | |
| 295 | // Common suffix length (from the end, not overlapping with prefix). |
| 296 | let old_remaining = old_chars.len() - common_prefix; |
| 297 | let new_remaining = new_chars.len() - common_prefix; |
| 298 | let common_suffix = old_chars[common_prefix..] |
| 299 | .iter() |
| 300 | .rev() |
| 301 | .zip(new_chars[common_prefix..].iter().rev()) |
| 302 | .take_while(|(a, b)| a == b) |
| 303 | .count(); |
| 304 | |
| 305 | let old_mid_len = old_remaining - common_suffix; |
| 306 | let new_mid_len = new_remaining - common_suffix; |
| 307 | |
| 308 | if old_mid_len == 0 && new_mid_len > 0 { |
| 309 | // Pure insertion. |
| 310 | let inserted: String = new_chars[common_prefix..common_prefix + new_mid_len] |
| 311 | .iter() |
| 312 | .collect(); |
| 313 | diffs.push(FieldDiff { |
| 314 | field: prefix.to_owned(), |
| 315 | op: DiffOp::TextInsert, |
| 316 | old_value: Some(serde_json::Value::Number(serde_json::Number::from( |
| 317 | common_prefix, |
| 318 | ))), |
| 319 | new_value: Some(serde_json::Value::String(inserted)), |
| 320 | }); |
| 321 | } else if new_mid_len == 0 && old_mid_len > 0 { |
| 322 | // Pure deletion. |
| 323 | let deleted: String = old_chars[common_prefix..common_prefix + old_mid_len] |
| 324 | .iter() |
| 325 | .collect(); |
| 326 | diffs.push(FieldDiff { |
| 327 | field: prefix.to_owned(), |
| 328 | op: DiffOp::TextDelete, |
| 329 | old_value: Some(serde_json::Value::String(deleted)), |
| 330 | new_value: Some(serde_json::Value::Number(serde_json::Number::from( |
| 331 | common_prefix, |
| 332 | ))), |
| 333 | }); |
| 334 | } else { |
| 335 | // Replacement or complex change — fall back to Modified. |
| 336 | diffs.push(FieldDiff { |
| 337 | field: prefix.to_owned(), |
| 338 | op: DiffOp::Modified, |
| 339 | old_value: Some(serde_json::Value::String(old_s.to_owned())), |
| 340 | new_value: Some(serde_json::Value::String(new_s.to_owned())), |
| 341 | }); |