1use std::time::Duration;
4
5use super::format::format_hunk_header;
6use super::intraline::{annotate_word_level_diffs, annotate_word_level_diffs_with_timeout};
7use super::parse::{parse_hunk_range, parse_hunk_starts, parse_omitted_line_count, trim_line_ending};
8use super::types::{
9 DiffChangeCounts, DiffDisplayKind, DiffDisplayLine, DiffHunk, DiffLineKind, SideBySideRow, default_inline_timeout,
10};
11
12#[must_use]
14pub fn count_diff_changes(hunks: &[DiffHunk]) -> DiffChangeCounts {
15 let mut counts = DiffChangeCounts::default();
16 for line in hunks.iter().flat_map(|hunk| &hunk.lines) {
17 match line.kind {
18 DiffLineKind::Addition => counts.additions += 1,
19 DiffLineKind::Deletion => counts.deletions += 1,
20 DiffLineKind::Context => {}
21 }
22 }
23 counts
24}
25#[must_use]
27pub fn display_lines_from_hunks(hunks: &[DiffHunk]) -> Vec<DiffDisplayLine> {
28 display_lines_from_hunks_with_timeout(hunks, default_inline_timeout())
29}
30
31pub(crate) fn display_lines_from_hunks_with_timeout(
32 hunks: &[DiffHunk],
33 inline_timeout: Duration,
34) -> Vec<DiffDisplayLine> {
35 let mut output = Vec::new();
36 for hunk in hunks {
37 output.push(DiffDisplayLine::body(
38 DiffDisplayKind::HunkHeader,
39 None,
40 None,
41 format_hunk_header(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines),
42 ));
43 output.extend(hunk.lines.iter().map(|line| {
44 let kind = match line.kind {
45 DiffLineKind::Context => DiffDisplayKind::Context,
46 DiffLineKind::Addition => DiffDisplayKind::Addition,
47 DiffLineKind::Deletion => DiffDisplayKind::Deletion,
48 };
49 DiffDisplayLine::body(kind, line.old_line, line.new_line, trim_line_ending(&line.text).to_owned())
50 }));
51 }
52 annotate_word_level_diffs_with_timeout(&mut output, inline_timeout);
53 output
54}
55
56#[must_use]
58pub fn display_lines_from_unified_diff(input: &str) -> Vec<DiffDisplayLine> {
59 let mut output = Vec::new();
60 let mut old_line = 0u32;
61 let mut new_line = 0u32;
62 let mut in_hunk = false;
63 let mut remaining_old = 0usize;
64 let mut remaining_new = 0usize;
65 let mut omission_in_hunk = false;
66 let mut hunk_old_start = 0u32;
67 let mut hunk_new_start = 0u32;
68 let mut hunk_old_count = 0usize;
69 let mut hunk_new_count = 0usize;
70 let mut omitted_tail = Vec::new();
71 for raw in input.lines() {
72 if let Some((old_start, new_start)) = parse_hunk_starts(raw) {
73 assign_omitted_tail_line_numbers(
74 &mut output,
75 &omitted_tail,
76 hunk_old_start,
77 hunk_old_count,
78 hunk_new_start,
79 hunk_new_count,
80 );
81 omitted_tail.clear();
82 old_line = old_start;
83 new_line = new_start;
84 if let Some((_, old_count, _, new_count)) = parse_hunk_range(raw) {
85 remaining_old = old_count;
86 remaining_new = new_count;
87 hunk_old_count = old_count;
88 hunk_new_count = new_count;
89 }
90 hunk_old_start = old_start;
91 hunk_new_start = new_start;
92 in_hunk = true;
93 omission_in_hunk = false;
94 output.push(DiffDisplayLine::body(DiffDisplayKind::HunkHeader, None, None, raw.to_owned()));
98 } else if in_hunk && (omission_in_hunk || remaining_new > 0) && raw.starts_with('+') {
99 let output_index = output.len();
100 output.push(DiffDisplayLine::body(
101 DiffDisplayKind::Addition,
102 None,
103 (!omission_in_hunk).then_some(new_line),
104 raw[1..].to_owned(),
105 ));
106 if omission_in_hunk {
107 omitted_tail.push(output_index);
108 }
109 new_line = new_line.saturating_add(1);
110 remaining_new = remaining_new.saturating_sub(1);
111 } else if in_hunk && (omission_in_hunk || remaining_old > 0) && raw.starts_with('-') {
112 let output_index = output.len();
113 output.push(DiffDisplayLine::body(
114 DiffDisplayKind::Deletion,
115 (!omission_in_hunk).then_some(old_line),
116 None,
117 raw[1..].to_owned(),
118 ));
119 if omission_in_hunk {
120 omitted_tail.push(output_index);
121 }
122 old_line = old_line.saturating_add(1);
123 remaining_old = remaining_old.saturating_sub(1);
124 } else if in_hunk && (omission_in_hunk || (remaining_old > 0 && remaining_new > 0)) && raw.starts_with(' ') {
125 let output_index = output.len();
126 output.push(DiffDisplayLine::body(
127 DiffDisplayKind::Context,
128 (!omission_in_hunk).then_some(old_line),
129 (!omission_in_hunk).then_some(new_line),
130 raw[1..].to_owned(),
131 ));
132 if omission_in_hunk {
133 omitted_tail.push(output_index);
134 }
135 old_line = old_line.saturating_add(1);
136 new_line = new_line.saturating_add(1);
137 remaining_old = remaining_old.saturating_sub(1);
138 remaining_new = remaining_new.saturating_sub(1);
139 } else if in_hunk && let Some(omitted) = parse_omitted_line_count(raw) {
140 output.push(DiffDisplayLine::body(DiffDisplayKind::Metadata, None, None, raw.to_owned()));
141 omission_in_hunk = true;
142 let omitted = omitted.min(remaining_old).min(remaining_new);
143 old_line = old_line.saturating_add(u32::try_from(omitted).unwrap_or(u32::MAX));
144 new_line = new_line.saturating_add(u32::try_from(omitted).unwrap_or(u32::MAX));
145 remaining_old = remaining_old.saturating_sub(omitted);
146 remaining_new = remaining_new.saturating_sub(omitted);
147 } else {
148 output.push(DiffDisplayLine::body(DiffDisplayKind::Metadata, None, None, raw.to_owned()));
149 }
150 }
151 assign_omitted_tail_line_numbers(
152 &mut output,
153 &omitted_tail,
154 hunk_old_start,
155 hunk_old_count,
156 hunk_new_start,
157 hunk_new_count,
158 );
159 annotate_word_level_diffs(&mut output);
160 output
161}
162
163fn assign_omitted_tail_line_numbers(
164 lines: &mut [DiffDisplayLine],
165 tail: &[usize],
166 old_start: u32,
167 old_count: usize,
168 new_start: u32,
169 new_count: usize,
170) {
171 if tail.is_empty() {
172 return;
173 }
174 let old_tail_count = tail
175 .iter()
176 .filter(|&&index| lines[index].kind != DiffDisplayKind::Addition)
177 .count();
178 let new_tail_count = tail
179 .iter()
180 .filter(|&&index| lines[index].kind != DiffDisplayKind::Deletion)
181 .count();
182 let mut old_line = old_start
183 .saturating_add(u32::try_from(old_count).unwrap_or(u32::MAX))
184 .saturating_sub(u32::try_from(old_tail_count).unwrap_or(u32::MAX));
185 let mut new_line = new_start
186 .saturating_add(u32::try_from(new_count).unwrap_or(u32::MAX))
187 .saturating_sub(u32::try_from(new_tail_count).unwrap_or(u32::MAX));
188 for &index in tail {
189 match lines[index].kind {
190 DiffDisplayKind::Addition => {
191 lines[index].new_line = Some(new_line);
192 new_line = new_line.saturating_add(1);
193 }
194 DiffDisplayKind::Deletion => {
195 lines[index].old_line = Some(old_line);
196 old_line = old_line.saturating_add(1);
197 }
198 DiffDisplayKind::Context => {
199 lines[index].old_line = Some(old_line);
200 lines[index].new_line = Some(new_line);
201 old_line = old_line.saturating_add(1);
202 new_line = new_line.saturating_add(1);
203 }
204 DiffDisplayKind::Metadata | DiffDisplayKind::HunkHeader => {}
205 }
206 }
207}
208#[must_use]
210pub fn side_by_side_rows(lines: &[DiffDisplayLine]) -> Vec<SideBySideRow> {
211 let mut rows = Vec::new();
212 for_each_side_by_side_pair(lines, |left, right| {
213 rows.push(SideBySideRow { left: left.cloned(), right: right.cloned() });
214 });
215 rows
216}
217
218pub(crate) fn for_each_side_by_side_pair<'a, F>(lines: &'a [DiffDisplayLine], mut visit: F)
219where
220 F: FnMut(Option<&'a DiffDisplayLine>, Option<&'a DiffDisplayLine>),
221{
222 let mut index = 0usize;
223 while index < lines.len() {
224 match lines[index].kind {
225 DiffDisplayKind::HunkHeader | DiffDisplayKind::Metadata => {
226 visit(Some(&lines[index]), None);
227 index += 1;
228 }
229 DiffDisplayKind::Context => {
230 let line = &lines[index];
231 visit(Some(line), Some(line));
232 index += 1;
233 }
234 DiffDisplayKind::Deletion => {
235 let delete_start = index;
236 while index < lines.len() && lines[index].kind == DiffDisplayKind::Deletion {
237 index += 1;
238 }
239 let insert_start = index;
240 while index < lines.len() && lines[index].kind == DiffDisplayKind::Addition {
241 index += 1;
242 }
243 let delete_count = insert_start - delete_start;
244 let insert_count = index - insert_start;
245 for offset in 0..delete_count.max(insert_count) {
246 visit(
247 (offset < delete_count).then(|| &lines[delete_start + offset]),
248 (offset < insert_count).then(|| &lines[insert_start + offset]),
249 );
250 }
251 }
252 DiffDisplayKind::Addition => {
253 visit(None, Some(&lines[index]));
254 index += 1;
255 }
256 }
257 }
258}