use colored::Colorize;
use difference::{Changeset, Difference};
use std::fmt;
#[derive(PartialEq, Debug, Clone)]
enum Mode {
Same,
Add,
Rem,
}
#[derive(PartialEq, Debug, Clone)]
struct Lineno(Option<usize>);
impl fmt::Display for Lineno {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
None => f.pad(""),
Some(lineno) => fmt::Display::fmt(&lineno.to_string().dimmed(), f),
}
}
}
#[derive(PartialEq, Debug, Clone)]
struct PrintInfo<'a>(Mode, Lineno, Lineno, &'a str);
impl PrintInfo<'_> {
fn to_diff(&self) -> String {
let PrintInfo(node, line_a, line_b, line) = self;
let (diff_sign, col_line) = match node {
Mode::Add => ("+".green(), line.green()),
Mode::Rem => ("-".red(), line.red()),
Mode::Same => {
let trimmed = line
.get(..80)
.map(|sl| sl.to_owned() + " ...")
.unwrap_or_else(|| line.to_string());
(" ".normal(), trimmed.dimmed())
}
};
format!("{:>5} {:>3}│{} {}\n", line_a, line_b, diff_sign, col_line)
}
}
fn diff_with_lineno(changes: &Changeset) -> Vec<PrintInfo> {
let mut line_a = 0;
let mut line_b = 0;
changes
.diffs
.iter()
.flat_map(|diff| match diff {
Difference::Same(x) => x
.split('\n')
.map(|line| {
line_a += 1;
line_b += 1;
PrintInfo(
Mode::Same,
Lineno(Some(line_a)),
Lineno(Some(line_b)),
line.trim_end(),
)
})
.collect::<Vec<PrintInfo>>(),
Difference::Add(x) => x
.split('\n')
.map(|line| {
line_b += 1;
PrintInfo(
Mode::Add,
Lineno(None),
Lineno(Some(line_b)),
line.trim_end(),
)
})
.collect::<Vec<PrintInfo>>(),
Difference::Rem(x) => x
.split('\n')
.map(|line| {
line_a += 1;
PrintInfo(
Mode::Rem,
Lineno(Some(line_a)),
Lineno(None),
line.trim_end(),
)
})
.collect::<Vec<PrintInfo>>(),
})
.collect()
}
fn get_chunks<'a>(print_info: &'a [PrintInfo]) -> Vec<Vec<PrintInfo<'a>>> {
use Mode as M;
let mut printable: Vec<Vec<PrintInfo>> = Vec::new();
let ctx_size = 2;
let mut running = false;
let mut end_window = ctx_size;
let mut cur_slice: Vec<PrintInfo> = Vec::new();
for (idx, node) in print_info.iter().enumerate() {
if !running && node.0 == M::Same {
}
else if !running && node.0 != M::Same {
running = true;
print_info[idx.saturating_sub(ctx_size)..idx]
.iter()
.for_each(|el| cur_slice.push(el.clone()));
cur_slice.push(node.clone());
}
else if running && node.0 != M::Same {
cur_slice.push(node.clone());
end_window = ctx_size;
}
else if running && node.0 == M::Same && end_window != 0 {
end_window -= 1;
cur_slice.push(node.clone());
}
else if end_window == 0 {
running = false;
printable.push(cur_slice);
cur_slice = Vec::new();
} else {
}
}
if !cur_slice.is_empty() {
printable.push(cur_slice);
}
printable
}
pub fn gen_diff(org: &str, new: &str) -> String {
let changes = &Changeset::new(org, new, "\n");
let print_info = diff_with_lineno(changes);
let print_chunks = get_chunks(&print_info);
let mut str_buf = String::new();
str_buf.push_str(format!("{:>9}~\n", " ").as_str());
for chunk in print_chunks {
for info in chunk {
str_buf.push_str(&info.to_diff());
}
str_buf.push_str(format!("{:>9}~\n", " ").as_str());
}
str_buf.trim_end().to_string()
}