Skip to main content

harness_write/
diff.rs

1use similar::{ChangeTag, TextDiff};
2
3pub struct UnifiedDiffArgs<'a> {
4    pub old_path: &'a str,
5    pub new_path: &'a str,
6    pub old_content: &'a str,
7    pub new_content: &'a str,
8}
9
10/// Produce a unified-diff string. Uses the `similar` crate's grouped
11/// hunks. Output shape: `--- old\n+++ new\n@@ ... @@\n ...\n- ...\n+ ...`.
12pub fn unified_diff(args: UnifiedDiffArgs<'_>) -> String {
13    let diff = TextDiff::from_lines(args.old_content, args.new_content);
14    let mut out = String::new();
15    out.push_str(&format!("--- {}\n", args.old_path));
16    out.push_str(&format!("+++ {}\n", args.new_path));
17
18    for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
19        if idx > 0 {
20            out.push('\n');
21        }
22        let first = &group[0];
23        let last = &group[group.len() - 1];
24        let old_start = first.old_range().start + 1;
25        let old_len = last.old_range().end - first.old_range().start;
26        let new_start = first.new_range().start + 1;
27        let new_len = last.new_range().end - first.new_range().start;
28        out.push_str(&format!(
29            "@@ -{},{} +{},{} @@\n",
30            old_start, old_len, new_start, new_len
31        ));
32        for op in group {
33            for change in diff.iter_changes(op) {
34                let sign = match change.tag() {
35                    ChangeTag::Equal => ' ',
36                    ChangeTag::Delete => '-',
37                    ChangeTag::Insert => '+',
38                };
39                out.push(sign);
40                out.push_str(change.value());
41                if !out.ends_with('\n') {
42                    out.push('\n');
43                }
44            }
45        }
46    }
47
48    out
49}