use ansi_term::Colour::{Fixed, Green, Red}; | |
use ansi_term::Style; | |
use difference::{Changeset, Difference}; | |
use std::fmt; | |
macro_rules! paint { | |
($f:ident, $colour:expr, $fmt:expr, $($args:tt)*) => ( | |
write!($f, "{}", $colour.paint(format!($fmt, $($args)*))) | |
) | |
} | |
const SIGN_RIGHT: char = '>'; // + > → | |
const SIGN_LEFT: char = '<'; // - < ← | |
// Adapted from: | |
// https://github.com/johannhof/difference.rs/blob/c5749ad7d82aa3d480c15cb61af9f6baa08f116f/examples/github-style.rs | |
// Credits johannhof (MIT License) | |
pub fn format_changeset(f: &mut fmt::Formatter, changeset: &Changeset) -> fmt::Result { | |
let ref diffs = changeset.diffs; | |
writeln!( | |
f, | |
"{} {} / {} :", | |
Style::new().bold().paint("Diff"), | |
Red.paint(format!("{} left", SIGN_LEFT)), | |
Green.paint(format!("right {}", SIGN_RIGHT)) | |
)?; | |
for i in 0..diffs.len() { | |
match diffs[i] { | |
Difference::Same(ref same) => { | |
// Have to split line by line in order to have the extra whitespace | |
// at the beginning. | |
for line in same.split('\n') { | |
writeln!(f, " {}", line)?; | |
} | |
} | |
Difference::Add(ref added) => { | |
let prev = i.checked_sub(1).and_then(|x| diffs.get(x)); | |
match prev { | |
Some(&Difference::Rem(ref removed)) => { | |
// The addition is preceded by an removal. | |
// | |
// Let's highlight the character-differences in this replaced | |
// chunk. Note that this chunk can span over multiple lines. | |
format_replacement(f, added, removed)?; | |
} | |
_ => { | |
for line in added.split('\n') { | |
paint!(f, Green, "{}{}\n", SIGN_RIGHT, line)?; | |
} | |
} | |
}; | |
} | |
Difference::Rem(ref removed) => { | |
let next = i.checked_add(1).and_then(|x| diffs.get(x)); | |
match next { | |
Some(&Difference::Add(_)) => { | |
// The removal is followed by an addition. | |
// | |
// ... we'll handle both in the next iteration. | |
} | |
_ => { | |
for line in removed.split('\n') { | |
paint!(f, Red, "{}{}\n", SIGN_LEFT, line)?; | |
} | |
} | |
} | |
} | |
} | |
} | |
Ok(()) | |
} | |
macro_rules! join { | |
( | |
$elem:ident in ($iter:expr) { | |
$( $body:tt )* | |
} seperated by { | |
$( $separator:tt )* | |
} | |
) => ( | |
let mut iter = $iter; | |
if let Some($elem) = iter.next() { | |
$( $body )* | |
} | |
for $elem in iter { | |
$( $separator )* | |
$( $body )* | |
} | |
) | |
} | |
pub fn format_replacement(f: &mut fmt::Write, added: &str, removed: &str) -> fmt::Result { | |
let Changeset { diffs, .. } = Changeset::new(removed, added, ""); | |
// LEFT side (==what's been) | |
paint!(f, Red, "{}", SIGN_LEFT)?; | |
for c in &diffs { | |
match *c { | |
Difference::Same(ref word_diff) => { | |
join!(chunk in (word_diff.split('\n')) { | |
paint!(f, Red, "{}", chunk)?; | |
} seperated by { | |
writeln!(f)?; | |
paint!(f, Red, "{}", SIGN_LEFT)?; | |
}); | |
} | |
Difference::Rem(ref word_diff) => { | |
join!(chunk in (word_diff.split('\n')) { | |
paint!(f, Red.on(Fixed(52)).bold(), "{}", chunk)?; | |
} seperated by { | |
writeln!(f)?; | |
paint!(f, Red.bold(), "{}", SIGN_LEFT)?; | |
}); | |
} | |
_ => (), | |
} | |
} | |
writeln!(f, "")?; | |
// RIGHT side (==what's new) | |
paint!(f, Green, "{}", SIGN_RIGHT)?; | |
for c in &diffs { | |
match *c { | |
Difference::Same(ref word_diff) => { | |
join!(chunk in (word_diff.split('\n')) { | |
paint!(f, Green, "{}", chunk)?; | |
} seperated by { | |
writeln!(f)?; | |
paint!(f, Green, "{}", SIGN_RIGHT)?; | |
}); | |
} | |
Difference::Add(ref word_diff) => { | |
join!(chunk in (word_diff.split('\n')) { | |
paint!(f, Green.on(Fixed(22)).bold(), "{}", chunk)?; | |
} seperated by { | |
writeln!(f)?; | |
paint!(f, Green.bold(), "{}", SIGN_RIGHT)?; | |
}); | |
} | |
_ => (), | |
} | |
} | |
writeln!(f, "") | |
} | |
#[test] | |
fn test_format_replacement() { | |
let added = " 84,\ | |
\n 248,"; | |
let removed = " 0,\ | |
\n 0,\ | |
\n 128,"; | |
let mut buf = String::new(); | |
let _ = format_replacement(&mut buf, added, removed); | |
println!( | |
"## removed ##\ | |
\n{}\ | |
\n## added ##\ | |
\n{}\ | |
\n## diff ##\ | |
\n{}", | |
removed, added, buf | |
); | |
assert_eq!( | |
buf, | |
"\u{1b}[31m<\u{1b}[0m\u{1b}[31m \u{1b}[0m\u{1b}[1;48;5;52;31m0\u{1b}[0m\u{1b}[31m,\u{1b}[0m\n\u{1b}[31m<\u{1b}[0m\u{1b}[31m \u{1b}[0m\u{1b}[1;48;5;52;31m0,\u{1b}[0m\n\u{1b}[1;31m<\u{1b}[0m\u{1b}[1;48;5;52;31m 1\u{1b}[0m\u{1b}[31m2\u{1b}[0m\u{1b}[31m8,\u{1b}[0m\n\u{1b}[32m>\u{1b}[0m\u{1b}[32m \u{1b}[0m\u{1b}[1;48;5;22;32m84\u{1b}[0m\u{1b}[32m,\u{1b}[0m\n\u{1b}[32m>\u{1b}[0m\u{1b}[32m \u{1b}[0m\u{1b}[32m2\u{1b}[0m\u{1b}[1;48;5;22;32m4\u{1b}[0m\u{1b}[32m8,\u{1b}[0m\n" | |
); | |
} |