1 #[cfg(feature = "alloc")]
5 Color
::{Fixed, Green, Red, Unset}
,
10 ($f
:expr
, $colour
:expr
, $fmt
:expr
, $
($args
:tt
)*) => (
11 write
!($f
, "{}", $colour
.paint(format
!($fmt
, $
($args
)*)))
15 const SIGN_RIGHT
: char = '
>'
; // + > →
16 const SIGN_LEFT
: char = '
<'
; // - < ←
18 /// Present the diff output for two mutliline strings in a pretty, colorised manner.
19 pub(crate) fn write_header(f
: &mut fmt
::Formatter
) -> fmt
::Result
{
23 Style
::new(Unset
).bold().paint("Diff"),
24 Red
.paint(format
!("{} left", SIGN_LEFT
)),
25 Green
.paint(format
!("right {}", SIGN_RIGHT
))
29 /// Delay formatting this deleted chunk until later.
31 /// It can be formatted as a whole chunk by calling `flush`, or the inner value
32 /// obtained with `take` for further processing (such as an inline diff).
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
41 impl<'a
> LatentDeletion
<'a
> {
42 /// Set the chunk value.
43 fn set(&mut self, value
: &'a
str) {
44 self.value
= Some(value
);
48 /// Take the underlying chunk value, if it's suitable for inline diffing.
50 /// If there is no value or we've seen more than one line, return `None`.
51 fn take(&mut self) -> Option
<&'a
str> {
59 /// If a value is set, print it as a whole chunk, using the given formatter.
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).
63 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
)?
;
77 // https://github.com/johannhof/difference.rs/blob/c5749ad7d82aa3d480c15cb61af9f6baa08f116f/examples/github-style.rs
78 // Credits johannhof (MIT License)
80 /// Present the diff output for two mutliline strings in a pretty, colorised manner.
81 pub(crate) fn write_lines
<TWrite
: fmt
::Write
>(
86 let diff
= ::diff
::lines(left
, right
);
88 let mut changes
= diff
.into_iter().peekable();
89 let mut previous_deletion
= LatentDeletion
::default();
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
)?
;
98 // Defer any deletions to next loop
99 (::diff
::Result
::Left(deleted
), _
) => {
100 previous_deletion
.flush(f
)?
;
101 previous_deletion
.set(deleted
);
103 // If we're being followed by more insertions, don't inline diff
104 (::diff
::Result
::Right(inserted
), Some(::diff
::Result
::Right(_
))) => {
105 previous_deletion
.flush(f
)?
;
106 paint
!(f
, Green
, "{}{}", SIGN_RIGHT
, inserted
)?
;
109 // Otherwise, check if we need to inline diff with the previous line (if it was a deletion)
110 (::diff
::Result
::Right(inserted
), _
) => {
111 if let Some(deleted
) = previous_deletion
.take() {
112 write_inline_diff(f
, deleted
, inserted
)?
;
114 previous_deletion
.flush(f
)?
;
115 paint
!(f
, Green
, "{}{}", SIGN_RIGHT
, inserted
)?
;
122 previous_deletion
.flush(f
)?
;
126 /// Group character styling for an inline diff, to prevent wrapping each single
127 /// character in terminal styling codes.
129 /// Styles are applied automatically each time a new style is given in `write_with_style`.
130 struct InlineWriter
<'a
, Writer
> {
135 impl<'a
, Writer
> InlineWriter
<'a
, Writer
>
139 fn new(f
: &'a
mut Writer
) -> Self {
142 style
: Style
::new(Unset
),
146 /// Push a new character into the buffer, specifying the style it should be written in.
147 fn write_with_style(&mut self, c
: &char, style
: Style
) -> fmt
::Result
{
148 // If the style is the same as previously, just write character
149 if style
== self.style
{
150 write
!(self.f
, "{}", c
)?
;
152 // Close out previous style
153 self.style
.fmt_suffix(self.f
)?
;
155 // Store new style and start writing it
156 style
.fmt_prefix(self.f
)?
;
157 write
!(self.f
, "{}", c
)?
;
163 /// Finish any existing style and reset to default state.
164 fn finish(&mut self) -> fmt
::Result
{
165 // Close out previous style
166 self.style
.fmt_suffix(self.f
)?
;
168 self.style
= Style
::new(Unset
);
173 /// Format a single line to show an inline diff of the two strings given.
175 /// The given strings should not have a trailing newline.
177 /// The output of this function will be two lines, each with a trailing newline.
178 fn write_inline_diff
<TWrite
: fmt
::Write
>(f
: &mut TWrite
, left
: &str, right
: &str) -> fmt
::Result
{
179 let diff
= ::diff
::chars(left
, right
);
180 let mut writer
= InlineWriter
::new(f
);
182 // Print the left string on one line, with differences highlighted
183 let light
= Style
::new(Red
);
184 let heavy
= Style
::new(Red
).bg(Fixed(52)).bold();
185 writer
.write_with_style(&SIGN_LEFT
, light
)?
;
186 for change
in diff
.iter() {
188 ::diff
::Result
::Both(value
, _
) => writer
.write_with_style(value
, light
)?
,
189 ::diff
::Result
::Left(value
) => writer
.write_with_style(value
, heavy
)?
,
195 // Print the right string on one line, with differences highlighted
196 let light
= Style
::new(Green
);
197 let heavy
= Style
::new(Green
).bg(Fixed(22)).bold();
198 writer
.write_with_style(&SIGN_RIGHT
, light
)?
;
199 for change
in diff
.iter() {
201 ::diff
::Result
::Both(value
, _
) => writer
.write_with_style(value
, light
)?
,
202 ::diff
::Result
::Right(value
) => writer
.write_with_style(value
, heavy
)?
,
213 #[cfg(feature = "alloc")]
214 use alloc
::string
::String
;
216 // ANSI terminal codes used in our outputs.
218 // Interpolate these into test strings to make expected values easier to read.
219 const RED_LIGHT
: &str = "\u{1b}[31m";
220 const GREEN_LIGHT
: &str = "\u{1b}[32m";
221 const RED_HEAVY
: &str = "\u{1b}[1;48;5;52;31m";
222 const GREEN_HEAVY
: &str = "\u{1b}[1;48;5;22;32m";
223 const RESET
: &str = "\u{1b}[0m";
225 /// Given that both of our diff printing functions have the same
226 /// type signature, we can reuse the same test code for them.
228 /// This could probably be nicer with traits!
229 fn check_printer
<TPrint
>(printer
: TPrint
, left
: &str, right
: &str, expected
: &str)
231 TPrint
: Fn(&mut String
, &str, &str) -> fmt
::Result
,
233 let mut actual
= String
::new();
234 printer(&mut actual
, left
, right
).expect("printer function failed");
236 // Cannot use IO without stdlib
237 #[cfg(feature = "std")]
245 ## expected diff ##\n\
247 left
, right
, actual
, expected
249 assert_eq
!(actual
, expected
);
253 fn write_inline_diff_empty() {
256 let expected
= format
!(
257 "{red_light}<{reset}\n\
258 {green_light}>{reset}\n",
259 red_light
= RED_LIGHT
,
260 green_light
= GREEN_LIGHT
,
264 check_printer(write_inline_diff
, left
, right
, &expected
);
268 fn write_inline_diff_added() {
270 let right
= "polymerase";
271 let expected
= format
!(
272 "{red_light}<{reset}\n\
273 {green_light}>{reset}{green_heavy}polymerase{reset}\n",
274 red_light
= RED_LIGHT
,
275 green_light
= GREEN_LIGHT
,
276 green_heavy
= GREEN_HEAVY
,
280 check_printer(write_inline_diff
, left
, right
, &expected
);
284 fn write_inline_diff_removed() {
285 let left
= "polyacrylamide";
287 let expected
= format
!(
288 "{red_light}<{reset}{red_heavy}polyacrylamide{reset}\n\
289 {green_light}>{reset}\n",
290 red_light
= RED_LIGHT
,
291 green_light
= GREEN_LIGHT
,
292 red_heavy
= RED_HEAVY
,
296 check_printer(write_inline_diff
, left
, right
, &expected
);
300 fn write_inline_diff_changed() {
301 let left
= "polymerase";
302 let right
= "polyacrylamide";
303 let expected
= format
!(
304 "{red_light}<poly{reset}{red_heavy}me{reset}{red_light}ra{reset}{red_heavy}s{reset}{red_light}e{reset}\n\
305 {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",
306 red_light
= RED_LIGHT
,
307 green_light
= GREEN_LIGHT
,
308 red_heavy
= RED_HEAVY
,
309 green_heavy
= GREEN_HEAVY
,
313 check_printer(write_inline_diff
, left
, right
, &expected
);
316 /// If one of our strings is empty, it should not be shown at all in the output.
318 fn write_lines_empty_string() {
320 let right
= "content";
321 let expected
= format
!(
322 "{green_light}>content{reset}\n",
323 green_light
= GREEN_LIGHT
,
327 check_printer(write_lines
, left
, right
, &expected
);
330 /// Realistic multiline struct diffing case.
332 fn write_lines_struct() {
335 lorem: "Hello World!",
344 lorem: "Hello Wrold!",
351 let expected
= format
!(
354 {red_light}< lorem: "Hello W{reset}{red_heavy}o{reset}{red_light}rld!",{reset}
355 {green_light}> lorem: "Hello Wr{reset}{green_heavy}o{reset}{green_light}ld!",{reset}
358 {red_light}< "hey",{reset}
359 {green_light}> "hey{reset}{green_heavy} ho!{reset}{green_light}",{reset}
364 red_light
= RED_LIGHT
,
365 red_heavy
= RED_HEAVY
,
366 green_light
= GREEN_LIGHT
,
367 green_heavy
= GREEN_HEAVY
,
371 check_printer(write_lines
, left
, right
, &expected
);
374 /// Relistic multiple line chunks
376 /// We can't support realistic line diffing in large blocks
377 /// (also, it's unclear how usefult this is)
379 /// So if we have more than one line in a single removal chunk, disable inline diffing.
381 fn write_lines_multiline_block() {
382 let left
= r
#"Proboscis
384 let right
= r
#"Probed
386 let expected
= format
!(
387 r
#"{red_light}<Proboscis{reset}
388 {red_light}<Cabbage{reset}
389 {green_light}>Probed{reset}
390 {green_light}>Caravaggio{reset}
392 red_light
= RED_LIGHT
,
393 green_light
= GREEN_LIGHT
,
397 check_printer(write_lines
, left
, right
, &expected
);
400 /// Single deletion line, multiple insertions - no inline diffing.
402 fn write_lines_multiline_insert() {
403 let left
= r
#"Cabbage"#;
404 let right
= r
#"Probed
406 let expected
= format
!(
407 r
#"{red_light}<Cabbage{reset}
408 {green_light}>Probed{reset}
409 {green_light}>Caravaggio{reset}
411 red_light
= RED_LIGHT
,
412 green_light
= GREEN_LIGHT
,
416 check_printer(write_lines
, left
, right
, &expected
);
419 /// Multiple deletion, single insertion - no inline diffing.
421 fn write_lines_multiline_delete() {
422 let left
= r
#"Proboscis
424 let right
= r
#"Probed"#;
425 let expected
= format
!(
426 r
#"{red_light}<Proboscis{reset}
427 {red_light}<Cabbage{reset}
428 {green_light}>Probed{reset}
430 red_light
= RED_LIGHT
,
431 green_light
= GREEN_LIGHT
,
435 check_printer(write_lines
, left
, right
, &expected
);
438 /// Regression test for multiline highlighting issue
440 fn write_lines_issue12() {
457 let expected
= format
!(
459 {red_light}< 0,{reset}
460 {red_light}< 0,{reset}
461 {red_light}< 0,{reset}
462 {red_light}< 128,{reset}
463 {red_light}< 10,{reset}
464 {red_light}< 191,{reset}
465 {red_light}< 5,{reset}
466 {green_light}> 84,{reset}
467 {green_light}> 248,{reset}
468 {green_light}> 45,{reset}
472 red_light
= RED_LIGHT
,
473 green_light
= GREEN_LIGHT
,
477 check_printer(write_lines
, left
, right
, &expected
);
480 mod write_lines_edge_newlines
{
487 // Note the additional space at the bottom is caused by a trailing newline
488 // adding an additional line with zero content to both sides of the diff
489 let expected
= format
!(
490 r
#"{red_light}<{reset}{red_heavy}fan{reset}
491 {green_light}>{reset}{green_heavy}mug{reset}
494 red_light
= RED_LIGHT
,
495 red_heavy
= RED_HEAVY
,
496 green_light
= GREEN_LIGHT
,
497 green_heavy
= GREEN_HEAVY
,
501 check_printer(write_lines
, left
, right
, &expected
);
508 // Note the additional space at the top is caused by a leading newline
509 // adding an additional line with zero content to both sides of the diff
510 let expected
= format
!(
512 {red_light}<{reset}{red_heavy}fan{reset}
513 {green_light}>{reset}{green_heavy}mug{reset}
515 red_light
= RED_LIGHT
,
516 red_heavy
= RED_HEAVY
,
517 green_light
= GREEN_LIGHT
,
518 green_heavy
= GREEN_HEAVY
,
522 check_printer(write_lines
, left
, right
, &expected
);
529 let expected
= format
!(
530 r
#"{red_light}<fan{reset}
531 {green_light}>{reset}
532 {green_light}>mug{reset}
534 red_light
= RED_LIGHT
,
535 green_light
= GREEN_LIGHT
,
539 check_printer(write_lines
, left
, right
, &expected
);
543 fn leading_deleted() {
546 let expected
= format
!(
547 r
#"{red_light}<{reset}
548 {red_light}<fan{reset}
549 {green_light}>mug{reset}
551 red_light
= RED_LIGHT
,
552 green_light
= GREEN_LIGHT
,
556 check_printer(write_lines
, left
, right
, &expected
);
560 fn trailing_added() {
563 let expected
= format
!(
564 r
#"{red_light}<fan{reset}
565 {green_light}>mug{reset}
566 {green_light}>{reset}
568 red_light
= RED_LIGHT
,
569 green_light
= GREEN_LIGHT
,
573 check_printer(write_lines
, left
, right
, &expected
);
576 /// Regression test for double abort
578 /// See: https://github.com/rust-pretty-assertions/rust-pretty-assertions/issues/96
580 fn trailing_deleted() {
581 // The below inputs caused an abort via double panic
582 // we panicked at 'insertion followed by deletion'
585 let expected
= format
!(
586 r
#"{red_light}<{reset}{red_heavy}fan{reset}
587 {green_light}>{reset}{green_heavy}mug{reset}
590 red_light
= RED_LIGHT
,
591 red_heavy
= RED_HEAVY
,
592 green_light
= GREEN_LIGHT
,
593 green_heavy
= GREEN_HEAVY
,
597 check_printer(write_lines
, left
, right
, &expected
);