Skip to main content

qcdiff_core/
diff.rs

1//! Alignment and rendering of two operation streams.
2
3use crate::stream::{Extracted, Op};
4use similar::{capture_diff_slices, Algorithm, DiffOp};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum Change {
8    Same,
9    Removed,
10    Added,
11    /// Same alignment key (op + operands) but different parameters/content.
12    Modified,
13}
14
15#[derive(Debug)]
16pub struct DiffLine {
17    pub change: Change,
18    /// Rendering of the old op (Removed/Same/Modified) or new op (Added).
19    pub text: String,
20    /// For Modified: rendering of the new parameters.
21    pub new_params: Option<String>,
22}
23
24pub struct DiffResult {
25    pub lines: Vec<DiffLine>,
26    pub added: usize,
27    pub removed: usize,
28    pub modified: usize,
29}
30
31impl DiffResult {
32    pub fn has_changes(&self) -> bool {
33        self.added + self.removed + self.modified > 0
34    }
35}
36
37/// Diff two streams. Alignment uses the parameter-free key so that a
38/// parameter-only change pairs up and reports as `~` instead of `-`/`+`.
39pub fn diff(a: &Extracted, b: &Extracted) -> DiffResult {
40    let keys_a: Vec<String> = a.ops.iter().map(Op::align_key).collect();
41    let keys_b: Vec<String> = b.ops.iter().map(Op::align_key).collect();
42    let ops = capture_diff_slices(Algorithm::Patience, &keys_a, &keys_b);
43
44    let mut lines = Vec::new();
45    let (mut added, mut removed, mut modified) = (0usize, 0usize, 0usize);
46
47    for op in ops {
48        match op {
49            DiffOp::Equal {
50                old_index,
51                new_index,
52                len,
53            } => {
54                for k in 0..len {
55                    let oa = &a.ops[old_index + k];
56                    let ob = &b.ops[new_index + k];
57                    if oa.params == ob.params {
58                        lines.push(DiffLine {
59                            change: Change::Same,
60                            text: oa.render(),
61                            new_params: None,
62                        });
63                    } else {
64                        modified += 1;
65                        lines.push(DiffLine {
66                            change: Change::Modified,
67                            text: oa.render(),
68                            new_params: Some(ob.params.clone()),
69                        });
70                    }
71                }
72            }
73            DiffOp::Delete {
74                old_index, old_len, ..
75            } => {
76                for k in 0..old_len {
77                    removed += 1;
78                    lines.push(DiffLine {
79                        change: Change::Removed,
80                        text: a.ops[old_index + k].render(),
81                        new_params: None,
82                    });
83                }
84            }
85            DiffOp::Insert {
86                new_index, new_len, ..
87            } => {
88                for k in 0..new_len {
89                    added += 1;
90                    lines.push(DiffLine {
91                        change: Change::Added,
92                        text: b.ops[new_index + k].render(),
93                        new_params: None,
94                    });
95                }
96            }
97            DiffOp::Replace {
98                old_index,
99                old_len,
100                new_index,
101                new_len,
102            } => {
103                for k in 0..old_len {
104                    removed += 1;
105                    lines.push(DiffLine {
106                        change: Change::Removed,
107                        text: a.ops[old_index + k].render(),
108                        new_params: None,
109                    });
110                }
111                for k in 0..new_len {
112                    added += 1;
113                    lines.push(DiffLine {
114                        change: Change::Added,
115                        text: b.ops[new_index + k].render(),
116                        new_params: None,
117                    });
118                }
119            }
120        }
121    }
122
123    DiffResult {
124        lines,
125        added,
126        removed,
127        modified,
128    }
129}