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