use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HunkKind {
Add,
Change,
Delete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineOrigin {
Context,
Addition,
Deletion,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DiffLine {
pub origin: LineOrigin,
pub old_lineno: Option<usize>,
pub new_lineno: Option<usize>,
pub text: Vec<u8>,
pub has_newline: bool,
}
impl DiffLine {
pub fn text_str(&self) -> std::borrow::Cow<'_, str> {
String::from_utf8_lossy(&self.text)
}
pub fn bytes_with_terminator(&self) -> Vec<u8> {
let mut b = self.text.clone();
if self.has_newline {
b.push(b'\n');
}
b
}
}
#[derive(Debug, Clone)]
pub struct Hunk {
pub kind: HunkKind,
pub new_start: usize,
pub new_count: usize,
pub old_start: usize,
pub old_count: usize,
pub lines: Vec<DiffLine>,
}
#[derive(Debug, Clone)]
pub struct FileDiff {
pub path: PathBuf,
pub hunks: Vec<Hunk>,
pub added: usize,
pub deleted: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sign {
AddOrChange,
DeleteAfter,
}
impl Hunk {
pub fn signs(&self) -> Vec<(usize, Sign)> {
let mut out = Vec::new();
let mut nl = self.new_start;
for line in &self.lines {
match line.origin {
LineOrigin::Addition => {
out.push((nl, Sign::AddOrChange));
nl += 1;
}
LineOrigin::Deletion => out.push((nl, Sign::DeleteAfter)),
LineOrigin::Context => nl += 1,
}
}
out
}
pub fn changed_region(&self) -> (usize, usize, usize, usize) {
let mut nl = self.new_start;
let mut ol = self.old_start;
let mut new_lines = Vec::new();
let mut old_lines = Vec::new();
for line in &self.lines {
match line.origin {
LineOrigin::Addition => {
new_lines.push(nl);
nl += 1;
}
LineOrigin::Deletion => {
old_lines.push(ol);
ol += 1;
}
LineOrigin::Context => {
nl += 1;
ol += 1;
}
}
}
let new_first = new_lines.first().copied().unwrap_or(nl);
let old_first = old_lines.first().copied().unwrap_or(ol);
(new_first, new_lines.len(), old_first, old_lines.len())
}
pub fn covers(&self, line_1based: usize, total_lines: usize) -> bool {
self.signs().iter().any(|&(l, kind)| match kind {
Sign::AddOrChange => l == line_1based,
Sign::DeleteAfter => l.min(total_lines) == line_1based,
})
}
pub fn header(&self) -> String {
format!(
"@@ -{},{} +{},{} @@",
self.old_start, self.old_count, self.new_start, self.new_count
)
}
pub fn build(
old_start: usize,
old_count: usize,
new_start: usize,
new_count: usize,
lines: Vec<DiffLine>,
) -> Self {
let has_add = lines.iter().any(|l| l.origin == LineOrigin::Addition);
let has_del = lines.iter().any(|l| l.origin == LineOrigin::Deletion);
let kind = match (has_add, has_del) {
(true, false) => HunkKind::Add,
(false, true) => HunkKind::Delete,
_ => HunkKind::Change,
};
Hunk {
kind,
new_start,
new_count,
old_start,
old_count,
lines,
}
}
}