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