1use imara_diff::{Algorithm, BasicLineDiffPrinter, Diff, InternedInput, UnifiedDiffConfig};
2
3pub fn unified_diff(path: &str, original: &str, formatted: &str) -> String {
8 let input = InternedInput::new(original, formatted);
9 let mut diff = Diff::compute(Algorithm::Histogram, &input);
10 diff.postprocess_lines(&input);
11
12 let mut config = UnifiedDiffConfig::default();
13 config.context_len(3);
14
15 let body = diff
16 .unified_diff(&BasicLineDiffPrinter(&input.interner), config, &input)
17 .to_string();
18
19 if body.is_empty() {
20 return String::new();
21 }
22 format!("--- a/{}\n+++ b/{}\n{}", path, path, body)
23}
24
25pub fn print_diff(path: &str, original: &str, formatted: &str) {
27 let diff = unified_diff(path, original, formatted);
28 if !diff.is_empty() {
29 print!("{}", diff);
30 }
31}
32
33#[cfg(test)]
34mod tests {
35 use super::*;
36
37 #[test]
38 fn identical_texts_produce_empty_diff() {
39 let text = "Hello world.\nLine two.\n";
40 assert!(unified_diff("test.md", text, text).is_empty());
41 }
42
43 #[test]
44 fn split_lines_show_correctly() {
45 let old = "First.\nSecond line. Third line.\nFourth.\n";
46 let new = "First.\nSecond line.\nThird line.\nFourth.\n";
47 let diff = unified_diff("test.md", old, new);
48 assert!(diff.contains("-Second line. Third line."));
49 assert!(diff.contains("+Second line."));
50 assert!(diff.contains("+Third line."));
51 assert!(diff.contains(" First."));
52 assert!(diff.contains(" Fourth."));
53 }
54
55 #[test]
56 fn preserves_structural_context() {
57 let old = "# Heading\n\nHello world. This is a test.\n\n# Other\n";
58 let new = "# Heading\n\nHello world.\nThis is a test.\n\n# Other\n";
59 let diff = unified_diff("test.md", old, new);
60 assert!(diff.contains(" # Heading"));
61 assert!(diff.contains("-Hello world. This is a test."));
62 assert!(diff.contains("+Hello world."));
63 }
64
65 #[test]
66 fn file_headers_present() {
67 let diff = unified_diff("foo.org", "a\n", "b\n");
68 assert!(diff.starts_with("--- a/foo.org\n+++ b/foo.org\n"));
69 }
70}