use std::io;
use std::ops::Range;
use super::function::{FunctionLines, Matcher};
use crate::content_diff::TextDiff;
pub fn write_hunks(
out: impl io::Write,
text: &TextDiff<'_>,
context_lines: usize,
) -> io::Result<()> {
write_hunks_with(out, text, context_lines, &FunctionLines::default())
}
pub fn write_hunks_with(
mut out: impl io::Write,
text: &TextDiff<'_>,
context_lines: usize,
function: &FunctionLines,
) -> io::Result<()> {
let (old, new, edits) = (text.old_bytes(), text.new_bytes(), text.edits());
let mut function = FunctionContext::new(function.matcher());
let mut index = 0;
while index < edits.len() {
let mut end = index;
while end + 1 < edits.len()
&& edits[end + 1].old_lines.start - edits[end].old_lines.end
<= context_lines.saturating_mul(2)
{
end += 1;
}
let (first, last) = (&edits[index], &edits[end]);
let leading = context_lines.min(first.old_lines.start);
let start = lines_before(old, first.old_bytes.start, leading);
let (finish, trailing) = lines_after(old, last.old_bytes.end, context_lines);
let old_start = first.old_lines.start - leading;
let new_start = first.new_lines.start - leading;
let old_count = last.old_lines.end + trailing - old_start;
let new_count = last.new_lines.end + trailing - new_start;
write!(out, "@@ -")?;
write_range(&mut out, old_start, old_count)?;
write!(out, " +")?;
write_range(&mut out, new_start, new_count)?;
write!(out, " @@")?;
if let Some(name) = function.before(old, start) {
out.write_all(b" ")?;
out.write_all(name)?;
}
out.write_all(b"\n")?;
let mut position = start;
for edit in &edits[index..=end] {
write_lines(&mut out, b' ', &old[position..edit.old_bytes.start])?;
write_lines(&mut out, b'-', &old[edit.old_bytes.clone()])?;
write_lines(&mut out, b'+', &new[edit.new_bytes.clone()])?;
position = edit.old_bytes.end;
}
write_lines(&mut out, b' ', &old[position..finish])?;
index = end + 1;
}
Ok(())
}
fn write_range(out: &mut impl io::Write, start: usize, count: usize) -> io::Result<()> {
match count {
0 => write!(out, "{start},0"),
1 => write!(out, "{}", start + 1),
_ => write!(out, "{},{count}", start + 1),
}
}
fn write_lines(out: &mut impl io::Write, marker: u8, bytes: &[u8]) -> io::Result<()> {
for line in bytes.split_inclusive(|byte| *byte == b'\n') {
out.write_all(&[marker])?;
out.write_all(line)?;
if !line.ends_with(b"\n") {
out.write_all(b"\n\\ No newline at end of file\n")?;
}
}
Ok(())
}
fn lines_before(bytes: &[u8], end: usize, count: usize) -> usize {
let mut start = end;
for _ in 0..count {
start = bytes[..start - 1]
.iter()
.rposition(|byte| *byte == b'\n')
.map_or(0, |lf| lf + 1);
}
start
}
fn lines_after(bytes: &[u8], start: usize, count: usize) -> (usize, usize) {
let mut end = start;
let mut lines = 0;
while lines < count && end < bytes.len() {
end = bytes[end..]
.iter()
.position(|byte| *byte == b'\n')
.map_or(bytes.len(), |lf| end + lf + 1);
lines += 1;
}
(end, lines)
}
struct FunctionContext<'a> {
matcher: Matcher<'a>,
searched: usize,
found: Option<Range<usize>>,
}
impl<'a> FunctionContext<'a> {
fn new(matcher: Matcher<'a>) -> Self {
Self {
matcher,
searched: 0,
found: None,
}
}
fn before<'b>(&mut self, bytes: &'b [u8], start: usize) -> Option<&'b [u8]> {
let mut end = start;
while end > self.searched {
let line_start = bytes[..end - 1]
.iter()
.rposition(|byte| *byte == b'\n')
.map_or(0, |lf| lf + 1);
let line = &bytes[line_start..end - 1];
if let Some(name) = self.matcher.name(line) {
self.found = Some(line_start + name.start..line_start + name.end);
break;
}
end = line_start;
}
self.searched = start;
Some(&bytes[self.found.clone()?])
}
}