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