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