1#[cfg(feature = "alloc")]
2use alloc::format;
3use ansi_term::{
4 Colour::{Fixed, Green, Red},
5 Style,
6};
7use core::fmt;
8
9macro_rules! paint {
10 ($f:expr, $colour:expr, $fmt:expr, $($args:tt)*) => (
11 write!($f, "{}", $colour.paint(format!($fmt, $($args)*)))
12 )
13}
14
15const SIGN_RIGHT: char = '>'; const SIGN_LEFT: char = '<'; 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#[derive(Default)]
34struct LatentDeletion<'a> {
35 value: Option<&'a str>,
37 count: usize,
39}
40
41impl<'a> LatentDeletion<'a> {
42 fn set(&mut self, value: &'a str) {
44 self.value = Some(value);
45 self.count += 1;
46 }
47
48 fn take(&mut self) -> Option<&'a str> {
52 if self.count == 1 {
53 self.value.take()
54 } else {
55 None
56 }
57 }
58
59 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
76pub(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 (::diff::Result::Both(value, _), _) => {
95 previous_deletion.flush(f)?;
96 writeln!(f, " {}", value)?;
97 }
98 (::diff::Result::Left(deleted), _) => {
100 previous_deletion.flush(f)?;
101 previous_deletion.set(deleted);
102 }
103 (::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 (::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
126struct InlineWriter<'a, Writer> {
131 f: &'a mut Writer,
132 style: Style,
133}
134
135impl<'a, Writer> InlineWriter<'a, Writer>
136where
137 Writer: fmt::Write,
138{
139 fn new(f: &'a mut Writer) -> Self {
140 InlineWriter {
141 f,
142 style: Style::new(),
143 }
144 }
145
146 fn write_with_style(&mut self, c: &char, style: Style) -> fmt::Result {
148 if style == self.style {
150 write!(self.f, "{}", c)?;
151 } else {
152 write!(self.f, "{}", self.style.suffix())?;
154
155 write!(self.f, "{}{}", style.prefix(), c)?;
157 self.style = style;
158 }
159 Ok(())
160 }
161
162 fn finish(&mut self) -> fmt::Result {
164 writeln!(self.f, "{}", self.style.suffix())?;
166 self.style = Default::default();
167 Ok(())
168 }
169}
170
171fn write_inline_diff<TWrite: fmt::Write>(f: &mut TWrite, left: &str, right: &str) -> fmt::Result {
177 let diff = ::diff::chars(left, right);
178 let mut writer = InlineWriter::new(f);
179
180 let light = Red.into();
182 let heavy = Red.on(Fixed(52)).bold();
183 writer.write_with_style(&SIGN_LEFT, light)?;
184 for change in diff.iter() {
185 match change {
186 ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
187 ::diff::Result::Left(value) => writer.write_with_style(value, heavy)?,
188 _ => (),
189 }
190 }
191 writer.finish()?;
192
193 let light = Green.into();
195 let heavy = Green.on(Fixed(22)).bold();
196 writer.write_with_style(&SIGN_RIGHT, light)?;
197 for change in diff.iter() {
198 match change {
199 ::diff::Result::Both(value, _) => writer.write_with_style(value, light)?,
200 ::diff::Result::Right(value) => writer.write_with_style(value, heavy)?,
201 _ => (),
202 }
203 }
204 writer.finish()
205}
206
207#[cfg(test)]
208mod test {
209 use super::*;
210
211 #[cfg(feature = "alloc")]
212 use alloc::string::String;
213
214 const RED_LIGHT: &str = "\u{1b}[31m";
218 const GREEN_LIGHT: &str = "\u{1b}[32m";
219 const RED_HEAVY: &str = "\u{1b}[1;48;5;52;31m";
220 const GREEN_HEAVY: &str = "\u{1b}[1;48;5;22;32m";
221 const RESET: &str = "\u{1b}[0m";
222
223 fn check_printer<TPrint>(printer: TPrint, left: &str, right: &str, expected: &str)
228 where
229 TPrint: Fn(&mut String, &str, &str) -> fmt::Result,
230 {
231 let mut actual = String::new();
232 printer(&mut actual, left, right).expect("printer function failed");
233
234 #[cfg(feature = "std")]
236 println!(
237 "## left ##\n\
238 {}\n\
239 ## right ##\n\
240 {}\n\
241 ## actual diff ##\n\
242 {}\n\
243 ## expected diff ##\n\
244 {}",
245 left, right, actual, expected
246 );
247 assert_eq!(actual, expected);
248 }
249
250 #[test]
251 fn write_inline_diff_empty() {
252 let left = "";
253 let right = "";
254 let expected = format!(
255 "{red_light}<{reset}\n\
256 {green_light}>{reset}\n",
257 red_light = RED_LIGHT,
258 green_light = GREEN_LIGHT,
259 reset = RESET,
260 );
261
262 check_printer(write_inline_diff, left, right, &expected);
263 }
264
265 #[test]
266 fn write_inline_diff_added() {
267 let left = "";
268 let right = "polymerase";
269 let expected = format!(
270 "{red_light}<{reset}\n\
271 {green_light}>{reset}{green_heavy}polymerase{reset}\n",
272 red_light = RED_LIGHT,
273 green_light = GREEN_LIGHT,
274 green_heavy = GREEN_HEAVY,
275 reset = RESET,
276 );
277
278 check_printer(write_inline_diff, left, right, &expected);
279 }
280
281 #[test]
282 fn write_inline_diff_removed() {
283 let left = "polyacrylamide";
284 let right = "";
285 let expected = format!(
286 "{red_light}<{reset}{red_heavy}polyacrylamide{reset}\n\
287 {green_light}>{reset}\n",
288 red_light = RED_LIGHT,
289 green_light = GREEN_LIGHT,
290 red_heavy = RED_HEAVY,
291 reset = RESET,
292 );
293
294 check_printer(write_inline_diff, left, right, &expected);
295 }
296
297 #[test]
298 fn write_inline_diff_changed() {
299 let left = "polymerase";
300 let right = "polyacrylamide";
301 let expected = format!(
302 "{red_light}<poly{reset}{red_heavy}me{reset}{red_light}ra{reset}{red_heavy}s{reset}{red_light}e{reset}\n\
303 {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",
304 red_light = RED_LIGHT,
305 green_light = GREEN_LIGHT,
306 red_heavy = RED_HEAVY,
307 green_heavy = GREEN_HEAVY,
308 reset = RESET,
309 );
310
311 check_printer(write_inline_diff, left, right, &expected);
312 }
313
314 #[test]
316 fn write_lines_empty_string() {
317 let left = "";
318 let right = "content";
319 let expected = format!(
320 "{green_light}>content{reset}\n",
321 green_light = GREEN_LIGHT,
322 reset = RESET,
323 );
324
325 check_printer(write_lines, left, right, &expected);
326 }
327
328 #[test]
330 fn write_lines_struct() {
331 let left = r#"Some(
332 Foo {
333 lorem: "Hello World!",
334 ipsum: 42,
335 dolor: Ok(
336 "hey",
337 ),
338 },
339)"#;
340 let right = r#"Some(
341 Foo {
342 lorem: "Hello Wrold!",
343 ipsum: 42,
344 dolor: Ok(
345 "hey ho!",
346 ),
347 },
348)"#;
349 let expected = format!(
350 r#" Some(
351 Foo {{
352{red_light}< lorem: "Hello W{reset}{red_heavy}o{reset}{red_light}rld!",{reset}
353{green_light}> lorem: "Hello Wr{reset}{green_heavy}o{reset}{green_light}ld!",{reset}
354 ipsum: 42,
355 dolor: Ok(
356{red_light}< "hey",{reset}
357{green_light}> "hey{reset}{green_heavy} ho!{reset}{green_light}",{reset}
358 ),
359 }},
360 )
361"#,
362 red_light = RED_LIGHT,
363 red_heavy = RED_HEAVY,
364 green_light = GREEN_LIGHT,
365 green_heavy = GREEN_HEAVY,
366 reset = RESET,
367 );
368
369 check_printer(write_lines, left, right, &expected);
370 }
371
372 #[test]
379 fn write_lines_multiline_block() {
380 let left = r#"Proboscis
381Cabbage"#;
382 let right = r#"Probed
383Caravaggio"#;
384 let expected = format!(
385 r#"{red_light}<Proboscis{reset}
386{red_light}<Cabbage{reset}
387{green_light}>Probed{reset}
388{green_light}>Caravaggio{reset}
389"#,
390 red_light = RED_LIGHT,
391 green_light = GREEN_LIGHT,
392 reset = RESET,
393 );
394
395 check_printer(write_lines, left, right, &expected);
396 }
397
398 #[test]
400 fn write_lines_multiline_insert() {
401 let left = r#"Cabbage"#;
402 let right = r#"Probed
403Caravaggio"#;
404 let expected = format!(
405 r#"{red_light}<Cabbage{reset}
406{green_light}>Probed{reset}
407{green_light}>Caravaggio{reset}
408"#,
409 red_light = RED_LIGHT,
410 green_light = GREEN_LIGHT,
411 reset = RESET,
412 );
413
414 check_printer(write_lines, left, right, &expected);
415 }
416
417 #[test]
419 fn write_lines_multiline_delete() {
420 let left = r#"Proboscis
421Cabbage"#;
422 let right = r#"Probed"#;
423 let expected = format!(
424 r#"{red_light}<Proboscis{reset}
425{red_light}<Cabbage{reset}
426{green_light}>Probed{reset}
427"#,
428 red_light = RED_LIGHT,
429 green_light = GREEN_LIGHT,
430 reset = RESET,
431 );
432
433 check_printer(write_lines, left, right, &expected);
434 }
435
436 #[test]
438 fn write_lines_issue12() {
439 let left = r#"[
440 0,
441 0,
442 0,
443 128,
444 10,
445 191,
446 5,
447 64,
448]"#;
449 let right = r#"[
450 84,
451 248,
452 45,
453 64,
454]"#;
455 let expected = format!(
456 r#" [
457{red_light}< 0,{reset}
458{red_light}< 0,{reset}
459{red_light}< 0,{reset}
460{red_light}< 128,{reset}
461{red_light}< 10,{reset}
462{red_light}< 191,{reset}
463{red_light}< 5,{reset}
464{green_light}> 84,{reset}
465{green_light}> 248,{reset}
466{green_light}> 45,{reset}
467 64,
468 ]
469"#,
470 red_light = RED_LIGHT,
471 green_light = GREEN_LIGHT,
472 reset = RESET,
473 );
474
475 check_printer(write_lines, left, right, &expected);
476 }
477
478 mod write_lines_edge_newlines {
479 use super::*;
480
481 #[test]
482 fn both_trailing() {
483 let left = "fan\n";
484 let right = "mug\n";
485 let expected = format!(
488 r#"{red_light}<{reset}{red_heavy}fan{reset}
489{green_light}>{reset}{green_heavy}mug{reset}
490
491"#,
492 red_light = RED_LIGHT,
493 red_heavy = RED_HEAVY,
494 green_light = GREEN_LIGHT,
495 green_heavy = GREEN_HEAVY,
496 reset = RESET,
497 );
498
499 check_printer(write_lines, left, right, &expected);
500 }
501
502 #[test]
503 fn both_leading() {
504 let left = "\nfan";
505 let right = "\nmug";
506 let expected = format!(
509 r#"
510{red_light}<{reset}{red_heavy}fan{reset}
511{green_light}>{reset}{green_heavy}mug{reset}
512"#,
513 red_light = RED_LIGHT,
514 red_heavy = RED_HEAVY,
515 green_light = GREEN_LIGHT,
516 green_heavy = GREEN_HEAVY,
517 reset = RESET,
518 );
519
520 check_printer(write_lines, left, right, &expected);
521 }
522
523 #[test]
524 fn leading_added() {
525 let left = "fan";
526 let right = "\nmug";
527 let expected = format!(
528 r#"{red_light}<fan{reset}
529{green_light}>{reset}
530{green_light}>mug{reset}
531"#,
532 red_light = RED_LIGHT,
533 green_light = GREEN_LIGHT,
534 reset = RESET,
535 );
536
537 check_printer(write_lines, left, right, &expected);
538 }
539
540 #[test]
541 fn leading_deleted() {
542 let left = "\nfan";
543 let right = "mug";
544 let expected = format!(
545 r#"{red_light}<{reset}
546{red_light}<fan{reset}
547{green_light}>mug{reset}
548"#,
549 red_light = RED_LIGHT,
550 green_light = GREEN_LIGHT,
551 reset = RESET,
552 );
553
554 check_printer(write_lines, left, right, &expected);
555 }
556
557 #[test]
558 fn trailing_added() {
559 let left = "fan";
560 let right = "mug\n";
561 let expected = format!(
562 r#"{red_light}<fan{reset}
563{green_light}>mug{reset}
564{green_light}>{reset}
565"#,
566 red_light = RED_LIGHT,
567 green_light = GREEN_LIGHT,
568 reset = RESET,
569 );
570
571 check_printer(write_lines, left, right, &expected);
572 }
573
574 #[test]
578 fn trailing_deleted() {
579 let left = "fan\n";
582 let right = "mug";
583 let expected = format!(
584 r#"{red_light}<{reset}{red_heavy}fan{reset}
585{green_light}>{reset}{green_heavy}mug{reset}
586{red_light}<{reset}
587"#,
588 red_light = RED_LIGHT,
589 red_heavy = RED_HEAVY,
590 green_light = GREEN_LIGHT,
591 green_heavy = GREEN_HEAVY,
592 reset = RESET,
593 );
594
595 check_printer(write_lines, left, right, &expected);
596 }
597 }
598}