1use futures::channel::oneshot;
2use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
3use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task};
4use language::{Language, LanguageRegistry};
5use rope::Rope;
6use std::cmp::Ordering;
7use std::mem;
8use std::{future::Future, iter, ops::Range, sync::Arc};
9use sum_tree::SumTree;
10use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point};
11use text::{AnchorRangeExt, ToOffset as _};
12use util::ResultExt;
13
14pub struct BufferDiff {
15 pub buffer_id: BufferId,
16 inner: BufferDiffInner,
17 secondary_diff: Option<Entity<BufferDiff>>,
18}
19
20#[derive(Clone, Debug)]
21pub struct BufferDiffSnapshot {
22 inner: BufferDiffInner,
23 secondary_diff: Option<Box<BufferDiffSnapshot>>,
24}
25
26#[derive(Clone)]
27struct BufferDiffInner {
28 hunks: SumTree<InternalDiffHunk>,
29 pending_hunks: SumTree<PendingHunk>,
30 base_text: language::BufferSnapshot,
31 base_text_exists: bool,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35pub struct DiffHunkStatus {
36 pub kind: DiffHunkStatusKind,
37 pub secondary: DiffHunkSecondaryStatus,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum DiffHunkStatusKind {
42 Added,
43 Modified,
44 Deleted,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum DiffHunkSecondaryStatus {
49 HasSecondaryHunk,
50 OverlapsWithSecondaryHunk,
51 NoSecondaryHunk,
52 SecondaryHunkAdditionPending,
53 SecondaryHunkRemovalPending,
54}
55
56/// A diff hunk resolved to rows in the buffer.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct DiffHunk {
59 /// The buffer range as points.
60 pub range: Range<Point>,
61 /// The range in the buffer to which this hunk corresponds.
62 pub buffer_range: Range<Anchor>,
63 /// The range in the buffer's diff base text to which this hunk corresponds.
64 pub diff_base_byte_range: Range<usize>,
65 pub secondary_status: DiffHunkSecondaryStatus,
66}
67
68/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
69#[derive(Debug, Clone, PartialEq, Eq)]
70struct InternalDiffHunk {
71 buffer_range: Range<Anchor>,
72 diff_base_byte_range: Range<usize>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76struct PendingHunk {
77 buffer_range: Range<Anchor>,
78 diff_base_byte_range: Range<usize>,
79 buffer_version: clock::Global,
80 new_status: DiffHunkSecondaryStatus,
81}
82
83#[derive(Debug, Default, Clone)]
84pub struct DiffHunkSummary {
85 buffer_range: Range<Anchor>,
86}
87
88impl sum_tree::Item for InternalDiffHunk {
89 type Summary = DiffHunkSummary;
90
91 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
92 DiffHunkSummary {
93 buffer_range: self.buffer_range.clone(),
94 }
95 }
96}
97
98impl sum_tree::Item for PendingHunk {
99 type Summary = DiffHunkSummary;
100
101 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
102 DiffHunkSummary {
103 buffer_range: self.buffer_range.clone(),
104 }
105 }
106}
107
108impl sum_tree::Summary for DiffHunkSummary {
109 type Context = text::BufferSnapshot;
110
111 fn zero(_cx: &Self::Context) -> Self {
112 Default::default()
113 }
114
115 fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
116 self.buffer_range.start = self
117 .buffer_range
118 .start
119 .min(&other.buffer_range.start, buffer);
120 self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
121 }
122}
123
124impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
125 fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
126 if self
127 .cmp(&cursor_location.buffer_range.start, buffer)
128 .is_lt()
129 {
130 Ordering::Less
131 } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
132 Ordering::Greater
133 } else {
134 Ordering::Equal
135 }
136 }
137}
138
139impl std::fmt::Debug for BufferDiffInner {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 f.debug_struct("BufferDiffSnapshot")
142 .field("hunks", &self.hunks)
143 .finish()
144 }
145}
146
147impl BufferDiffSnapshot {
148 pub fn is_empty(&self) -> bool {
149 self.inner.hunks.is_empty()
150 }
151
152 pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
153 self.secondary_diff.as_deref()
154 }
155
156 pub fn hunks_intersecting_range<'a>(
157 &'a self,
158 range: Range<Anchor>,
159 buffer: &'a text::BufferSnapshot,
160 ) -> impl 'a + Iterator<Item = DiffHunk> {
161 let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
162 self.inner
163 .hunks_intersecting_range(range, buffer, unstaged_counterpart)
164 }
165
166 pub fn hunks_intersecting_range_rev<'a>(
167 &'a self,
168 range: Range<Anchor>,
169 buffer: &'a text::BufferSnapshot,
170 ) -> impl 'a + Iterator<Item = DiffHunk> {
171 self.inner.hunks_intersecting_range_rev(range, buffer)
172 }
173
174 pub fn base_text(&self) -> &language::BufferSnapshot {
175 &self.inner.base_text
176 }
177
178 pub fn base_texts_eq(&self, other: &Self) -> bool {
179 if self.inner.base_text_exists != other.inner.base_text_exists {
180 return false;
181 }
182 let left = &self.inner.base_text;
183 let right = &other.inner.base_text;
184 let (old_id, old_empty) = (left.remote_id(), left.is_empty());
185 let (new_id, new_empty) = (right.remote_id(), right.is_empty());
186 new_id == old_id || (new_empty && old_empty)
187 }
188}
189
190impl BufferDiffInner {
191 /// Returns the new index text and new pending hunks.
192 fn stage_or_unstage_hunks(
193 &mut self,
194 unstaged_diff: &Self,
195 stage: bool,
196 hunks: &[DiffHunk],
197 buffer: &text::BufferSnapshot,
198 file_exists: bool,
199 ) -> (Option<Rope>, SumTree<PendingHunk>) {
200 let head_text = self
201 .base_text_exists
202 .then(|| self.base_text.as_rope().clone());
203 let index_text = unstaged_diff
204 .base_text_exists
205 .then(|| unstaged_diff.base_text.as_rope().clone());
206
207 // If the file doesn't exist in either HEAD or the index, then the
208 // entire file must be either created or deleted in the index.
209 let (index_text, head_text) = match (index_text, head_text) {
210 (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
211 (index_text, head_text) => {
212 let (rope, new_status) = if stage {
213 log::debug!("stage all");
214 (
215 file_exists.then(|| buffer.as_rope().clone()),
216 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
217 )
218 } else {
219 log::debug!("unstage all");
220 (
221 head_text,
222 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
223 )
224 };
225
226 let hunk = PendingHunk {
227 buffer_range: Anchor::MIN..Anchor::MAX,
228 diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
229 buffer_version: buffer.version().clone(),
230 new_status,
231 };
232 let tree = SumTree::from_item(hunk, buffer);
233 return (rope, tree);
234 }
235 };
236
237 let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
238 unstaged_hunk_cursor.next(buffer);
239
240 let mut pending_hunks = SumTree::new(buffer);
241 let mut old_pending_hunks = unstaged_diff
242 .pending_hunks
243 .cursor::<DiffHunkSummary>(buffer);
244
245 // first, merge new hunks into pending_hunks
246 for DiffHunk {
247 buffer_range,
248 diff_base_byte_range,
249 secondary_status,
250 ..
251 } in hunks.iter().cloned()
252 {
253 let preceding_pending_hunks =
254 old_pending_hunks.slice(&buffer_range.start, Bias::Left, buffer);
255
256 pending_hunks.append(preceding_pending_hunks, buffer);
257
258 // skip all overlapping old pending hunks
259 while old_pending_hunks
260 .item()
261 .is_some_and(|preceding_pending_hunk_item| {
262 preceding_pending_hunk_item
263 .buffer_range
264 .overlaps(&buffer_range, buffer)
265 })
266 {
267 old_pending_hunks.next(buffer);
268 }
269
270 // merge into pending hunks
271 if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
272 || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
273 {
274 continue;
275 }
276
277 pending_hunks.push(
278 PendingHunk {
279 buffer_range,
280 diff_base_byte_range,
281 buffer_version: buffer.version().clone(),
282 new_status: if stage {
283 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
284 } else {
285 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
286 },
287 },
288 buffer,
289 );
290 }
291 // append the remainder
292 pending_hunks.append(old_pending_hunks.suffix(buffer), buffer);
293
294 let mut prev_unstaged_hunk_buffer_offset = 0;
295 let mut prev_unstaged_hunk_base_text_offset = 0;
296 let mut edits = Vec::<(Range<usize>, String)>::new();
297
298 // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
299 for PendingHunk {
300 buffer_range,
301 diff_base_byte_range,
302 ..
303 } in pending_hunks.iter().cloned()
304 {
305 let skipped_hunks = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left, buffer);
306
307 if let Some(secondary_hunk) = skipped_hunks.last() {
308 prev_unstaged_hunk_base_text_offset = secondary_hunk.diff_base_byte_range.end;
309 prev_unstaged_hunk_buffer_offset =
310 secondary_hunk.buffer_range.end.to_offset(buffer);
311 }
312
313 let mut buffer_offset_range = buffer_range.to_offset(buffer);
314 let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_offset;
315 let mut index_start = prev_unstaged_hunk_base_text_offset + start_overshoot;
316
317 while let Some(unstaged_hunk) = unstaged_hunk_cursor.item().filter(|item| {
318 item.buffer_range
319 .start
320 .cmp(&buffer_range.end, buffer)
321 .is_le()
322 }) {
323 let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
324 prev_unstaged_hunk_base_text_offset = unstaged_hunk.diff_base_byte_range.end;
325 prev_unstaged_hunk_buffer_offset = unstaged_hunk_offset_range.end;
326
327 index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
328 buffer_offset_range.start = buffer_offset_range
329 .start
330 .min(unstaged_hunk_offset_range.start);
331
332 unstaged_hunk_cursor.next(buffer);
333 }
334
335 let end_overshoot = buffer_offset_range
336 .end
337 .saturating_sub(prev_unstaged_hunk_buffer_offset);
338 let index_end = prev_unstaged_hunk_base_text_offset + end_overshoot;
339
340 let index_range = index_start..index_end;
341 buffer_offset_range.end = buffer_offset_range
342 .end
343 .max(prev_unstaged_hunk_buffer_offset);
344
345 let replacement_text = if stage {
346 log::debug!("stage hunk {:?}", buffer_offset_range);
347 buffer
348 .text_for_range(buffer_offset_range)
349 .collect::<String>()
350 } else {
351 log::debug!("unstage hunk {:?}", buffer_offset_range);
352 head_text
353 .chunks_in_range(diff_base_byte_range.clone())
354 .collect::<String>()
355 };
356
357 edits.push((index_range, replacement_text));
358 }
359
360 debug_assert!(edits.iter().is_sorted_by_key(|(range, _)| range.start));
361
362 let mut new_index_text = Rope::new();
363 let mut index_cursor = index_text.cursor(0);
364
365 for (old_range, replacement_text) in edits {
366 new_index_text.append(index_cursor.slice(old_range.start));
367 index_cursor.seek_forward(old_range.end);
368 new_index_text.push(&replacement_text);
369 }
370 new_index_text.append(index_cursor.suffix());
371 (Some(new_index_text), pending_hunks)
372 }
373
374 fn hunks_intersecting_range<'a>(
375 &'a self,
376 range: Range<Anchor>,
377 buffer: &'a text::BufferSnapshot,
378 secondary: Option<&'a Self>,
379 ) -> impl 'a + Iterator<Item = DiffHunk> {
380 let range = range.to_offset(buffer);
381
382 let mut cursor = self
383 .hunks
384 .filter::<_, DiffHunkSummary>(buffer, move |summary| {
385 let summary_range = summary.buffer_range.to_offset(buffer);
386 let before_start = summary_range.end < range.start;
387 let after_end = summary_range.start > range.end;
388 !before_start && !after_end
389 });
390
391 let anchor_iter = iter::from_fn(move || {
392 cursor.next(buffer);
393 cursor.item()
394 })
395 .flat_map(move |hunk| {
396 [
397 (
398 &hunk.buffer_range.start,
399 (hunk.buffer_range.start, hunk.diff_base_byte_range.start),
400 ),
401 (
402 &hunk.buffer_range.end,
403 (hunk.buffer_range.end, hunk.diff_base_byte_range.end),
404 ),
405 ]
406 });
407
408 let mut secondary_cursor = None;
409 let mut pending_hunks_cursor = None;
410 if let Some(secondary) = secondary.as_ref() {
411 let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
412 cursor.next(buffer);
413 secondary_cursor = Some(cursor);
414 let mut cursor = secondary.pending_hunks.cursor::<DiffHunkSummary>(buffer);
415 cursor.next(buffer);
416 pending_hunks_cursor = Some(cursor);
417 }
418
419 let max_point = buffer.max_point();
420 let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
421 iter::from_fn(move || loop {
422 let (start_point, (start_anchor, start_base)) = summaries.next()?;
423 let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;
424
425 if !start_anchor.is_valid(buffer) {
426 continue;
427 }
428
429 if end_point.column > 0 && end_point < max_point {
430 end_point.row += 1;
431 end_point.column = 0;
432 end_anchor = buffer.anchor_before(end_point);
433 }
434
435 let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
436
437 let mut has_pending = false;
438 if let Some(pending_cursor) = pending_hunks_cursor.as_mut() {
439 if start_anchor
440 .cmp(&pending_cursor.start().buffer_range.start, buffer)
441 .is_gt()
442 {
443 pending_cursor.seek_forward(&start_anchor, Bias::Left, buffer);
444 }
445
446 if let Some(pending_hunk) = pending_cursor.item() {
447 let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
448 if pending_range.end.column > 0 {
449 pending_range.end.row += 1;
450 pending_range.end.column = 0;
451 }
452
453 if pending_range == (start_point..end_point) {
454 if !buffer.has_edits_since_in_range(
455 &pending_hunk.buffer_version,
456 start_anchor..end_anchor,
457 ) {
458 has_pending = true;
459 secondary_status = pending_hunk.new_status;
460 }
461 }
462 }
463 }
464
465 if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
466 if start_anchor
467 .cmp(&secondary_cursor.start().buffer_range.start, buffer)
468 .is_gt()
469 {
470 secondary_cursor.seek_forward(&start_anchor, Bias::Left, buffer);
471 }
472
473 if let Some(secondary_hunk) = secondary_cursor.item() {
474 let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
475 if secondary_range.end.column > 0 {
476 secondary_range.end.row += 1;
477 secondary_range.end.column = 0;
478 }
479 if secondary_range.is_empty() && secondary_hunk.diff_base_byte_range.is_empty()
480 {
481 // ignore
482 } else if secondary_range == (start_point..end_point) {
483 secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
484 } else if secondary_range.start <= end_point {
485 secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
486 }
487 }
488 }
489
490 return Some(DiffHunk {
491 range: start_point..end_point,
492 diff_base_byte_range: start_base..end_base,
493 buffer_range: start_anchor..end_anchor,
494 secondary_status,
495 });
496 })
497 }
498
499 fn hunks_intersecting_range_rev<'a>(
500 &'a self,
501 range: Range<Anchor>,
502 buffer: &'a text::BufferSnapshot,
503 ) -> impl 'a + Iterator<Item = DiffHunk> {
504 let mut cursor = self
505 .hunks
506 .filter::<_, DiffHunkSummary>(buffer, move |summary| {
507 let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
508 let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
509 !before_start && !after_end
510 });
511
512 iter::from_fn(move || {
513 cursor.prev(buffer);
514
515 let hunk = cursor.item()?;
516 let range = hunk.buffer_range.to_point(buffer);
517
518 Some(DiffHunk {
519 range,
520 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
521 buffer_range: hunk.buffer_range.clone(),
522 // The secondary status is not used by callers of this method.
523 secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
524 })
525 })
526 }
527
528 fn compare(&self, old: &Self, new_snapshot: &text::BufferSnapshot) -> Option<Range<Anchor>> {
529 let mut new_cursor = self.hunks.cursor::<()>(new_snapshot);
530 let mut old_cursor = old.hunks.cursor::<()>(new_snapshot);
531 old_cursor.next(new_snapshot);
532 new_cursor.next(new_snapshot);
533 let mut start = None;
534 let mut end = None;
535
536 loop {
537 match (new_cursor.item(), old_cursor.item()) {
538 (Some(new_hunk), Some(old_hunk)) => {
539 match new_hunk
540 .buffer_range
541 .start
542 .cmp(&old_hunk.buffer_range.start, new_snapshot)
543 {
544 Ordering::Less => {
545 start.get_or_insert(new_hunk.buffer_range.start);
546 end.replace(new_hunk.buffer_range.end);
547 new_cursor.next(new_snapshot);
548 }
549 Ordering::Equal => {
550 if new_hunk != old_hunk {
551 start.get_or_insert(new_hunk.buffer_range.start);
552 if old_hunk
553 .buffer_range
554 .end
555 .cmp(&new_hunk.buffer_range.end, new_snapshot)
556 .is_ge()
557 {
558 end.replace(old_hunk.buffer_range.end);
559 } else {
560 end.replace(new_hunk.buffer_range.end);
561 }
562 }
563
564 new_cursor.next(new_snapshot);
565 old_cursor.next(new_snapshot);
566 }
567 Ordering::Greater => {
568 start.get_or_insert(old_hunk.buffer_range.start);
569 end.replace(old_hunk.buffer_range.end);
570 old_cursor.next(new_snapshot);
571 }
572 }
573 }
574 (Some(new_hunk), None) => {
575 start.get_or_insert(new_hunk.buffer_range.start);
576 end.replace(new_hunk.buffer_range.end);
577 new_cursor.next(new_snapshot);
578 }
579 (None, Some(old_hunk)) => {
580 start.get_or_insert(old_hunk.buffer_range.start);
581 end.replace(old_hunk.buffer_range.end);
582 old_cursor.next(new_snapshot);
583 }
584 (None, None) => break,
585 }
586 }
587
588 start.zip(end).map(|(start, end)| start..end)
589 }
590}
591
592fn compute_hunks(
593 diff_base: Option<(Arc<String>, Rope)>,
594 buffer: text::BufferSnapshot,
595) -> SumTree<InternalDiffHunk> {
596 let mut tree = SumTree::new(&buffer);
597
598 if let Some((diff_base, diff_base_rope)) = diff_base {
599 let buffer_text = buffer.as_rope().to_string();
600
601 let mut options = GitOptions::default();
602 options.context_lines(0);
603 let patch = GitPatch::from_buffers(
604 diff_base.as_bytes(),
605 None,
606 buffer_text.as_bytes(),
607 None,
608 Some(&mut options),
609 )
610 .log_err();
611
612 // A common case in Zed is that the empty buffer is represented as just a newline,
613 // but if we just compute a naive diff you get a "preserved" line in the middle,
614 // which is a bit odd.
615 if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
616 tree.push(
617 InternalDiffHunk {
618 buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
619 diff_base_byte_range: 0..diff_base.len() - 1,
620 },
621 &buffer,
622 );
623 return tree;
624 }
625
626 if let Some(patch) = patch {
627 let mut divergence = 0;
628 for hunk_index in 0..patch.num_hunks() {
629 let hunk = process_patch_hunk(
630 &patch,
631 hunk_index,
632 &diff_base_rope,
633 &buffer,
634 &mut divergence,
635 );
636 tree.push(hunk, &buffer);
637 }
638 }
639 } else {
640 tree.push(
641 InternalDiffHunk {
642 buffer_range: Anchor::MIN..Anchor::MAX,
643 diff_base_byte_range: 0..0,
644 },
645 &buffer,
646 );
647 }
648
649 tree
650}
651
652fn process_patch_hunk(
653 patch: &GitPatch<'_>,
654 hunk_index: usize,
655 diff_base: &Rope,
656 buffer: &text::BufferSnapshot,
657 buffer_row_divergence: &mut i64,
658) -> InternalDiffHunk {
659 let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
660 assert!(line_item_count > 0);
661
662 let mut first_deletion_buffer_row: Option<u32> = None;
663 let mut buffer_row_range: Option<Range<u32>> = None;
664 let mut diff_base_byte_range: Option<Range<usize>> = None;
665 let mut first_addition_old_row: Option<u32> = None;
666
667 for line_index in 0..line_item_count {
668 let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
669 let kind = line.origin_value();
670 let content_offset = line.content_offset() as isize;
671 let content_len = line.content().len() as isize;
672 match kind {
673 GitDiffLineType::Addition => {
674 if first_addition_old_row.is_none() {
675 first_addition_old_row = Some(
676 (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
677 );
678 }
679 *buffer_row_divergence += 1;
680 let row = line.new_lineno().unwrap().saturating_sub(1);
681
682 match &mut buffer_row_range {
683 Some(Range { end, .. }) => *end = row + 1,
684 None => buffer_row_range = Some(row..row + 1),
685 }
686 }
687 GitDiffLineType::Deletion => {
688 let end = content_offset + content_len;
689
690 match &mut diff_base_byte_range {
691 Some(head_byte_range) => head_byte_range.end = end as usize,
692 None => diff_base_byte_range = Some(content_offset as usize..end as usize),
693 }
694
695 if first_deletion_buffer_row.is_none() {
696 let old_row = line.old_lineno().unwrap().saturating_sub(1);
697 let row = old_row as i64 + *buffer_row_divergence;
698 first_deletion_buffer_row = Some(row as u32);
699 }
700
701 *buffer_row_divergence -= 1;
702 }
703 _ => {}
704 }
705 }
706
707 let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
708 // Pure deletion hunk without addition.
709 let row = first_deletion_buffer_row.unwrap();
710 row..row
711 });
712 let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
713 // Pure addition hunk without deletion.
714 let row = first_addition_old_row.unwrap();
715 let offset = diff_base.point_to_offset(Point::new(row, 0));
716 offset..offset
717 });
718
719 let start = Point::new(buffer_row_range.start, 0);
720 let end = Point::new(buffer_row_range.end, 0);
721 let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
722 InternalDiffHunk {
723 buffer_range,
724 diff_base_byte_range,
725 }
726}
727
728impl std::fmt::Debug for BufferDiff {
729 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
730 f.debug_struct("BufferChangeSet")
731 .field("buffer_id", &self.buffer_id)
732 .field("snapshot", &self.inner)
733 .finish()
734 }
735}
736
737#[derive(Clone, Debug)]
738pub enum BufferDiffEvent {
739 DiffChanged {
740 changed_range: Option<Range<text::Anchor>>,
741 },
742 LanguageChanged,
743 HunksStagedOrUnstaged(Option<Rope>),
744}
745
746impl EventEmitter<BufferDiffEvent> for BufferDiff {}
747
748impl BufferDiff {
749 #[cfg(test)]
750 fn build_sync(
751 buffer: text::BufferSnapshot,
752 diff_base: String,
753 cx: &mut gpui::TestAppContext,
754 ) -> BufferDiffInner {
755 let snapshot =
756 cx.update(|cx| Self::build(buffer, Some(Arc::new(diff_base)), None, None, cx));
757 cx.executor().block(snapshot)
758 }
759
760 fn build(
761 buffer: text::BufferSnapshot,
762 base_text: Option<Arc<String>>,
763 language: Option<Arc<Language>>,
764 language_registry: Option<Arc<LanguageRegistry>>,
765 cx: &mut App,
766 ) -> impl Future<Output = BufferDiffInner> {
767 let base_text_pair;
768 let base_text_exists;
769 let base_text_snapshot;
770 if let Some(text) = &base_text {
771 let base_text_rope = Rope::from(text.as_str());
772 base_text_pair = Some((text.clone(), base_text_rope.clone()));
773 let snapshot = language::Buffer::build_snapshot(
774 base_text_rope,
775 language.clone(),
776 language_registry.clone(),
777 cx,
778 );
779 base_text_snapshot = cx.background_spawn(snapshot);
780 base_text_exists = true;
781 } else {
782 base_text_pair = None;
783 base_text_snapshot = Task::ready(language::Buffer::build_empty_snapshot(cx));
784 base_text_exists = false;
785 };
786
787 let hunks = cx.background_spawn({
788 let buffer = buffer.clone();
789 async move { compute_hunks(base_text_pair, buffer) }
790 });
791
792 async move {
793 let (base_text, hunks) = futures::join!(base_text_snapshot, hunks);
794 BufferDiffInner {
795 base_text,
796 hunks,
797 base_text_exists,
798 pending_hunks: SumTree::new(&buffer),
799 }
800 }
801 }
802
803 fn build_with_base_buffer(
804 buffer: text::BufferSnapshot,
805 base_text: Option<Arc<String>>,
806 base_text_snapshot: language::BufferSnapshot,
807 cx: &App,
808 ) -> impl Future<Output = BufferDiffInner> {
809 let base_text_exists = base_text.is_some();
810 let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));
811 cx.background_spawn(async move {
812 BufferDiffInner {
813 base_text: base_text_snapshot,
814 pending_hunks: SumTree::new(&buffer),
815 hunks: compute_hunks(base_text_pair, buffer),
816 base_text_exists,
817 }
818 })
819 }
820
821 fn build_empty(buffer: &text::BufferSnapshot, cx: &mut App) -> BufferDiffInner {
822 BufferDiffInner {
823 base_text: language::Buffer::build_empty_snapshot(cx),
824 hunks: SumTree::new(buffer),
825 pending_hunks: SumTree::new(buffer),
826 base_text_exists: false,
827 }
828 }
829
830 pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
831 self.secondary_diff = Some(diff);
832 }
833
834 pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
835 self.secondary_diff.clone()
836 }
837
838 pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
839 if let Some(secondary_diff) = &self.secondary_diff {
840 secondary_diff.update(cx, |diff, _| {
841 diff.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary::default());
842 });
843 cx.emit(BufferDiffEvent::DiffChanged {
844 changed_range: Some(Anchor::MIN..Anchor::MAX),
845 });
846 }
847 }
848
849 pub fn stage_or_unstage_hunks(
850 &mut self,
851 stage: bool,
852 hunks: &[DiffHunk],
853 buffer: &text::BufferSnapshot,
854 file_exists: bool,
855 cx: &mut Context<Self>,
856 ) -> Option<Rope> {
857 let (new_index_text, new_pending_hunks) = self.inner.stage_or_unstage_hunks(
858 &self.secondary_diff.as_ref()?.read(cx).inner,
859 stage,
860 &hunks,
861 buffer,
862 file_exists,
863 );
864
865 if let Some(unstaged_diff) = &self.secondary_diff {
866 unstaged_diff.update(cx, |diff, _| {
867 diff.inner.pending_hunks = new_pending_hunks;
868 });
869 }
870 cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
871 new_index_text.clone(),
872 ));
873 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
874 let changed_range = first.buffer_range.start..last.buffer_range.end;
875 cx.emit(BufferDiffEvent::DiffChanged {
876 changed_range: Some(changed_range),
877 });
878 }
879 new_index_text
880 }
881
882 pub fn range_to_hunk_range(
883 &self,
884 range: Range<Anchor>,
885 buffer: &text::BufferSnapshot,
886 cx: &App,
887 ) -> Option<Range<Anchor>> {
888 let start = self
889 .hunks_intersecting_range(range.clone(), &buffer, cx)
890 .next()?
891 .buffer_range
892 .start;
893 let end = self
894 .hunks_intersecting_range_rev(range.clone(), &buffer)
895 .next()?
896 .buffer_range
897 .end;
898 Some(start..end)
899 }
900
901 pub async fn update_diff(
902 this: Entity<BufferDiff>,
903 buffer: text::BufferSnapshot,
904 base_text: Option<Arc<String>>,
905 base_text_changed: bool,
906 language_changed: bool,
907 language: Option<Arc<Language>>,
908 language_registry: Option<Arc<LanguageRegistry>>,
909 cx: &mut AsyncApp,
910 ) -> anyhow::Result<BufferDiffSnapshot> {
911 let inner = if base_text_changed || language_changed {
912 cx.update(|cx| {
913 Self::build(
914 buffer.clone(),
915 base_text,
916 language.clone(),
917 language_registry.clone(),
918 cx,
919 )
920 })?
921 .await
922 } else {
923 this.read_with(cx, |this, cx| {
924 Self::build_with_base_buffer(
925 buffer.clone(),
926 base_text,
927 this.base_text().clone(),
928 cx,
929 )
930 })?
931 .await
932 };
933 Ok(BufferDiffSnapshot {
934 inner,
935 secondary_diff: None,
936 })
937 }
938
939 pub fn set_snapshot(
940 &mut self,
941 buffer: &text::BufferSnapshot,
942 new_snapshot: BufferDiffSnapshot,
943 language_changed: bool,
944 secondary_changed_range: Option<Range<Anchor>>,
945 cx: &mut Context<Self>,
946 ) -> Option<Range<Anchor>> {
947 let changed_range = self.set_state(new_snapshot.inner, buffer);
948 if language_changed {
949 cx.emit(BufferDiffEvent::LanguageChanged);
950 }
951
952 let changed_range = match (secondary_changed_range, changed_range) {
953 (None, None) => None,
954 (Some(unstaged_range), None) => self.range_to_hunk_range(unstaged_range, &buffer, cx),
955 (None, Some(uncommitted_range)) => Some(uncommitted_range),
956 (Some(unstaged_range), Some(uncommitted_range)) => {
957 let mut start = uncommitted_range.start;
958 let mut end = uncommitted_range.end;
959 if let Some(unstaged_range) = self.range_to_hunk_range(unstaged_range, &buffer, cx)
960 {
961 start = unstaged_range.start.min(&uncommitted_range.start, &buffer);
962 end = unstaged_range.end.max(&uncommitted_range.end, &buffer);
963 }
964 Some(start..end)
965 }
966 };
967
968 cx.emit(BufferDiffEvent::DiffChanged {
969 changed_range: changed_range.clone(),
970 });
971 changed_range
972 }
973
974 fn set_state(
975 &mut self,
976 new_state: BufferDiffInner,
977 buffer: &text::BufferSnapshot,
978 ) -> Option<Range<Anchor>> {
979 let (base_text_changed, changed_range) =
980 match (self.inner.base_text_exists, new_state.base_text_exists) {
981 (false, false) => (true, None),
982 (true, true)
983 if self.inner.base_text.remote_id() == new_state.base_text.remote_id() =>
984 {
985 (false, new_state.compare(&self.inner, buffer))
986 }
987 _ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),
988 };
989
990 let pending_hunks = mem::replace(&mut self.inner.pending_hunks, SumTree::new(buffer));
991
992 self.inner = new_state;
993 if !base_text_changed {
994 self.inner.pending_hunks = pending_hunks;
995 }
996 changed_range
997 }
998
999 pub fn base_text(&self) -> &language::BufferSnapshot {
1000 &self.inner.base_text
1001 }
1002
1003 pub fn base_text_exists(&self) -> bool {
1004 self.inner.base_text_exists
1005 }
1006
1007 pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1008 BufferDiffSnapshot {
1009 inner: self.inner.clone(),
1010 secondary_diff: self
1011 .secondary_diff
1012 .as_ref()
1013 .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1014 }
1015 }
1016
1017 pub fn hunks<'a>(
1018 &'a self,
1019 buffer_snapshot: &'a text::BufferSnapshot,
1020 cx: &'a App,
1021 ) -> impl 'a + Iterator<Item = DiffHunk> {
1022 self.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer_snapshot, cx)
1023 }
1024
1025 pub fn hunks_intersecting_range<'a>(
1026 &'a self,
1027 range: Range<text::Anchor>,
1028 buffer_snapshot: &'a text::BufferSnapshot,
1029 cx: &'a App,
1030 ) -> impl 'a + Iterator<Item = DiffHunk> {
1031 let unstaged_counterpart = self
1032 .secondary_diff
1033 .as_ref()
1034 .map(|diff| &diff.read(cx).inner);
1035 self.inner
1036 .hunks_intersecting_range(range, buffer_snapshot, unstaged_counterpart)
1037 }
1038
1039 pub fn hunks_intersecting_range_rev<'a>(
1040 &'a self,
1041 range: Range<text::Anchor>,
1042 buffer_snapshot: &'a text::BufferSnapshot,
1043 ) -> impl 'a + Iterator<Item = DiffHunk> {
1044 self.inner
1045 .hunks_intersecting_range_rev(range, buffer_snapshot)
1046 }
1047
1048 pub fn hunks_in_row_range<'a>(
1049 &'a self,
1050 range: Range<u32>,
1051 buffer: &'a text::BufferSnapshot,
1052 cx: &'a App,
1053 ) -> impl 'a + Iterator<Item = DiffHunk> {
1054 let start = buffer.anchor_before(Point::new(range.start, 0));
1055 let end = buffer.anchor_after(Point::new(range.end, 0));
1056 self.hunks_intersecting_range(start..end, buffer, cx)
1057 }
1058
1059 /// Used in cases where the change set isn't derived from git.
1060 pub fn set_base_text(
1061 &mut self,
1062 base_buffer: Entity<language::Buffer>,
1063 buffer: text::BufferSnapshot,
1064 cx: &mut Context<Self>,
1065 ) -> oneshot::Receiver<()> {
1066 let (tx, rx) = oneshot::channel();
1067 let this = cx.weak_entity();
1068 let base_buffer = base_buffer.read(cx);
1069 let language_registry = base_buffer.language_registry();
1070 let base_buffer = base_buffer.snapshot();
1071 let base_text = Arc::new(base_buffer.text());
1072
1073 let snapshot = BufferDiff::build(
1074 buffer.clone(),
1075 Some(base_text),
1076 base_buffer.language().cloned(),
1077 language_registry,
1078 cx,
1079 );
1080 let complete_on_drop = util::defer(|| {
1081 tx.send(()).ok();
1082 });
1083 cx.spawn(async move |_, cx| {
1084 let snapshot = snapshot.await;
1085 let Some(this) = this.upgrade() else {
1086 return;
1087 };
1088 this.update(cx, |this, _| {
1089 this.set_state(snapshot, &buffer);
1090 })
1091 .log_err();
1092 drop(complete_on_drop)
1093 })
1094 .detach();
1095 rx
1096 }
1097
1098 pub fn base_text_string(&self) -> Option<String> {
1099 self.inner
1100 .base_text_exists
1101 .then(|| self.inner.base_text.text())
1102 }
1103
1104 pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1105 BufferDiff {
1106 buffer_id: buffer.remote_id(),
1107 inner: BufferDiff::build_empty(buffer, cx),
1108 secondary_diff: None,
1109 }
1110 }
1111
1112 #[cfg(any(test, feature = "test-support"))]
1113 pub fn new_with_base_text(
1114 base_text: &str,
1115 buffer: &Entity<language::Buffer>,
1116 cx: &mut App,
1117 ) -> Self {
1118 let mut base_text = base_text.to_owned();
1119 text::LineEnding::normalize(&mut base_text);
1120 let snapshot = BufferDiff::build(
1121 buffer.read(cx).text_snapshot(),
1122 Some(base_text.into()),
1123 None,
1124 None,
1125 cx,
1126 );
1127 let snapshot = cx.background_executor().block(snapshot);
1128 BufferDiff {
1129 buffer_id: buffer.read(cx).remote_id(),
1130 inner: snapshot,
1131 secondary_diff: None,
1132 }
1133 }
1134
1135 #[cfg(any(test, feature = "test-support"))]
1136 pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
1137 let base_text = self.base_text_string().map(Arc::new);
1138 let snapshot = BufferDiff::build_with_base_buffer(
1139 buffer.clone(),
1140 base_text,
1141 self.inner.base_text.clone(),
1142 cx,
1143 );
1144 let snapshot = cx.background_executor().block(snapshot);
1145 let changed_range = self.set_state(snapshot, &buffer);
1146 cx.emit(BufferDiffEvent::DiffChanged { changed_range });
1147 }
1148}
1149
1150impl DiffHunk {
1151 pub fn is_created_file(&self) -> bool {
1152 self.diff_base_byte_range == (0..0) && self.buffer_range == (Anchor::MIN..Anchor::MAX)
1153 }
1154
1155 pub fn status(&self) -> DiffHunkStatus {
1156 let kind = if self.buffer_range.start == self.buffer_range.end {
1157 DiffHunkStatusKind::Deleted
1158 } else if self.diff_base_byte_range.is_empty() {
1159 DiffHunkStatusKind::Added
1160 } else {
1161 DiffHunkStatusKind::Modified
1162 };
1163 DiffHunkStatus {
1164 kind,
1165 secondary: self.secondary_status,
1166 }
1167 }
1168}
1169
1170impl DiffHunkStatus {
1171 pub fn has_secondary_hunk(&self) -> bool {
1172 matches!(
1173 self.secondary,
1174 DiffHunkSecondaryStatus::HasSecondaryHunk
1175 | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1176 | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1177 )
1178 }
1179
1180 pub fn is_pending(&self) -> bool {
1181 matches!(
1182 self.secondary,
1183 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1184 | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1185 )
1186 }
1187
1188 pub fn is_deleted(&self) -> bool {
1189 self.kind == DiffHunkStatusKind::Deleted
1190 }
1191
1192 pub fn is_added(&self) -> bool {
1193 self.kind == DiffHunkStatusKind::Added
1194 }
1195
1196 pub fn is_modified(&self) -> bool {
1197 self.kind == DiffHunkStatusKind::Modified
1198 }
1199
1200 pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1201 Self {
1202 kind: DiffHunkStatusKind::Added,
1203 secondary,
1204 }
1205 }
1206
1207 pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1208 Self {
1209 kind: DiffHunkStatusKind::Modified,
1210 secondary,
1211 }
1212 }
1213
1214 pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1215 Self {
1216 kind: DiffHunkStatusKind::Deleted,
1217 secondary,
1218 }
1219 }
1220
1221 pub fn deleted_none() -> Self {
1222 Self {
1223 kind: DiffHunkStatusKind::Deleted,
1224 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1225 }
1226 }
1227
1228 pub fn added_none() -> Self {
1229 Self {
1230 kind: DiffHunkStatusKind::Added,
1231 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1232 }
1233 }
1234
1235 pub fn modified_none() -> Self {
1236 Self {
1237 kind: DiffHunkStatusKind::Modified,
1238 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1239 }
1240 }
1241}
1242
1243/// Range (crossing new lines), old, new
1244#[cfg(any(test, feature = "test-support"))]
1245#[track_caller]
1246pub fn assert_hunks<ExpectedText, HunkIter>(
1247 diff_hunks: HunkIter,
1248 buffer: &text::BufferSnapshot,
1249 diff_base: &str,
1250 expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1251) where
1252 HunkIter: Iterator<Item = DiffHunk>,
1253 ExpectedText: AsRef<str>,
1254{
1255 let actual_hunks = diff_hunks
1256 .map(|hunk| {
1257 (
1258 hunk.range.clone(),
1259 &diff_base[hunk.diff_base_byte_range.clone()],
1260 buffer
1261 .text_for_range(hunk.range.clone())
1262 .collect::<String>(),
1263 hunk.status(),
1264 )
1265 })
1266 .collect::<Vec<_>>();
1267
1268 let expected_hunks: Vec<_> = expected_hunks
1269 .iter()
1270 .map(|(r, old_text, new_text, status)| {
1271 (
1272 Point::new(r.start, 0)..Point::new(r.end, 0),
1273 old_text.as_ref(),
1274 new_text.as_ref().to_string(),
1275 *status,
1276 )
1277 })
1278 .collect();
1279
1280 pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1281}
1282
1283#[cfg(test)]
1284mod tests {
1285 use std::fmt::Write as _;
1286
1287 use super::*;
1288 use gpui::TestAppContext;
1289 use rand::{rngs::StdRng, Rng as _};
1290 use text::{Buffer, BufferId, Rope};
1291 use unindent::Unindent as _;
1292 use util::test::marked_text_ranges;
1293
1294 #[ctor::ctor]
1295 fn init_logger() {
1296 if std::env::var("RUST_LOG").is_ok() {
1297 env_logger::init();
1298 }
1299 }
1300
1301 #[gpui::test]
1302 async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1303 let diff_base = "
1304 one
1305 two
1306 three
1307 "
1308 .unindent();
1309
1310 let buffer_text = "
1311 one
1312 HELLO
1313 three
1314 "
1315 .unindent();
1316
1317 let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1318 let mut diff = BufferDiff::build_sync(buffer.clone(), diff_base.clone(), cx);
1319 assert_hunks(
1320 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
1321 &buffer,
1322 &diff_base,
1323 &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1324 );
1325
1326 buffer.edit([(0..0, "point five\n")]);
1327 diff = BufferDiff::build_sync(buffer.clone(), diff_base.clone(), cx);
1328 assert_hunks(
1329 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
1330 &buffer,
1331 &diff_base,
1332 &[
1333 (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1334 (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1335 ],
1336 );
1337
1338 diff = cx.update(|cx| BufferDiff::build_empty(&buffer, cx));
1339 assert_hunks::<&str, _>(
1340 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
1341 &buffer,
1342 &diff_base,
1343 &[],
1344 );
1345 }
1346
1347 #[gpui::test]
1348 async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1349 let head_text = "
1350 zero
1351 one
1352 two
1353 three
1354 four
1355 five
1356 six
1357 seven
1358 eight
1359 nine
1360 "
1361 .unindent();
1362
1363 let index_text = "
1364 zero
1365 one
1366 TWO
1367 three
1368 FOUR
1369 five
1370 six
1371 seven
1372 eight
1373 NINE
1374 "
1375 .unindent();
1376
1377 let buffer_text = "
1378 zero
1379 one
1380 TWO
1381 three
1382 FOUR
1383 FIVE
1384 six
1385 SEVEN
1386 eight
1387 nine
1388 "
1389 .unindent();
1390
1391 let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1392 let unstaged_diff = BufferDiff::build_sync(buffer.clone(), index_text.clone(), cx);
1393
1394 let uncommitted_diff = BufferDiff::build_sync(buffer.clone(), head_text.clone(), cx);
1395
1396 let expected_hunks = vec![
1397 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1398 (
1399 4..6,
1400 "four\nfive\n",
1401 "FOUR\nFIVE\n",
1402 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1403 ),
1404 (
1405 7..8,
1406 "seven\n",
1407 "SEVEN\n",
1408 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1409 ),
1410 ];
1411
1412 assert_hunks(
1413 uncommitted_diff.hunks_intersecting_range(
1414 Anchor::MIN..Anchor::MAX,
1415 &buffer,
1416 Some(&unstaged_diff),
1417 ),
1418 &buffer,
1419 &head_text,
1420 &expected_hunks,
1421 );
1422 }
1423
1424 #[gpui::test]
1425 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1426 let diff_base = Arc::new(
1427 "
1428 one
1429 two
1430 three
1431 four
1432 five
1433 six
1434 seven
1435 eight
1436 nine
1437 ten
1438 "
1439 .unindent(),
1440 );
1441
1442 let buffer_text = "
1443 A
1444 one
1445 B
1446 two
1447 C
1448 three
1449 HELLO
1450 four
1451 five
1452 SIXTEEN
1453 seven
1454 eight
1455 WORLD
1456 nine
1457
1458 ten
1459
1460 "
1461 .unindent();
1462
1463 let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1464 let diff = cx
1465 .update(|cx| {
1466 BufferDiff::build(buffer.snapshot(), Some(diff_base.clone()), None, None, cx)
1467 })
1468 .await;
1469 assert_eq!(
1470 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None)
1471 .count(),
1472 8
1473 );
1474
1475 assert_hunks(
1476 diff.hunks_intersecting_range(
1477 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1478 &buffer,
1479 None,
1480 ),
1481 &buffer,
1482 &diff_base,
1483 &[
1484 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1485 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1486 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1487 ],
1488 );
1489 }
1490
1491 #[gpui::test]
1492 async fn test_stage_hunk(cx: &mut TestAppContext) {
1493 struct Example {
1494 name: &'static str,
1495 head_text: String,
1496 index_text: String,
1497 buffer_marked_text: String,
1498 final_index_text: String,
1499 }
1500
1501 let table = [
1502 Example {
1503 name: "uncommitted hunk straddles end of unstaged hunk",
1504 head_text: "
1505 one
1506 two
1507 three
1508 four
1509 five
1510 "
1511 .unindent(),
1512 index_text: "
1513 one
1514 TWO_HUNDRED
1515 three
1516 FOUR_HUNDRED
1517 five
1518 "
1519 .unindent(),
1520 buffer_marked_text: "
1521 ZERO
1522 one
1523 two
1524 «THREE_HUNDRED
1525 FOUR_HUNDRED»
1526 five
1527 SIX
1528 "
1529 .unindent(),
1530 final_index_text: "
1531 one
1532 two
1533 THREE_HUNDRED
1534 FOUR_HUNDRED
1535 five
1536 "
1537 .unindent(),
1538 },
1539 Example {
1540 name: "uncommitted hunk straddles start of unstaged hunk",
1541 head_text: "
1542 one
1543 two
1544 three
1545 four
1546 five
1547 "
1548 .unindent(),
1549 index_text: "
1550 one
1551 TWO_HUNDRED
1552 three
1553 FOUR_HUNDRED
1554 five
1555 "
1556 .unindent(),
1557 buffer_marked_text: "
1558 ZERO
1559 one
1560 «TWO_HUNDRED
1561 THREE_HUNDRED»
1562 four
1563 five
1564 SIX
1565 "
1566 .unindent(),
1567 final_index_text: "
1568 one
1569 TWO_HUNDRED
1570 THREE_HUNDRED
1571 four
1572 five
1573 "
1574 .unindent(),
1575 },
1576 Example {
1577 name: "uncommitted hunk strictly contains unstaged hunks",
1578 head_text: "
1579 one
1580 two
1581 three
1582 four
1583 five
1584 six
1585 seven
1586 "
1587 .unindent(),
1588 index_text: "
1589 one
1590 TWO
1591 THREE
1592 FOUR
1593 FIVE
1594 SIX
1595 seven
1596 "
1597 .unindent(),
1598 buffer_marked_text: "
1599 one
1600 TWO
1601 «THREE_HUNDRED
1602 FOUR
1603 FIVE_HUNDRED»
1604 SIX
1605 seven
1606 "
1607 .unindent(),
1608 final_index_text: "
1609 one
1610 TWO
1611 THREE_HUNDRED
1612 FOUR
1613 FIVE_HUNDRED
1614 SIX
1615 seven
1616 "
1617 .unindent(),
1618 },
1619 Example {
1620 name: "uncommitted deletion hunk",
1621 head_text: "
1622 one
1623 two
1624 three
1625 four
1626 five
1627 "
1628 .unindent(),
1629 index_text: "
1630 one
1631 two
1632 three
1633 four
1634 five
1635 "
1636 .unindent(),
1637 buffer_marked_text: "
1638 one
1639 ˇfive
1640 "
1641 .unindent(),
1642 final_index_text: "
1643 one
1644 five
1645 "
1646 .unindent(),
1647 },
1648 ];
1649
1650 for example in table {
1651 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
1652 let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1653 let hunk_range =
1654 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
1655
1656 let unstaged = BufferDiff::build_sync(buffer.clone(), example.index_text.clone(), cx);
1657 let uncommitted = BufferDiff::build_sync(buffer.clone(), example.head_text.clone(), cx);
1658
1659 let unstaged_diff = cx.new(|cx| {
1660 let mut diff = BufferDiff::new(&buffer, cx);
1661 diff.set_state(unstaged, &buffer);
1662 diff
1663 });
1664
1665 let uncommitted_diff = cx.new(|cx| {
1666 let mut diff = BufferDiff::new(&buffer, cx);
1667 diff.set_state(uncommitted, &buffer);
1668 diff.set_secondary_diff(unstaged_diff);
1669 diff
1670 });
1671
1672 uncommitted_diff.update(cx, |diff, cx| {
1673 let hunks = diff
1674 .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
1675 .collect::<Vec<_>>();
1676 for hunk in &hunks {
1677 assert_ne!(
1678 hunk.secondary_status,
1679 DiffHunkSecondaryStatus::NoSecondaryHunk
1680 )
1681 }
1682
1683 let new_index_text = diff
1684 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
1685 .unwrap()
1686 .to_string();
1687
1688 let hunks = diff
1689 .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
1690 .collect::<Vec<_>>();
1691 for hunk in &hunks {
1692 assert_eq!(
1693 hunk.secondary_status,
1694 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1695 )
1696 }
1697
1698 pretty_assertions::assert_eq!(
1699 new_index_text,
1700 example.final_index_text,
1701 "example: {}",
1702 example.name
1703 );
1704 });
1705 }
1706 }
1707
1708 #[gpui::test]
1709 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
1710 let base_text = "
1711 zero
1712 one
1713 two
1714 three
1715 four
1716 five
1717 six
1718 seven
1719 eight
1720 nine
1721 "
1722 .unindent();
1723
1724 let buffer_text_1 = "
1725 one
1726 three
1727 four
1728 five
1729 SIX
1730 seven
1731 eight
1732 NINE
1733 "
1734 .unindent();
1735
1736 let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
1737
1738 let empty_diff = cx.update(|cx| BufferDiff::build_empty(&buffer, cx));
1739 let diff_1 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1740 let range = diff_1.compare(&empty_diff, &buffer).unwrap();
1741 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
1742
1743 // Edit does not affect the diff.
1744 buffer.edit_via_marked_text(
1745 &"
1746 one
1747 three
1748 four
1749 five
1750 «SIX.5»
1751 seven
1752 eight
1753 NINE
1754 "
1755 .unindent(),
1756 );
1757 let diff_2 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1758 assert_eq!(None, diff_2.compare(&diff_1, &buffer));
1759
1760 // Edit turns a deletion hunk into a modification.
1761 buffer.edit_via_marked_text(
1762 &"
1763 one
1764 «THREE»
1765 four
1766 five
1767 SIX.5
1768 seven
1769 eight
1770 NINE
1771 "
1772 .unindent(),
1773 );
1774 let diff_3 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1775 let range = diff_3.compare(&diff_2, &buffer).unwrap();
1776 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
1777
1778 // Edit turns a modification hunk into a deletion.
1779 buffer.edit_via_marked_text(
1780 &"
1781 one
1782 THREE
1783 four
1784 five«»
1785 seven
1786 eight
1787 NINE
1788 "
1789 .unindent(),
1790 );
1791 let diff_4 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1792 let range = diff_4.compare(&diff_3, &buffer).unwrap();
1793 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
1794
1795 // Edit introduces a new insertion hunk.
1796 buffer.edit_via_marked_text(
1797 &"
1798 one
1799 THREE
1800 four«
1801 FOUR.5
1802 »five
1803 seven
1804 eight
1805 NINE
1806 "
1807 .unindent(),
1808 );
1809 let diff_5 = BufferDiff::build_sync(buffer.snapshot(), base_text.clone(), cx);
1810 let range = diff_5.compare(&diff_4, &buffer).unwrap();
1811 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
1812
1813 // Edit removes a hunk.
1814 buffer.edit_via_marked_text(
1815 &"
1816 one
1817 THREE
1818 four
1819 FOUR.5
1820 five
1821 seven
1822 eight
1823 «nine»
1824 "
1825 .unindent(),
1826 );
1827 let diff_6 = BufferDiff::build_sync(buffer.snapshot(), base_text, cx);
1828 let range = diff_6.compare(&diff_5, &buffer).unwrap();
1829 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
1830 }
1831
1832 #[gpui::test(iterations = 100)]
1833 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
1834 fn gen_line(rng: &mut StdRng) -> String {
1835 if rng.gen_bool(0.2) {
1836 "\n".to_owned()
1837 } else {
1838 let c = rng.gen_range('A'..='Z');
1839 format!("{c}{c}{c}\n")
1840 }
1841 }
1842
1843 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
1844 let mut old_lines = {
1845 let mut old_lines = Vec::new();
1846 let mut old_lines_iter = head.lines();
1847 while let Some(line) = old_lines_iter.next() {
1848 assert!(!line.ends_with("\n"));
1849 old_lines.push(line.to_owned());
1850 }
1851 if old_lines.last().is_some_and(|line| line.is_empty()) {
1852 old_lines.pop();
1853 }
1854 old_lines.into_iter()
1855 };
1856 let mut result = String::new();
1857 let unchanged_count = rng.gen_range(0..=old_lines.len());
1858 result +=
1859 &old_lines
1860 .by_ref()
1861 .take(unchanged_count)
1862 .fold(String::new(), |mut s, line| {
1863 writeln!(&mut s, "{line}").unwrap();
1864 s
1865 });
1866 while old_lines.len() > 0 {
1867 let deleted_count = rng.gen_range(0..=old_lines.len());
1868 let _advance = old_lines
1869 .by_ref()
1870 .take(deleted_count)
1871 .map(|line| line.len() + 1)
1872 .sum::<usize>();
1873 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
1874 let added_count = rng.gen_range(minimum_added..=5);
1875 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
1876 result += &addition;
1877
1878 if old_lines.len() > 0 {
1879 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
1880 if blank_lines == old_lines.len() {
1881 break;
1882 };
1883 let unchanged_count = rng.gen_range((blank_lines + 1).max(1)..=old_lines.len());
1884 result += &old_lines.by_ref().take(unchanged_count).fold(
1885 String::new(),
1886 |mut s, line| {
1887 writeln!(&mut s, "{line}").unwrap();
1888 s
1889 },
1890 );
1891 }
1892 }
1893 result
1894 }
1895
1896 fn uncommitted_diff(
1897 working_copy: &language::BufferSnapshot,
1898 index_text: &Rope,
1899 head_text: String,
1900 cx: &mut TestAppContext,
1901 ) -> Entity<BufferDiff> {
1902 let inner = BufferDiff::build_sync(working_copy.text.clone(), head_text, cx);
1903 let secondary = BufferDiff {
1904 buffer_id: working_copy.remote_id(),
1905 inner: BufferDiff::build_sync(
1906 working_copy.text.clone(),
1907 index_text.to_string(),
1908 cx,
1909 ),
1910 secondary_diff: None,
1911 };
1912 let secondary = cx.new(|_| secondary);
1913 cx.new(|_| BufferDiff {
1914 buffer_id: working_copy.remote_id(),
1915 inner,
1916 secondary_diff: Some(secondary),
1917 })
1918 }
1919
1920 let operations = std::env::var("OPERATIONS")
1921 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1922 .unwrap_or(10);
1923
1924 let rng = &mut rng;
1925 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
1926 writeln!(&mut s, "{c}{c}{c}").unwrap();
1927 s
1928 });
1929 let working_copy = gen_working_copy(rng, &head_text);
1930 let working_copy = cx.new(|cx| {
1931 language::Buffer::local_normalized(
1932 Rope::from(working_copy.as_str()),
1933 text::LineEnding::default(),
1934 cx,
1935 )
1936 });
1937 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
1938 let mut index_text = if rng.gen() {
1939 Rope::from(head_text.as_str())
1940 } else {
1941 working_copy.as_rope().clone()
1942 };
1943
1944 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
1945 let mut hunks = diff.update(cx, |diff, cx| {
1946 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
1947 .collect::<Vec<_>>()
1948 });
1949 if hunks.len() == 0 {
1950 return;
1951 }
1952
1953 for _ in 0..operations {
1954 let i = rng.gen_range(0..hunks.len());
1955 let hunk = &mut hunks[i];
1956 let hunk_to_change = hunk.clone();
1957 let stage = match hunk.secondary_status {
1958 DiffHunkSecondaryStatus::HasSecondaryHunk => {
1959 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
1960 true
1961 }
1962 DiffHunkSecondaryStatus::NoSecondaryHunk => {
1963 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
1964 false
1965 }
1966 _ => unreachable!(),
1967 };
1968
1969 index_text = diff.update(cx, |diff, cx| {
1970 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
1971 .unwrap()
1972 });
1973
1974 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
1975 let found_hunks = diff.update(cx, |diff, cx| {
1976 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
1977 .collect::<Vec<_>>()
1978 });
1979 assert_eq!(hunks.len(), found_hunks.len());
1980
1981 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
1982 assert_eq!(
1983 expected_hunk.buffer_range.to_point(&working_copy),
1984 found_hunk.buffer_range.to_point(&working_copy)
1985 );
1986 assert_eq!(
1987 expected_hunk.diff_base_byte_range,
1988 found_hunk.diff_base_byte_range
1989 );
1990 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
1991 }
1992 hunks = found_hunks;
1993 }
1994 }
1995}