diffy_imara/patch/
format.rs

1use super::{Hunk, Line, Patch, NO_NEWLINE_AT_EOF};
2use nu_ansi_term::{Color, Style};
3use std::{
4    fmt::{Display, Formatter, Result},
5    io,
6};
7
8/// Struct used to adjust the formatting of a `Patch`
9#[derive(Debug)]
10pub struct PatchFormatter {
11    with_color: bool,
12    with_missing_newline_message: bool,
13    suppress_blank_empty: bool,
14
15    context: Style,
16    delete: Style,
17    insert: Style,
18    hunk_header: Style,
19    patch_header: Style,
20    function_context: Style,
21}
22
23impl PatchFormatter {
24    /// Construct a new formatter
25    pub fn new() -> Self {
26        Self {
27            with_color: false,
28            with_missing_newline_message: true,
29
30            // TODO the default in git-diff and GNU diff is to have this set to false, on the next
31            // semver breaking release we should contemplate switching this to be false by default
32            suppress_blank_empty: true,
33
34            context: Style::new(),
35            delete: Color::Red.normal(),
36            insert: Color::Green.normal(),
37            hunk_header: Color::Cyan.normal(),
38            patch_header: Style::new().bold(),
39            function_context: Style::new(),
40        }
41    }
42
43    /// Enable formatting a patch with color
44    pub fn with_color(mut self) -> Self {
45        self.with_color = true;
46        self
47    }
48
49    /// Sets whether to format a patch with a "No newline at end of file" message.
50    ///
51    /// Default is `true`.
52    ///
53    /// Note: If this is disabled by setting to `false`, formatted patches will no longer contain
54    /// sufficient information to determine if a file ended with a newline character (`\n`) or not
55    /// and the patch will be formatted as if both the original and modified files ended with a
56    /// newline character (`\n`).
57    pub fn missing_newline_message(mut self, enable: bool) -> Self {
58        self.with_missing_newline_message = enable;
59        self
60    }
61
62    /// Sets whether to suppress printing of a space before empty lines.
63    ///
64    /// Defaults to `true`.
65    ///
66    /// For more information you can refer to the [Omitting trailing blanks] manual page of GNU
67    /// diff or the [diff.suppressBlankEmpty] config for `git-diff`.
68    ///
69    /// [Omitting trailing blanks]: https://www.gnu.org/software/diffutils/manual/html_node/Trailing-Blanks.html
70    /// [diff.suppressBlankEmpty]: https://git-scm.com/docs/git-diff#Documentation/git-diff.txt-codediffsuppressBlankEmptycode
71    pub fn suppress_blank_empty(mut self, enable: bool) -> Self {
72        self.suppress_blank_empty = enable;
73        self
74    }
75
76    /// Returns a `Display` impl which can be used to print a Patch
77    pub fn fmt_patch<'a>(&'a self, patch: &'a Patch<'a, str>) -> impl Display + 'a {
78        PatchDisplay { f: self, patch }
79    }
80
81    pub fn write_patch_into<T: ToOwned + AsRef<[u8]> + ?Sized, W: io::Write>(
82        &self,
83        patch: &Patch<'_, T>,
84        w: W,
85    ) -> io::Result<()> {
86        PatchDisplay { f: self, patch }.write_into(w)
87    }
88
89    fn fmt_hunk<'a>(&'a self, hunk: &'a Hunk<'a, str>) -> impl Display + 'a {
90        HunkDisplay { f: self, hunk }
91    }
92
93    fn write_hunk_into<T: AsRef<[u8]> + ?Sized, W: io::Write>(
94        &self,
95        hunk: &Hunk<'_, T>,
96        w: W,
97    ) -> io::Result<()> {
98        HunkDisplay { f: self, hunk }.write_into(w)
99    }
100
101    fn fmt_line<'a>(&'a self, line: &'a Line<'a, str>) -> impl Display + 'a {
102        LineDisplay { f: self, line }
103    }
104
105    fn write_line_into<T: AsRef<[u8]> + ?Sized, W: io::Write>(
106        &self,
107        line: &Line<'_, T>,
108        w: W,
109    ) -> io::Result<()> {
110        LineDisplay { f: self, line }.write_into(w)
111    }
112}
113
114impl Default for PatchFormatter {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120struct PatchDisplay<'a, T: ToOwned + ?Sized> {
121    f: &'a PatchFormatter,
122    patch: &'a Patch<'a, T>,
123}
124
125impl<T: ToOwned + AsRef<[u8]> + ?Sized> PatchDisplay<'_, T> {
126    fn write_into<W: io::Write>(&self, mut w: W) -> io::Result<()> {
127        if self.patch.original.is_some() || self.patch.modified.is_some() {
128            if self.f.with_color {
129                write!(w, "{}", self.f.patch_header.prefix())?;
130            }
131            if let Some(original) = &self.patch.original {
132                write!(w, "--- ")?;
133                original.write_into(&mut w)?;
134                writeln!(w)?;
135            }
136            if let Some(modified) = &self.patch.modified {
137                write!(w, "+++ ")?;
138                modified.write_into(&mut w)?;
139                writeln!(w)?;
140            }
141            if self.f.with_color {
142                write!(w, "{}", self.f.patch_header.suffix())?;
143            }
144        }
145
146        for hunk in &self.patch.hunks {
147            self.f.write_hunk_into(hunk, &mut w)?;
148        }
149
150        Ok(())
151    }
152}
153
154impl Display for PatchDisplay<'_, str> {
155    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
156        if self.patch.original.is_some() || self.patch.modified.is_some() {
157            if self.f.with_color {
158                write!(f, "{}", self.f.patch_header.prefix())?;
159            }
160            if let Some(original) = &self.patch.original {
161                writeln!(f, "--- {}", original)?;
162            }
163            if let Some(modified) = &self.patch.modified {
164                writeln!(f, "+++ {}", modified)?;
165            }
166            if self.f.with_color {
167                write!(f, "{}", self.f.patch_header.suffix())?;
168            }
169        }
170
171        for hunk in &self.patch.hunks {
172            write!(f, "{}", self.f.fmt_hunk(hunk))?;
173        }
174
175        Ok(())
176    }
177}
178
179struct HunkDisplay<'a, T: ?Sized> {
180    f: &'a PatchFormatter,
181    hunk: &'a Hunk<'a, T>,
182}
183
184impl<T: AsRef<[u8]> + ?Sized> HunkDisplay<'_, T> {
185    fn write_into<W: io::Write>(&self, mut w: W) -> io::Result<()> {
186        if self.f.with_color {
187            write!(w, "{}", self.f.hunk_header.prefix())?;
188        }
189        write!(w, "@@ -{} +{} @@", self.hunk.old_range, self.hunk.new_range)?;
190        if self.f.with_color {
191            write!(w, "{}", self.f.hunk_header.suffix())?;
192        }
193
194        if let Some(ctx) = self.hunk.function_context {
195            write!(w, " ")?;
196            if self.f.with_color {
197                write!(w, "{}", self.f.function_context.prefix())?;
198            }
199            write!(w, " ")?;
200            w.write_all(ctx.as_ref())?;
201            if self.f.with_color {
202                write!(w, "{}", self.f.function_context.suffix())?;
203            }
204        }
205        writeln!(w)?;
206
207        for line in &self.hunk.lines {
208            self.f.write_line_into(line, &mut w)?;
209        }
210
211        Ok(())
212    }
213}
214
215impl Display for HunkDisplay<'_, str> {
216    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
217        if self.f.with_color {
218            write!(f, "{}", self.f.hunk_header.prefix())?;
219        }
220        write!(f, "@@ -{} +{} @@", self.hunk.old_range, self.hunk.new_range)?;
221        if self.f.with_color {
222            write!(f, "{}", self.f.hunk_header.suffix())?;
223        }
224
225        if let Some(ctx) = self.hunk.function_context {
226            write!(f, " ")?;
227            if self.f.with_color {
228                write!(f, "{}", self.f.function_context.prefix())?;
229            }
230            write!(f, " {}", ctx)?;
231            if self.f.with_color {
232                write!(f, "{}", self.f.function_context.suffix())?;
233            }
234        }
235        writeln!(f)?;
236
237        for line in &self.hunk.lines {
238            write!(f, "{}", self.f.fmt_line(line))?;
239        }
240
241        Ok(())
242    }
243}
244
245struct LineDisplay<'a, T: ?Sized> {
246    f: &'a PatchFormatter,
247    line: &'a Line<'a, T>,
248}
249
250impl<T: AsRef<[u8]> + ?Sized> LineDisplay<'_, T> {
251    fn write_into<W: io::Write>(&self, mut w: W) -> io::Result<()> {
252        let (sign, line, style) = match self.line {
253            Line::Context(line) => (' ', line.as_ref(), self.f.context),
254            Line::Delete(line) => ('-', line.as_ref(), self.f.delete),
255            Line::Insert(line) => ('+', line.as_ref(), self.f.insert),
256        };
257
258        if self.f.with_color {
259            write!(w, "{}", style.prefix())?;
260        }
261
262        if self.f.suppress_blank_empty && sign == ' ' && line == b"\n" {
263            w.write_all(line)?;
264        } else {
265            write!(w, "{}", sign)?;
266            w.write_all(line)?;
267        }
268
269        if self.f.with_color {
270            write!(w, "{}", style.suffix())?;
271        }
272
273        if !line.ends_with(b"\n") {
274            writeln!(w)?;
275            if self.f.with_missing_newline_message {
276                writeln!(w, "{}", NO_NEWLINE_AT_EOF)?;
277            }
278        }
279
280        Ok(())
281    }
282}
283
284impl Display for LineDisplay<'_, str> {
285    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
286        let (sign, line, style) = match self.line {
287            Line::Context(line) => (' ', line, self.f.context),
288            Line::Delete(line) => ('-', line, self.f.delete),
289            Line::Insert(line) => ('+', line, self.f.insert),
290        };
291
292        if self.f.with_color {
293            write!(f, "{}", style.prefix())?;
294        }
295
296        if self.f.suppress_blank_empty && sign == ' ' && *line == "\n" {
297            write!(f, "{}", line)?;
298        } else {
299            write!(f, "{}{}", sign, line)?;
300        }
301
302        if self.f.with_color {
303            write!(f, "{}", style.suffix())?;
304        }
305
306        if !line.ends_with('\n') {
307            writeln!(f)?;
308            if self.f.with_missing_newline_message {
309                writeln!(f, "{}", NO_NEWLINE_AT_EOF)?;
310            }
311        }
312
313        Ok(())
314    }
315}