side_by_side_diff/
lib.rs

1use similar::{ChangeTag, TextDiff};
2use unicode_width::UnicodeWidthStr;
3
4pub fn create_side_by_side_diff(text1: &str, text2: &str, max_width: usize) -> String {
5    TextDiff::from_lines(text1, text2)
6        .iter_all_changes()
7        .map(|change| {
8            let content = change.to_string().trim_end_matches('\n').to_string();
9            let width = max_width - (content.width() - content.chars().count());
10            match change.tag() {
11                ChangeTag::Delete => format!(
12                    "{:>6} | {:<width$} | {:>6} | {:<blank_width$} |",
13                    change.old_index().unwrap() + 1,
14                    content,
15                    "",
16                    "",
17                    blank_width = max_width,
18                    width = width,
19                ),
20                ChangeTag::Insert => format!(
21                    "{:>6} | {:<blank_width$} | {:>6} | {:<width$} |",
22                    "",
23                    "",
24                    change.new_index().unwrap() + 1,
25                    content,
26                    blank_width = max_width,
27                    width = width
28                ),
29                ChangeTag::Equal => format!(
30                    "{:>6} | {token:<width$} | {:>6} | {token:<width$} |",
31                    change.old_index().unwrap() + 1,
32                    change.new_index().unwrap() + 1,
33                    token = content,
34                    width = width
35                ),
36            }
37        })
38        .collect::<Vec<_>>()
39        .join("\n")
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn it_works() {
48        let actual = create_side_by_side_diff(
49            "
50あああ
51bbb
52ccc
53ddd
54いいいいいーeee
55かかお
56FFFFFFFFFF
57ggg"
58            .trim_matches('\n'),
59            "
60あああ
61bbb
62いいいいいーeee
63かかか
64ffffffffff
65ggg"
66            .trim_matches('\n'),
67            20,
68        );
69
70        let expected = "
71     1 | あああ               |      1 | あああ               |
72     2 | bbb                  |      2 | bbb                  |
73     3 | ccc                  |        |                      |
74     4 | ddd                  |        |                      |
75     5 | いいいいいーeee      |      3 | いいいいいーeee      |
76     6 | かかお               |        |                      |
77     7 | FFFFFFFFFF           |        |                      |
78       |                      |      4 | かかか               |
79       |                      |      5 | ffffffffff           |
80     8 | ggg                  |      6 | ggg                  |
81"
82        .trim_matches('\n');
83
84        assert_eq!(expected, actual);
85    }
86}