]> git.proxmox.com Git - rustc.git/blob - vendor/pretty_assertions/src/printer.rs
New upstream version 1.67.1+dfsg1
[rustc.git] / vendor / pretty_assertions / src / printer.rs
1 #[cfg(feature = "alloc")]
2 use alloc::format;
3 use core::fmt;
4 use yansi::{
5 Color::{Fixed, Green, Red, Unset},
6 Style,
7 };
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.
19 pub(crate) fn write_header(f: &mut fmt::Formatter) -> fmt::Result {
20 writeln!(
21 f,
22 "{} {} / {} :",
23 Style::new(Unset).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 (such as an inline diff).
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.
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 or we've seen more than one line, return `None`.
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).
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)?;
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.
81 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 // 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)?;
107 writeln!(f)?;
108 }
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)?;
113 } else {
114 previous_deletion.flush(f)?;
115 paint!(f, Green, "{}{}", SIGN_RIGHT, inserted)?;
116 writeln!(f)?;
117 }
118 }
119 };
120 }
121
122 previous_deletion.flush(f)?;
123 Ok(())
124 }
125
126 /// Group character styling for an inline diff, to prevent wrapping each single
127 /// character in terminal styling codes.
128 ///
129 /// Styles are applied automatically each time a new style is given in `write_with_style`.
130 struct InlineWriter<'a, Writer> {
131 f: &'a mut Writer,
132 style: Style,
133 }
134
135 impl<'a, Writer> InlineWriter<'a, Writer>
136 where
137 Writer: fmt::Write,
138 {
139 fn new(f: &'a mut Writer) -> Self {
140 InlineWriter {
141 f,
142 style: Style::new(Unset),
143 }
144 }
145
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)?;
151 } else {
152 // Close out previous style
153 self.style.fmt_suffix(self.f)?;
154
155 // Store new style and start writing it
156 style.fmt_prefix(self.f)?;
157 write!(self.f, "{}", c)?;
158 self.style = style;
159 }
160 Ok(())
161 }
162
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)?;
167 writeln!(self.f)?;
168 self.style = Style::new(Unset);
169 Ok(())
170 }
171 }
172
173 /// Format a single line to show an inline diff of the two strings given.
174 ///
175 /// The given strings should not have a trailing newline.
176 ///
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);
181
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() {
187 match change {
188 ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
189 ::diff::Result::Left(value) => writer.write_with_style(value, heavy)?,
190 _ => (),
191 }
192 }
193 writer.finish()?;
194
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() {
200 match change {
201 ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
202 ::diff::Result::Right(value) => writer.write_with_style(value, heavy)?,
203 _ => (),
204 }
205 }
206 writer.finish()
207 }
208
209 #[cfg(test)]
210 mod test {
211 use super::*;
212
213 #[cfg(feature = "alloc")]
214 use alloc::string::String;
215
216 // ANSI terminal codes used in our outputs.
217 //
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";
224
225 /// Given that both of our diff printing functions have the same
226 /// type signature, we can reuse the same test code for them.
227 ///
228 /// This could probably be nicer with traits!
229 fn check_printer<TPrint>(printer: TPrint, left: &str, right: &str, expected: &str)
230 where
231 TPrint: Fn(&mut String, &str, &str) -> fmt::Result,
232 {
233 let mut actual = String::new();
234 printer(&mut actual, left, right).expect("printer function failed");
235
236 // Cannot use IO without stdlib
237 #[cfg(feature = "std")]
238 println!(
239 "## left ##\n\
240 {}\n\
241 ## right ##\n\
242 {}\n\
243 ## actual diff ##\n\
244 {}\n\
245 ## expected diff ##\n\
246 {}",
247 left, right, actual, expected
248 );
249 assert_eq!(actual, expected);
250 }
251
252 #[test]
253 fn write_inline_diff_empty() {
254 let left = "";
255 let right = "";
256 let expected = format!(
257 "{red_light}<{reset}\n\
258 {green_light}>{reset}\n",
259 red_light = RED_LIGHT,
260 green_light = GREEN_LIGHT,
261 reset = RESET,
262 );
263
264 check_printer(write_inline_diff, left, right, &expected);
265 }
266
267 #[test]
268 fn write_inline_diff_added() {
269 let left = "";
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,
277 reset = RESET,
278 );
279
280 check_printer(write_inline_diff, left, right, &expected);
281 }
282
283 #[test]
284 fn write_inline_diff_removed() {
285 let left = "polyacrylamide";
286 let right = "";
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,
293 reset = RESET,
294 );
295
296 check_printer(write_inline_diff, left, right, &expected);
297 }
298
299 #[test]
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,
310 reset = RESET,
311 );
312
313 check_printer(write_inline_diff, left, right, &expected);
314 }
315
316 /// If one of our strings is empty, it should not be shown at all in the output.
317 #[test]
318 fn write_lines_empty_string() {
319 let left = "";
320 let right = "content";
321 let expected = format!(
322 "{green_light}>content{reset}\n",
323 green_light = GREEN_LIGHT,
324 reset = RESET,
325 );
326
327 check_printer(write_lines, left, right, &expected);
328 }
329
330 /// Realistic multiline struct diffing case.
331 #[test]
332 fn write_lines_struct() {
333 let left = r#"Some(
334 Foo {
335 lorem: "Hello World!",
336 ipsum: 42,
337 dolor: Ok(
338 "hey",
339 ),
340 },
341 )"#;
342 let right = r#"Some(
343 Foo {
344 lorem: "Hello Wrold!",
345 ipsum: 42,
346 dolor: Ok(
347 "hey ho!",
348 ),
349 },
350 )"#;
351 let expected = format!(
352 r#" Some(
353 Foo {{
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}
356 ipsum: 42,
357 dolor: Ok(
358 {red_light}< "hey",{reset}
359 {green_light}> "hey{reset}{green_heavy} ho!{reset}{green_light}",{reset}
360 ),
361 }},
362 )
363 "#,
364 red_light = RED_LIGHT,
365 red_heavy = RED_HEAVY,
366 green_light = GREEN_LIGHT,
367 green_heavy = GREEN_HEAVY,
368 reset = RESET,
369 );
370
371 check_printer(write_lines, left, right, &expected);
372 }
373
374 /// Relistic multiple line chunks
375 ///
376 /// We can't support realistic line diffing in large blocks
377 /// (also, it's unclear how usefult this is)
378 ///
379 /// So if we have more than one line in a single removal chunk, disable inline diffing.
380 #[test]
381 fn write_lines_multiline_block() {
382 let left = r#"Proboscis
383 Cabbage"#;
384 let right = r#"Probed
385 Caravaggio"#;
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}
391 "#,
392 red_light = RED_LIGHT,
393 green_light = GREEN_LIGHT,
394 reset = RESET,
395 );
396
397 check_printer(write_lines, left, right, &expected);
398 }
399
400 /// Single deletion line, multiple insertions - no inline diffing.
401 #[test]
402 fn write_lines_multiline_insert() {
403 let left = r#"Cabbage"#;
404 let right = r#"Probed
405 Caravaggio"#;
406 let expected = format!(
407 r#"{red_light}<Cabbage{reset}
408 {green_light}>Probed{reset}
409 {green_light}>Caravaggio{reset}
410 "#,
411 red_light = RED_LIGHT,
412 green_light = GREEN_LIGHT,
413 reset = RESET,
414 );
415
416 check_printer(write_lines, left, right, &expected);
417 }
418
419 /// Multiple deletion, single insertion - no inline diffing.
420 #[test]
421 fn write_lines_multiline_delete() {
422 let left = r#"Proboscis
423 Cabbage"#;
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}
429 "#,
430 red_light = RED_LIGHT,
431 green_light = GREEN_LIGHT,
432 reset = RESET,
433 );
434
435 check_printer(write_lines, left, right, &expected);
436 }
437
438 /// Regression test for multiline highlighting issue
439 #[test]
440 fn write_lines_issue12() {
441 let left = r#"[
442 0,
443 0,
444 0,
445 128,
446 10,
447 191,
448 5,
449 64,
450 ]"#;
451 let right = r#"[
452 84,
453 248,
454 45,
455 64,
456 ]"#;
457 let expected = format!(
458 r#" [
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}
469 64,
470 ]
471 "#,
472 red_light = RED_LIGHT,
473 green_light = GREEN_LIGHT,
474 reset = RESET,
475 );
476
477 check_printer(write_lines, left, right, &expected);
478 }
479
480 mod write_lines_edge_newlines {
481 use super::*;
482
483 #[test]
484 fn both_trailing() {
485 let left = "fan\n";
486 let right = "mug\n";
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}
492
493 "#,
494 red_light = RED_LIGHT,
495 red_heavy = RED_HEAVY,
496 green_light = GREEN_LIGHT,
497 green_heavy = GREEN_HEAVY,
498 reset = RESET,
499 );
500
501 check_printer(write_lines, left, right, &expected);
502 }
503
504 #[test]
505 fn both_leading() {
506 let left = "\nfan";
507 let right = "\nmug";
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!(
511 r#"
512 {red_light}<{reset}{red_heavy}fan{reset}
513 {green_light}>{reset}{green_heavy}mug{reset}
514 "#,
515 red_light = RED_LIGHT,
516 red_heavy = RED_HEAVY,
517 green_light = GREEN_LIGHT,
518 green_heavy = GREEN_HEAVY,
519 reset = RESET,
520 );
521
522 check_printer(write_lines, left, right, &expected);
523 }
524
525 #[test]
526 fn leading_added() {
527 let left = "fan";
528 let right = "\nmug";
529 let expected = format!(
530 r#"{red_light}<fan{reset}
531 {green_light}>{reset}
532 {green_light}>mug{reset}
533 "#,
534 red_light = RED_LIGHT,
535 green_light = GREEN_LIGHT,
536 reset = RESET,
537 );
538
539 check_printer(write_lines, left, right, &expected);
540 }
541
542 #[test]
543 fn leading_deleted() {
544 let left = "\nfan";
545 let right = "mug";
546 let expected = format!(
547 r#"{red_light}<{reset}
548 {red_light}<fan{reset}
549 {green_light}>mug{reset}
550 "#,
551 red_light = RED_LIGHT,
552 green_light = GREEN_LIGHT,
553 reset = RESET,
554 );
555
556 check_printer(write_lines, left, right, &expected);
557 }
558
559 #[test]
560 fn trailing_added() {
561 let left = "fan";
562 let right = "mug\n";
563 let expected = format!(
564 r#"{red_light}<fan{reset}
565 {green_light}>mug{reset}
566 {green_light}>{reset}
567 "#,
568 red_light = RED_LIGHT,
569 green_light = GREEN_LIGHT,
570 reset = RESET,
571 );
572
573 check_printer(write_lines, left, right, &expected);
574 }
575
576 /// Regression test for double abort
577 ///
578 /// See: https://github.com/rust-pretty-assertions/rust-pretty-assertions/issues/96
579 #[test]
580 fn trailing_deleted() {
581 // The below inputs caused an abort via double panic
582 // we panicked at 'insertion followed by deletion'
583 let left = "fan\n";
584 let right = "mug";
585 let expected = format!(
586 r#"{red_light}<{reset}{red_heavy}fan{reset}
587 {green_light}>{reset}{green_heavy}mug{reset}
588 {red_light}<{reset}
589 "#,
590 red_light = RED_LIGHT,
591 red_heavy = RED_HEAVY,
592 green_light = GREEN_LIGHT,
593 green_heavy = GREEN_HEAVY,
594 reset = RESET,
595 );
596
597 check_printer(write_lines, left, right, &expected);
598 }
599 }
600 }