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