imara_diff/
unified_diff.rs

1use std::fmt::{Display, Write};
2use std::ops::Range;
3
4use crate::intern::{InternedInput, Interner, Token};
5use crate::Sink;
6
7/// A [`Sink`] that creates a textual diff
8/// in the format typically output by git or gnu-diff if the `-u` option is used
9pub struct UnifiedDiffBuilder<'a, W, T>
10where
11    W: Write,
12    T: Display,
13{
14    before: &'a [Token],
15    after: &'a [Token],
16    interner: &'a Interner<T>,
17
18    pos: u32,
19    before_hunk_start: u32,
20    after_hunk_start: u32,
21    before_hunk_len: u32,
22    after_hunk_len: u32,
23
24    buffer: String,
25    dst: W,
26}
27
28impl<'a, T> UnifiedDiffBuilder<'a, String, T>
29where
30    T: Display,
31{
32    /// Create a new `UnifiedDiffBuilder` for the given `input`,
33    /// that will return a [`String`].
34    pub fn new(input: &'a InternedInput<T>) -> Self {
35        Self {
36            before_hunk_start: 0,
37            after_hunk_start: 0,
38            before_hunk_len: 0,
39            after_hunk_len: 0,
40            buffer: String::with_capacity(8),
41            dst: String::new(),
42            interner: &input.interner,
43            before: &input.before,
44            after: &input.after,
45            pos: 0,
46        }
47    }
48}
49
50impl<'a, W, T> UnifiedDiffBuilder<'a, W, T>
51where
52    W: Write,
53    T: Display,
54{
55    /// Create a new `UnifiedDiffBuilder` for the given `input`,
56    /// that will writes it output to the provided implementation of [`Write`].
57    pub fn with_writer(input: &'a InternedInput<T>, writer: W) -> Self {
58        Self {
59            before_hunk_start: 0,
60            after_hunk_start: 0,
61            before_hunk_len: 0,
62            after_hunk_len: 0,
63            buffer: String::with_capacity(8),
64            dst: writer,
65            interner: &input.interner,
66            before: &input.before,
67            after: &input.after,
68            pos: 0,
69        }
70    }
71
72    fn print_tokens(&mut self, tokens: &[Token], prefix: char) {
73        for &token in tokens {
74            writeln!(&mut self.buffer, "{prefix}{}", self.interner[token]).unwrap();
75        }
76    }
77
78    fn flush(&mut self) {
79        if self.before_hunk_len == 0 && self.after_hunk_len == 0 {
80            return;
81        }
82
83        let end = (self.pos + 3).min(self.before.len() as u32);
84        self.update_pos(end, end);
85
86        writeln!(
87            &mut self.dst,
88            "@@ -{},{} +{},{} @@",
89            self.before_hunk_start + 1,
90            self.before_hunk_len,
91            self.after_hunk_start + 1,
92            self.after_hunk_len,
93        )
94        .unwrap();
95        write!(&mut self.dst, "{}", &self.buffer).unwrap();
96        self.buffer.clear();
97        self.before_hunk_len = 0;
98        self.after_hunk_len = 0
99    }
100
101    fn update_pos(&mut self, print_to: u32, move_to: u32) {
102        self.print_tokens(&self.before[self.pos as usize..print_to as usize], ' ');
103        let len = print_to - self.pos;
104        self.pos = move_to;
105        self.before_hunk_len += len;
106        self.after_hunk_len += len;
107    }
108}
109
110impl<W, T> Sink for UnifiedDiffBuilder<'_, W, T>
111where
112    W: Write,
113    T: Display,
114{
115    type Out = W;
116
117    fn process_change(&mut self, before: Range<u32>, after: Range<u32>) {
118        if before.start - self.pos > 6 {
119            self.flush();
120            self.pos = before.start - 3;
121            self.before_hunk_start = self.pos;
122            self.after_hunk_start = after.start - 3;
123        }
124        self.update_pos(before.start, before.end);
125        self.before_hunk_len += before.end - before.start;
126        self.after_hunk_len += after.end - after.start;
127        self.print_tokens(
128            &self.before[before.start as usize..before.end as usize],
129            '-',
130        );
131        self.print_tokens(&self.after[after.start as usize..after.end as usize], '+');
132    }
133
134    fn finish(mut self) -> Self::Out {
135        self.flush();
136        self.dst
137    }
138}