aboutsummaryrefslogtreecommitdiff
path: root/src/diff.rs
blob: 442448f69870a2ce029b5932edd038e60c121f7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use difference::Changeset;
use difference::Difference;
use sputnik::html_escape;
use std::cmp;
use std::fmt::Write as FmtWrite;

pub fn diff(first: &str, second: &str) -> String {
    if first == second {
        return "<em>(no changes)</em>".into();
    }

    let Changeset { diffs, .. } = Changeset::new(first, second, "\n");

    let mut output = String::new();

    output.push_str("<pre>");

    for i in 0..diffs.len() {
        match diffs[i] {
            Difference::Same(ref text) => {
                let text = html_escape(text);
                let lines: Vec<_> = text.split('\n').collect();
                if i == 0 {
                    output.push_str(&lines[lines.len().saturating_sub(3)..].join("\n"));
                } else if i == diffs.len() - 1 {
                    output.push_str(&lines[..cmp::min(3, lines.len())].join("\n"));
                } else {
                    output.push_str(&text);
                }
            }
            Difference::Add(ref text) => {
                output.push_str("<div class=addition>");
                if i == 0 {
                    output.push_str(&html_escape(text).replace('\n', "<br>"));
                } else {
                    match diffs.get(i - 1) {
                        Some(Difference::Rem(ref rem)) => {
                            word_diff(&mut output, rem, text, "ins");
                        }
                        _ => {
                            output.push_str(&html_escape(text).replace('\n', "<br>"));
                        }
                    }
                }
                output.push_str("\n</div>");
            }
            Difference::Rem(ref text) => {
                output.push_str("<div class=deletion>");
                match diffs.get(i + 1) {
                    Some(Difference::Add(ref add)) => {
                        word_diff(&mut output, add, text, "del");
                    }
                    _ => {
                        output.push_str(&html_escape(text).replace('\n', "<br>"));
                    }
                }
                output.push_str("\n</div>");
            }
        }
    }

    output.push_str("</pre>");
    output
}

fn word_diff(out: &mut String, text1: &str, text2: &str, tagname: &str) {
    let Changeset { diffs, .. } = Changeset::new(text1, text2, " ");
    for c in diffs {
        match c {
            Difference::Same(ref z) => {
                out.push_str(&html_escape(z).replace('\n', "<br>"));
                out.push(' ');
            }
            Difference::Add(ref z) => {
                write!(
                    out,
                    "<{0}>{1}</{0}> ",
                    tagname,
                    html_escape(z).replace('\n', "<br>")
                )
                .expect("write error");
            }
            _ => {}
        }
    }
}