Check if two lines are similar enough to be considered a modification rather than unrelated content. Uses simple word overlap: if more than half the whitespace-separated words match, the lines are similar.
(old: &[u8], new: &[u8])
| 392 | /// rather than unrelated content. Uses simple word overlap: if more than |
| 393 | /// half the whitespace-separated words match, the lines are similar. |
| 394 | fn lines_are_similar(old: &[u8], new: &[u8]) -> bool { |
| 395 | let old_str = std::str::from_utf8(old).unwrap_or(""); |
| 396 | let new_str = std::str::from_utf8(new).unwrap_or(""); |
| 397 | |
| 398 | let old_words: Vec<&str> = old_str.split_whitespace().collect(); |
| 399 | let new_words: Vec<&str> = new_str.split_whitespace().collect(); |
| 400 | |
| 401 | // Both empty or single-char lines — not enough signal |
| 402 | if old_words.is_empty() && new_words.is_empty() { |
| 403 | return false; |
| 404 | } |
| 405 | |
| 406 | let max_words = old_words.len().max(new_words.len()); |
| 407 | if max_words == 0 { |
| 408 | return false; |
| 409 | } |
| 410 | |
| 411 | let matching = old_words.iter().filter(|w| new_words.contains(w)).count(); |
| 412 | |
| 413 | // More than 30% word overlap → similar (modification) |
| 414 | // This catches "line two" → "line three" (1/2 = 50% overlap on "line") |
| 415 | // but rejects "line3" vs "line2" insertion (completely different words) |
| 416 | matching * 100 / max_words > 30 |
| 417 | } |
| 418 | |
| 419 | /// Strict common-affix detection: returns the maximal common prefix and |
| 420 | /// suffix without any reduction. |