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