1use super::display::display_lines_from_unified_diff;
4use super::parse::trim_line_ending;
5use super::types::{DiffDisplayLine, DiffDocument, DiffHunk, DiffLineKind, DiffOptions};
6
7#[must_use]
14pub fn format_unified_diff(old: &str, new: &str, options: DiffOptions<'_>) -> String {
15 let document = DiffDocument::between(old, new, options.clone());
16 format_unified_hunks(&document.hunks, &options)
17}
18
19#[must_use]
21pub fn format_unified_hunks(hunks: &[DiffHunk], options: &DiffOptions<'_>) -> String {
22 if hunks.is_empty() {
23 return String::new();
24 }
25
26 let mut output = String::new();
27 if let (Some(old_label), Some(new_label)) = (options.old_label, options.new_label) {
28 output.push_str("--- ");
29 output.push_str(old_label);
30 output.push('\n');
31 output.push_str("+++ ");
32 output.push_str(new_label);
33 output.push('\n');
34 }
35
36 for hunk in hunks {
37 output.push_str("@@ -");
38 output.push_str(&format_unified_range(hunk.old_start, hunk.old_lines));
39 output.push_str(" +");
40 output.push_str(&format_unified_range(hunk.new_start, hunk.new_lines));
41 output.push_str(" @@\n");
42 for line in &hunk.lines {
43 let prefix = match line.kind {
44 DiffLineKind::Context => ' ',
45 DiffLineKind::Addition => '+',
46 DiffLineKind::Deletion => '-',
47 };
48 output.push(prefix);
49 output.push_str(trim_line_ending(&line.text));
50 output.push('\n');
51
52 let has_line_terminator = line.text.ends_with('\n') || line.text.ends_with('\r');
53 if options.missing_newline_hint && !has_line_terminator {
54 output.push_str(r"\ No newline at end of file");
55 output.push('\n');
56 }
57 }
58 }
59 output
60}
61
62fn format_unified_range(start: usize, count: usize) -> String {
63 if count == 0 {
64 return format!("{},0", start.saturating_sub(1));
65 }
66 if count == 1 {
67 return start.to_string();
68 }
69 format!("{start},{count}")
70}
71
72#[must_use]
78pub fn format_hunk_header(old_start: usize, old_count: usize, new_start: usize, new_count: usize) -> String {
79 format!(
80 "@@ -{} +{} @@",
81 format_unified_range(old_start, old_count),
82 format_unified_range(new_start, new_count)
83 )
84}
85#[must_use]
87pub fn format_numbered_unified_diff(input: &str) -> Vec<String> {
88 let lines = display_lines_from_unified_diff(input);
89 let width = diff_display_line_number_width(&lines);
90 lines.iter().map(|line| line.numbered_text(width)).collect()
91}
92
93#[must_use]
95pub fn diff_display_line_number_width(lines: &[DiffDisplayLine]) -> usize {
96 let maximum = lines
97 .iter()
98 .flat_map(|line| [line.old_line, line.new_line])
99 .flatten()
100 .max()
101 .unwrap_or_default();
102 decimal_digits(maximum).clamp(5, 6)
103}
104fn decimal_digits(mut number: u32) -> usize {
105 let mut digits = 1usize;
106 while number >= 10 {
107 number /= 10;
108 digits += 1;
109 }
110 digits
111}