Skip to main content

lvz_context/
diff.rs

1//! Token-efficient diffs (§6.1): emit minimal unified hunks, never full-file
2//! rewrites. Small context radii keep the token cost proportional to what actually changed.
3
4use similar::TextDiff;
5
6/// A compact unified diff between `old` and `new`, with `context` unchanged lines around each
7/// hunk. Returns an empty string when the inputs are identical.
8pub fn unified_diff(old: &str, new: &str, context: usize) -> String {
9    if old == new {
10        return String::new();
11    }
12    TextDiff::from_lines(old, new)
13        .unified_diff()
14        .context_radius(context)
15        .header("a", "b")
16        .to_string()
17}
18
19/// The number of changed (inserted or deleted) lines between `old` and `new` — a cheap
20/// proxy for edit size, useful for budgeting and for choosing whole-file vs diff transport.
21pub fn changed_lines(old: &str, new: &str) -> usize {
22    TextDiff::from_lines(old, new)
23        .iter_all_changes()
24        .filter(|c| c.tag() != similar::ChangeTag::Equal)
25        .count()
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn identical_inputs_produce_no_diff() {
34        assert_eq!(unified_diff("a\nb\n", "a\nb\n", 1), "");
35        assert_eq!(changed_lines("a\nb\n", "a\nb\n"), 0);
36    }
37
38    #[test]
39    fn diff_shows_only_changed_hunk_with_context() {
40        let old = "one\ntwo\nthree\nfour\nfive\n";
41        let new = "one\ntwo\nTHREE\nfour\nfive\n";
42        let d = unified_diff(old, new, 1);
43        assert!(d.contains("-three"));
44        assert!(d.contains("+THREE"));
45        // With radius 1, distant unchanged lines ("one", "five") are excluded.
46        assert!(!d.contains("one"));
47        assert!(!d.contains("five"));
48    }
49
50    #[test]
51    fn changed_lines_counts_inserts_and_deletes() {
52        // one line replaced => one delete + one insert.
53        assert_eq!(changed_lines("a\nb\nc\n", "a\nB\nc\n"), 2);
54    }
55}