use super::display::display_lines_from_unified_diff;
use super::parse::trim_line_ending;
use super::types::{DiffDisplayLine, DiffDocument, DiffHunk, DiffLineKind, DiffOptions};
#[must_use]
pub fn format_unified_diff(old: &str, new: &str, options: DiffOptions<'_>) -> String {
let document = DiffDocument::between(old, new, options.clone());
format_unified_hunks(&document.hunks, &options)
}
#[must_use]
pub fn format_unified_hunks(hunks: &[DiffHunk], options: &DiffOptions<'_>) -> String {
if hunks.is_empty() {
return String::new();
}
let mut output = String::new();
if let (Some(old_label), Some(new_label)) = (options.old_label, options.new_label) {
output.push_str("--- ");
output.push_str(old_label);
output.push('\n');
output.push_str("+++ ");
output.push_str(new_label);
output.push('\n');
}
for hunk in hunks {
output.push_str("@@ -");
output.push_str(&format_unified_range(hunk.old_start, hunk.old_lines));
output.push_str(" +");
output.push_str(&format_unified_range(hunk.new_start, hunk.new_lines));
output.push_str(" @@\n");
for line in &hunk.lines {
let prefix = match line.kind {
DiffLineKind::Context => ' ',
DiffLineKind::Addition => '+',
DiffLineKind::Deletion => '-',
};
output.push(prefix);
output.push_str(trim_line_ending(&line.text));
output.push('\n');
let has_line_terminator = line.text.ends_with('\n') || line.text.ends_with('\r');
if options.missing_newline_hint && !has_line_terminator {
output.push_str(r"\ No newline at end of file");
output.push('\n');
}
}
}
output
}
fn format_unified_range(start: usize, count: usize) -> String {
if count == 0 {
return format!("{},0", start.saturating_sub(1));
}
if count == 1 {
return start.to_string();
}
format!("{start},{count}")
}
#[must_use]
pub fn format_hunk_header(old_start: usize, old_count: usize, new_start: usize, new_count: usize) -> String {
format!(
"@@ -{} +{} @@",
format_unified_range(old_start, old_count),
format_unified_range(new_start, new_count)
)
}
#[must_use]
pub fn format_numbered_unified_diff(input: &str) -> Vec<String> {
let lines = display_lines_from_unified_diff(input);
let width = diff_display_line_number_width(&lines);
lines.iter().map(|line| line.numbered_text(width)).collect()
}
#[must_use]
pub fn diff_display_line_number_width(lines: &[DiffDisplayLine]) -> usize {
let maximum = lines
.iter()
.flat_map(|line| [line.old_line, line.new_line])
.flatten()
.max()
.unwrap_or_default();
decimal_digits(maximum).clamp(5, 6)
}
fn decimal_digits(mut number: u32) -> usize {
let mut digits = 1usize;
while number >= 10 {
number /= 10;
digits += 1;
}
digits
}