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