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}