1 #[cfg(feature = "alloc")]
2 use alloc::format;
3 use ansi_term::{
4     Colour::{Fixed, Green, Red},
5     Style,
6 };
7 use core::fmt;
8 
9 macro_rules! paint {
10     ($f:expr, $colour:expr, $fmt:expr, $($args:tt)*) => (
11         write!($f, "{}", $colour.paint(format!($fmt, $($args)*)))
12     )
13 }
14 
15 const SIGN_RIGHT: char = '>'; // + > →
16 const SIGN_LEFT: char = '<'; // - < ←
17 
18 /// Present the diff output for two mutliline strings in a pretty, colorised manner.
write_header(f: &mut fmt::Formatter) -> fmt::Result19 pub(crate) fn write_header(f: &mut fmt::Formatter) -> fmt::Result {
20     writeln!(
21         f,
22         "{} {} / {} :",
23         Style::new().bold().paint("Diff"),
24         Red.paint(format!("{} left", SIGN_LEFT)),
25         Green.paint(format!("right {}", SIGN_RIGHT))
26     )
27 }
28 
29 /// Delay formatting this deleted chunk until later.
30 ///
31 /// It can be formatted as a whole chunk by calling `flush`, or the inner value
32 /// obtained with `take` for further processing.
33 #[derive(Default)]
34 struct LatentDeletion<'a> {
35     // The most recent deleted line we've seen
36     value: Option<&'a str>,
37     // The number of deleted lines we've seen, including the current value
38     count: usize,
39 }
40 
41 impl<'a> LatentDeletion<'a> {
42     /// Set the chunk value.
set(&mut self, value: &'a str)43     fn set(&mut self, value: &'a str) {
44         self.value = Some(value);
45         self.count += 1;
46     }
47 
48     /// Take the underlying chunk value, if it's suitable for inline diffing.
49     ///
50     /// If there is no value of we've seen more than one line, return `None`.
take(&mut self) -> Option<&'a str>51     fn take(&mut self) -> Option<&'a str> {
52         if self.count == 1 {
53             self.value.take()
54         } else {
55             None
56         }
57     }
58 
59     /// If a value is set, print it as a whole chunk, using the given formatter.
60     ///
61     /// If a value is not set, reset the count to zero (as we've called `flush` twice,
62     /// without seeing another deletion. Therefore the line in the middle was something else).
flush<TWrite: fmt::Write>(&mut self, f: &mut TWrite) -> fmt::Result63     fn flush<TWrite: fmt::Write>(&mut self, f: &mut TWrite) -> fmt::Result {
64         if let Some(value) = self.value {
65             paint!(f, Red, "{}{}", SIGN_LEFT, value)?;
66             writeln!(f)?;
67             self.value = None;
68         } else {
69             self.count = 0;
70         }
71 
72         Ok(())
73     }
74 }
75 
76 // Adapted from:
77 // https://github.com/johannhof/difference.rs/blob/c5749ad7d82aa3d480c15cb61af9f6baa08f116f/examples/github-style.rs
78 // Credits johannhof (MIT License)
79 
80 /// Present the diff output for two mutliline strings in a pretty, colorised manner.
write_lines<TWrite: fmt::Write>( f: &mut TWrite, left: &str, right: &str, ) -> fmt::Result81 pub(crate) fn write_lines<TWrite: fmt::Write>(
82     f: &mut TWrite,
83     left: &str,
84     right: &str,
85 ) -> fmt::Result {
86     let diff = ::diff::lines(left, right);
87 
88     let mut changes = diff.into_iter().peekable();
89     let mut previous_deletion = LatentDeletion::default();
90 
91     while let Some(change) = changes.next() {
92         match (change, changes.peek()) {
93             // If the text is unchanged, just print it plain
94             (::diff::Result::Both(value, _), _) => {
95                 previous_deletion.flush(f)?;
96                 writeln!(f, " {}", value)?;
97             }
98             // Defer any deletions to next loop
99             (::diff::Result::Left(deleted), _) => {
100                 previous_deletion.flush(f)?;
101                 previous_deletion.set(deleted);
102             }
103             // Underlying diff library should never return this sequence
104             (::diff::Result::Right(_), Some(::diff::Result::Left(_))) => {
105                 panic!("insertion followed by deletion");
106             }
107             // If we're being followed by more insertions, don't inline diff
108             (::diff::Result::Right(inserted), Some(::diff::Result::Right(_))) => {
109                 previous_deletion.flush(f)?;
110                 paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?;
111                 writeln!(f)?;
112             }
113             // Otherwise, check if we need to inline diff with the previous line (if it was a deletion)
114             (::diff::Result::Right(inserted), _) => {
115                 if let Some(deleted) = previous_deletion.take() {
116                     write_inline_diff(f, deleted, inserted)?;
117                 } else {
118                     previous_deletion.flush(f)?;
119                     paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?;
120                     writeln!(f)?;
121                 }
122             }
123         };
124     }
125 
126     previous_deletion.flush(f)?;
127     Ok(())
128 }
129 
130 /// Group character styling for an inline diff, to prevent wrapping each single
131 /// character in terminal styling codes.
132 ///
133 /// Styles are applied automatically each time a new style is given in `write_with_style`.
134 struct InlineWriter<'a, Writer> {
135     f: &'a mut Writer,
136     style: Style,
137 }
138 
139 impl<'a, Writer> InlineWriter<'a, Writer>
140 where
141     Writer: fmt::Write,
142 {
new(f: &'a mut Writer) -> Self143     fn new(f: &'a mut Writer) -> Self {
144         InlineWriter {
145             f,
146             style: Style::new(),
147         }
148     }
149 
150     /// Push a new character into the buffer, specifying the style it should be written in.
write_with_style(&mut self, c: &char, style: Style) -> fmt::Result151     fn write_with_style(&mut self, c: &char, style: Style) -> fmt::Result {
152         // If the style is the same as previously, just write character
153         if style == self.style {
154             write!(self.f, "{}", c)?;
155         } else {
156             // Close out previous style
157             write!(self.f, "{}", self.style.suffix())?;
158 
159             // Store new style and start writing it
160             write!(self.f, "{}{}", style.prefix(), c)?;
161             self.style = style;
162         }
163         Ok(())
164     }
165 
166     /// Finish any existing style and reset to default state.
finish(&mut self) -> fmt::Result167     fn finish(&mut self) -> fmt::Result {
168         // Close out previous style
169         writeln!(self.f, "{}", self.style.suffix())?;
170         self.style = Default::default();
171         Ok(())
172     }
173 }
174 
175 /// Format a single line to show an inline diff of the two strings given.
176 ///
177 /// The given strings should not have a trailing newline.
178 ///
179 /// The output of this function will be two lines, each with a trailing newline.
write_inline_diff<TWrite: fmt::Write>(f: &mut TWrite, left: &str, right: &str) -> fmt::Result180 fn write_inline_diff<TWrite: fmt::Write>(f: &mut TWrite, left: &str, right: &str) -> fmt::Result {
181     let diff = ::diff::chars(left, right);
182     let mut writer = InlineWriter::new(f);
183 
184     // Print the left string on one line, with differences highlighted
185     let light = Red.into();
186     let heavy = Red.on(Fixed(52)).bold();
187     writer.write_with_style(&SIGN_LEFT, light)?;
188     for change in diff.iter() {
189         match change {
190             ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
191             ::diff::Result::Left(value) => writer.write_with_style(value, heavy)?,
192             _ => (),
193         }
194     }
195     writer.finish()?;
196 
197     // Print the right string on one line, with differences highlighted
198     let light = Green.into();
199     let heavy = Green.on(Fixed(22)).bold();
200     writer.write_with_style(&SIGN_RIGHT, light)?;
201     for change in diff.iter() {
202         match change {
203             ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
204             ::diff::Result::Right(value) => writer.write_with_style(value, heavy)?,
205             _ => (),
206         }
207     }
208     writer.finish()
209 }
210 
211 #[cfg(test)]
212 mod test {
213     use super::*;
214 
215     #[cfg(feature = "alloc")]
216     use alloc::string::String;
217 
218     // ANSI terminal codes used in our outputs.
219     //
220     // Interpolate these into test strings to make expected values easier to read.
221     const RED_LIGHT: &str = "\u{1b}[31m";
222     const GREEN_LIGHT: &str = "\u{1b}[32m";
223     const RED_HEAVY: &str = "\u{1b}[1;48;5;52;31m";
224     const GREEN_HEAVY: &str = "\u{1b}[1;48;5;22;32m";
225     const RESET: &str = "\u{1b}[0m";
226 
227     /// Given that both of our diff printing functions have the same
228     /// type signature, we can reuse the same test code for them.
229     ///
230     /// This could probably be nicer with traits!
check_printer<TPrint>(printer: TPrint, left: &str, right: &str, expected: &str) where TPrint: Fn(&mut String, &str, &str) -> fmt::Result,231     fn check_printer<TPrint>(printer: TPrint, left: &str, right: &str, expected: &str)
232     where
233         TPrint: Fn(&mut String, &str, &str) -> fmt::Result,
234     {
235         let mut actual = String::new();
236         printer(&mut actual, left, right).expect("printer function failed");
237 
238         // Cannot use IO without stdlib
239         #[cfg(feature = "std")]
240         println!(
241             "## left ##\n\
242              {}\n\
243              ## right ##\n\
244              {}\n\
245              ## actual diff ##\n\
246              {}\n\
247              ## expected diff ##\n\
248              {}",
249             left, right, actual, expected
250         );
251         assert_eq!(actual, expected);
252     }
253 
254     #[test]
write_inline_diff_empty()255     fn write_inline_diff_empty() {
256         let left = "";
257         let right = "";
258         let expected = format!(
259             "{red_light}<{reset}\n\
260              {green_light}>{reset}\n",
261             red_light = RED_LIGHT,
262             green_light = GREEN_LIGHT,
263             reset = RESET,
264         );
265 
266         check_printer(write_inline_diff, left, right, &expected);
267     }
268 
269     #[test]
write_inline_diff_added()270     fn write_inline_diff_added() {
271         let left = "";
272         let right = "polymerase";
273         let expected = format!(
274             "{red_light}<{reset}\n\
275              {green_light}>{reset}{green_heavy}polymerase{reset}\n",
276             red_light = RED_LIGHT,
277             green_light = GREEN_LIGHT,
278             green_heavy = GREEN_HEAVY,
279             reset = RESET,
280         );
281 
282         check_printer(write_inline_diff, left, right, &expected);
283     }
284 
285     #[test]
write_inline_diff_removed()286     fn write_inline_diff_removed() {
287         let left = "polyacrylamide";
288         let right = "";
289         let expected = format!(
290             "{red_light}<{reset}{red_heavy}polyacrylamide{reset}\n\
291              {green_light}>{reset}\n",
292             red_light = RED_LIGHT,
293             green_light = GREEN_LIGHT,
294             red_heavy = RED_HEAVY,
295             reset = RESET,
296         );
297 
298         check_printer(write_inline_diff, left, right, &expected);
299     }
300 
301     #[test]
write_inline_diff_changed()302     fn write_inline_diff_changed() {
303         let left = "polymerase";
304         let right = "polyacrylamide";
305         let expected = format!(
306             "{red_light}<poly{reset}{red_heavy}me{reset}{red_light}ra{reset}{red_heavy}s{reset}{red_light}e{reset}\n\
307              {green_light}>poly{reset}{green_heavy}ac{reset}{green_light}r{reset}{green_heavy}yl{reset}{green_light}a{reset}{green_heavy}mid{reset}{green_light}e{reset}\n",
308             red_light = RED_LIGHT,
309             green_light = GREEN_LIGHT,
310             red_heavy = RED_HEAVY,
311             green_heavy = GREEN_HEAVY,
312             reset = RESET,
313         );
314 
315         check_printer(write_inline_diff, left, right, &expected);
316     }
317 
318     /// If one of our strings is empty, it should not be shown at all in the output.
319     #[test]
write_lines_empty_string()320     fn write_lines_empty_string() {
321         let left = "";
322         let right = "content";
323         let expected = format!(
324             "{green_light}>content{reset}\n",
325             green_light = GREEN_LIGHT,
326             reset = RESET,
327         );
328 
329         check_printer(write_lines, left, right, &expected);
330     }
331 
332     /// Realistic multiline struct diffing case.
333     #[test]
write_lines_struct()334     fn write_lines_struct() {
335         let left = r#"Some(
336     Foo {
337         lorem: "Hello World!",
338         ipsum: 42,
339         dolor: Ok(
340             "hey",
341         ),
342     },
343 )"#;
344         let right = r#"Some(
345     Foo {
346         lorem: "Hello Wrold!",
347         ipsum: 42,
348         dolor: Ok(
349             "hey ho!",
350         ),
351     },
352 )"#;
353         let expected = format!(
354             r#" Some(
355      Foo {{
356 {red_light}<        lorem: "Hello W{reset}{red_heavy}o{reset}{red_light}rld!",{reset}
357 {green_light}>        lorem: "Hello Wr{reset}{green_heavy}o{reset}{green_light}ld!",{reset}
358          ipsum: 42,
359          dolor: Ok(
360 {red_light}<            "hey",{reset}
361 {green_light}>            "hey{reset}{green_heavy} ho!{reset}{green_light}",{reset}
362          ),
363      }},
364  )
365 "#,
366             red_light = RED_LIGHT,
367             red_heavy = RED_HEAVY,
368             green_light = GREEN_LIGHT,
369             green_heavy = GREEN_HEAVY,
370             reset = RESET,
371         );
372 
373         check_printer(write_lines, left, right, &expected);
374     }
375 
376     /// Relistic multiple line chunks
377     ///
378     /// We can't support realistic line diffing in large blocks
379     /// (also, it's unclear how usefult this is)
380     ///
381     /// So if we have more than one line in a single removal chunk, disable inline diffing.
382     #[test]
write_lines_multiline_block()383     fn write_lines_multiline_block() {
384         let left = r#"Proboscis
385 Cabbage"#;
386         let right = r#"Probed
387 Caravaggio"#;
388         let expected = format!(
389             r#"{red_light}<Proboscis{reset}
390 {red_light}<Cabbage{reset}
391 {green_light}>Probed{reset}
392 {green_light}>Caravaggio{reset}
393 "#,
394             red_light = RED_LIGHT,
395             green_light = GREEN_LIGHT,
396             reset = RESET,
397         );
398 
399         check_printer(write_lines, left, right, &expected);
400     }
401 
402     /// Single deletion line, multiple insertions - no inline diffing.
403     #[test]
write_lines_multiline_insert()404     fn write_lines_multiline_insert() {
405         let left = r#"Cabbage"#;
406         let right = r#"Probed
407 Caravaggio"#;
408         let expected = format!(
409             r#"{red_light}<Cabbage{reset}
410 {green_light}>Probed{reset}
411 {green_light}>Caravaggio{reset}
412 "#,
413             red_light = RED_LIGHT,
414             green_light = GREEN_LIGHT,
415             reset = RESET,
416         );
417 
418         check_printer(write_lines, left, right, &expected);
419     }
420 
421     /// Multiple deletion, single insertion - no inline diffing.
422     #[test]
write_lines_multiline_delete()423     fn write_lines_multiline_delete() {
424         let left = r#"Proboscis
425 Cabbage"#;
426         let right = r#"Probed"#;
427         let expected = format!(
428             r#"{red_light}<Proboscis{reset}
429 {red_light}<Cabbage{reset}
430 {green_light}>Probed{reset}
431 "#,
432             red_light = RED_LIGHT,
433             green_light = GREEN_LIGHT,
434             reset = RESET,
435         );
436 
437         check_printer(write_lines, left, right, &expected);
438     }
439 
440     /// Regression test for multiline highlighting issue
441     #[test]
write_lines_issue12()442     fn write_lines_issue12() {
443         let left = r#"[
444     0,
445     0,
446     0,
447     128,
448     10,
449     191,
450     5,
451     64,
452 ]"#;
453         let right = r#"[
454     84,
455     248,
456     45,
457     64,
458 ]"#;
459         let expected = format!(
460             r#" [
461 {red_light}<    0,{reset}
462 {red_light}<    0,{reset}
463 {red_light}<    0,{reset}
464 {red_light}<    128,{reset}
465 {red_light}<    10,{reset}
466 {red_light}<    191,{reset}
467 {red_light}<    5,{reset}
468 {green_light}>    84,{reset}
469 {green_light}>    248,{reset}
470 {green_light}>    45,{reset}
471      64,
472  ]
473 "#,
474             red_light = RED_LIGHT,
475             green_light = GREEN_LIGHT,
476             reset = RESET,
477         );
478 
479         check_printer(write_lines, left, right, &expected);
480     }
481 }
482