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