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