imara_diff/
unified_diff.rs

1use std::fmt::{self, Display};
2use std::hash::Hash;
3
4use crate::intern::{InternedInput, Interner, Token};
5use crate::Diff;
6
7impl Diff {
8    pub fn unified_diff<'a, P: UnifiedDiffPrinter, T: Hash + Eq>(
9        &'a self,
10        printer: &'a P,
11        config: UnifiedDiffConfig,
12        input: &'a InternedInput<T>,
13    ) -> UnifiedDiff<'a, P> {
14        self.unified_diff_with(printer, config, &input.before, &input.after)
15    }
16
17    pub fn unified_diff_with<'a, P: UnifiedDiffPrinter>(
18        &'a self,
19        printer: &'a P,
20        config: UnifiedDiffConfig,
21        before: &'a [Token],
22        after: &'a [Token],
23    ) -> UnifiedDiff<'a, P> {
24        UnifiedDiff {
25            printer,
26            diff: self,
27            config,
28            before,
29            after,
30        }
31    }
32}
33
34pub trait UnifiedDiffPrinter {
35    fn display_header(
36        &self,
37        f: impl fmt::Write,
38        start_before: u32,
39        start_after: u32,
40        len_before: u32,
41        len_after: u32,
42    ) -> fmt::Result;
43    fn display_context_token(&self, f: impl fmt::Write, token: Token) -> fmt::Result;
44    fn display_hunk(&self, f: impl fmt::Write, before: &[Token], after: &[Token]) -> fmt::Result;
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct UnifiedDiffConfig {
49    context_len: u32,
50}
51
52impl Default for UnifiedDiffConfig {
53    fn default() -> Self {
54        UnifiedDiffConfig { context_len: 3 }
55    }
56}
57
58impl UnifiedDiffConfig {
59    pub fn context_len(&mut self, len: u32) -> &mut Self {
60        self.context_len = len;
61        self
62    }
63}
64
65pub trait EndsWithNewline {
66    fn ends_with_newline(&self) -> bool;
67}
68
69impl<T: AsRef<[u8]> + ?Sized> EndsWithNewline for T {
70    fn ends_with_newline(&self) -> bool {
71        self.as_ref().ends_with(b"\n")
72    }
73}
74
75pub struct BasicLineDiffPrinter<'a, T: EndsWithNewline + ?Sized + Hash + Eq + Display>(
76    pub &'a Interner<&'a T>,
77);
78
79impl<T: EndsWithNewline + Hash + Eq + Display + ?Sized> UnifiedDiffPrinter
80    for BasicLineDiffPrinter<'_, T>
81{
82    fn display_header(
83        &self,
84        mut f: impl fmt::Write,
85        start_before: u32,
86        start_after: u32,
87        len_before: u32,
88        len_after: u32,
89    ) -> fmt::Result {
90        writeln!(
91            f,
92            "@@ -{},{} +{},{} @@",
93            start_before + 1,
94            len_before,
95            start_after + 1,
96            len_after
97        )
98    }
99
100    fn display_context_token(&self, mut f: impl fmt::Write, token: Token) -> fmt::Result {
101        write!(f, " {}", &self.0[token])?;
102        if !&self.0[token].ends_with_newline() {
103            writeln!(f)?;
104        }
105        Ok(())
106    }
107
108    fn display_hunk(
109        &self,
110        mut f: impl fmt::Write,
111        before: &[Token],
112        after: &[Token],
113    ) -> fmt::Result {
114        if let Some(&last) = before.last() {
115            for &token in before {
116                let token = self.0[token];
117                write!(f, "-{token}")?;
118            }
119            if !self.0[last].ends_with_newline() {
120                writeln!(f)?;
121            }
122        }
123        if let Some(&last) = after.last() {
124            for &token in after {
125                let token = self.0[token];
126                write!(f, "+{token}")?;
127            }
128            if !self.0[last].ends_with_newline() {
129                writeln!(f)?;
130            }
131        }
132        Ok(())
133    }
134}
135
136pub struct UnifiedDiff<'a, P: UnifiedDiffPrinter> {
137    printer: &'a P,
138    diff: &'a Diff,
139    config: UnifiedDiffConfig,
140    before: &'a [Token],
141    after: &'a [Token],
142}
143
144impl<P: UnifiedDiffPrinter> Display for UnifiedDiff<'_, P> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        let mut pos = 0;
147        let mut before_context_len = 0;
148        let mut after_context_len = 0;
149        let first_hunk = self.diff.hunks().next().unwrap_or_default();
150        let mut before_context_start = first_hunk
151            .before
152            .start
153            .saturating_sub(self.config.context_len);
154        let mut after_context_start = first_hunk
155            .after
156            .start
157            .saturating_sub(self.config.context_len);
158        let mut buffer = String::new();
159        for hunk in self.diff.hunks() {
160            if hunk.before.start - pos > 2 * self.config.context_len {
161                if !buffer.is_empty() {
162                    let end = (pos + self.config.context_len).min(self.before.len() as u32);
163                    self.printer.display_header(
164                        &mut *f,
165                        before_context_start,
166                        after_context_start,
167                        before_context_len + end - pos,
168                        after_context_len + end - pos,
169                    )?;
170                    write!(f, "{buffer}")?;
171                    for &token in &self.before[pos as usize..end as usize] {
172                        self.printer.display_context_token(&mut *f, token)?;
173                    }
174                    buffer.clear();
175                }
176                pos = hunk.before.start - self.config.context_len;
177                before_context_start = pos;
178                after_context_start = hunk.after.start - self.config.context_len;
179                before_context_len = 0;
180                after_context_len = 0;
181            }
182            for &token in &self.before[pos as usize..hunk.before.start as usize] {
183                self.printer.display_context_token(&mut buffer, token)?;
184            }
185            let context_len = hunk.before.start - pos;
186            before_context_len += hunk.before.len() as u32 + context_len;
187            after_context_len += hunk.after.len() as u32 + context_len;
188            self.printer.display_hunk(
189                &mut buffer,
190                &self.before[hunk.before.start as usize..hunk.before.end as usize],
191                &self.after[hunk.after.start as usize..hunk.after.end as usize],
192            )?;
193            pos = hunk.before.end;
194        }
195        if !buffer.is_empty() {
196            let end = (pos + self.config.context_len).min(self.before.len() as u32);
197            self.printer.display_header(
198                &mut *f,
199                before_context_start,
200                after_context_start,
201                before_context_len + end - pos,
202                after_context_len + end - pos,
203            )?;
204            write!(f, "{buffer}")?;
205            for &token in &self.before[pos as usize..end as usize] {
206                self.printer.display_context_token(&mut *f, token)?;
207            }
208            buffer.clear();
209        }
210        Ok(())
211    }
212}