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