1use futures::channel::oneshot;
2use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
3use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
4use language::{
5 Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry,
6 language_settings::language_settings, word_diff_ranges,
7};
8use rope::Rope;
9use std::{
10 cmp::Ordering,
11 future::Future,
12 iter,
13 ops::{Range, RangeInclusive},
14 sync::Arc,
15};
16use sum_tree::SumTree;
17use text::{
18 Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,
19};
20use util::ResultExt;
21
22pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
23
24pub struct BufferDiff {
25 pub buffer_id: BufferId,
26 inner: BufferDiffInner<Entity<language::Buffer>>,
27 secondary_diff: Option<Entity<BufferDiff>>,
28}
29
30#[derive(Clone)]
31pub struct BufferDiffSnapshot {
32 inner: BufferDiffInner<language::BufferSnapshot>,
33 secondary_diff: Option<Arc<BufferDiffSnapshot>>,
34}
35
36impl std::fmt::Debug for BufferDiffSnapshot {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("BufferDiffSnapshot")
39 .field("inner", &self.inner)
40 .field("secondary_diff", &self.secondary_diff)
41 .finish()
42 }
43}
44
45#[derive(Clone)]
46pub struct BufferDiffUpdate {
47 inner: BufferDiffInner<Arc<str>>,
48 buffer_snapshot: text::BufferSnapshot,
49 base_text_edits: Option<Diff>,
50 base_text_changed: bool,
51}
52
53impl BufferDiffUpdate {
54 pub fn from_hunks(
55 base_text: Arc<str>,
56 base_snapshot: &Rope,
57 buffer_snapshot: text::BufferSnapshot,
58 edits: Patch<usize>,
59 diff_options: Option<DiffOptions>,
60 ) -> Self {
61 let hunks = edits.into_iter().map(|edit| {
62 let buffer_range = buffer_snapshot.anchor_after(edit.new.start)
63 ..buffer_snapshot.anchor_after(edit.new.end);
64
65 let (base_word_diffs, buffer_word_diffs) = if let Some(options) = &diff_options {
66 word_diffs(
67 base_snapshot,
68 edit.old.clone(),
69 &buffer_snapshot,
70 buffer_range,
71 options,
72 )
73 } else {
74 (Vec::new(), Vec::new())
75 };
76 InternalDiffHunk {
77 buffer_range: buffer_snapshot.anchor_before(edit.new.start)
78 ..buffer_snapshot.anchor_before(edit.new.end),
79 diff_base_byte_range: edit.old,
80 base_word_diffs,
81 buffer_word_diffs,
82 }
83 });
84
85 Self {
86 inner: BufferDiffInner {
87 hunks: SumTree::from_iter(hunks, &buffer_snapshot),
88 pending_hunks: SumTree::new(&buffer_snapshot),
89 base_text,
90 base_text_exists: true,
91 buffer_snapshot: buffer_snapshot.clone(),
92 },
93 buffer_snapshot,
94 base_text_edits: None,
95 base_text_changed: false,
96 }
97 }
98}
99
100#[derive(Clone)]
101struct BufferDiffInner<BaseText> {
102 hunks: SumTree<InternalDiffHunk>,
103 pending_hunks: SumTree<PendingHunk>,
104 base_text: BaseText,
105 base_text_exists: bool,
106 buffer_snapshot: text::BufferSnapshot,
107}
108
109impl<BaseText> BufferDiffInner<BaseText> {
110 fn buffer_version(&self) -> &clock::Global {
111 self.buffer_snapshot.version()
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
116pub struct DiffHunkStatus {
117 pub kind: DiffHunkStatusKind,
118 pub secondary: DiffHunkSecondaryStatus,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub enum DiffHunkStatusKind {
123 Added,
124 Modified,
125 Deleted,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
129/// Diff of Working Copy vs Index
130/// aka 'is this hunk staged or not'
131pub enum DiffHunkSecondaryStatus {
132 /// Unstaged
133 HasSecondaryHunk,
134 /// Partially staged
135 OverlapsWithSecondaryHunk,
136 /// Staged
137 NoSecondaryHunk,
138 /// We are unstaging
139 SecondaryHunkAdditionPending,
140 /// We are stagind
141 SecondaryHunkRemovalPending,
142}
143
144/// A diff hunk resolved to rows in the buffer.
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct DiffHunk {
147 /// The buffer range as points.
148 pub range: Range<Point>,
149 /// The range in the buffer to which this hunk corresponds.
150 pub buffer_range: Range<Anchor>,
151 /// The range in the buffer's diff base text to which this hunk corresponds.
152 pub diff_base_byte_range: Range<usize>,
153 pub secondary_status: DiffHunkSecondaryStatus,
154 // Anchors representing the word diff locations in the active buffer
155 pub buffer_word_diffs: Vec<Range<Anchor>>,
156 // Offsets relative to the start of the deleted diff that represent word diff locations
157 pub base_word_diffs: Vec<Range<usize>>,
158}
159
160/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
161#[derive(Debug, Clone, PartialEq, Eq)]
162struct InternalDiffHunk {
163 buffer_range: Range<Anchor>,
164 diff_base_byte_range: Range<usize>,
165 base_word_diffs: Vec<Range<usize>>,
166 buffer_word_diffs: Vec<Range<Anchor>>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170struct PendingHunk {
171 buffer_range: Range<Anchor>,
172 diff_base_byte_range: Range<usize>,
173 buffer_version: clock::Global,
174 new_status: DiffHunkSecondaryStatus,
175}
176
177#[derive(Debug, Clone)]
178pub struct DiffHunkSummary {
179 buffer_range: Range<Anchor>,
180 diff_base_byte_range: Range<usize>,
181}
182
183impl sum_tree::Item for InternalDiffHunk {
184 type Summary = DiffHunkSummary;
185
186 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
187 DiffHunkSummary {
188 buffer_range: self.buffer_range.clone(),
189 diff_base_byte_range: self.diff_base_byte_range.clone(),
190 }
191 }
192}
193
194impl sum_tree::Item for PendingHunk {
195 type Summary = DiffHunkSummary;
196
197 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
198 DiffHunkSummary {
199 buffer_range: self.buffer_range.clone(),
200 diff_base_byte_range: self.diff_base_byte_range.clone(),
201 }
202 }
203}
204
205impl sum_tree::Summary for DiffHunkSummary {
206 type Context<'a> = &'a text::BufferSnapshot;
207
208 fn zero(_cx: Self::Context<'_>) -> Self {
209 DiffHunkSummary {
210 buffer_range: Anchor::MIN..Anchor::MIN,
211 diff_base_byte_range: 0..0,
212 }
213 }
214
215 fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
216 self.buffer_range.start = *self
217 .buffer_range
218 .start
219 .min(&other.buffer_range.start, buffer);
220 self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
221
222 self.diff_base_byte_range.start = self
223 .diff_base_byte_range
224 .start
225 .min(other.diff_base_byte_range.start);
226 self.diff_base_byte_range.end = self
227 .diff_base_byte_range
228 .end
229 .max(other.diff_base_byte_range.end);
230 }
231}
232
233impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
234 fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
235 if self
236 .cmp(&cursor_location.buffer_range.start, buffer)
237 .is_lt()
238 {
239 Ordering::Less
240 } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
241 Ordering::Greater
242 } else {
243 Ordering::Equal
244 }
245 }
246}
247
248impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize {
249 fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering {
250 if *self < cursor_location.diff_base_byte_range.start {
251 Ordering::Less
252 } else if *self > cursor_location.diff_base_byte_range.end {
253 Ordering::Greater
254 } else {
255 Ordering::Equal
256 }
257 }
258}
259
260impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 f.debug_struct("BufferDiffSnapshot")
263 .field("hunks", &self.hunks)
264 .field("remote_id", &self.base_text.remote_id())
265 .finish()
266 }
267}
268
269impl BufferDiffSnapshot {
270 #[cfg(test)]
271 fn new_sync(
272 buffer: &text::BufferSnapshot,
273 diff_base: String,
274 cx: &mut gpui::TestAppContext,
275 ) -> BufferDiffSnapshot {
276 let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, buffer, cx));
277 buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
278 }
279
280 pub fn is_empty(&self) -> bool {
281 self.inner.hunks.is_empty()
282 }
283
284 pub fn base_text_string(&self) -> Option<String> {
285 self.inner
286 .base_text_exists
287 .then(|| self.inner.base_text.text())
288 }
289
290 pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
291 self.secondary_diff.as_deref()
292 }
293
294 pub fn buffer_version(&self) -> &clock::Global {
295 self.inner.buffer_version()
296 }
297
298 fn original_buffer_snapshot(&self) -> &text::BufferSnapshot {
299 &self.inner.buffer_snapshot
300 }
301
302 #[ztracing::instrument(skip_all)]
303 pub fn hunks_intersecting_range<'a>(
304 &'a self,
305 range: Range<Anchor>,
306 buffer: &'a text::BufferSnapshot,
307 ) -> impl 'a + Iterator<Item = DiffHunk> {
308 let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
309 self.inner
310 .hunks_intersecting_range(range, buffer, unstaged_counterpart)
311 }
312
313 pub fn hunks_intersecting_range_rev<'a>(
314 &'a self,
315 range: Range<Anchor>,
316 buffer: &'a text::BufferSnapshot,
317 ) -> impl 'a + Iterator<Item = DiffHunk> {
318 let filter = move |summary: &DiffHunkSummary| {
319 let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
320 let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
321 !before_start && !after_end
322 };
323 self.inner.hunks_intersecting_range_rev_impl(filter, buffer)
324 }
325
326 pub fn hunks_intersecting_base_text_range<'a>(
327 &'a self,
328 range: Range<usize>,
329 main_buffer: &'a text::BufferSnapshot,
330 ) -> impl 'a + Iterator<Item = DiffHunk> {
331 let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
332 let filter = move |summary: &DiffHunkSummary| {
333 let before_start = summary.diff_base_byte_range.end < range.start;
334 let after_end = summary.diff_base_byte_range.start > range.end;
335 !before_start && !after_end
336 };
337 self.inner
338 .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart)
339 }
340
341 pub fn hunks_intersecting_base_text_range_rev<'a>(
342 &'a self,
343 range: Range<usize>,
344 main_buffer: &'a text::BufferSnapshot,
345 ) -> impl 'a + Iterator<Item = DiffHunk> {
346 let filter = move |summary: &DiffHunkSummary| {
347 let before_start = summary.diff_base_byte_range.end.cmp(&range.start).is_lt();
348 let after_end = summary.diff_base_byte_range.start.cmp(&range.end).is_gt();
349 !before_start && !after_end
350 };
351 self.inner
352 .hunks_intersecting_range_rev_impl(filter, main_buffer)
353 }
354
355 pub fn hunks<'a>(
356 &'a self,
357 buffer_snapshot: &'a text::BufferSnapshot,
358 ) -> impl 'a + Iterator<Item = DiffHunk> {
359 self.hunks_intersecting_range(
360 Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()),
361 buffer_snapshot,
362 )
363 }
364
365 pub fn hunks_in_row_range<'a>(
366 &'a self,
367 range: Range<u32>,
368 buffer: &'a text::BufferSnapshot,
369 ) -> impl 'a + Iterator<Item = DiffHunk> {
370 let start = buffer.anchor_before(Point::new(range.start, 0));
371 let end = buffer.anchor_after(Point::new(range.end, 0));
372 self.hunks_intersecting_range(start..end, buffer)
373 }
374
375 pub fn range_to_hunk_range(
376 &self,
377 range: Range<Anchor>,
378 buffer: &text::BufferSnapshot,
379 ) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
380 let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next();
381 let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next();
382 let range = first_hunk
383 .as_ref()
384 .zip(last_hunk.as_ref())
385 .map(|(first, last)| first.buffer_range.start..last.buffer_range.end);
386 let base_text_range = first_hunk
387 .zip(last_hunk)
388 .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end);
389 (range, base_text_range)
390 }
391
392 pub fn base_text(&self) -> &language::BufferSnapshot {
393 &self.inner.base_text
394 }
395
396 /// If this function returns `true`, the base texts are equal. If this
397 /// function returns `false`, they might be equal, but might not. This
398 /// result is used to avoid recalculating diffs in situations where we know
399 /// nothing has changed.
400 pub fn base_texts_definitely_eq(&self, other: &Self) -> bool {
401 if self.inner.base_text_exists != other.inner.base_text_exists {
402 return false;
403 }
404 let left = &self.inner.base_text;
405 let right = &other.inner.base_text;
406 let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty());
407 let (new_id, new_version, new_empty) =
408 (right.remote_id(), right.version(), right.is_empty());
409 (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
410 }
411
412 /// Returns the last hunk whose start is less than or equal to the given position.
413 fn hunk_before_base_text_offset<'a>(
414 &self,
415 target: usize,
416 cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
417 ) -> Option<&'a InternalDiffHunk> {
418 cursor.seek_forward(&target, Bias::Left);
419 if cursor
420 .item()
421 .is_none_or(|hunk| target < hunk.diff_base_byte_range.start)
422 {
423 cursor.prev();
424 }
425 cursor
426 .item()
427 .filter(|hunk| target >= hunk.diff_base_byte_range.start)
428 }
429
430 fn hunk_before_buffer_anchor<'a>(
431 &self,
432 target: Anchor,
433 cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
434 buffer: &text::BufferSnapshot,
435 ) -> Option<&'a InternalDiffHunk> {
436 cursor.seek_forward(&target, Bias::Left);
437 if cursor
438 .item()
439 .is_none_or(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_lt())
440 {
441 cursor.prev();
442 }
443 cursor
444 .item()
445 .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge())
446 }
447
448 /// Returns a patch mapping the provided main buffer snapshot to the base text of this diff.
449 ///
450 /// The returned patch is guaranteed to be accurate for all main buffer points in the provided range,
451 /// but not necessarily for points outside that range.
452 pub fn patch_for_buffer_range<'a>(
453 &'a self,
454 range: RangeInclusive<Point>,
455 buffer: &'a text::BufferSnapshot,
456 ) -> Patch<Point> {
457 if !self.inner.base_text_exists {
458 return Patch::new(vec![Edit {
459 old: Point::zero()..buffer.max_point(),
460 new: Point::zero()..Point::zero(),
461 }]);
462 }
463
464 let mut edits_since_diff = Patch::new(
465 buffer
466 .edits_since::<Point>(&self.inner.buffer_snapshot.version)
467 .collect::<Vec<_>>(),
468 );
469 edits_since_diff.invert();
470
471 let mut start_point = edits_since_diff.old_to_new(*range.start());
472 if let Some(first_edit) = edits_since_diff.edits().first() {
473 start_point = start_point.min(first_edit.new.start);
474 }
475
476 let original_snapshot = self.original_buffer_snapshot();
477 let base_text = self.base_text();
478
479 let mut cursor = self.inner.hunks.cursor(original_snapshot);
480 self.hunk_before_buffer_anchor(
481 original_snapshot.anchor_before(start_point),
482 &mut cursor,
483 original_snapshot,
484 );
485 if cursor.item().is_none() {
486 cursor.next();
487 }
488
489 let mut prefix_edit = cursor.prev_item().map(|prev_hunk| Edit {
490 old: Point::zero()..prev_hunk.buffer_range.end.to_point(original_snapshot),
491 new: Point::zero()..prev_hunk.diff_base_byte_range.end.to_point(base_text),
492 });
493
494 let mut range_end = edits_since_diff.old_to_new(*range.end());
495 if let Some(last_edit) = edits_since_diff.edits().last() {
496 range_end = range_end.max(last_edit.new.end);
497 }
498 let range_end = original_snapshot.anchor_before(range_end);
499
500 let hunk_iter = std::iter::from_fn(move || {
501 if let Some(edit) = prefix_edit.take() {
502 return Some(edit);
503 }
504 let hunk = cursor.item()?;
505 if hunk
506 .buffer_range
507 .start
508 .cmp(&range_end, original_snapshot)
509 .is_gt()
510 {
511 return None;
512 }
513 let edit = Edit {
514 old: hunk.buffer_range.to_point(original_snapshot),
515 new: hunk.diff_base_byte_range.to_point(base_text),
516 };
517 cursor.next();
518 Some(edit)
519 });
520
521 edits_since_diff.compose(hunk_iter)
522 }
523
524 #[cfg(test)]
525 pub(crate) fn patch_for_buffer_range_naive<'a>(
526 &'a self,
527 buffer: &'a text::BufferSnapshot,
528 ) -> Patch<Point> {
529 let original_snapshot = self.original_buffer_snapshot();
530
531 let edits_since: Vec<Edit<Point>> = buffer
532 .edits_since::<Point>(original_snapshot.version())
533 .collect();
534 let mut inverted_edits_since = Patch::new(edits_since);
535 inverted_edits_since.invert();
536
537 inverted_edits_since.compose(
538 self.inner
539 .hunks
540 .iter()
541 .map(|hunk| {
542 let old_start = hunk.buffer_range.start.to_point(original_snapshot);
543 let old_end = hunk.buffer_range.end.to_point(original_snapshot);
544 let new_start = self
545 .base_text()
546 .offset_to_point(hunk.diff_base_byte_range.start);
547 let new_end = self
548 .base_text()
549 .offset_to_point(hunk.diff_base_byte_range.end);
550 Edit {
551 old: old_start..old_end,
552 new: new_start..new_end,
553 }
554 })
555 .chain(
556 if !self.inner.base_text_exists && self.inner.hunks.is_empty() {
557 Some(Edit {
558 old: Point::zero()..original_snapshot.max_point(),
559 new: Point::zero()..Point::zero(),
560 })
561 } else {
562 None
563 },
564 ),
565 )
566 }
567
568 /// Returns a patch mapping the base text of this diff to the provided main buffer snapshot.
569 ///
570 /// The returned patch is guaranteed to be accurate for all base text points in the provided range,
571 /// but not necessarily for points outside that range.
572 pub fn patch_for_base_text_range<'a>(
573 &'a self,
574 range: RangeInclusive<Point>,
575 buffer: &'a text::BufferSnapshot,
576 ) -> Patch<Point> {
577 if !self.inner.base_text_exists {
578 return Patch::new(vec![Edit {
579 old: Point::zero()..Point::zero(),
580 new: Point::zero()..buffer.max_point(),
581 }]);
582 }
583
584 let edits_since_diff = buffer
585 .edits_since::<Point>(&self.inner.buffer_snapshot.version)
586 .collect::<Vec<_>>();
587
588 let mut hunk_patch = Vec::new();
589 let mut cursor = self.inner.hunks.cursor(self.original_buffer_snapshot());
590 let hunk_before = self
591 .hunk_before_base_text_offset(range.start().to_offset(self.base_text()), &mut cursor);
592
593 if let Some(hunk) = hunk_before
594 && let Some(first_edit) = edits_since_diff.first()
595 && hunk
596 .buffer_range
597 .start
598 .to_point(self.original_buffer_snapshot())
599 > first_edit.old.start
600 {
601 cursor.reset();
602 self.hunk_before_buffer_anchor(
603 self.original_buffer_snapshot()
604 .anchor_before(first_edit.old.start),
605 &mut cursor,
606 self.original_buffer_snapshot(),
607 );
608 }
609 if cursor.item().is_none() {
610 cursor.next();
611 }
612 if let Some(prev_hunk) = cursor.prev_item() {
613 hunk_patch.push(Edit {
614 old: Point::zero()
615 ..prev_hunk
616 .diff_base_byte_range
617 .end
618 .to_point(self.base_text()),
619 new: Point::zero()
620 ..prev_hunk
621 .buffer_range
622 .end
623 .to_point(self.original_buffer_snapshot()),
624 })
625 }
626 let range_end = range.end().to_offset(self.base_text());
627 while let Some(hunk) = cursor.item()
628 && (hunk.diff_base_byte_range.start <= range_end
629 || edits_since_diff.last().is_some_and(|last_edit| {
630 hunk.buffer_range
631 .start
632 .to_point(self.original_buffer_snapshot())
633 <= last_edit.old.end
634 }))
635 {
636 hunk_patch.push(Edit {
637 old: hunk.diff_base_byte_range.to_point(self.base_text()),
638 new: hunk.buffer_range.to_point(self.original_buffer_snapshot()),
639 });
640 cursor.next();
641 }
642
643 Patch::new(hunk_patch).compose(edits_since_diff)
644 }
645
646 #[cfg(test)]
647 pub(crate) fn patch_for_base_text_range_naive<'a>(
648 &'a self,
649 buffer: &'a text::BufferSnapshot,
650 ) -> Patch<Point> {
651 let original_snapshot = self.original_buffer_snapshot();
652
653 let mut hunk_edits: Vec<Edit<Point>> = Vec::new();
654 for hunk in self.inner.hunks.iter() {
655 let old_start = self
656 .base_text()
657 .offset_to_point(hunk.diff_base_byte_range.start);
658 let old_end = self
659 .base_text()
660 .offset_to_point(hunk.diff_base_byte_range.end);
661 let new_start = hunk.buffer_range.start.to_point(original_snapshot);
662 let new_end = hunk.buffer_range.end.to_point(original_snapshot);
663 hunk_edits.push(Edit {
664 old: old_start..old_end,
665 new: new_start..new_end,
666 });
667 }
668 if !self.inner.base_text_exists && hunk_edits.is_empty() {
669 hunk_edits.push(Edit {
670 old: Point::zero()..Point::zero(),
671 new: Point::zero()..original_snapshot.max_point(),
672 })
673 }
674 let hunk_patch = Patch::new(hunk_edits);
675
676 hunk_patch.compose(buffer.edits_since::<Point>(original_snapshot.version()))
677 }
678
679 pub fn buffer_point_to_base_text_range(
680 &self,
681 point: Point,
682 buffer: &text::BufferSnapshot,
683 ) -> Range<Point> {
684 let patch = self.patch_for_buffer_range(point..=point, buffer);
685 let edit = patch.edit_for_old_position(point);
686 edit.new
687 }
688
689 pub fn base_text_point_to_buffer_range(
690 &self,
691 point: Point,
692 buffer: &text::BufferSnapshot,
693 ) -> Range<Point> {
694 let patch = self.patch_for_base_text_range(point..=point, buffer);
695 let edit = patch.edit_for_old_position(point);
696 edit.new
697 }
698
699 pub fn buffer_point_to_base_text_point(
700 &self,
701 point: Point,
702 buffer: &text::BufferSnapshot,
703 ) -> Point {
704 let patch = self.patch_for_buffer_range(point..=point, buffer);
705 let edit = patch.edit_for_old_position(point);
706 if point == edit.old.end {
707 edit.new.end
708 } else {
709 edit.new.start
710 }
711 }
712
713 pub fn base_text_point_to_buffer_point(
714 &self,
715 point: Point,
716 buffer: &text::BufferSnapshot,
717 ) -> Point {
718 let patch = self.patch_for_base_text_range(point..=point, buffer);
719 let edit = patch.edit_for_old_position(point);
720 if point == edit.old.end {
721 edit.new.end
722 } else {
723 edit.new.start
724 }
725 }
726}
727
728impl BufferDiffInner<Entity<language::Buffer>> {
729 /// Returns the new index text and new pending hunks.
730 fn stage_or_unstage_hunks_impl(
731 &mut self,
732 unstaged_diff: &Self,
733 stage: bool,
734 hunks: &[DiffHunk],
735 buffer: &text::BufferSnapshot,
736 file_exists: bool,
737 cx: &mut Context<BufferDiff>,
738 ) -> Option<Rope> {
739 let head_text = self
740 .base_text_exists
741 .then(|| self.base_text.read(cx).as_rope().clone());
742 let index_text = unstaged_diff
743 .base_text_exists
744 .then(|| unstaged_diff.base_text.read(cx).as_rope().clone());
745
746 // If the file doesn't exist in either HEAD or the index, then the
747 // entire file must be either created or deleted in the index.
748 let (index_text, head_text) = match (index_text, head_text) {
749 (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
750 (index_text, head_text) => {
751 let (new_index_text, new_status) = if stage {
752 log::debug!("stage all");
753 (
754 file_exists.then(|| buffer.as_rope().clone()),
755 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
756 )
757 } else {
758 log::debug!("unstage all");
759 (
760 head_text,
761 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
762 )
763 };
764
765 let hunk = PendingHunk {
766 buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
767 diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
768 buffer_version: buffer.version().clone(),
769 new_status,
770 };
771 self.pending_hunks = SumTree::from_item(hunk, buffer);
772 return new_index_text;
773 }
774 };
775
776 let mut pending_hunks = SumTree::new(buffer);
777 let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
778
779 // first, merge new hunks into pending_hunks
780 for DiffHunk {
781 buffer_range,
782 diff_base_byte_range,
783 secondary_status,
784 ..
785 } in hunks.iter().cloned()
786 {
787 let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
788 pending_hunks.append(preceding_pending_hunks, buffer);
789
790 // Skip all overlapping or adjacent old pending hunks
791 while old_pending_hunks.item().is_some_and(|old_hunk| {
792 old_hunk
793 .buffer_range
794 .start
795 .cmp(&buffer_range.end, buffer)
796 .is_le()
797 }) {
798 old_pending_hunks.next();
799 }
800
801 if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
802 || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
803 {
804 continue;
805 }
806
807 pending_hunks.push(
808 PendingHunk {
809 buffer_range,
810 diff_base_byte_range,
811 buffer_version: buffer.version().clone(),
812 new_status: if stage {
813 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
814 } else {
815 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
816 },
817 },
818 buffer,
819 );
820 }
821 // append the remainder
822 pending_hunks.append(old_pending_hunks.suffix(), buffer);
823
824 let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
825 unstaged_hunk_cursor.next();
826
827 // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
828 let mut prev_unstaged_hunk_buffer_end = 0;
829 let mut prev_unstaged_hunk_base_text_end = 0;
830 let mut edits = Vec::<(Range<usize>, String)>::new();
831 let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
832 while let Some(PendingHunk {
833 buffer_range,
834 diff_base_byte_range,
835 new_status,
836 ..
837 }) = pending_hunks_iter.next()
838 {
839 // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
840 let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
841
842 if let Some(unstaged_hunk) = skipped_unstaged.last() {
843 prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
844 prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
845 }
846
847 // Find where this hunk is in the index if it doesn't overlap
848 let mut buffer_offset_range = buffer_range.to_offset(buffer);
849 let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
850 let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
851
852 loop {
853 // Merge this hunk with any overlapping unstaged hunks.
854 if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
855 let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
856 if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
857 prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
858 prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
859
860 index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
861 buffer_offset_range.start = buffer_offset_range
862 .start
863 .min(unstaged_hunk_offset_range.start);
864 buffer_offset_range.end =
865 buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
866
867 unstaged_hunk_cursor.next();
868 continue;
869 }
870 }
871
872 // If any unstaged hunks were merged, then subsequent pending hunks may
873 // now overlap this hunk. Merge them.
874 if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
875 let next_pending_hunk_offset_range =
876 next_pending_hunk.buffer_range.to_offset(buffer);
877 if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
878 buffer_offset_range.end = buffer_offset_range
879 .end
880 .max(next_pending_hunk_offset_range.end);
881 pending_hunks_iter.next();
882 continue;
883 }
884 }
885
886 break;
887 }
888
889 let end_overshoot = buffer_offset_range
890 .end
891 .saturating_sub(prev_unstaged_hunk_buffer_end);
892 let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
893
894 // Clamp to the index text bounds. The overshoot mapping assumes that
895 // text between unstaged hunks is identical in the buffer and index.
896 // When the buffer has been edited since the diff was computed, anchor
897 // positions shift while diff_base_byte_range values don't, which can
898 // cause index_end to exceed index_text.len().
899 // See `test_stage_all_with_stale_buffer` which would hit an assert
900 // without these min calls
901 let index_end = index_end.min(index_text.len());
902 let index_start = index_start.min(index_end);
903 let index_byte_range = index_start..index_end;
904
905 let replacement_text = match new_status {
906 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
907 log::debug!("staging hunk {:?}", buffer_offset_range);
908 buffer
909 .text_for_range(buffer_offset_range)
910 .collect::<String>()
911 }
912 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
913 log::debug!("unstaging hunk {:?}", buffer_offset_range);
914 head_text
915 .chunks_in_range(diff_base_byte_range.clone())
916 .collect::<String>()
917 }
918 _ => {
919 debug_assert!(false);
920 continue;
921 }
922 };
923
924 edits.push((index_byte_range, replacement_text));
925 }
926 drop(pending_hunks_iter);
927 drop(old_pending_hunks);
928 self.pending_hunks = pending_hunks;
929
930 #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
931 {
932 for window in edits.windows(2) {
933 let (range_a, range_b) = (&window[0].0, &window[1].0);
934 debug_assert!(range_a.end < range_b.start);
935 }
936 }
937
938 let mut new_index_text = Rope::new();
939 let mut index_cursor = index_text.cursor(0);
940
941 for (old_range, replacement_text) in edits {
942 new_index_text.append(index_cursor.slice(old_range.start));
943 index_cursor.seek_forward(old_range.end);
944 new_index_text.push(&replacement_text);
945 }
946 new_index_text.append(index_cursor.suffix());
947 Some(new_index_text)
948 }
949}
950
951impl BufferDiffInner<language::BufferSnapshot> {
952 fn hunks_intersecting_range<'a>(
953 &'a self,
954 range: Range<Anchor>,
955 buffer: &'a text::BufferSnapshot,
956 secondary: Option<&'a Self>,
957 ) -> impl 'a + Iterator<Item = DiffHunk> {
958 let range = range.to_offset(buffer);
959 let filter = move |summary: &DiffHunkSummary| {
960 let summary_range = summary.buffer_range.to_offset(buffer);
961 let before_start = summary_range.end < range.start;
962 let after_end = summary_range.start > range.end;
963 !before_start && !after_end
964 };
965 self.hunks_intersecting_range_impl(filter, buffer, secondary)
966 }
967
968 fn hunks_intersecting_range_impl<'a>(
969 &'a self,
970 filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
971 buffer: &'a text::BufferSnapshot,
972 secondary: Option<&'a Self>,
973 ) -> impl 'a + Iterator<Item = DiffHunk> {
974 let anchor_iter = self
975 .hunks
976 .filter::<_, DiffHunkSummary>(buffer, filter)
977 .flat_map(move |hunk| {
978 [
979 (
980 &hunk.buffer_range.start,
981 (
982 hunk.buffer_range.start,
983 hunk.diff_base_byte_range.start,
984 hunk,
985 ),
986 ),
987 (
988 &hunk.buffer_range.end,
989 (hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
990 ),
991 ]
992 });
993
994 let mut pending_hunks_cursor = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
995 pending_hunks_cursor.next();
996
997 let mut secondary_cursor = None;
998 if let Some(secondary) = secondary.as_ref() {
999 let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
1000 cursor.next();
1001 secondary_cursor = Some(cursor);
1002 }
1003
1004 let max_point = buffer.max_point();
1005 let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
1006 iter::from_fn(move || {
1007 loop {
1008 let (start_point, (start_anchor, start_base, hunk)) = summaries.next()?;
1009 let (mut end_point, (mut end_anchor, end_base, _)) = summaries.next()?;
1010
1011 let base_word_diffs = hunk.base_word_diffs.clone();
1012 let buffer_word_diffs = hunk.buffer_word_diffs.clone();
1013
1014 if !start_anchor.is_valid(buffer) {
1015 continue;
1016 }
1017
1018 if end_point.column > 0 && end_point < max_point {
1019 end_point.row += 1;
1020 end_point.column = 0;
1021 end_anchor = buffer.anchor_before(end_point);
1022 }
1023
1024 let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
1025
1026 let mut has_pending = false;
1027 if start_anchor
1028 .cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
1029 .is_gt()
1030 {
1031 pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
1032 }
1033
1034 if let Some(pending_hunk) = pending_hunks_cursor.item() {
1035 let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
1036 if pending_range.end.column > 0 {
1037 pending_range.end.row += 1;
1038 pending_range.end.column = 0;
1039 }
1040
1041 if pending_range == (start_point..end_point)
1042 && !buffer.has_edits_since_in_range(
1043 &pending_hunk.buffer_version,
1044 start_anchor..end_anchor,
1045 )
1046 {
1047 has_pending = true;
1048 secondary_status = pending_hunk.new_status;
1049 }
1050 }
1051
1052 if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
1053 if start_anchor
1054 .cmp(&secondary_cursor.start().buffer_range.start, buffer)
1055 .is_gt()
1056 {
1057 secondary_cursor.seek_forward(&start_anchor, Bias::Left);
1058 }
1059
1060 if let Some(secondary_hunk) = secondary_cursor.item() {
1061 let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
1062 if secondary_range.end.column > 0 {
1063 secondary_range.end.row += 1;
1064 secondary_range.end.column = 0;
1065 }
1066 if secondary_range.is_empty()
1067 && secondary_hunk.diff_base_byte_range.is_empty()
1068 {
1069 // ignore
1070 } else if secondary_range == (start_point..end_point) {
1071 secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
1072 } else if secondary_range.start <= end_point {
1073 secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
1074 }
1075 }
1076 }
1077
1078 return Some(DiffHunk {
1079 range: start_point..end_point,
1080 diff_base_byte_range: start_base..end_base,
1081 buffer_range: start_anchor..end_anchor,
1082 base_word_diffs,
1083 buffer_word_diffs,
1084 secondary_status,
1085 });
1086 }
1087 })
1088 }
1089
1090 fn hunks_intersecting_range_rev_impl<'a>(
1091 &'a self,
1092 filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
1093 buffer: &'a text::BufferSnapshot,
1094 ) -> impl 'a + Iterator<Item = DiffHunk> {
1095 let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
1096
1097 iter::from_fn(move || {
1098 cursor.prev();
1099
1100 let hunk = cursor.item()?;
1101 let range = hunk.buffer_range.to_point(buffer);
1102
1103 Some(DiffHunk {
1104 range,
1105 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
1106 buffer_range: hunk.buffer_range.clone(),
1107 // The secondary status is not used by callers of this method.
1108 secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
1109 base_word_diffs: hunk.base_word_diffs.clone(),
1110 buffer_word_diffs: hunk.buffer_word_diffs.clone(),
1111 })
1112 })
1113 }
1114}
1115
1116fn build_diff_options(
1117 file: Option<&Arc<dyn File>>,
1118 language: Option<LanguageName>,
1119 language_scope: Option<language::LanguageScope>,
1120 cx: &App,
1121) -> Option<DiffOptions> {
1122 #[cfg(any(test, feature = "test-support"))]
1123 {
1124 if !cx.has_global::<settings::SettingsStore>() {
1125 return Some(DiffOptions {
1126 language_scope,
1127 max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
1128 ..Default::default()
1129 });
1130 }
1131 }
1132
1133 language_settings(language, file, cx)
1134 .word_diff_enabled
1135 .then_some(DiffOptions {
1136 language_scope,
1137 max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
1138 ..Default::default()
1139 })
1140}
1141
1142fn compute_hunks(
1143 diff_base: Option<(Arc<str>, Rope)>,
1144 buffer: &text::BufferSnapshot,
1145 diff_options: Option<DiffOptions>,
1146) -> SumTree<InternalDiffHunk> {
1147 let mut tree = SumTree::new(buffer);
1148
1149 if let Some((diff_base, diff_base_rope)) = diff_base {
1150 let buffer_text = buffer.as_rope().to_string();
1151
1152 let mut options = GitOptions::default();
1153 options.context_lines(0);
1154 let patch = GitPatch::from_buffers(
1155 diff_base.as_bytes(),
1156 None,
1157 buffer_text.as_bytes(),
1158 None,
1159 Some(&mut options),
1160 )
1161 .log_err();
1162
1163 // A common case in Zed is that the empty buffer is represented as just a newline,
1164 // but if we just compute a naive diff you get a "preserved" line in the middle,
1165 // which is a bit odd.
1166 if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
1167 tree.push(
1168 InternalDiffHunk {
1169 buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
1170 diff_base_byte_range: 0..diff_base.len() - 1,
1171 base_word_diffs: Vec::default(),
1172 buffer_word_diffs: Vec::default(),
1173 },
1174 buffer,
1175 );
1176 return tree;
1177 }
1178
1179 if let Some(patch) = patch {
1180 let mut divergence = 0;
1181 for hunk_index in 0..patch.num_hunks() {
1182 let hunk = process_patch_hunk(
1183 &patch,
1184 hunk_index,
1185 &diff_base_rope,
1186 buffer,
1187 &mut divergence,
1188 diff_options.as_ref(),
1189 );
1190 tree.push(hunk, buffer);
1191 }
1192 }
1193 } else {
1194 tree.push(
1195 InternalDiffHunk {
1196 buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
1197 diff_base_byte_range: 0..0,
1198 base_word_diffs: Vec::default(),
1199 buffer_word_diffs: Vec::default(),
1200 },
1201 buffer,
1202 );
1203 }
1204
1205 tree
1206}
1207
1208fn compare_hunks(
1209 new_hunks: &SumTree<InternalDiffHunk>,
1210 old_hunks: &SumTree<InternalDiffHunk>,
1211 old_snapshot: &text::BufferSnapshot,
1212 new_snapshot: &text::BufferSnapshot,
1213 old_base_text: &text::BufferSnapshot,
1214 new_base_text: &text::BufferSnapshot,
1215) -> DiffChanged {
1216 let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
1217 let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
1218 old_cursor.next();
1219 new_cursor.next();
1220 let mut start = None;
1221 let mut end = None;
1222 let mut base_text_start: Option<Anchor> = None;
1223 let mut base_text_end: Option<Anchor> = None;
1224
1225 let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1226 let mut has_changes = false;
1227 let mut extended_end_candidate: Option<text::Anchor> = None;
1228
1229 loop {
1230 match (new_cursor.item(), old_cursor.item()) {
1231 (Some(new_hunk), Some(old_hunk)) => {
1232 match new_hunk
1233 .buffer_range
1234 .start
1235 .cmp(&old_hunk.buffer_range.start, new_snapshot)
1236 {
1237 Ordering::Less => {
1238 has_changes = true;
1239 extended_end_candidate = None;
1240 start.get_or_insert(new_hunk.buffer_range.start);
1241 base_text_start.get_or_insert(
1242 new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1243 );
1244 end.replace(new_hunk.buffer_range.end);
1245 let new_diff_range_end =
1246 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1247 if base_text_end.is_none_or(|base_text_end| {
1248 new_diff_range_end
1249 .cmp(&base_text_end, &new_base_text)
1250 .is_gt()
1251 }) {
1252 base_text_end = Some(new_diff_range_end)
1253 }
1254 new_cursor.next();
1255 }
1256 Ordering::Equal => {
1257 if new_hunk != old_hunk {
1258 has_changes = true;
1259 extended_end_candidate = None;
1260 start.get_or_insert(new_hunk.buffer_range.start);
1261 base_text_start.get_or_insert(
1262 new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1263 );
1264 if old_hunk
1265 .buffer_range
1266 .end
1267 .cmp(&new_hunk.buffer_range.end, new_snapshot)
1268 .is_ge()
1269 {
1270 end.replace(old_hunk.buffer_range.end);
1271 } else {
1272 end.replace(new_hunk.buffer_range.end);
1273 }
1274
1275 let old_hunk_diff_base_range_end =
1276 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1277 let new_hunk_diff_base_range_end =
1278 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1279
1280 base_text_end.replace(
1281 *old_hunk_diff_base_range_end
1282 .max(&new_hunk_diff_base_range_end, new_base_text),
1283 );
1284 } else {
1285 if !has_changes {
1286 last_unchanged_new_hunk_end = Some(new_hunk.buffer_range.end);
1287 } else if extended_end_candidate.is_none() {
1288 extended_end_candidate = Some(new_hunk.buffer_range.start);
1289 }
1290 }
1291
1292 new_cursor.next();
1293 old_cursor.next();
1294 }
1295 Ordering::Greater => {
1296 has_changes = true;
1297 extended_end_candidate = None;
1298 start.get_or_insert(old_hunk.buffer_range.start);
1299 base_text_start.get_or_insert(
1300 old_base_text.anchor_after(old_hunk.diff_base_byte_range.start),
1301 );
1302 end.replace(old_hunk.buffer_range.end);
1303 let old_diff_range_end =
1304 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1305 if base_text_end.is_none_or(|base_text_end| {
1306 old_diff_range_end
1307 .cmp(&base_text_end, new_base_text)
1308 .is_gt()
1309 }) {
1310 base_text_end = Some(old_diff_range_end)
1311 }
1312 old_cursor.next();
1313 }
1314 }
1315 }
1316 (Some(new_hunk), None) => {
1317 has_changes = true;
1318 extended_end_candidate = None;
1319 start.get_or_insert(new_hunk.buffer_range.start);
1320 base_text_start
1321 .get_or_insert(new_base_text.anchor_after(new_hunk.diff_base_byte_range.start));
1322 if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1323 {
1324 end.replace(new_hunk.buffer_range.end);
1325 }
1326 let new_base_text_end =
1327 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1328 if base_text_end.is_none_or(|base_text_end| {
1329 new_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1330 }) {
1331 base_text_end = Some(new_base_text_end)
1332 }
1333 new_cursor.next();
1334 }
1335 (None, Some(old_hunk)) => {
1336 has_changes = true;
1337 extended_end_candidate = None;
1338 start.get_or_insert(old_hunk.buffer_range.start);
1339 base_text_start
1340 .get_or_insert(old_base_text.anchor_after(old_hunk.diff_base_byte_range.start));
1341 if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1342 {
1343 end.replace(old_hunk.buffer_range.end);
1344 }
1345 let old_base_text_end =
1346 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1347 if base_text_end.is_none_or(|base_text_end| {
1348 old_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1349 }) {
1350 base_text_end = Some(old_base_text_end);
1351 }
1352 old_cursor.next();
1353 }
1354 (None, None) => break,
1355 }
1356 }
1357
1358 let changed_range = start.zip(end).map(|(start, end)| start..end);
1359 let base_text_changed_range = base_text_start
1360 .zip(base_text_end)
1361 .map(|(start, end)| (start..end).to_offset(new_base_text));
1362
1363 let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1364 let extended_start = *last_unchanged_new_hunk_end
1365 .unwrap_or(text::Anchor::min_for_buffer(new_snapshot.remote_id()))
1366 .min(&changed_range.start, new_snapshot);
1367 let extended_start = new_snapshot
1368 .anchored_edits_since_in_range::<usize>(
1369 &old_snapshot.version(),
1370 extended_start..changed_range.start,
1371 )
1372 .map(|(_, anchors)| anchors.start)
1373 .min_by(|a, b| a.cmp(b, new_snapshot))
1374 .unwrap_or(changed_range.start);
1375
1376 let extended_end = *extended_end_candidate
1377 .unwrap_or(text::Anchor::max_for_buffer(new_snapshot.remote_id()))
1378 .max(&changed_range.end, new_snapshot);
1379 let extended_end = new_snapshot
1380 .anchored_edits_since_in_range::<usize>(
1381 &old_snapshot.version(),
1382 changed_range.end..extended_end,
1383 )
1384 .map(|(_, anchors)| anchors.end)
1385 .max_by(|a, b| a.cmp(b, new_snapshot))
1386 .unwrap_or(changed_range.end);
1387
1388 Some(extended_start..extended_end)
1389 } else {
1390 None
1391 };
1392
1393 DiffChanged {
1394 changed_range,
1395 base_text_changed_range,
1396 extended_range,
1397 }
1398}
1399
1400fn process_patch_hunk(
1401 patch: &GitPatch<'_>,
1402 hunk_index: usize,
1403 diff_base: &Rope,
1404 buffer: &text::BufferSnapshot,
1405 buffer_row_divergence: &mut i64,
1406 diff_options: Option<&DiffOptions>,
1407) -> InternalDiffHunk {
1408 let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
1409 assert!(line_item_count > 0);
1410
1411 let mut first_deletion_buffer_row: Option<u32> = None;
1412 let mut buffer_row_range: Option<Range<u32>> = None;
1413 let mut diff_base_byte_range: Option<Range<usize>> = None;
1414 let mut first_addition_old_row: Option<u32> = None;
1415
1416 for line_index in 0..line_item_count {
1417 let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
1418 let kind = line.origin_value();
1419 let content_offset = line.content_offset() as isize;
1420 let content_len = line.content().len() as isize;
1421 match kind {
1422 GitDiffLineType::Addition => {
1423 if first_addition_old_row.is_none() {
1424 first_addition_old_row = Some(
1425 (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
1426 );
1427 }
1428 *buffer_row_divergence += 1;
1429 let row = line.new_lineno().unwrap().saturating_sub(1);
1430
1431 match &mut buffer_row_range {
1432 Some(Range { end, .. }) => *end = row + 1,
1433 None => buffer_row_range = Some(row..row + 1),
1434 }
1435 }
1436 GitDiffLineType::Deletion => {
1437 let end = content_offset + content_len;
1438
1439 match &mut diff_base_byte_range {
1440 Some(head_byte_range) => head_byte_range.end = end as usize,
1441 None => diff_base_byte_range = Some(content_offset as usize..end as usize),
1442 }
1443
1444 if first_deletion_buffer_row.is_none() {
1445 let old_row = line.old_lineno().unwrap().saturating_sub(1);
1446 let row = old_row as i64 + *buffer_row_divergence;
1447 first_deletion_buffer_row = Some(row as u32);
1448 }
1449
1450 *buffer_row_divergence -= 1;
1451 }
1452 _ => {}
1453 }
1454 }
1455
1456 let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
1457 // Pure deletion hunk without addition.
1458 let row = first_deletion_buffer_row.unwrap();
1459 row..row
1460 });
1461 let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
1462 // Pure addition hunk without deletion.
1463 let row = first_addition_old_row.unwrap();
1464 let offset = diff_base.point_to_offset(Point::new(row, 0));
1465 offset..offset
1466 });
1467
1468 let start = Point::new(buffer_row_range.start, 0);
1469 let end = Point::new(buffer_row_range.end, 0);
1470 let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
1471
1472 let (base_word_diffs, buffer_word_diffs) = if let Some(options) = diff_options {
1473 word_diffs(
1474 diff_base,
1475 diff_base_byte_range.clone(),
1476 buffer,
1477 buffer_range.clone(),
1478 options,
1479 )
1480 } else {
1481 (Vec::new(), Vec::new())
1482 };
1483
1484 InternalDiffHunk {
1485 buffer_range,
1486 diff_base_byte_range,
1487 base_word_diffs,
1488 buffer_word_diffs,
1489 }
1490}
1491
1492fn word_diffs(
1493 diff_base: &Rope,
1494 diff_base_byte_range: Range<usize>,
1495 buffer: &text::BufferSnapshot,
1496 buffer_range: Range<Anchor>,
1497 options: &DiffOptions,
1498) -> (Vec<Range<usize>>, Vec<Range<Anchor>>) {
1499 let buffer_rows = buffer.text_summary_for_range::<Point, _>(buffer_range.clone());
1500 let base_text_rows = diff_base
1501 .cursor(diff_base_byte_range.start)
1502 .summary::<Point>(diff_base_byte_range.end);
1503 if buffer_rows == Point::zero()
1504 || base_text_rows.row != buffer_rows.row
1505 || base_text_rows.row > options.max_word_diff_line_count as u32
1506 {
1507 return (Vec::default(), Vec::default());
1508 }
1509
1510 let base_text: String = diff_base.chunks_in_range(diff_base_byte_range).collect();
1511
1512 let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
1513
1514 let (base_word_diffs, buffer_word_diffs_relative) =
1515 word_diff_ranges(&base_text, &buffer_text, options.clone());
1516
1517 let buffer_start_offset = buffer_range.start.to_offset(buffer);
1518 let buffer_word_diffs = buffer_word_diffs_relative
1519 .into_iter()
1520 .map(|range| {
1521 let start = buffer.anchor_after(buffer_start_offset + range.start);
1522 let end = buffer.anchor_after(buffer_start_offset + range.end);
1523 start..end
1524 })
1525 .collect();
1526
1527 (base_word_diffs, buffer_word_diffs)
1528}
1529
1530impl std::fmt::Debug for BufferDiff {
1531 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1532 f.debug_struct("BufferChangeSet")
1533 .field("buffer_id", &self.buffer_id)
1534 .finish()
1535 }
1536}
1537
1538#[derive(Clone, Debug, Default)]
1539pub struct DiffChanged {
1540 pub changed_range: Option<Range<text::Anchor>>,
1541 pub base_text_changed_range: Option<Range<usize>>,
1542 pub extended_range: Option<Range<text::Anchor>>,
1543}
1544
1545#[derive(Clone, Debug)]
1546pub enum BufferDiffEvent {
1547 DiffChanged(DiffChanged),
1548 LanguageChanged,
1549 HunksStagedOrUnstaged(Option<Rope>),
1550}
1551
1552impl EventEmitter<BufferDiffEvent> for BufferDiff {}
1553
1554impl BufferDiff {
1555 pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1556 let base_text = cx.new(|cx| {
1557 let mut buffer = language::Buffer::local("", cx);
1558 buffer.set_capability(Capability::ReadOnly, cx);
1559 buffer
1560 });
1561
1562 BufferDiff {
1563 buffer_id: buffer.remote_id(),
1564 inner: BufferDiffInner {
1565 base_text,
1566 hunks: SumTree::new(buffer),
1567 pending_hunks: SumTree::new(buffer),
1568 base_text_exists: false,
1569 buffer_snapshot: buffer.clone(),
1570 },
1571 secondary_diff: None,
1572 }
1573 }
1574
1575 pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1576 let base_text = buffer.text();
1577 let base_text = cx.new(|cx| {
1578 let mut buffer = language::Buffer::local(base_text, cx);
1579 buffer.set_capability(Capability::ReadOnly, cx);
1580 buffer
1581 });
1582
1583 BufferDiff {
1584 buffer_id: buffer.remote_id(),
1585 inner: BufferDiffInner {
1586 base_text,
1587 hunks: SumTree::new(buffer),
1588 pending_hunks: SumTree::new(buffer),
1589 base_text_exists: true,
1590 buffer_snapshot: buffer.clone(),
1591 },
1592 secondary_diff: None,
1593 }
1594 }
1595
1596 #[cfg(any(test, feature = "test-support"))]
1597 pub fn new_with_base_text(
1598 base_text: &str,
1599 buffer: &text::BufferSnapshot,
1600 cx: &mut Context<Self>,
1601 ) -> Self {
1602 let mut this = BufferDiff::new(&buffer, cx);
1603 let mut base_text = base_text.to_owned();
1604 text::LineEnding::normalize(&mut base_text);
1605 let inner = cx.foreground_executor().block_on(this.update_diff(
1606 buffer.clone(),
1607 Some(Arc::from(base_text)),
1608 Some(false),
1609 None,
1610 cx,
1611 ));
1612 this.set_snapshot(inner, &buffer, cx).detach();
1613 this
1614 }
1615
1616 pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1617 self.secondary_diff = Some(diff);
1618 }
1619
1620 pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1621 self.secondary_diff.clone()
1622 }
1623
1624 pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1625 if self.secondary_diff.is_some() {
1626 self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1627 buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1628 diff_base_byte_range: 0..0,
1629 });
1630 let changed_range = Some(Anchor::min_max_range_for_buffer(self.buffer_id));
1631 let base_text_range = Some(0..self.base_text(cx).len());
1632 cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1633 changed_range: changed_range.clone(),
1634 base_text_changed_range: base_text_range,
1635 extended_range: changed_range,
1636 }));
1637 }
1638 }
1639
1640 pub fn stage_or_unstage_hunks(
1641 &mut self,
1642 stage: bool,
1643 hunks: &[DiffHunk],
1644 buffer: &text::BufferSnapshot,
1645 file_exists: bool,
1646 cx: &mut Context<Self>,
1647 ) -> Option<Rope> {
1648 let new_index_text = self
1649 .secondary_diff
1650 .as_ref()?
1651 .update(cx, |secondary_diff, cx| {
1652 self.inner.stage_or_unstage_hunks_impl(
1653 &secondary_diff.inner,
1654 stage,
1655 hunks,
1656 buffer,
1657 file_exists,
1658 cx,
1659 )
1660 });
1661
1662 cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1663 new_index_text.clone(),
1664 ));
1665 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1666 let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1667 let base_text_changed_range =
1668 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1669 cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1670 changed_range: changed_range.clone(),
1671 base_text_changed_range,
1672 extended_range: changed_range,
1673 }));
1674 }
1675 new_index_text
1676 }
1677
1678 pub fn stage_or_unstage_all_hunks(
1679 &mut self,
1680 stage: bool,
1681 buffer: &text::BufferSnapshot,
1682 file_exists: bool,
1683 cx: &mut Context<Self>,
1684 ) {
1685 let hunks = self
1686 .snapshot(cx)
1687 .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1688 .collect::<Vec<_>>();
1689 let Some(secondary) = self.secondary_diff.clone() else {
1690 return;
1691 };
1692 let secondary = secondary.read(cx).inner.clone();
1693 self.inner
1694 .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1695 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1696 let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1697 let base_text_changed_range =
1698 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1699 cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1700 changed_range: changed_range.clone(),
1701 base_text_changed_range,
1702 extended_range: changed_range,
1703 }));
1704 }
1705 }
1706
1707 pub fn update_diff(
1708 &self,
1709 buffer: text::BufferSnapshot,
1710 base_text: Option<Arc<str>>,
1711 base_text_change: Option<bool>,
1712 language: Option<Arc<Language>>,
1713 cx: &App,
1714 ) -> Task<BufferDiffUpdate> {
1715 let prev_base_text = self.base_text(cx).as_rope().clone();
1716 let base_text_changed = base_text_change.is_some();
1717 let compute_base_text_edits = base_text_change == Some(true);
1718 let diff_options = build_diff_options(
1719 None,
1720 language.as_ref().map(|l| l.name()),
1721 language.as_ref().map(|l| l.default_scope()),
1722 cx,
1723 );
1724 let buffer_snapshot = buffer.clone();
1725
1726 let base_text_diff_task = if base_text_changed && compute_base_text_edits {
1727 base_text
1728 .as_ref()
1729 .map(|new_text| self.inner.base_text.read(cx).diff(new_text.clone(), cx))
1730 } else {
1731 None
1732 };
1733
1734 let hunk_task = cx.background_executor().spawn({
1735 let buffer_snapshot = buffer_snapshot.clone();
1736 async move {
1737 let base_text_rope = if let Some(base_text) = &base_text {
1738 if base_text_changed {
1739 Rope::from(base_text.as_ref())
1740 } else {
1741 prev_base_text
1742 }
1743 } else {
1744 Rope::new()
1745 };
1746 let base_text_exists = base_text.is_some();
1747 let hunks = compute_hunks(
1748 base_text
1749 .clone()
1750 .map(|base_text| (base_text, base_text_rope.clone())),
1751 &buffer,
1752 diff_options,
1753 );
1754 let base_text = base_text.unwrap_or_default();
1755 BufferDiffInner {
1756 base_text,
1757 hunks,
1758 base_text_exists,
1759 pending_hunks: SumTree::new(&buffer),
1760 buffer_snapshot,
1761 }
1762 }
1763 });
1764
1765 cx.background_executor().spawn(async move {
1766 let (inner, base_text_edits) = match base_text_diff_task {
1767 Some(diff_task) => {
1768 let (inner, diff) = futures::join!(hunk_task, diff_task);
1769 (inner, Some(diff))
1770 }
1771 None => (hunk_task.await, None),
1772 };
1773
1774 BufferDiffUpdate {
1775 inner,
1776 buffer_snapshot,
1777 base_text_edits,
1778 base_text_changed,
1779 }
1780 })
1781 }
1782
1783 #[ztracing::instrument(skip_all)]
1784 pub fn language_changed(
1785 &mut self,
1786 language: Option<Arc<Language>>,
1787 language_registry: Option<Arc<LanguageRegistry>>,
1788 cx: &mut Context<Self>,
1789 ) {
1790 let fut = self.inner.base_text.update(cx, |base_text, cx| {
1791 if let Some(language_registry) = language_registry {
1792 base_text.set_language_registry(language_registry);
1793 }
1794 base_text.set_language_async(language, cx);
1795 base_text.parsing_idle()
1796 });
1797 cx.spawn(async move |this, cx| {
1798 fut.await;
1799 this.update(cx, |_, cx| {
1800 cx.emit(BufferDiffEvent::LanguageChanged);
1801 })
1802 .ok();
1803 })
1804 .detach();
1805 }
1806
1807 fn set_snapshot_with_secondary_inner(
1808 &mut self,
1809 update: BufferDiffUpdate,
1810 buffer: &text::BufferSnapshot,
1811 secondary_diff_change: Option<Range<Anchor>>,
1812 clear_pending_hunks: bool,
1813 cx: &mut Context<Self>,
1814 ) -> impl Future<Output = DiffChanged> + use<> {
1815 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1816
1817 let old_snapshot = self.snapshot(cx);
1818 let new_state = update.inner;
1819 let base_text_changed = update.base_text_changed;
1820
1821 let state = &mut self.inner;
1822 state.base_text_exists = new_state.base_text_exists;
1823 let should_compare_hunks = update.base_text_edits.is_some() || !base_text_changed;
1824 let parsing_idle = if let Some(diff) = update.base_text_edits {
1825 state.base_text.update(cx, |base_text, cx| {
1826 base_text.set_sync_parse_timeout(None);
1827 base_text.set_capability(Capability::ReadWrite, cx);
1828 base_text.apply_diff(diff, cx);
1829 base_text.set_capability(Capability::ReadOnly, cx);
1830 Some(base_text.parsing_idle())
1831 })
1832 } else if update.base_text_changed {
1833 state.base_text.update(cx, |base_text, cx| {
1834 base_text.set_sync_parse_timeout(None);
1835 base_text.set_capability(Capability::ReadWrite, cx);
1836 base_text.set_text(new_state.base_text.clone(), cx);
1837 base_text.set_capability(Capability::ReadOnly, cx);
1838 Some(base_text.parsing_idle())
1839 })
1840 } else {
1841 None
1842 };
1843
1844 let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1845 let old_base_snapshot = &old_snapshot.inner.base_text;
1846 let new_base_snapshot = state.base_text.read(cx).snapshot();
1847 let DiffChanged {
1848 mut changed_range,
1849 mut base_text_changed_range,
1850 mut extended_range,
1851 } = match (state.base_text_exists, new_state.base_text_exists) {
1852 (false, false) => DiffChanged::default(),
1853 (true, true) if should_compare_hunks => compare_hunks(
1854 &new_state.hunks,
1855 &old_snapshot.inner.hunks,
1856 old_buffer_snapshot,
1857 buffer,
1858 old_base_snapshot,
1859 &new_base_snapshot,
1860 ),
1861 _ => {
1862 let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
1863 let full_base_range = 0..new_state.base_text.len();
1864 DiffChanged {
1865 changed_range: Some(full_range.clone()),
1866 base_text_changed_range: Some(full_base_range),
1867 extended_range: Some(full_range),
1868 }
1869 }
1870 };
1871 state.hunks = new_state.hunks;
1872 state.buffer_snapshot = update.buffer_snapshot;
1873
1874 if base_text_changed || clear_pending_hunks {
1875 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1876 {
1877 let pending_range = first.buffer_range.start..last.buffer_range.end;
1878 if let Some(range) = &mut changed_range {
1879 range.start = *range.start.min(&pending_range.start, buffer);
1880 range.end = *range.end.max(&pending_range.end, buffer);
1881 } else {
1882 changed_range = Some(pending_range.clone());
1883 }
1884
1885 if let Some(base_text_range) = base_text_changed_range.as_mut() {
1886 base_text_range.start =
1887 base_text_range.start.min(first.diff_base_byte_range.start);
1888 base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1889 } else {
1890 base_text_changed_range =
1891 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1892 }
1893
1894 if let Some(ext) = &mut extended_range {
1895 ext.start = *ext.start.min(&pending_range.start, buffer);
1896 ext.end = *ext.end.max(&pending_range.end, buffer);
1897 } else {
1898 extended_range = Some(pending_range);
1899 }
1900 }
1901 state.pending_hunks = SumTree::new(buffer);
1902 }
1903
1904 if let Some(secondary_changed_range) = secondary_diff_change
1905 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1906 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1907 {
1908 if let Some(range) = &mut changed_range {
1909 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1910 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1911 } else {
1912 changed_range = Some(secondary_hunk_range.clone());
1913 }
1914
1915 if let Some(base_text_range) = base_text_changed_range.as_mut() {
1916 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1917 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1918 } else {
1919 base_text_changed_range = Some(secondary_base_range);
1920 }
1921
1922 if let Some(ext) = &mut extended_range {
1923 ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1924 ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1925 } else {
1926 extended_range = Some(secondary_hunk_range);
1927 }
1928 }
1929
1930 async move {
1931 if let Some(parsing_idle) = parsing_idle {
1932 parsing_idle.await;
1933 }
1934 DiffChanged {
1935 changed_range,
1936 base_text_changed_range,
1937 extended_range,
1938 }
1939 }
1940 }
1941
1942 pub fn set_snapshot(
1943 &mut self,
1944 new_state: BufferDiffUpdate,
1945 buffer: &text::BufferSnapshot,
1946 cx: &mut Context<Self>,
1947 ) -> Task<Option<Range<Anchor>>> {
1948 self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1949 }
1950
1951 pub fn set_snapshot_with_secondary(
1952 &mut self,
1953 update: BufferDiffUpdate,
1954 buffer: &text::BufferSnapshot,
1955 secondary_diff_change: Option<Range<Anchor>>,
1956 clear_pending_hunks: bool,
1957 cx: &mut Context<Self>,
1958 ) -> Task<Option<Range<Anchor>>> {
1959 let fut = self.set_snapshot_with_secondary_inner(
1960 update,
1961 buffer,
1962 secondary_diff_change,
1963 clear_pending_hunks,
1964 cx,
1965 );
1966
1967 cx.spawn(async move |this, cx| {
1968 let change = fut.await;
1969 this.update(cx, |_, cx| {
1970 cx.emit(BufferDiffEvent::DiffChanged(change.clone()));
1971 })
1972 .ok();
1973 change.changed_range
1974 })
1975 }
1976
1977 pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1978 self.inner.base_text.read(cx).snapshot()
1979 }
1980
1981 pub fn base_text_exists(&self) -> bool {
1982 self.inner.base_text_exists
1983 }
1984
1985 pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1986 BufferDiffSnapshot {
1987 inner: BufferDiffInner {
1988 hunks: self.inner.hunks.clone(),
1989 pending_hunks: self.inner.pending_hunks.clone(),
1990 base_text: self.inner.base_text.read(cx).snapshot(),
1991 base_text_exists: self.inner.base_text_exists,
1992 buffer_snapshot: self.inner.buffer_snapshot.clone(),
1993 },
1994 secondary_diff: self.secondary_diff.as_ref().map(|diff| {
1995 debug_assert!(diff.read(cx).secondary_diff.is_none());
1996 Arc::new(diff.read(cx).snapshot(cx))
1997 }),
1998 }
1999 }
2000
2001 /// Used in cases where the change set isn't derived from git.
2002 pub fn set_base_text(
2003 &mut self,
2004 base_text: Option<Arc<str>>,
2005 language: Option<Arc<Language>>,
2006 buffer: text::BufferSnapshot,
2007 cx: &mut Context<Self>,
2008 ) -> oneshot::Receiver<()> {
2009 let (tx, rx) = oneshot::channel();
2010 let complete_on_drop = util::defer(|| {
2011 tx.send(()).ok();
2012 });
2013 cx.spawn(async move |this, cx| {
2014 let Some(state) = this
2015 .update(cx, |this, cx| {
2016 this.update_diff(buffer.clone(), base_text, Some(false), language, cx)
2017 })
2018 .log_err()
2019 else {
2020 return;
2021 };
2022 let state = state.await;
2023 if let Some(task) = this
2024 .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
2025 .log_err()
2026 {
2027 task.await;
2028 }
2029 drop(complete_on_drop)
2030 })
2031 .detach();
2032 rx
2033 }
2034
2035 pub fn base_text_string(&self, cx: &App) -> Option<String> {
2036 self.inner
2037 .base_text_exists
2038 .then(|| self.inner.base_text.read(cx).text())
2039 }
2040
2041 #[cfg(any(test, feature = "test-support"))]
2042 pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
2043 let language = self.base_text(cx).language().cloned();
2044 let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
2045 let fut = self.update_diff(buffer.clone(), base_text, None, language, cx);
2046 let fg_executor = cx.foreground_executor().clone();
2047 let snapshot = fg_executor.block_on(fut);
2048 let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
2049 let change = fg_executor.block_on(fut);
2050 cx.emit(BufferDiffEvent::DiffChanged(change));
2051 }
2052
2053 pub fn base_text_buffer(&self) -> &Entity<language::Buffer> {
2054 &self.inner.base_text
2055 }
2056}
2057
2058impl DiffHunk {
2059 pub fn is_created_file(&self) -> bool {
2060 self.diff_base_byte_range == (0..0)
2061 && self.buffer_range.start.is_min()
2062 && self.buffer_range.end.is_max()
2063 }
2064
2065 pub fn status(&self) -> DiffHunkStatus {
2066 let kind = if self.buffer_range.start == self.buffer_range.end {
2067 DiffHunkStatusKind::Deleted
2068 } else if self.diff_base_byte_range.is_empty() {
2069 DiffHunkStatusKind::Added
2070 } else {
2071 DiffHunkStatusKind::Modified
2072 };
2073 DiffHunkStatus {
2074 kind,
2075 secondary: self.secondary_status,
2076 }
2077 }
2078}
2079
2080impl DiffHunkStatus {
2081 pub fn has_secondary_hunk(&self) -> bool {
2082 matches!(
2083 self.secondary,
2084 DiffHunkSecondaryStatus::HasSecondaryHunk
2085 | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2086 | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
2087 )
2088 }
2089
2090 pub fn is_pending(&self) -> bool {
2091 matches!(
2092 self.secondary,
2093 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2094 | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2095 )
2096 }
2097
2098 pub fn is_deleted(&self) -> bool {
2099 self.kind == DiffHunkStatusKind::Deleted
2100 }
2101
2102 pub fn is_added(&self) -> bool {
2103 self.kind == DiffHunkStatusKind::Added
2104 }
2105
2106 pub fn is_modified(&self) -> bool {
2107 self.kind == DiffHunkStatusKind::Modified
2108 }
2109
2110 pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
2111 Self {
2112 kind: DiffHunkStatusKind::Added,
2113 secondary,
2114 }
2115 }
2116
2117 pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
2118 Self {
2119 kind: DiffHunkStatusKind::Modified,
2120 secondary,
2121 }
2122 }
2123
2124 pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
2125 Self {
2126 kind: DiffHunkStatusKind::Deleted,
2127 secondary,
2128 }
2129 }
2130
2131 pub fn deleted_none() -> Self {
2132 Self {
2133 kind: DiffHunkStatusKind::Deleted,
2134 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2135 }
2136 }
2137
2138 pub fn added_none() -> Self {
2139 Self {
2140 kind: DiffHunkStatusKind::Added,
2141 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2142 }
2143 }
2144
2145 pub fn modified_none() -> Self {
2146 Self {
2147 kind: DiffHunkStatusKind::Modified,
2148 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2149 }
2150 }
2151}
2152
2153#[cfg(any(test, feature = "test-support"))]
2154#[track_caller]
2155pub fn assert_hunks<ExpectedText, HunkIter>(
2156 diff_hunks: HunkIter,
2157 buffer: &text::BufferSnapshot,
2158 diff_base: &str,
2159 // Line range, deleted, added, status
2160 expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
2161) where
2162 HunkIter: Iterator<Item = DiffHunk>,
2163 ExpectedText: AsRef<str>,
2164{
2165 let actual_hunks = diff_hunks
2166 .map(|hunk| {
2167 (
2168 hunk.range.clone(),
2169 &diff_base[hunk.diff_base_byte_range.clone()],
2170 buffer
2171 .text_for_range(hunk.range.clone())
2172 .collect::<String>(),
2173 hunk.status(),
2174 )
2175 })
2176 .collect::<Vec<_>>();
2177
2178 let expected_hunks: Vec<_> = expected_hunks
2179 .iter()
2180 .map(|(line_range, deleted_text, added_text, status)| {
2181 (
2182 Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
2183 deleted_text.as_ref(),
2184 added_text.as_ref().to_string(),
2185 *status,
2186 )
2187 })
2188 .collect();
2189
2190 pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
2191}
2192
2193#[cfg(test)]
2194mod tests {
2195 use std::{fmt::Write as _, sync::mpsc};
2196
2197 use super::*;
2198 use gpui::TestAppContext;
2199 use pretty_assertions::{assert_eq, assert_ne};
2200 use rand::{Rng as _, rngs::StdRng};
2201 use text::{Buffer, BufferId, ReplicaId, Rope};
2202 use unindent::Unindent as _;
2203 use util::test::marked_text_ranges;
2204
2205 #[ctor::ctor]
2206 fn init_logger() {
2207 zlog::init_test();
2208 }
2209
2210 #[gpui::test]
2211 async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
2212 let diff_base = "
2213 one
2214 two
2215 three
2216 "
2217 .unindent();
2218
2219 let buffer_text = "
2220 one
2221 HELLO
2222 three
2223 "
2224 .unindent();
2225
2226 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2227 let mut diff = BufferDiffSnapshot::new_sync(&buffer, diff_base.clone(), cx);
2228 assert_hunks(
2229 diff.hunks_intersecting_range(
2230 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2231 &buffer,
2232 ),
2233 &buffer,
2234 &diff_base,
2235 &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
2236 );
2237
2238 buffer.edit([(0..0, "point five\n")]);
2239 diff = BufferDiffSnapshot::new_sync(&buffer, diff_base.clone(), cx);
2240 assert_hunks(
2241 diff.hunks_intersecting_range(
2242 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2243 &buffer,
2244 ),
2245 &buffer,
2246 &diff_base,
2247 &[
2248 (0..1, "", "point five\n", DiffHunkStatus::added_none()),
2249 (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
2250 ],
2251 );
2252
2253 diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2254 assert_hunks::<&str, _>(
2255 diff.hunks_intersecting_range(
2256 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2257 &buffer,
2258 ),
2259 &buffer,
2260 &diff_base,
2261 &[],
2262 );
2263 }
2264
2265 #[gpui::test]
2266 async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
2267 let head_text = "
2268 zero
2269 one
2270 two
2271 three
2272 four
2273 five
2274 six
2275 seven
2276 eight
2277 nine
2278 "
2279 .unindent();
2280
2281 let index_text = "
2282 zero
2283 one
2284 TWO
2285 three
2286 FOUR
2287 five
2288 six
2289 seven
2290 eight
2291 NINE
2292 "
2293 .unindent();
2294
2295 let buffer_text = "
2296 zero
2297 one
2298 TWO
2299 three
2300 FOUR
2301 FIVE
2302 six
2303 SEVEN
2304 eight
2305 nine
2306 "
2307 .unindent();
2308
2309 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2310 let unstaged_diff = BufferDiffSnapshot::new_sync(&buffer, index_text, cx);
2311 let mut uncommitted_diff = BufferDiffSnapshot::new_sync(&buffer, head_text.clone(), cx);
2312 uncommitted_diff.secondary_diff = Some(Arc::new(unstaged_diff));
2313
2314 let expected_hunks = vec![
2315 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
2316 (
2317 4..6,
2318 "four\nfive\n",
2319 "FOUR\nFIVE\n",
2320 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2321 ),
2322 (
2323 7..8,
2324 "seven\n",
2325 "SEVEN\n",
2326 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2327 ),
2328 ];
2329
2330 assert_hunks(
2331 uncommitted_diff.hunks_intersecting_range(
2332 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2333 &buffer,
2334 ),
2335 &buffer,
2336 &head_text,
2337 &expected_hunks,
2338 );
2339 }
2340
2341 #[gpui::test]
2342 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
2343 let diff_base = "
2344 one
2345 two
2346 three
2347 four
2348 five
2349 six
2350 seven
2351 eight
2352 nine
2353 ten
2354 "
2355 .unindent();
2356
2357 let buffer_text = "
2358 A
2359 one
2360 B
2361 two
2362 C
2363 three
2364 HELLO
2365 four
2366 five
2367 SIXTEEN
2368 seven
2369 eight
2370 WORLD
2371 nine
2372
2373 ten
2374
2375 "
2376 .unindent();
2377
2378 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2379 let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
2380 assert_eq!(
2381 diff.hunks_intersecting_range(
2382 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2383 &buffer
2384 )
2385 .count(),
2386 8
2387 );
2388
2389 assert_hunks(
2390 diff.hunks_intersecting_range(
2391 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
2392 &buffer,
2393 ),
2394 &buffer,
2395 &diff_base,
2396 &[
2397 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
2398 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
2399 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
2400 ],
2401 );
2402 }
2403
2404 #[gpui::test]
2405 async fn test_stage_hunk(cx: &mut TestAppContext) {
2406 struct Example {
2407 name: &'static str,
2408 head_text: String,
2409 index_text: String,
2410 buffer_marked_text: String,
2411 final_index_text: String,
2412 }
2413
2414 let table = [
2415 Example {
2416 name: "uncommitted hunk straddles end of unstaged hunk",
2417 head_text: "
2418 one
2419 two
2420 three
2421 four
2422 five
2423 "
2424 .unindent(),
2425 index_text: "
2426 one
2427 TWO_HUNDRED
2428 three
2429 FOUR_HUNDRED
2430 five
2431 "
2432 .unindent(),
2433 buffer_marked_text: "
2434 ZERO
2435 one
2436 two
2437 «THREE_HUNDRED
2438 FOUR_HUNDRED»
2439 five
2440 SIX
2441 "
2442 .unindent(),
2443 final_index_text: "
2444 one
2445 two
2446 THREE_HUNDRED
2447 FOUR_HUNDRED
2448 five
2449 "
2450 .unindent(),
2451 },
2452 Example {
2453 name: "uncommitted hunk straddles start of unstaged hunk",
2454 head_text: "
2455 one
2456 two
2457 three
2458 four
2459 five
2460 "
2461 .unindent(),
2462 index_text: "
2463 one
2464 TWO_HUNDRED
2465 three
2466 FOUR_HUNDRED
2467 five
2468 "
2469 .unindent(),
2470 buffer_marked_text: "
2471 ZERO
2472 one
2473 «TWO_HUNDRED
2474 THREE_HUNDRED»
2475 four
2476 five
2477 SIX
2478 "
2479 .unindent(),
2480 final_index_text: "
2481 one
2482 TWO_HUNDRED
2483 THREE_HUNDRED
2484 four
2485 five
2486 "
2487 .unindent(),
2488 },
2489 Example {
2490 name: "uncommitted hunk strictly contains unstaged hunks",
2491 head_text: "
2492 one
2493 two
2494 three
2495 four
2496 five
2497 six
2498 seven
2499 "
2500 .unindent(),
2501 index_text: "
2502 one
2503 TWO
2504 THREE
2505 FOUR
2506 FIVE
2507 SIX
2508 seven
2509 "
2510 .unindent(),
2511 buffer_marked_text: "
2512 one
2513 TWO
2514 «THREE_HUNDRED
2515 FOUR
2516 FIVE_HUNDRED»
2517 SIX
2518 seven
2519 "
2520 .unindent(),
2521 final_index_text: "
2522 one
2523 TWO
2524 THREE_HUNDRED
2525 FOUR
2526 FIVE_HUNDRED
2527 SIX
2528 seven
2529 "
2530 .unindent(),
2531 },
2532 Example {
2533 name: "uncommitted deletion hunk",
2534 head_text: "
2535 one
2536 two
2537 three
2538 four
2539 five
2540 "
2541 .unindent(),
2542 index_text: "
2543 one
2544 two
2545 three
2546 four
2547 five
2548 "
2549 .unindent(),
2550 buffer_marked_text: "
2551 one
2552 ˇfive
2553 "
2554 .unindent(),
2555 final_index_text: "
2556 one
2557 five
2558 "
2559 .unindent(),
2560 },
2561 Example {
2562 name: "one unstaged hunk that contains two uncommitted hunks",
2563 head_text: "
2564 one
2565 two
2566
2567 three
2568 four
2569 "
2570 .unindent(),
2571 index_text: "
2572 one
2573 two
2574 three
2575 four
2576 "
2577 .unindent(),
2578 buffer_marked_text: "
2579 «one
2580
2581 three // modified
2582 four»
2583 "
2584 .unindent(),
2585 final_index_text: "
2586 one
2587
2588 three // modified
2589 four
2590 "
2591 .unindent(),
2592 },
2593 Example {
2594 name: "one uncommitted hunk that contains two unstaged hunks",
2595 head_text: "
2596 one
2597 two
2598 three
2599 four
2600 five
2601 "
2602 .unindent(),
2603 index_text: "
2604 ZERO
2605 one
2606 TWO
2607 THREE
2608 FOUR
2609 five
2610 "
2611 .unindent(),
2612 buffer_marked_text: "
2613 «one
2614 TWO_HUNDRED
2615 THREE
2616 FOUR_HUNDRED
2617 five»
2618 "
2619 .unindent(),
2620 final_index_text: "
2621 ZERO
2622 one
2623 TWO_HUNDRED
2624 THREE
2625 FOUR_HUNDRED
2626 five
2627 "
2628 .unindent(),
2629 },
2630 ];
2631
2632 for example in table {
2633 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2634 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2635 let hunk_range =
2636 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2637
2638 let unstaged_diff =
2639 cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2640
2641 let uncommitted_diff = cx.new(|cx| {
2642 let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2643 diff.set_secondary_diff(unstaged_diff);
2644 diff
2645 });
2646
2647 uncommitted_diff.update(cx, |diff, cx| {
2648 let hunks = diff
2649 .snapshot(cx)
2650 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2651 .collect::<Vec<_>>();
2652 for hunk in &hunks {
2653 assert_ne!(
2654 hunk.secondary_status,
2655 DiffHunkSecondaryStatus::NoSecondaryHunk
2656 )
2657 }
2658
2659 let new_index_text = diff
2660 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2661 .unwrap()
2662 .to_string();
2663
2664 let hunks = diff
2665 .snapshot(cx)
2666 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2667 .collect::<Vec<_>>();
2668 for hunk in &hunks {
2669 assert_eq!(
2670 hunk.secondary_status,
2671 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2672 )
2673 }
2674
2675 pretty_assertions::assert_eq!(
2676 new_index_text,
2677 example.final_index_text,
2678 "example: {}",
2679 example.name
2680 );
2681 });
2682 }
2683 }
2684
2685 #[gpui::test]
2686 async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2687 // This test reproduces a crash where staging all hunks would cause an underflow
2688 // when there's one large unstaged hunk containing multiple uncommitted hunks.
2689 let head_text = "
2690 aaa
2691 bbb
2692 ccc
2693 ddd
2694 eee
2695 fff
2696 ggg
2697 hhh
2698 iii
2699 jjj
2700 kkk
2701 lll
2702 "
2703 .unindent();
2704
2705 let index_text = "
2706 aaa
2707 bbb
2708 CCC-index
2709 DDD-index
2710 EEE-index
2711 FFF-index
2712 GGG-index
2713 HHH-index
2714 III-index
2715 JJJ-index
2716 kkk
2717 lll
2718 "
2719 .unindent();
2720
2721 let buffer_text = "
2722 aaa
2723 bbb
2724 ccc-modified
2725 ddd
2726 eee-modified
2727 fff
2728 ggg
2729 hhh-modified
2730 iii
2731 jjj
2732 kkk
2733 lll
2734 "
2735 .unindent();
2736
2737 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2738
2739 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2740 let uncommitted_diff = cx.new(|cx| {
2741 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2742 diff.set_secondary_diff(unstaged_diff);
2743 diff
2744 });
2745
2746 uncommitted_diff.update(cx, |diff, cx| {
2747 diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2748 });
2749 }
2750
2751 #[gpui::test]
2752 async fn test_stage_all_with_stale_buffer(cx: &mut TestAppContext) {
2753 // Regression test for ZED-5R2: when the buffer is edited after the diff is
2754 // computed but before staging, anchor positions shift while diff_base_byte_range
2755 // values don't. If the primary (HEAD) hunk extends past the unstaged (index)
2756 // hunk, an edit in the extension region shifts the primary hunk end without
2757 // shifting the unstaged hunk end. The overshoot calculation then produces an
2758 // index_end that exceeds index_text.len().
2759 //
2760 // Setup:
2761 // HEAD: "aaa\nbbb\nccc\n" (primary hunk covers lines 1-2)
2762 // Index: "aaa\nbbb\nCCC\n" (unstaged hunk covers line 1 only)
2763 // Buffer: "aaa\nBBB\nCCC\n" (both lines differ from HEAD)
2764 //
2765 // The primary hunk spans buffer offsets 4..12, but the unstaged hunk only
2766 // spans 4..8. The pending hunk extends 4 bytes past the unstaged hunk.
2767 // An edit at offset 9 (inside "CCC") shifts the primary hunk end from 12
2768 // to 13 but leaves the unstaged hunk end at 8, making index_end = 13 > 12.
2769 let head_text = "aaa\nbbb\nccc\n";
2770 let index_text = "aaa\nbbb\nCCC\n";
2771 let buffer_text = "aaa\nBBB\nCCC\n";
2772
2773 let mut buffer = Buffer::new(
2774 ReplicaId::LOCAL,
2775 BufferId::new(1).unwrap(),
2776 buffer_text.to_string(),
2777 );
2778
2779 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(index_text, &buffer, cx));
2780 let uncommitted_diff = cx.new(|cx| {
2781 let mut diff = BufferDiff::new_with_base_text(head_text, &buffer, cx);
2782 diff.set_secondary_diff(unstaged_diff);
2783 diff
2784 });
2785
2786 // Edit the buffer in the region between the unstaged hunk end (offset 8)
2787 // and the primary hunk end (offset 12). This shifts the primary hunk end
2788 // but not the unstaged hunk end.
2789 buffer.edit([(9..9, "Z")]);
2790
2791 uncommitted_diff.update(cx, |diff, cx| {
2792 diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2793 });
2794 }
2795
2796 #[gpui::test]
2797 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2798 let head_text = "
2799 one
2800 two
2801 three
2802 "
2803 .unindent();
2804 let index_text = head_text.clone();
2805 let buffer_text = "
2806 one
2807 three
2808 "
2809 .unindent();
2810
2811 let buffer = Buffer::new(
2812 ReplicaId::LOCAL,
2813 BufferId::new(1).unwrap(),
2814 buffer_text.clone(),
2815 );
2816 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2817 let uncommitted_diff = cx.new(|cx| {
2818 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2819 diff.set_secondary_diff(unstaged_diff.clone());
2820 diff
2821 });
2822
2823 uncommitted_diff.update(cx, |diff, cx| {
2824 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2825
2826 let new_index_text = diff
2827 .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2828 .unwrap()
2829 .to_string();
2830 assert_eq!(new_index_text, buffer_text);
2831
2832 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2833 assert_eq!(
2834 hunk.secondary_status,
2835 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2836 );
2837
2838 let index_text = diff
2839 .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2840 .unwrap()
2841 .to_string();
2842 assert_eq!(index_text, head_text);
2843
2844 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2845 // optimistically unstaged (fine, could also be HasSecondaryHunk)
2846 assert_eq!(
2847 hunk.secondary_status,
2848 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2849 );
2850 });
2851 }
2852
2853 #[gpui::test]
2854 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2855 let base_text = "
2856 zero
2857 one
2858 two
2859 three
2860 four
2861 five
2862 six
2863 seven
2864 eight
2865 nine
2866 "
2867 .unindent();
2868
2869 let buffer_text_1 = "
2870 one
2871 three
2872 four
2873 five
2874 SIX
2875 seven
2876 eight
2877 NINE
2878 "
2879 .unindent();
2880
2881 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2882
2883 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2884 let diff_1 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2885 let DiffChanged {
2886 changed_range,
2887 base_text_changed_range,
2888 extended_range: _,
2889 } = compare_hunks(
2890 &diff_1.inner.hunks,
2891 &empty_diff.inner.hunks,
2892 &buffer,
2893 &buffer,
2894 &diff_1.base_text(),
2895 &diff_1.base_text(),
2896 );
2897 let range = changed_range.unwrap();
2898 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2899 let base_text_range = base_text_changed_range.unwrap();
2900 assert_eq!(
2901 base_text_range.to_point(diff_1.base_text()),
2902 Point::new(0, 0)..Point::new(10, 0)
2903 );
2904
2905 // Edit does affects the diff because it recalculates word diffs.
2906 buffer.edit_via_marked_text(
2907 &"
2908 one
2909 three
2910 four
2911 five
2912 «SIX.5»
2913 seven
2914 eight
2915 NINE
2916 "
2917 .unindent(),
2918 );
2919 let diff_2 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2920 let DiffChanged {
2921 changed_range,
2922 base_text_changed_range,
2923 extended_range: _,
2924 } = compare_hunks(
2925 &diff_2.inner.hunks,
2926 &diff_1.inner.hunks,
2927 &buffer,
2928 &buffer,
2929 diff_2.base_text(),
2930 diff_2.base_text(),
2931 );
2932 assert_eq!(
2933 changed_range.unwrap().to_point(&buffer),
2934 Point::new(4, 0)..Point::new(5, 0),
2935 );
2936 assert_eq!(
2937 base_text_changed_range
2938 .unwrap()
2939 .to_point(diff_2.base_text()),
2940 Point::new(6, 0)..Point::new(7, 0),
2941 );
2942
2943 // Edit turns a deletion hunk into a modification.
2944 buffer.edit_via_marked_text(
2945 &"
2946 one
2947 «THREE»
2948 four
2949 five
2950 SIX.5
2951 seven
2952 eight
2953 NINE
2954 "
2955 .unindent(),
2956 );
2957 let diff_3 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2958 let DiffChanged {
2959 changed_range,
2960 base_text_changed_range,
2961 extended_range: _,
2962 } = compare_hunks(
2963 &diff_3.inner.hunks,
2964 &diff_2.inner.hunks,
2965 &buffer,
2966 &buffer,
2967 diff_3.base_text(),
2968 diff_3.base_text(),
2969 );
2970 let range = changed_range.unwrap();
2971 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2972 let base_text_range = base_text_changed_range.unwrap();
2973 assert_eq!(
2974 base_text_range.to_point(diff_3.base_text()),
2975 Point::new(2, 0)..Point::new(4, 0)
2976 );
2977
2978 // Edit turns a modification hunk into a deletion.
2979 buffer.edit_via_marked_text(
2980 &"
2981 one
2982 THREE
2983 four
2984 five«»
2985 seven
2986 eight
2987 NINE
2988 "
2989 .unindent(),
2990 );
2991 let diff_4 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2992 let DiffChanged {
2993 changed_range,
2994 base_text_changed_range,
2995 extended_range: _,
2996 } = compare_hunks(
2997 &diff_4.inner.hunks,
2998 &diff_3.inner.hunks,
2999 &buffer,
3000 &buffer,
3001 diff_4.base_text(),
3002 diff_4.base_text(),
3003 );
3004 let range = changed_range.unwrap();
3005 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
3006 let base_text_range = base_text_changed_range.unwrap();
3007 assert_eq!(
3008 base_text_range.to_point(diff_4.base_text()),
3009 Point::new(6, 0)..Point::new(7, 0)
3010 );
3011
3012 // Edit introduces a new insertion hunk.
3013 buffer.edit_via_marked_text(
3014 &"
3015 one
3016 THREE
3017 four«
3018 FOUR.5
3019 »five
3020 seven
3021 eight
3022 NINE
3023 "
3024 .unindent(),
3025 );
3026 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
3027 let DiffChanged {
3028 changed_range,
3029 base_text_changed_range,
3030 extended_range: _,
3031 } = compare_hunks(
3032 &diff_5.inner.hunks,
3033 &diff_4.inner.hunks,
3034 &buffer,
3035 &buffer,
3036 diff_5.base_text(),
3037 diff_5.base_text(),
3038 );
3039 let range = changed_range.unwrap();
3040 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
3041 let base_text_range = base_text_changed_range.unwrap();
3042 assert_eq!(
3043 base_text_range.to_point(diff_5.base_text()),
3044 Point::new(5, 0)..Point::new(5, 0)
3045 );
3046
3047 // Edit removes a hunk.
3048 buffer.edit_via_marked_text(
3049 &"
3050 one
3051 THREE
3052 four
3053 FOUR.5
3054 five
3055 seven
3056 eight
3057 «nine»
3058 "
3059 .unindent(),
3060 );
3061 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
3062 let DiffChanged {
3063 changed_range,
3064 base_text_changed_range,
3065 extended_range: _,
3066 } = compare_hunks(
3067 &diff_6.inner.hunks,
3068 &diff_5.inner.hunks,
3069 &buffer,
3070 &buffer,
3071 diff_6.base_text(),
3072 diff_6.base_text(),
3073 );
3074 let range = changed_range.unwrap();
3075 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
3076 let base_text_range = base_text_changed_range.unwrap();
3077 assert_eq!(
3078 base_text_range.to_point(diff_6.base_text()),
3079 Point::new(9, 0)..Point::new(10, 0)
3080 );
3081
3082 buffer.edit_via_marked_text(
3083 &"
3084 one
3085 THREE
3086 four«»
3087 five
3088 seven
3089 eight
3090 «NINE»
3091 "
3092 .unindent(),
3093 );
3094
3095 let diff_7 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
3096 let DiffChanged {
3097 changed_range,
3098 base_text_changed_range,
3099 extended_range: _,
3100 } = compare_hunks(
3101 &diff_7.inner.hunks,
3102 &diff_6.inner.hunks,
3103 &buffer,
3104 &buffer,
3105 diff_7.base_text(),
3106 diff_7.base_text(),
3107 );
3108 let range = changed_range.unwrap();
3109 assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
3110 let base_text_range = base_text_changed_range.unwrap();
3111 assert_eq!(
3112 base_text_range.to_point(diff_7.base_text()),
3113 Point::new(5, 0)..Point::new(10, 0)
3114 );
3115
3116 buffer.edit_via_marked_text(
3117 &"
3118 one
3119 THREE
3120 four
3121 five«»seven
3122 eight
3123 NINE
3124 "
3125 .unindent(),
3126 );
3127
3128 let diff_8 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
3129 let DiffChanged {
3130 changed_range,
3131 base_text_changed_range,
3132 extended_range: _,
3133 } = compare_hunks(
3134 &diff_8.inner.hunks,
3135 &diff_7.inner.hunks,
3136 &buffer,
3137 &buffer,
3138 diff_8.base_text(),
3139 diff_8.base_text(),
3140 );
3141 let range = changed_range.unwrap();
3142 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
3143 let base_text_range = base_text_changed_range.unwrap();
3144 assert_eq!(
3145 base_text_range.to_point(diff_8.base_text()),
3146 Point::new(5, 0)..Point::new(8, 0)
3147 );
3148 }
3149
3150 #[gpui::test(iterations = 100)]
3151 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
3152 fn gen_line(rng: &mut StdRng) -> String {
3153 if rng.random_bool(0.2) {
3154 "\n".to_owned()
3155 } else {
3156 let c = rng.random_range('A'..='Z');
3157 format!("{c}{c}{c}\n")
3158 }
3159 }
3160
3161 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
3162 let mut old_lines = {
3163 let mut old_lines = Vec::new();
3164 let old_lines_iter = head.lines();
3165 for line in old_lines_iter {
3166 assert!(!line.ends_with("\n"));
3167 old_lines.push(line.to_owned());
3168 }
3169 if old_lines.last().is_some_and(|line| line.is_empty()) {
3170 old_lines.pop();
3171 }
3172 old_lines.into_iter()
3173 };
3174 let mut result = String::new();
3175 let unchanged_count = rng.random_range(0..=old_lines.len());
3176 result +=
3177 &old_lines
3178 .by_ref()
3179 .take(unchanged_count)
3180 .fold(String::new(), |mut s, line| {
3181 writeln!(&mut s, "{line}").unwrap();
3182 s
3183 });
3184 while old_lines.len() > 0 {
3185 let deleted_count = rng.random_range(0..=old_lines.len());
3186 let _advance = old_lines
3187 .by_ref()
3188 .take(deleted_count)
3189 .map(|line| line.len() + 1)
3190 .sum::<usize>();
3191 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
3192 let added_count = rng.random_range(minimum_added..=5);
3193 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
3194 result += &addition;
3195
3196 if old_lines.len() > 0 {
3197 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
3198 if blank_lines == old_lines.len() {
3199 break;
3200 };
3201 let unchanged_count =
3202 rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
3203 result += &old_lines.by_ref().take(unchanged_count).fold(
3204 String::new(),
3205 |mut s, line| {
3206 writeln!(&mut s, "{line}").unwrap();
3207 s
3208 },
3209 );
3210 }
3211 }
3212 result
3213 }
3214
3215 fn uncommitted_diff(
3216 working_copy: &language::BufferSnapshot,
3217 index_text: &Rope,
3218 head_text: String,
3219 cx: &mut TestAppContext,
3220 ) -> Entity<BufferDiff> {
3221 let secondary = cx.new(|cx| {
3222 BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
3223 });
3224 cx.new(|cx| {
3225 let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
3226 diff.secondary_diff = Some(secondary);
3227 diff
3228 })
3229 }
3230
3231 let operations = std::env::var("OPERATIONS")
3232 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3233 .unwrap_or(10);
3234
3235 let rng = &mut rng;
3236 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
3237 writeln!(&mut s, "{c}{c}{c}").unwrap();
3238 s
3239 });
3240 let working_copy = gen_working_copy(rng, &head_text);
3241 let working_copy = cx.new(|cx| {
3242 language::Buffer::local_normalized(
3243 Rope::from(working_copy.as_str()),
3244 text::LineEnding::default(),
3245 cx,
3246 )
3247 });
3248 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
3249 let mut index_text = if rng.random() {
3250 Rope::from(head_text.as_str())
3251 } else {
3252 working_copy.as_rope().clone()
3253 };
3254
3255 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3256 let mut hunks = diff.update(cx, |diff, cx| {
3257 diff.snapshot(cx)
3258 .hunks_intersecting_range(
3259 Anchor::min_max_range_for_buffer(diff.buffer_id),
3260 &working_copy,
3261 )
3262 .collect::<Vec<_>>()
3263 });
3264 if hunks.is_empty() {
3265 return;
3266 }
3267
3268 for _ in 0..operations {
3269 let i = rng.random_range(0..hunks.len());
3270 let hunk = &mut hunks[i];
3271 let hunk_to_change = hunk.clone();
3272 let stage = match hunk.secondary_status {
3273 DiffHunkSecondaryStatus::HasSecondaryHunk => {
3274 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
3275 true
3276 }
3277 DiffHunkSecondaryStatus::NoSecondaryHunk => {
3278 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
3279 false
3280 }
3281 _ => unreachable!(),
3282 };
3283
3284 index_text = diff.update(cx, |diff, cx| {
3285 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
3286 .unwrap()
3287 });
3288
3289 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3290 let found_hunks = diff.update(cx, |diff, cx| {
3291 diff.snapshot(cx)
3292 .hunks_intersecting_range(
3293 Anchor::min_max_range_for_buffer(diff.buffer_id),
3294 &working_copy,
3295 )
3296 .collect::<Vec<_>>()
3297 });
3298 assert_eq!(hunks.len(), found_hunks.len());
3299
3300 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
3301 assert_eq!(
3302 expected_hunk.buffer_range.to_point(&working_copy),
3303 found_hunk.buffer_range.to_point(&working_copy)
3304 );
3305 assert_eq!(
3306 expected_hunk.diff_base_byte_range,
3307 found_hunk.diff_base_byte_range
3308 );
3309 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
3310 }
3311 hunks = found_hunks;
3312 }
3313 }
3314
3315 #[gpui::test]
3316 async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
3317 let base_text = "
3318 one
3319 two
3320 three
3321 four
3322 five
3323 six
3324 "
3325 .unindent();
3326 let buffer_text = "
3327 one
3328 TWO
3329 three
3330 four
3331 FIVE
3332 six
3333 "
3334 .unindent();
3335 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3336 let diff = cx.new(|cx| {
3337 BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
3338 });
3339 cx.run_until_parked();
3340 let (tx, rx) = mpsc::channel();
3341 let subscription =
3342 cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
3343
3344 let snapshot = buffer.update(cx, |buffer, cx| {
3345 buffer.set_text(
3346 "
3347 ONE
3348 TWO
3349 THREE
3350 FOUR
3351 FIVE
3352 SIX
3353 "
3354 .unindent(),
3355 cx,
3356 );
3357 buffer.text_snapshot()
3358 });
3359 let update = diff
3360 .update(cx, |diff, cx| {
3361 diff.update_diff(
3362 snapshot.clone(),
3363 Some(base_text.as_str().into()),
3364 None,
3365 None,
3366 cx,
3367 )
3368 })
3369 .await;
3370 diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
3371 .await;
3372 cx.run_until_parked();
3373 drop(subscription);
3374 let events = rx.into_iter().collect::<Vec<_>>();
3375 match events.as_slice() {
3376 [
3377 BufferDiffEvent::DiffChanged(DiffChanged {
3378 changed_range: _,
3379 base_text_changed_range,
3380 extended_range: _,
3381 }),
3382 ] => {
3383 // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3384 // assert_eq!(
3385 // *changed_range,
3386 // Some(Anchor::min_max_range_for_buffer(
3387 // buffer.read_with(cx, |buffer, _| buffer.remote_id())
3388 // ))
3389 // );
3390 assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3391 }
3392 _ => panic!("unexpected events: {:?}", events),
3393 }
3394 }
3395
3396 #[gpui::test]
3397 async fn test_extended_range(cx: &mut TestAppContext) {
3398 let base_text = "
3399 aaa
3400 bbb
3401
3402
3403
3404
3405
3406 ccc
3407 ddd
3408 "
3409 .unindent();
3410
3411 let buffer_text = "
3412 aaa
3413 bbb
3414
3415
3416
3417
3418
3419 CCC
3420 ddd
3421 "
3422 .unindent();
3423
3424 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3425 let old_buffer = buffer.snapshot().clone();
3426 let diff_a = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
3427
3428 buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3429 let diff_b = BufferDiffSnapshot::new_sync(&buffer, base_text, cx);
3430
3431 let DiffChanged {
3432 changed_range,
3433 base_text_changed_range: _,
3434 extended_range,
3435 } = compare_hunks(
3436 &diff_b.inner.hunks,
3437 &diff_a.inner.hunks,
3438 &old_buffer,
3439 &buffer,
3440 &diff_a.base_text(),
3441 &diff_a.base_text(),
3442 );
3443
3444 let changed_range = changed_range.unwrap();
3445 assert_eq!(
3446 changed_range.to_point(&buffer),
3447 Point::new(7, 0)..Point::new(9, 0),
3448 "changed_range should span from old hunk position to new hunk end"
3449 );
3450
3451 let extended_range = extended_range.unwrap();
3452 assert_eq!(
3453 extended_range.start.to_point(&buffer),
3454 Point::new(1, 3),
3455 "extended_range.start should extend to include the edit outside changed_range"
3456 );
3457 assert_eq!(
3458 extended_range.end.to_point(&buffer),
3459 Point::new(9, 0),
3460 "extended_range.end should collapse to changed_range.end when no edits in end margin"
3461 );
3462
3463 let base_text_2 = "
3464 one
3465 two
3466 three
3467 four
3468 five
3469 six
3470 seven
3471 eight
3472 "
3473 .unindent();
3474
3475 let buffer_text_2 = "
3476 ONE
3477 two
3478 THREE
3479 four
3480 FIVE
3481 six
3482 SEVEN
3483 eight
3484 "
3485 .unindent();
3486
3487 let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3488 let old_buffer_2 = buffer_2.snapshot().clone();
3489 let diff_2a = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2.clone(), cx);
3490
3491 buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3492 let diff_2b = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2, cx);
3493
3494 let DiffChanged {
3495 changed_range,
3496 base_text_changed_range: _,
3497 extended_range,
3498 } = compare_hunks(
3499 &diff_2b.inner.hunks,
3500 &diff_2a.inner.hunks,
3501 &old_buffer_2,
3502 &buffer_2,
3503 &diff_2a.base_text(),
3504 &diff_2a.base_text(),
3505 );
3506
3507 let changed_range = changed_range.unwrap();
3508 assert_eq!(
3509 changed_range.to_point(&buffer_2),
3510 Point::new(4, 0)..Point::new(5, 0),
3511 "changed_range should be just the hunk that changed (FIVE)"
3512 );
3513
3514 let extended_range = extended_range.unwrap();
3515 assert_eq!(
3516 extended_range.to_point(&buffer_2),
3517 Point::new(4, 0)..Point::new(5, 0),
3518 "extended_range should equal changed_range when edit is within the hunk"
3519 );
3520 }
3521
3522 #[gpui::test]
3523 async fn test_buffer_diff_compare_with_base_text_change(_cx: &mut TestAppContext) {
3524 // Use a shared base text buffer so that anchors from old and new snapshots
3525 // share the same remote_id and resolve correctly across versions.
3526 let initial_base = "aaa\nbbb\nccc\nddd\neee\n";
3527 let mut base_text_buffer = Buffer::new(
3528 ReplicaId::LOCAL,
3529 BufferId::new(99).unwrap(),
3530 initial_base.to_string(),
3531 );
3532
3533 // --- Scenario 1: Base text gains a line, producing a new deletion hunk ---
3534 //
3535 // Buffer has a modification (ccc → CCC). When the base text gains
3536 // a new line "XXX" after "aaa", the diff now also contains a
3537 // deletion for that line, and the modification hunk shifts in the
3538 // base text.
3539 let buffer_text_1 = "aaa\nbbb\nCCC\nddd\neee\n";
3540 let buffer = Buffer::new(
3541 ReplicaId::LOCAL,
3542 BufferId::new(1).unwrap(),
3543 buffer_text_1.to_string(),
3544 );
3545
3546 let old_base_snapshot_1 = base_text_buffer.snapshot().clone();
3547 let old_hunks_1 = compute_hunks(
3548 Some((Arc::from(initial_base), Rope::from(initial_base))),
3549 buffer.snapshot(),
3550 None,
3551 );
3552
3553 // Insert "XXX\n" after "aaa\n" in the base text.
3554 base_text_buffer.edit([(4..4, "XXX\n")]);
3555 let new_base_str_1: Arc<str> = Arc::from(base_text_buffer.text().as_str());
3556 let new_base_snapshot_1 = base_text_buffer.snapshot();
3557
3558 let new_hunks_1 = compute_hunks(
3559 Some((new_base_str_1.clone(), Rope::from(new_base_str_1.as_ref()))),
3560 buffer.snapshot(),
3561 None,
3562 );
3563
3564 let DiffChanged {
3565 changed_range,
3566 base_text_changed_range,
3567 extended_range: _,
3568 } = compare_hunks(
3569 &new_hunks_1,
3570 &old_hunks_1,
3571 &buffer.snapshot(),
3572 &buffer.snapshot(),
3573 &old_base_snapshot_1,
3574 &new_base_snapshot_1,
3575 );
3576
3577 // The new deletion hunk (XXX) starts at buffer row 1 and the
3578 // modification hunk (ccc → CCC) now has a different
3579 // diff_base_byte_range, so the changed range spans both.
3580 let range = changed_range.unwrap();
3581 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(3, 0),);
3582 let base_range = base_text_changed_range.unwrap();
3583 assert_eq!(
3584 base_range.to_point(&new_base_snapshot_1),
3585 Point::new(1, 0)..Point::new(4, 0),
3586 );
3587
3588 // --- Scenario 2: Base text changes to match the buffer (hunk disappears) ---
3589 //
3590 // Start fresh with a simple base text.
3591 let simple_base = "one\ntwo\nthree\n";
3592 let mut base_buf_2 = Buffer::new(
3593 ReplicaId::LOCAL,
3594 BufferId::new(100).unwrap(),
3595 simple_base.to_string(),
3596 );
3597
3598 let buffer_text_2 = "one\nTWO\nthree\n";
3599 let buffer_2 = Buffer::new(
3600 ReplicaId::LOCAL,
3601 BufferId::new(2).unwrap(),
3602 buffer_text_2.to_string(),
3603 );
3604
3605 let old_base_snapshot_2 = base_buf_2.snapshot().clone();
3606 let old_hunks_2 = compute_hunks(
3607 Some((Arc::from(simple_base), Rope::from(simple_base))),
3608 buffer_2.snapshot(),
3609 None,
3610 );
3611
3612 // The base text is edited so "two" becomes "TWO", now matching the buffer.
3613 base_buf_2.edit([(4..7, "TWO")]);
3614 let new_base_str_2: Arc<str> = Arc::from(base_buf_2.text().as_str());
3615 let new_base_snapshot_2 = base_buf_2.snapshot();
3616
3617 let new_hunks_2 = compute_hunks(
3618 Some((new_base_str_2.clone(), Rope::from(new_base_str_2.as_ref()))),
3619 buffer_2.snapshot(),
3620 None,
3621 );
3622
3623 let DiffChanged {
3624 changed_range,
3625 base_text_changed_range,
3626 extended_range: _,
3627 } = compare_hunks(
3628 &new_hunks_2,
3629 &old_hunks_2,
3630 &buffer_2.snapshot(),
3631 &buffer_2.snapshot(),
3632 &old_base_snapshot_2,
3633 &new_base_snapshot_2,
3634 );
3635
3636 // The old modification hunk (two → TWO) is now gone because the
3637 // base text matches the buffer. The changed range covers where the
3638 // old hunk used to be.
3639 let range = changed_range.unwrap();
3640 assert_eq!(
3641 range.to_point(&buffer_2),
3642 Point::new(1, 0)..Point::new(2, 0),
3643 );
3644 let base_range = base_text_changed_range.unwrap();
3645 // The old hunk's diff_base_byte_range covered "two\n" (bytes 4..8).
3646 // anchor_after(4) is right-biased at the start of the deleted "two",
3647 // so after the edit replacing "two" with "TWO" it resolves past the
3648 // insertion to Point(1, 3).
3649 assert_eq!(
3650 base_range.to_point(&new_base_snapshot_2),
3651 Point::new(1, 3)..Point::new(2, 0),
3652 );
3653
3654 // --- Scenario 3: Base text edit changes one hunk but not another ---
3655 //
3656 // Two modification hunks exist. Only one of them is resolved by
3657 // the base text change; the other remains identical.
3658 let base_3 = "aaa\nbbb\nccc\nddd\neee\n";
3659 let mut base_buf_3 = Buffer::new(
3660 ReplicaId::LOCAL,
3661 BufferId::new(101).unwrap(),
3662 base_3.to_string(),
3663 );
3664
3665 let buffer_text_3 = "aaa\nBBB\nccc\nDDD\neee\n";
3666 let buffer_3 = Buffer::new(
3667 ReplicaId::LOCAL,
3668 BufferId::new(3).unwrap(),
3669 buffer_text_3.to_string(),
3670 );
3671
3672 let old_base_snapshot_3 = base_buf_3.snapshot().clone();
3673 let old_hunks_3 = compute_hunks(
3674 Some((Arc::from(base_3), Rope::from(base_3))),
3675 buffer_3.snapshot(),
3676 None,
3677 );
3678
3679 // Change "ddd" to "DDD" in the base text so that hunk disappears,
3680 // but "bbb" stays, so its hunk remains.
3681 base_buf_3.edit([(12..15, "DDD")]);
3682 let new_base_str_3: Arc<str> = Arc::from(base_buf_3.text().as_str());
3683 let new_base_snapshot_3 = base_buf_3.snapshot();
3684
3685 let new_hunks_3 = compute_hunks(
3686 Some((new_base_str_3.clone(), Rope::from(new_base_str_3.as_ref()))),
3687 buffer_3.snapshot(),
3688 None,
3689 );
3690
3691 let DiffChanged {
3692 changed_range,
3693 base_text_changed_range,
3694 extended_range: _,
3695 } = compare_hunks(
3696 &new_hunks_3,
3697 &old_hunks_3,
3698 &buffer_3.snapshot(),
3699 &buffer_3.snapshot(),
3700 &old_base_snapshot_3,
3701 &new_base_snapshot_3,
3702 );
3703
3704 // Only the second hunk (ddd → DDD) disappeared; the first hunk
3705 // (bbb → BBB) is unchanged, so the changed range covers only line 3.
3706 let range = changed_range.unwrap();
3707 assert_eq!(
3708 range.to_point(&buffer_3),
3709 Point::new(3, 0)..Point::new(4, 0),
3710 );
3711 let base_range = base_text_changed_range.unwrap();
3712 // anchor_after(12) is right-biased at the start of deleted "ddd",
3713 // so after the edit replacing "ddd" with "DDD" it resolves past
3714 // the insertion to Point(3, 3).
3715 assert_eq!(
3716 base_range.to_point(&new_base_snapshot_3),
3717 Point::new(3, 3)..Point::new(4, 0),
3718 );
3719
3720 // --- Scenario 4: Both buffer and base text change simultaneously ---
3721 //
3722 // The buffer gains an edit that introduces a new hunk while the
3723 // base text also changes.
3724 let base_4 = "alpha\nbeta\ngamma\ndelta\n";
3725 let mut base_buf_4 = Buffer::new(
3726 ReplicaId::LOCAL,
3727 BufferId::new(102).unwrap(),
3728 base_4.to_string(),
3729 );
3730
3731 let buffer_text_4 = "alpha\nBETA\ngamma\ndelta\n";
3732 let mut buffer_4 = Buffer::new(
3733 ReplicaId::LOCAL,
3734 BufferId::new(4).unwrap(),
3735 buffer_text_4.to_string(),
3736 );
3737
3738 let old_base_snapshot_4 = base_buf_4.snapshot().clone();
3739 let old_buffer_snapshot_4 = buffer_4.snapshot().clone();
3740 let old_hunks_4 = compute_hunks(
3741 Some((Arc::from(base_4), Rope::from(base_4))),
3742 buffer_4.snapshot(),
3743 None,
3744 );
3745
3746 // Edit the buffer: change "delta" to "DELTA" (new modification hunk).
3747 buffer_4.edit_via_marked_text(
3748 &"
3749 alpha
3750 BETA
3751 gamma
3752 «DELTA»
3753 "
3754 .unindent(),
3755 );
3756
3757 // Edit the base text: change "beta" to "BETA" (resolves that hunk).
3758 base_buf_4.edit([(6..10, "BETA")]);
3759 let new_base_str_4: Arc<str> = Arc::from(base_buf_4.text().as_str());
3760 let new_base_snapshot_4 = base_buf_4.snapshot();
3761
3762 let new_hunks_4 = compute_hunks(
3763 Some((new_base_str_4.clone(), Rope::from(new_base_str_4.as_ref()))),
3764 buffer_4.snapshot(),
3765 None,
3766 );
3767
3768 let DiffChanged {
3769 changed_range,
3770 base_text_changed_range,
3771 extended_range: _,
3772 } = compare_hunks(
3773 &new_hunks_4,
3774 &old_hunks_4,
3775 &old_buffer_snapshot_4,
3776 &buffer_4.snapshot(),
3777 &old_base_snapshot_4,
3778 &new_base_snapshot_4,
3779 );
3780
3781 // The old BETA hunk (line 1) is gone and a new DELTA hunk (line 3)
3782 // appeared, so the changed range spans from line 1 through line 4.
3783 let range = changed_range.unwrap();
3784 assert_eq!(
3785 range.to_point(&buffer_4),
3786 Point::new(1, 0)..Point::new(4, 0),
3787 );
3788 let base_range = base_text_changed_range.unwrap();
3789 // The old BETA hunk's base range started at byte 6 ("beta"). After
3790 // the base text edit replacing "beta" with "BETA", anchor_after(6)
3791 // resolves past the insertion to Point(1, 4).
3792 assert_eq!(
3793 base_range.to_point(&new_base_snapshot_4),
3794 Point::new(1, 4)..Point::new(4, 0),
3795 );
3796 }
3797
3798 #[gpui::test(iterations = 100)]
3799 async fn test_patch_for_range_random(cx: &mut TestAppContext, mut rng: StdRng) {
3800 fn gen_line(rng: &mut StdRng) -> String {
3801 if rng.random_bool(0.2) {
3802 "\n".to_owned()
3803 } else {
3804 let c = rng.random_range('A'..='Z');
3805 format!("{c}{c}{c}\n")
3806 }
3807 }
3808
3809 fn gen_text(rng: &mut StdRng, line_count: usize) -> String {
3810 (0..line_count).map(|_| gen_line(rng)).collect()
3811 }
3812
3813 fn gen_edits_from(rng: &mut StdRng, base: &str) -> String {
3814 let mut old_lines: Vec<&str> = base.lines().collect();
3815 let mut result = String::new();
3816
3817 while !old_lines.is_empty() {
3818 let unchanged_count = rng.random_range(0..=old_lines.len());
3819 for _ in 0..unchanged_count {
3820 if old_lines.is_empty() {
3821 break;
3822 }
3823 result.push_str(old_lines.remove(0));
3824 result.push('\n');
3825 }
3826
3827 if old_lines.is_empty() {
3828 break;
3829 }
3830
3831 let deleted_count = rng.random_range(0..=old_lines.len().min(3));
3832 for _ in 0..deleted_count {
3833 if old_lines.is_empty() {
3834 break;
3835 }
3836 old_lines.remove(0);
3837 }
3838
3839 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
3840 let added_count = rng.random_range(minimum_added..=3);
3841 for _ in 0..added_count {
3842 result.push_str(&gen_line(rng));
3843 }
3844 }
3845
3846 result
3847 }
3848
3849 fn random_point_in_text(rng: &mut StdRng, lines: &[&str]) -> Point {
3850 if lines.is_empty() {
3851 return Point::zero();
3852 }
3853 let row = rng.random_range(0..lines.len() as u32);
3854 let line = lines[row as usize];
3855 let col = if line.is_empty() {
3856 0
3857 } else {
3858 rng.random_range(0..=line.len() as u32)
3859 };
3860 Point::new(row, col)
3861 }
3862
3863 fn random_range_in_text(rng: &mut StdRng, lines: &[&str]) -> RangeInclusive<Point> {
3864 let start = random_point_in_text(rng, lines);
3865 let end = random_point_in_text(rng, lines);
3866 if start <= end {
3867 start..=end
3868 } else {
3869 end..=start
3870 }
3871 }
3872
3873 fn points_in_range(range: &RangeInclusive<Point>, lines: &[&str]) -> Vec<Point> {
3874 let mut points = Vec::new();
3875 for row in range.start().row..=range.end().row {
3876 if row as usize >= lines.len() {
3877 points.push(Point::new(row, 0));
3878 continue;
3879 }
3880 let line = lines[row as usize];
3881 let start_col = if row == range.start().row {
3882 range.start().column
3883 } else {
3884 0
3885 };
3886 let end_col = if row == range.end().row {
3887 range.end().column
3888 } else {
3889 line.len() as u32
3890 };
3891 for col in start_col..=end_col {
3892 points.push(Point::new(row, col));
3893 }
3894 }
3895 points
3896 }
3897
3898 let rng = &mut rng;
3899
3900 let line_count = rng.random_range(5..20);
3901 let base_text = gen_text(rng, line_count);
3902 let initial_buffer_text = gen_edits_from(rng, &base_text);
3903
3904 let mut buffer = Buffer::new(
3905 ReplicaId::LOCAL,
3906 BufferId::new(1).unwrap(),
3907 initial_buffer_text.clone(),
3908 );
3909
3910 let diff = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
3911
3912 let edit_count = rng.random_range(1..=5);
3913 for _ in 0..edit_count {
3914 let buffer_text = buffer.text();
3915 if buffer_text.is_empty() {
3916 buffer.edit([(0..0, gen_line(rng))]);
3917 } else {
3918 let lines: Vec<&str> = buffer_text.lines().collect();
3919 let start_row = rng.random_range(0..lines.len());
3920 let end_row = rng.random_range(start_row..=lines.len().min(start_row + 3));
3921
3922 let start_col = if start_row < lines.len() {
3923 rng.random_range(0..=lines[start_row].len())
3924 } else {
3925 0
3926 };
3927 let end_col = if end_row < lines.len() {
3928 rng.random_range(0..=lines[end_row].len())
3929 } else {
3930 0
3931 };
3932
3933 let start_offset = buffer
3934 .point_to_offset(Point::new(start_row as u32, start_col as u32))
3935 .min(buffer.len());
3936 let end_offset = buffer
3937 .point_to_offset(Point::new(end_row as u32, end_col as u32))
3938 .min(buffer.len());
3939
3940 let (start, end) = if start_offset <= end_offset {
3941 (start_offset, end_offset)
3942 } else {
3943 (end_offset, start_offset)
3944 };
3945
3946 let new_text = if rng.random_bool(0.3) {
3947 String::new()
3948 } else {
3949 let line_count = rng.random_range(0..=2);
3950 gen_text(rng, line_count)
3951 };
3952
3953 buffer.edit([(start..end, new_text)]);
3954 }
3955 }
3956
3957 let buffer_snapshot = buffer.snapshot();
3958
3959 let buffer_text = buffer_snapshot.text();
3960 let buffer_lines: Vec<&str> = buffer_text.lines().collect();
3961 let base_lines: Vec<&str> = base_text.lines().collect();
3962
3963 let test_count = 10;
3964 for _ in 0..test_count {
3965 let range = random_range_in_text(rng, &buffer_lines);
3966 let points = points_in_range(&range, &buffer_lines);
3967
3968 let optimized_patch = diff.patch_for_buffer_range(range.clone(), &buffer_snapshot);
3969 let naive_patch = diff.patch_for_buffer_range_naive(&buffer_snapshot);
3970
3971 for point in points {
3972 let optimized_edit = optimized_patch.edit_for_old_position(point);
3973 let naive_edit = naive_patch.edit_for_old_position(point);
3974
3975 assert_eq!(
3976 optimized_edit,
3977 naive_edit,
3978 "patch_for_buffer_range mismatch at point {:?} in range {:?}\nbase_text: {:?}\ninitial_buffer: {:?}\ncurrent_buffer: {:?}",
3979 point,
3980 range,
3981 base_text,
3982 initial_buffer_text,
3983 buffer_snapshot.text()
3984 );
3985 }
3986 }
3987
3988 for _ in 0..test_count {
3989 let range = random_range_in_text(rng, &base_lines);
3990 let points = points_in_range(&range, &base_lines);
3991
3992 let optimized_patch = diff.patch_for_base_text_range(range.clone(), &buffer_snapshot);
3993 let naive_patch = diff.patch_for_base_text_range_naive(&buffer_snapshot);
3994
3995 for point in points {
3996 let optimized_edit = optimized_patch.edit_for_old_position(point);
3997 let naive_edit = naive_patch.edit_for_old_position(point);
3998
3999 assert_eq!(
4000 optimized_edit,
4001 naive_edit,
4002 "patch_for_base_text_range mismatch at point {:?} in range {:?}\nbase_text: {:?}\ninitial_buffer: {:?}\ncurrent_buffer: {:?}",
4003 point,
4004 range,
4005 base_text,
4006 initial_buffer_text,
4007 buffer_snapshot.text()
4008 );
4009 }
4010 }
4011 }
4012}