1use std::ops::Range;
2
3use sum_tree::{Bias, SumTree};
4use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToPoint};
5
6pub use git2 as libgit;
7use libgit::{DiffOptions as GitOptions, Patch as GitPatch};
8
9#[derive(Debug, Clone, Copy)]
10pub enum DiffHunkStatus {
11 Added,
12 Modified,
13 Removed,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct DiffHunk<T> {
18 pub buffer_range: Range<T>,
19 pub head_range: Range<usize>,
20}
21
22impl DiffHunk<u32> {
23 pub fn status(&self) -> DiffHunkStatus {
24 if self.head_range.is_empty() {
25 DiffHunkStatus::Added
26 } else if self.buffer_range.is_empty() {
27 DiffHunkStatus::Removed
28 } else {
29 DiffHunkStatus::Modified
30 }
31 }
32}
33
34impl sum_tree::Item for DiffHunk<Anchor> {
35 type Summary = DiffHunkSummary;
36
37 fn summary(&self) -> Self::Summary {
38 DiffHunkSummary {
39 buffer_range: self.buffer_range.clone(),
40 head_range: self.head_range.clone(),
41 }
42 }
43}
44
45#[derive(Debug, Default, Clone)]
46pub struct DiffHunkSummary {
47 buffer_range: Range<Anchor>,
48 head_range: Range<usize>,
49}
50
51impl sum_tree::Summary for DiffHunkSummary {
52 type Context = text::BufferSnapshot;
53
54 fn add_summary(&mut self, other: &Self, _: &Self::Context) {
55 self.head_range.start = self.head_range.start.min(other.head_range.start);
56 self.head_range.end = self.head_range.end.max(other.head_range.end);
57 }
58}
59
60#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
61struct HunkHeadEnd(usize);
62
63impl<'a> sum_tree::Dimension<'a, DiffHunkSummary> for HunkHeadEnd {
64 fn add_summary(&mut self, summary: &'a DiffHunkSummary, _: &text::BufferSnapshot) {
65 self.0 = summary.head_range.end;
66 }
67
68 fn from_summary(summary: &'a DiffHunkSummary, _: &text::BufferSnapshot) -> Self {
69 HunkHeadEnd(summary.head_range.end)
70 }
71}
72
73#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
74struct HunkBufferEnd(u32);
75
76impl<'a> sum_tree::Dimension<'a, DiffHunkSummary> for HunkBufferEnd {
77 fn add_summary(&mut self, summary: &'a DiffHunkSummary, buffer: &text::BufferSnapshot) {
78 self.0 = summary.buffer_range.end.to_point(buffer).row;
79 }
80
81 fn from_summary(summary: &'a DiffHunkSummary, buffer: &text::BufferSnapshot) -> Self {
82 HunkBufferEnd(summary.buffer_range.end.to_point(buffer).row)
83 }
84}
85
86// struct HunkIter<'a> {
87// index: usize,
88// patch: GitPatch<'a>,
89// }
90
91// impl<'a> HunkIter<'a> {
92// fn diff(head: &'a [u8], current: &'a [u8]) -> Option<Self> {
93// let mut options = GitOptions::default();
94// options.context_lines(0);
95// let patch = match GitPatch::from_buffers(head, None, current, None, Some(&mut options)) {
96// Ok(patch) => patch,
97// Err(_) => return None,
98// };
99
100// Some(HunkIter { index: 0, patch })
101// }
102
103// fn next(&mut self, buffer: &BufferSnapshot) -> Option<DiffHunk<Anchor>> {
104// if self.index >= self.patch.num_hunks() {
105// return None;
106// }
107
108// let (hunk, _) = match self.patch.hunk(self.index) {
109// Ok(it) => it,
110// Err(_) => return None,
111// };
112// let hunk_line_count = self.patch.num_lines_in_hunk(self.index).unwrap();
113
114// println!("{hunk:#?}");
115// for index in 0..hunk_line_count {
116// println!("{:?}", self.patch.line_in_hunk(self.index, index));
117// }
118
119// let new_start = hunk.new_start() - 1;
120// let new_end = new_start + hunk.new_lines();
121// let start_anchor = buffer.anchor_at(Point::new(new_start, 0), Bias::Left);
122// let end_anchor = buffer.anchor_at(Point::new(new_end, 0), Bias::Left);
123// let buffer_range = start_anchor..end_anchor;
124
125// //This is probably wrong? When does this trigger? Should buffer range also do this?
126// let head_range = if hunk.old_start() == 0 {
127// 0..0
128// } else {
129// let old_start = hunk.old_start() - 1;
130// let old_end = old_start + hunk.old_lines();
131// old_start..old_end
132// };
133
134// // let head_start_index = self.patch.line_in_hunk(self.index, 0)
135
136// self.index += 1;
137// Some(DiffHunk {
138// buffer_range,
139// head_range,
140// })
141// }
142// }
143
144#[derive(Clone)]
145pub struct BufferDiffSnapshot {
146 tree: SumTree<DiffHunk<Anchor>>,
147}
148
149impl BufferDiffSnapshot {
150 pub fn hunks_in_range<'a>(
151 &'a self,
152 query_row_range: Range<u32>,
153 buffer: &'a BufferSnapshot,
154 ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
155 // println!("{} hunks overall", self.tree.iter().count());
156
157 self.tree.iter().filter_map(move |hunk| {
158 let range = hunk.buffer_range.to_point(&buffer);
159
160 if range.start.row < query_row_range.end && query_row_range.start < range.end.row {
161 let end_row = if range.end.column > 0 {
162 range.end.row + 1
163 } else {
164 range.end.row
165 };
166
167 Some(DiffHunk {
168 buffer_range: range.start.row..end_row,
169 head_range: hunk.head_range.clone(),
170 })
171 } else {
172 None
173 }
174 })
175 }
176
177 #[cfg(test)]
178 fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
179 self.hunks_in_range(0..u32::MAX, text)
180 }
181}
182
183pub struct BufferDiff {
184 last_update_version: clock::Global,
185 snapshot: BufferDiffSnapshot,
186}
187
188impl BufferDiff {
189 pub fn new(head_text: &Option<String>, buffer: &text::BufferSnapshot) -> BufferDiff {
190 let hunks = if let Some(head_text) = head_text {
191 let buffer_string = buffer.as_rope().to_string();
192 let buffer_bytes = buffer_string.as_bytes();
193
194 let mut options = GitOptions::default();
195 options.context_lines(0);
196 let patch = match GitPatch::from_buffers(
197 head_text.as_bytes(),
198 None,
199 buffer_bytes,
200 None,
201 Some(&mut options),
202 ) {
203 Ok(patch) => patch,
204 Err(_) => todo!("This needs to be handled"),
205 };
206
207 let mut hunks = SumTree::<DiffHunk<Anchor>>::new();
208 let mut delta = 0i64;
209 for i in 0..patch.num_hunks() {
210 let diff_line_item_count = patch.num_lines_in_hunk(i).unwrap();
211
212 // if diff_line_item_count == 0 {
213 // continue;
214 // }
215
216 // let calc_line_diff_hunk = || {
217
218 // };
219
220 // let first_line = patch.line_in_hunk(0).unwrap();
221 // let mut hunk =
222
223 for j in 0..diff_line_item_count {
224 let line = patch.line_in_hunk(i, j).unwrap();
225
226 let hunk = match line.origin_value() {
227 libgit::DiffLineType::Addition => {
228 let buffer_start = line.content_offset();
229 let buffer_end = buffer_start as usize + line.content().len();
230 let head_offset = (buffer_start - delta) as usize;
231 delta += line.content().len() as i64;
232 DiffHunk {
233 buffer_range: buffer.anchor_before(buffer_start as usize)
234 ..buffer.anchor_after(buffer_end),
235 head_range: head_offset..head_offset,
236 }
237 }
238 libgit::DiffLineType::Deletion => {
239 let head_start = line.content_offset();
240 let head_end = head_start as usize + line.content().len();
241 let buffer_offset = (head_start + delta) as usize;
242 delta -= line.content().len() as i64;
243 DiffHunk {
244 buffer_range: buffer.anchor_before(buffer_offset)
245 ..buffer.anchor_after(buffer_offset),
246 head_range: (head_start as usize)..head_end,
247 }
248 }
249
250 libgit::DiffLineType::AddEOFNL => todo!(),
251 libgit::DiffLineType::ContextEOFNL => todo!(),
252 libgit::DiffLineType::DeleteEOFNL => todo!(),
253 libgit::DiffLineType::Context => unreachable!(),
254 libgit::DiffLineType::FileHeader => continue,
255 libgit::DiffLineType::HunkHeader => continue,
256 libgit::DiffLineType::Binary => continue,
257 };
258
259 let mut combined = false;
260 hunks.update_last(
261 |last_hunk| {
262 if last_hunk.head_range.end == hunk.head_range.start {
263 last_hunk.head_range.end = hunk.head_range.end;
264 last_hunk.buffer_range.end = hunk.buffer_range.end;
265 combined = true;
266 }
267 },
268 buffer,
269 );
270 if !combined {
271 hunks.push(hunk, buffer);
272 }
273 }
274 }
275
276 // let iter = HunkIter::diff(head_text.as_bytes(), buffer_bytes);
277 // if let Some(mut iter) = iter {
278 // let mut hunks = SumTree::new();
279 // while let Some(hunk) = iter.next(buffer) {
280 // hunks.push(hunk, buffer);
281 // }
282 // println!("========");
283 // hunks
284 // } else {
285 // SumTree::new()
286 // }
287 hunks
288 } else {
289 SumTree::new()
290 };
291
292 BufferDiff {
293 last_update_version: buffer.version().clone(),
294 snapshot: BufferDiffSnapshot { tree: hunks },
295 }
296 }
297
298 pub fn snapshot(&self) -> BufferDiffSnapshot {
299 self.snapshot.clone()
300 }
301
302 pub fn update(&mut self, head: &Rope, buffer: &text::BufferSnapshot) {
303 // let expand_by = 20;
304 // let combine_distance = 5;
305
306 // struct EditRange {
307 // head_start: u32,
308 // head_end: u32,
309 // buffer_start: u32,
310 // buffer_end: u32,
311 // }
312
313 // let mut ranges = Vec::<EditRange>::new();
314
315 // for edit in buffer.edits_since::<Point>(&self.last_update_version) {
316 // //This bit is extremely wrong, this is not where these row lines should come from
317 // let head_start = edit.old.start.row.saturating_sub(expand_by);
318 // let head_end = (edit.old.end.row + expand_by).min(head.summary().lines.row + 1);
319
320 // let buffer_start = edit.new.start.row.saturating_sub(expand_by);
321 // let buffer_end = (edit.new.end.row + expand_by).min(buffer.row_count());
322
323 // if let Some(last_range) = ranges.last_mut() {
324 // let head_distance = last_range.head_end.abs_diff(head_end);
325 // let buffer_distance = last_range.buffer_end.abs_diff(buffer_end);
326
327 // if head_distance <= combine_distance || buffer_distance <= combine_distance {
328 // last_range.head_start = last_range.head_start.min(head_start);
329 // last_range.head_end = last_range.head_end.max(head_end);
330
331 // last_range.buffer_start = last_range.buffer_start.min(buffer_start);
332 // last_range.buffer_end = last_range.buffer_end.max(buffer_end);
333 // } else {
334 // ranges.push(EditRange {
335 // head_start,
336 // head_end,
337 // buffer_start,
338 // buffer_end,
339 // });
340 // }
341 // } else {
342 // ranges.push(EditRange {
343 // head_start,
344 // head_end,
345 // buffer_start,
346 // buffer_end,
347 // });
348 // }
349 // }
350
351 // self.last_update_version = buffer.version().clone();
352
353 // let mut new_hunks = SumTree::new();
354 // let mut cursor = self.snapshot.tree.cursor::<HunkHeadEnd>();
355
356 // for range in ranges {
357 // let head_range = range.head_start..range.head_end;
358 // let head_slice = head.slice_rows(head_range.clone());
359 // let head_str = head_slice.to_string();
360
361 // let buffer_range = range.buffer_start..range.buffer_end;
362 // let buffer_slice = buffer.as_rope().slice_rows(buffer_range.clone());
363 // let buffer_str = buffer_slice.to_string();
364
365 // println!("diffing head {:?}, buffer {:?}", head_range, buffer_range);
366
367 // let mut iter = match HunkIter::diff(head_str.as_bytes(), buffer_str.as_bytes()) {
368 // Some(iter) => iter,
369 // None => continue,
370 // };
371
372 // while let Some(hunk) = iter.next(buffer) {
373 // println!("hunk");
374 // let prefix = cursor.slice(&HunkHeadEnd(hunk.head_range.end), Bias::Right, buffer);
375 // println!("prefix len: {}", prefix.iter().count());
376 // new_hunks.extend(prefix.iter().cloned(), buffer);
377
378 // new_hunks.push(hunk.clone(), buffer);
379
380 // cursor.seek(&HunkHeadEnd(hunk.head_range.end), Bias::Right, buffer);
381 // println!("item: {:?}", cursor.item());
382 // if let Some(item) = cursor.item() {
383 // if item.head_range.end <= hunk.head_range.end {
384 // println!("skipping");
385 // cursor.next(buffer);
386 // }
387 // }
388 // }
389 // }
390
391 // new_hunks.extend(
392 // cursor
393 // .suffix(buffer)
394 // .iter()
395 // .map(|i| {
396 // println!("extending with {i:?}");
397 // i
398 // })
399 // .cloned(),
400 // buffer,
401 // );
402 // drop(cursor);
403
404 // self.snapshot.tree = new_hunks;
405 }
406}
407
408#[derive(Debug, Clone, Copy)]
409pub enum GitDiffEdit {
410 Added(u32),
411 Modified(u32),
412 Removed(u32),
413}
414
415impl GitDiffEdit {
416 pub fn line(self) -> u32 {
417 use GitDiffEdit::*;
418
419 match self {
420 Added(line) | Modified(line) | Removed(line) => line,
421 }
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use text::Buffer;
429 use unindent::Unindent as _;
430
431 #[gpui::test]
432 fn test_buffer_diff_simple() {
433 let head_text = "
434 one
435 two
436 three
437 "
438 .unindent();
439
440 let buffer_text = "
441 one
442 hello
443 three
444 "
445 .unindent();
446
447 let mut buffer = Buffer::new(0, 0, buffer_text);
448 let diff = BufferDiff::new(&Some(head_text.clone()), &buffer);
449 assert_hunks(&diff, &buffer, &head_text, &[(1..2, "two\n")]);
450
451 buffer.edit([(0..0, "point five\n")]);
452 assert_hunks(&diff, &buffer, &head_text, &[(2..3, "two\n")]);
453 }
454
455 #[track_caller]
456 fn assert_hunks(
457 diff: &BufferDiff,
458 buffer: &BufferSnapshot,
459 head_text: &str,
460 expected_hunks: &[(Range<u32>, &str)],
461 ) {
462 let hunks = diff.snapshot.hunks(buffer).collect::<Vec<_>>();
463 assert_eq!(
464 hunks.len(),
465 expected_hunks.len(),
466 "actual hunks are {hunks:#?}"
467 );
468
469 let diff_iter = hunks.iter().enumerate();
470 for ((index, hunk), (expected_range, expected_str)) in diff_iter.zip(expected_hunks) {
471 assert_eq!(&hunk.buffer_range, expected_range, "for hunk {index}");
472 assert_eq!(
473 &head_text[hunk.head_range.clone()],
474 *expected_str,
475 "for hunk {index}"
476 );
477 }
478 }
479
480 // use rand::rngs::StdRng;
481 // #[gpui::test(iterations = 100)]
482 // fn test_buffer_diff_random(mut rng: StdRng) {}
483}