]>
git.proxmox.com Git - rustc.git/blob - vendor/pretty_assertions-0.7.2/src/printer.rs
2 Colour
::{Fixed, Green, Red}
,
8 ($f
:expr
, $colour
:expr
, $fmt
:expr
, $
($args
:tt
)*) => (
9 write
!($f
, "{}", $colour
.paint(format
!($fmt
, $
($args
)*)))
13 const SIGN_RIGHT
: char = '
>'
; // + > →
14 const SIGN_LEFT
: char = '
<'
; // - < ←
16 /// Present the diff output for two mutliline strings in a pretty, colorised manner.
17 pub(crate) fn write_header(f
: &mut fmt
::Formatter
) -> fmt
::Result
{
21 Style
::new().bold().paint("Diff"),
22 Red
.paint(format
!("{} left", SIGN_LEFT
)),
23 Green
.paint(format
!("right {}", SIGN_RIGHT
))
27 /// Delay formatting this deleted chunk until later.
29 /// It can be formatted as a whole chunk by calling `flush`, or the inner value
30 /// obtained with `take` for further processing.
32 struct 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
39 impl<'a
> LatentDeletion
<'a
> {
40 /// Set the chunk value.
41 fn set(&mut self, value
: &'a
str) {
42 self.value
= Some(value
);
46 /// Take the underlying chunk value, if it's suitable for inline diffing.
48 /// If there is no value of we've seen more than one line, return `None`.
49 fn take(&mut self) -> Option
<&'a
str> {
57 /// If a value is set, print it as a whole chunk, using the given formatter.
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
)?
;
75 // https://github.com/johannhof/difference.rs/blob/c5749ad7d82aa3d480c15cb61af9f6baa08f116f/examples/github-style.rs
76 // Credits johannhof (MIT License)
78 /// Present the diff output for two mutliline strings in a pretty, colorised manner.
79 pub(crate) fn write_lines
<TWrite
: fmt
::Write
>(
84 let diff
= ::diff
::lines(left
, right
);
86 let mut changes
= diff
.into_iter().peekable();
87 let mut previous_deletion
= LatentDeletion
::default();
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
)?
;
96 // Defer any deletions to next loop
97 (::diff
::Result
::Left(deleted
), _
) => {
98 previous_deletion
.flush(f
)?
;
99 previous_deletion
.set(deleted
);
101 // Underlying diff library should never return this sequence
102 (::diff
::Result
::Right(_
), Some(::diff
::Result
::Left(_
))) => {
103 panic
!("insertion followed by deletion");
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
)?
;
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
)?
;
116 previous_deletion
.flush(f
)?
;
117 paint
!(f
, Green
, "{}{}", SIGN_RIGHT
, inserted
)?
;
124 previous_deletion
.flush(f
)?
;
128 /// Group character styling for an inline diff, to prevent wrapping each single
129 /// character in terminal styling codes.
131 /// Styles are applied automatically each time a new style is given in `write_with_style`.
132 struct InlineWriter
<'a
, Writer
> {
137 impl<'a
, Writer
> InlineWriter
<'a
, Writer
>
141 fn new(f
: &'a
mut Writer
) -> Self {
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
)?
;
154 // Close out previous style
155 write
!(self.f
, "{}", self.style
.suffix())?
;
157 // Store new style and start writing it
158 write
!(self.f
, "{}{}", style
.prefix(), c
)?
;
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();
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
= Red
.into();
184 let heavy
= Red
.on(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
= Green
.into();
197 let heavy
= Green
.on(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 // ANSI terminal codes used in our outputs.
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";
222 /// Given that both of our diff printing functions have the same
223 /// type signature, we can reuse the same test code for them.
225 /// This could probably be nicer with traits!
226 fn check_printer
<TPrint
>(printer
: TPrint
, left
: &str, right
: &str, expected
: &str)
228 TPrint
: Fn(&mut String
, &str, &str) -> fmt
::Result
,
230 let mut actual
= String
::new();
231 printer(&mut actual
, left
, right
).expect("printer function failed");
240 ## expected diff ##\n\
242 left
, right
, actual
, expected
244 assert_eq
!(actual
, expected
);
248 fn write_inline_diff_empty() {
251 let expected
= format
!(
252 "{red_light}<{reset}\n\
253 {green_light}>{reset}\n",
254 red_light
= RED_LIGHT
,
255 green_light
= GREEN_LIGHT
,
259 check_printer(write_inline_diff
, left
, right
, &expected
);
263 fn write_inline_diff_added() {
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
,
275 check_printer(write_inline_diff
, left
, right
, &expected
);
279 fn write_inline_diff_removed() {
280 let left
= "polyacrylamide";
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
,
291 check_printer(write_inline_diff
, left
, right
, &expected
);
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
,
308 check_printer(write_inline_diff
, left
, right
, &expected
);
311 /// If one of our strings is empty, it should not be shown at all in the output.
313 fn write_lines_empty_string() {
315 let right
= "content";
316 let expected
= format
!(
317 "{green_light}>content{reset}\n",
318 green_light
= GREEN_LIGHT
,
322 check_printer(write_lines
, left
, right
, &expected
);
325 /// Realistic multiline struct diffing case.
327 fn write_lines_struct() {
330 lorem: "Hello World!",
339 lorem: "Hello Wrold!",
346 let expected
= format
!(
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}
353 {red_light}< "hey",{reset}
354 {green_light}> "hey{reset}{green_heavy} ho!{reset}{green_light}",{reset}
359 red_light
= RED_LIGHT
,
360 red_heavy
= RED_HEAVY
,
361 green_light
= GREEN_LIGHT
,
362 green_heavy
= GREEN_HEAVY
,
366 check_printer(write_lines
, left
, right
, &expected
);
369 /// Relistic multiple line chunks
371 /// We can't support realistic line diffing in large blocks
372 /// (also, it's unclear how usefult this is)
374 /// So if we have more than one line in a single removal chunk, disable inline diffing.
376 fn write_lines_multiline_block() {
377 let left
= r
#"Proboscis
379 let right
= r
#"Probed
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}
387 red_light
= RED_LIGHT
,
388 green_light
= GREEN_LIGHT
,
392 check_printer(write_lines
, left
, right
, &expected
);
395 /// Single deletion line, multiple insertions - no inline diffing.
397 fn write_lines_multiline_insert() {
398 let left
= r
#"Cabbage"#;
399 let right
= r
#"Probed
401 let expected
= format
!(
402 r
#"{red_light}<Cabbage{reset}
403 {green_light}>Probed{reset}
404 {green_light}>Caravaggio{reset}
406 red_light
= RED_LIGHT
,
407 green_light
= GREEN_LIGHT
,
411 check_printer(write_lines
, left
, right
, &expected
);
414 /// Multiple deletion, single insertion - no inline diffing.
416 fn write_lines_multiline_delete() {
417 let left
= r
#"Proboscis
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}
425 red_light
= RED_LIGHT
,
426 green_light
= GREEN_LIGHT
,
430 check_printer(write_lines
, left
, right
, &expected
);
433 /// Regression test for multiline highlighting issue
435 fn write_lines_issue12() {
452 let expected
= format
!(
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}
467 red_light
= RED_LIGHT
,
468 green_light
= GREEN_LIGHT
,
472 check_printer(write_lines
, left
, right
, &expected
);