1use regex::Regex;
4use std::collections::HashMap;
5
6use crate::diff_utils::DiffUtils;
7use crate::patch::change_delta::ChangeDelta;
8use crate::patch::chunk::Chunk;
9use crate::patch::delta::Delta;
10use crate::patch::Patch;
11
12const NULL_FILE_INDICATOR: &str = "/dev/null";
13
14pub struct UnifiedDiffUtils;
16
17impl UnifiedDiffUtils {
18 pub fn parse_unified_diff(diff: &[String]) -> Patch<String> {
20 let chunk_regex =
21 Regex::new(r"^@@\s+-(\d+)(?:,(\d+))?\s+\+(\d+)(?:,(\d+))?\s+@@.*$").unwrap();
22
23 let mut in_prelude = true;
24 let mut raw_chunk: Vec<(String, String)> = Vec::new();
25 let mut patch = Patch::new();
26
27 let mut old_ln: usize = 0;
28 let mut new_ln: usize = 0;
29
30 for line in diff {
31 if in_prelude {
32 if line.starts_with("+++") {
33 in_prelude = false;
34 }
35 continue;
36 }
37
38 if let Some(caps) = chunk_regex.captures(line) {
39 Self::process_lines_in_prev_chunk(&mut raw_chunk, &mut patch, old_ln, new_ln);
40
41 old_ln = caps
42 .get(1)
43 .map_or(1, |m| m.as_str().parse::<usize>().unwrap_or(1));
44 new_ln = caps
45 .get(3)
46 .map_or(1, |m| m.as_str().parse::<usize>().unwrap_or(1));
47
48 if old_ln == 0 {
49 old_ln = 1;
50 }
51 if new_ln == 0 {
52 new_ln = 1;
53 }
54 } else if !line.is_empty() {
55 let tag = &line[0..1];
56 let rest = &line[1..];
57 if tag == " " || tag == "+" || tag == "-" {
58 raw_chunk.push((tag.to_string(), rest.to_string()));
59 }
60 } else {
61 raw_chunk.push((" ".to_string(), String::new()));
62 }
63 }
64
65 Self::process_lines_in_prev_chunk(&mut raw_chunk, &mut patch, old_ln, new_ln);
66
67 patch
68 }
69
70 fn process_lines_in_prev_chunk(
71 raw_chunk: &mut Vec<(String, String)>,
72 patch: &mut Patch<String>,
73 old_ln: usize,
74 new_ln: usize,
75 ) {
76 if raw_chunk.is_empty() {
77 return;
78 }
79
80 let mut old_chunk_lines = Vec::new();
81 let mut new_chunk_lines = Vec::new();
82 let mut remove_position = Vec::new();
83 let mut add_position = Vec::new();
84
85 let mut remove_num = 0usize;
86 let mut add_num = 0usize;
87
88 for (tag, rest) in raw_chunk.iter() {
89 if tag == " " || tag == "-" {
90 remove_num += 1;
91 old_chunk_lines.push(rest.clone());
92 if tag == "-" {
93 remove_position.push((old_ln - 1) + remove_num);
94 }
95 }
96 if tag == " " || tag == "+" {
97 add_num += 1;
98 new_chunk_lines.push(rest.clone());
99 if tag == "+" {
100 add_position.push((new_ln - 1) + add_num);
101 }
102 }
103 }
104
105 let source_chunk = Chunk::new(
106 old_ln.saturating_sub(1),
107 old_chunk_lines,
108 Some(remove_position),
109 );
110 let target_chunk = Chunk::new(
111 new_ln.saturating_sub(1),
112 new_chunk_lines,
113 Some(add_position),
114 );
115
116 let delta = ChangeDelta::new(source_chunk, target_chunk);
117 patch.add_delta(delta);
118 raw_chunk.clear();
119 }
120
121 pub fn generate_unified_diff(
123 original_file_name: Option<&str>,
124 revised_file_name: Option<&str>,
125 original_lines: &[String],
126 patch: &Patch<String>,
127 context_size: usize,
128 ) -> Vec<String> {
129 let patch_deltas = patch.deltas();
130 if patch_deltas.is_empty() {
131 return Vec::new();
132 }
133
134 let mut ret = Vec::new();
135 ret.push(format!(
136 "--- {}",
137 original_file_name.unwrap_or(NULL_FILE_INDICATOR)
138 ));
139 ret.push(format!(
140 "+++ {}",
141 revised_file_name.unwrap_or(NULL_FILE_INDICATOR)
142 ));
143
144 let mut deltas: Vec<&Delta<String>> = Vec::new();
145 let mut delta = patch_deltas[0].as_ref();
146 deltas.push(delta);
147
148 if patch_deltas.len() > 1 {
149 for next_delta_box in patch_deltas.iter().skip(1) {
150 let position = delta.source().position();
151 let next_delta = next_delta_box;
152
153 if (position + delta.source().size() + context_size)
154 >= next_delta.source().position().saturating_sub(context_size)
155 {
156 deltas.push(next_delta);
157 } else {
158 let cur_block = Self::process_deltas(original_lines, &deltas, context_size);
159 ret.extend(cur_block);
160 deltas.clear();
161 deltas.push(next_delta);
162 }
163 delta = next_delta;
164 }
165 }
166
167 let cur_block = Self::process_deltas(original_lines, &deltas, context_size);
168 ret.extend(cur_block);
169
170 ret
171 }
172
173 fn process_deltas(
174 orig_lines: &[String],
175 deltas: &[&Delta<String>],
176 context_size: usize,
177 ) -> Vec<String> {
178 let mut buffer = Vec::new();
179 let mut orig_total = 0usize;
180 let mut rev_total = 0usize;
181
182 let cur_delta = deltas[0];
183
184 let context_start = cur_delta.source().position().saturating_sub(context_size);
185
186 for line in context_start..cur_delta.source().position().min(orig_lines.len()) {
187 buffer.push(format!(" {}", orig_lines[line]));
188 orig_total += 1;
189 rev_total += 1;
190 }
191
192 buffer.extend(Self::get_delta_text(cur_delta));
193 orig_total += cur_delta.source().lines().len();
194 rev_total += cur_delta.target().lines().len();
195
196 let mut last_delta = cur_delta;
197 for &next_delta in deltas.iter().skip(1) {
198 let intermediate_start =
199 last_delta.source().position() + last_delta.source().lines().len();
200
201 for line in intermediate_start..next_delta.source().position().min(orig_lines.len()) {
202 buffer.push(format!(" {}", orig_lines[line]));
203 orig_total += 1;
204 rev_total += 1;
205 }
206
207 buffer.extend(Self::get_delta_text(next_delta));
208 orig_total += next_delta.source().lines().len();
209 rev_total += next_delta.target().lines().len();
210 last_delta = next_delta;
211 }
212
213 let post_context_start = last_delta.source().position() + last_delta.source().lines().len();
214 let post_context_end = (post_context_start + context_size).min(orig_lines.len());
215
216 for line in post_context_start..post_context_end {
217 buffer.push(format!(" {}", orig_lines[line]));
218 orig_total += 1;
219 rev_total += 1;
220 }
221
222 let orig_start = if orig_total == 0 {
223 0
224 } else {
225 let pos_plus_one = cur_delta.source().position() + 1;
226 if pos_plus_one > context_size {
227 pos_plus_one - context_size
228 } else {
229 1
230 }
231 };
232
233 let rev_start = if rev_total == 0 {
234 0
235 } else {
236 let rev_pos_plus_one = cur_delta.target().position() + 1;
237 if rev_pos_plus_one > context_size {
238 rev_pos_plus_one - context_size
239 } else {
240 1
241 }
242 };
243
244 let header = format!(
245 "@@ -{},{} +{},{} @@",
246 orig_start, orig_total, rev_start, rev_total
247 );
248 buffer.insert(0, header);
249
250 buffer
251 }
252
253 fn get_delta_text(delta: &Delta<String>) -> Vec<String> {
254 let mut buffer = Vec::new();
255 for line in delta.source().lines() {
256 buffer.push(format!("-{}", line));
257 }
258 for line in delta.target().lines() {
259 buffer.push(format!("+{}", line));
260 }
261 buffer
262 }
263
264 pub fn generate_original_and_diff(
266 original: &[String],
267 revised: &[String],
268 original_file_name: Option<&str>,
269 revised_file_name: Option<&str>,
270 ) -> Vec<String> {
271 let orig_name = original_file_name.unwrap_or("original");
272 let rev_name = revised_file_name.unwrap_or("revised");
273
274 let patch = DiffUtils::diff(original, revised, None);
275 let mut unified_diff =
276 Self::generate_unified_diff(Some(orig_name), Some(rev_name), original, &patch, 0);
277
278 if unified_diff.is_empty() {
279 unified_diff.push(format!("--- {}", orig_name));
280 unified_diff.push(format!("+++ {}", rev_name));
281 unified_diff.push("@@ -0,0 +0,0 @@".to_string());
282 } else if unified_diff.len() >= 3 && !unified_diff[2].contains("@@ -1,") {
283 unified_diff.insert(2, "@@ -0,0 +0,0 @@".to_string());
284 }
285
286 let original_with_prefix: Vec<String> =
287 original.iter().map(|v| format!(" {}", v)).collect();
288 Self::insert_orig(&original_with_prefix, &unified_diff)
289 }
290
291 fn insert_orig(original: &[String], unified_diff: &[String]) -> Vec<String> {
292 let mut result = Vec::new();
293 let mut diff_list: Vec<Vec<String>> = Vec::new();
294 let mut diff = Vec::new();
295
296 for (i, u) in unified_diff.iter().enumerate() {
297 if u.starts_with("@@") && u != "@@ -0,0 +0,0 @@" && !u.contains("@@ -1,") {
298 diff_list.push(diff.clone());
299 diff.clear();
300 diff.push(u.clone());
301 continue;
302 }
303 if i == unified_diff.len() - 1 {
304 diff.push(u.clone());
305 diff_list.push(diff.clone());
306 diff.clear();
307 break;
308 }
309 diff.push(u.clone());
310 }
311
312 Self::insert_orig_chunks(&diff_list, &mut result, original);
313 result
314 }
315
316 fn insert_orig_chunks(
317 diff_list: &[Vec<String>],
318 result: &mut Vec<String>,
319 original: &[String],
320 ) {
321 for (i, diff) in diff_list.iter().enumerate() {
322 let nex_diff = diff_list.get(i + 1);
323 let simb = if i == 0 { &diff[2] } else { &diff[0] };
324 let nex_simb = nex_diff.map(|d| &d[0]);
325
326 result.extend(diff.clone());
327 let map = Self::get_row_map(simb);
328
329 if let Some(n_simb) = nex_simb {
330 let nex_map = Self::get_row_map(n_simb);
331 let mut start = 0usize;
332 if map.get("orgRow").cloned().unwrap_or(0) != 0 {
333 start = (map["orgRow"] + map["orgDel"]).wrapping_sub(1);
334 }
335 let end = nex_map["revRow"].saturating_sub(2);
336 result.extend(Self::get_orig_list(original, start, end));
337 }
338
339 let mut start = (map["orgRow"] + map["orgDel"]).wrapping_sub(1);
340 if start == usize::MAX {
341 start = 0;
342 }
343
344 if simb.contains("@@ -1,") && nex_simb.is_none() && map["orgDel"] != original.len() {
345 result.extend(Self::get_orig_list(original, start, original.len() - 1));
346 } else if nex_simb.is_none()
347 && (map["orgRow"] + map["orgDel"]).wrapping_sub(1) < original.len()
348 {
349 result.extend(Self::get_orig_list(original, start, original.len() - 1));
350 }
351 }
352 }
353
354 fn get_row_map(str_header: &str) -> HashMap<&'static str, usize> {
355 let mut map = HashMap::new();
356 if str_header.starts_with("@@") {
357 let sp: Vec<&str> = str_header.split(' ').collect();
358 if sp.len() > 1 {
359 let org = sp[1];
360 let org_sp: Vec<&str> = org.split(',').collect();
361 if org_sp.len() >= 2 {
362 let org_row = org_sp[0][1..].parse::<usize>().unwrap_or(0);
363 let org_del = org_sp[1].parse::<usize>().unwrap_or(0);
364 map.insert("orgRow", org_row);
365 map.insert("orgDel", org_del);
366 map.insert("revRow", org_row);
367 map.insert("revAdd", org_del);
368 }
369 }
370 }
371 map
372 }
373
374 fn get_orig_list(original_with_prefix: &[String], start: usize, end: usize) -> Vec<String> {
375 let mut list = Vec::new();
376 if !original_with_prefix.is_empty() && start <= end && end < original_with_prefix.len() {
377 for item in original_with_prefix.iter().take(end + 1).skip(start) {
378 list.push(item.clone());
379 }
380 }
381 list
382 }
383}