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 let index_byte_range = index_start..index_end;
894
895 let replacement_text = match new_status {
896 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
897 log::debug!("staging hunk {:?}", buffer_offset_range);
898 buffer
899 .text_for_range(buffer_offset_range)
900 .collect::<String>()
901 }
902 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
903 log::debug!("unstaging hunk {:?}", buffer_offset_range);
904 head_text
905 .chunks_in_range(diff_base_byte_range.clone())
906 .collect::<String>()
907 }
908 _ => {
909 debug_assert!(false);
910 continue;
911 }
912 };
913
914 edits.push((index_byte_range, replacement_text));
915 }
916 drop(pending_hunks_iter);
917 drop(old_pending_hunks);
918 self.pending_hunks = pending_hunks;
919
920 #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
921 {
922 for window in edits.windows(2) {
923 let (range_a, range_b) = (&window[0].0, &window[1].0);
924 debug_assert!(range_a.end < range_b.start);
925 }
926 }
927
928 let mut new_index_text = Rope::new();
929 let mut index_cursor = index_text.cursor(0);
930
931 for (old_range, replacement_text) in edits {
932 new_index_text.append(index_cursor.slice(old_range.start));
933 index_cursor.seek_forward(old_range.end);
934 new_index_text.push(&replacement_text);
935 }
936 new_index_text.append(index_cursor.suffix());
937 Some(new_index_text)
938 }
939}
940
941impl BufferDiffInner<language::BufferSnapshot> {
942 fn hunks_intersecting_range<'a>(
943 &'a self,
944 range: Range<Anchor>,
945 buffer: &'a text::BufferSnapshot,
946 secondary: Option<&'a Self>,
947 ) -> impl 'a + Iterator<Item = DiffHunk> {
948 let range = range.to_offset(buffer);
949 let filter = move |summary: &DiffHunkSummary| {
950 let summary_range = summary.buffer_range.to_offset(buffer);
951 let before_start = summary_range.end < range.start;
952 let after_end = summary_range.start > range.end;
953 !before_start && !after_end
954 };
955 self.hunks_intersecting_range_impl(filter, buffer, secondary)
956 }
957
958 fn hunks_intersecting_range_impl<'a>(
959 &'a self,
960 filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
961 buffer: &'a text::BufferSnapshot,
962 secondary: Option<&'a Self>,
963 ) -> impl 'a + Iterator<Item = DiffHunk> {
964 let anchor_iter = self
965 .hunks
966 .filter::<_, DiffHunkSummary>(buffer, filter)
967 .flat_map(move |hunk| {
968 [
969 (
970 &hunk.buffer_range.start,
971 (
972 hunk.buffer_range.start,
973 hunk.diff_base_byte_range.start,
974 hunk,
975 ),
976 ),
977 (
978 &hunk.buffer_range.end,
979 (hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
980 ),
981 ]
982 });
983
984 let mut pending_hunks_cursor = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
985 pending_hunks_cursor.next();
986
987 let mut secondary_cursor = None;
988 if let Some(secondary) = secondary.as_ref() {
989 let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
990 cursor.next();
991 secondary_cursor = Some(cursor);
992 }
993
994 let max_point = buffer.max_point();
995 let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
996 iter::from_fn(move || {
997 loop {
998 let (start_point, (start_anchor, start_base, hunk)) = summaries.next()?;
999 let (mut end_point, (mut end_anchor, end_base, _)) = summaries.next()?;
1000
1001 let base_word_diffs = hunk.base_word_diffs.clone();
1002 let buffer_word_diffs = hunk.buffer_word_diffs.clone();
1003
1004 if !start_anchor.is_valid(buffer) {
1005 continue;
1006 }
1007
1008 if end_point.column > 0 && end_point < max_point {
1009 end_point.row += 1;
1010 end_point.column = 0;
1011 end_anchor = buffer.anchor_before(end_point);
1012 }
1013
1014 let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
1015
1016 let mut has_pending = false;
1017 if start_anchor
1018 .cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
1019 .is_gt()
1020 {
1021 pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
1022 }
1023
1024 if let Some(pending_hunk) = pending_hunks_cursor.item() {
1025 let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
1026 if pending_range.end.column > 0 {
1027 pending_range.end.row += 1;
1028 pending_range.end.column = 0;
1029 }
1030
1031 if pending_range == (start_point..end_point)
1032 && !buffer.has_edits_since_in_range(
1033 &pending_hunk.buffer_version,
1034 start_anchor..end_anchor,
1035 )
1036 {
1037 has_pending = true;
1038 secondary_status = pending_hunk.new_status;
1039 }
1040 }
1041
1042 if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
1043 if start_anchor
1044 .cmp(&secondary_cursor.start().buffer_range.start, buffer)
1045 .is_gt()
1046 {
1047 secondary_cursor.seek_forward(&start_anchor, Bias::Left);
1048 }
1049
1050 if let Some(secondary_hunk) = secondary_cursor.item() {
1051 let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
1052 if secondary_range.end.column > 0 {
1053 secondary_range.end.row += 1;
1054 secondary_range.end.column = 0;
1055 }
1056 if secondary_range.is_empty()
1057 && secondary_hunk.diff_base_byte_range.is_empty()
1058 {
1059 // ignore
1060 } else if secondary_range == (start_point..end_point) {
1061 secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
1062 } else if secondary_range.start <= end_point {
1063 secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
1064 }
1065 }
1066 }
1067
1068 return Some(DiffHunk {
1069 range: start_point..end_point,
1070 diff_base_byte_range: start_base..end_base,
1071 buffer_range: start_anchor..end_anchor,
1072 base_word_diffs,
1073 buffer_word_diffs,
1074 secondary_status,
1075 });
1076 }
1077 })
1078 }
1079
1080 fn hunks_intersecting_range_rev_impl<'a>(
1081 &'a self,
1082 filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
1083 buffer: &'a text::BufferSnapshot,
1084 ) -> impl 'a + Iterator<Item = DiffHunk> {
1085 let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
1086
1087 iter::from_fn(move || {
1088 cursor.prev();
1089
1090 let hunk = cursor.item()?;
1091 let range = hunk.buffer_range.to_point(buffer);
1092
1093 Some(DiffHunk {
1094 range,
1095 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
1096 buffer_range: hunk.buffer_range.clone(),
1097 // The secondary status is not used by callers of this method.
1098 secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
1099 base_word_diffs: hunk.base_word_diffs.clone(),
1100 buffer_word_diffs: hunk.buffer_word_diffs.clone(),
1101 })
1102 })
1103 }
1104}
1105
1106fn build_diff_options(
1107 file: Option<&Arc<dyn File>>,
1108 language: Option<LanguageName>,
1109 language_scope: Option<language::LanguageScope>,
1110 cx: &App,
1111) -> Option<DiffOptions> {
1112 #[cfg(any(test, feature = "test-support"))]
1113 {
1114 if !cx.has_global::<settings::SettingsStore>() {
1115 return Some(DiffOptions {
1116 language_scope,
1117 max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
1118 ..Default::default()
1119 });
1120 }
1121 }
1122
1123 language_settings(language, file, cx)
1124 .word_diff_enabled
1125 .then_some(DiffOptions {
1126 language_scope,
1127 max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
1128 ..Default::default()
1129 })
1130}
1131
1132fn compute_hunks(
1133 diff_base: Option<(Arc<str>, Rope)>,
1134 buffer: &text::BufferSnapshot,
1135 diff_options: Option<DiffOptions>,
1136) -> SumTree<InternalDiffHunk> {
1137 let mut tree = SumTree::new(buffer);
1138
1139 if let Some((diff_base, diff_base_rope)) = diff_base {
1140 let buffer_text = buffer.as_rope().to_string();
1141
1142 let mut options = GitOptions::default();
1143 options.context_lines(0);
1144 let patch = GitPatch::from_buffers(
1145 diff_base.as_bytes(),
1146 None,
1147 buffer_text.as_bytes(),
1148 None,
1149 Some(&mut options),
1150 )
1151 .log_err();
1152
1153 // A common case in Zed is that the empty buffer is represented as just a newline,
1154 // but if we just compute a naive diff you get a "preserved" line in the middle,
1155 // which is a bit odd.
1156 if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
1157 tree.push(
1158 InternalDiffHunk {
1159 buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
1160 diff_base_byte_range: 0..diff_base.len() - 1,
1161 base_word_diffs: Vec::default(),
1162 buffer_word_diffs: Vec::default(),
1163 },
1164 buffer,
1165 );
1166 return tree;
1167 }
1168
1169 if let Some(patch) = patch {
1170 let mut divergence = 0;
1171 for hunk_index in 0..patch.num_hunks() {
1172 let hunk = process_patch_hunk(
1173 &patch,
1174 hunk_index,
1175 &diff_base_rope,
1176 buffer,
1177 &mut divergence,
1178 diff_options.as_ref(),
1179 );
1180 tree.push(hunk, buffer);
1181 }
1182 }
1183 } else {
1184 tree.push(
1185 InternalDiffHunk {
1186 buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
1187 diff_base_byte_range: 0..0,
1188 base_word_diffs: Vec::default(),
1189 buffer_word_diffs: Vec::default(),
1190 },
1191 buffer,
1192 );
1193 }
1194
1195 tree
1196}
1197
1198fn compare_hunks(
1199 new_hunks: &SumTree<InternalDiffHunk>,
1200 old_hunks: &SumTree<InternalDiffHunk>,
1201 old_snapshot: &text::BufferSnapshot,
1202 new_snapshot: &text::BufferSnapshot,
1203 old_base_text: &text::BufferSnapshot,
1204 new_base_text: &text::BufferSnapshot,
1205) -> DiffChanged {
1206 let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
1207 let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
1208 old_cursor.next();
1209 new_cursor.next();
1210 let mut start = None;
1211 let mut end = None;
1212 let mut base_text_start: Option<Anchor> = None;
1213 let mut base_text_end: Option<Anchor> = None;
1214
1215 let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1216 let mut has_changes = false;
1217 let mut extended_end_candidate: Option<text::Anchor> = None;
1218
1219 loop {
1220 match (new_cursor.item(), old_cursor.item()) {
1221 (Some(new_hunk), Some(old_hunk)) => {
1222 match new_hunk
1223 .buffer_range
1224 .start
1225 .cmp(&old_hunk.buffer_range.start, new_snapshot)
1226 {
1227 Ordering::Less => {
1228 has_changes = true;
1229 extended_end_candidate = None;
1230 start.get_or_insert(new_hunk.buffer_range.start);
1231 base_text_start.get_or_insert(
1232 new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1233 );
1234 end.replace(new_hunk.buffer_range.end);
1235 let new_diff_range_end =
1236 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1237 if base_text_end.is_none_or(|base_text_end| {
1238 new_diff_range_end
1239 .cmp(&base_text_end, &new_base_text)
1240 .is_gt()
1241 }) {
1242 base_text_end = Some(new_diff_range_end)
1243 }
1244 new_cursor.next();
1245 }
1246 Ordering::Equal => {
1247 if new_hunk != old_hunk {
1248 has_changes = true;
1249 extended_end_candidate = None;
1250 start.get_or_insert(new_hunk.buffer_range.start);
1251 base_text_start.get_or_insert(
1252 new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1253 );
1254 if old_hunk
1255 .buffer_range
1256 .end
1257 .cmp(&new_hunk.buffer_range.end, new_snapshot)
1258 .is_ge()
1259 {
1260 end.replace(old_hunk.buffer_range.end);
1261 } else {
1262 end.replace(new_hunk.buffer_range.end);
1263 }
1264
1265 let old_hunk_diff_base_range_end =
1266 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1267 let new_hunk_diff_base_range_end =
1268 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1269
1270 base_text_end.replace(
1271 *old_hunk_diff_base_range_end
1272 .max(&new_hunk_diff_base_range_end, new_base_text),
1273 );
1274 } else {
1275 if !has_changes {
1276 last_unchanged_new_hunk_end = Some(new_hunk.buffer_range.end);
1277 } else if extended_end_candidate.is_none() {
1278 extended_end_candidate = Some(new_hunk.buffer_range.start);
1279 }
1280 }
1281
1282 new_cursor.next();
1283 old_cursor.next();
1284 }
1285 Ordering::Greater => {
1286 has_changes = true;
1287 extended_end_candidate = None;
1288 start.get_or_insert(old_hunk.buffer_range.start);
1289 base_text_start.get_or_insert(
1290 old_base_text.anchor_after(old_hunk.diff_base_byte_range.start),
1291 );
1292 end.replace(old_hunk.buffer_range.end);
1293 let old_diff_range_end =
1294 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1295 if base_text_end.is_none_or(|base_text_end| {
1296 old_diff_range_end
1297 .cmp(&base_text_end, new_base_text)
1298 .is_gt()
1299 }) {
1300 base_text_end = Some(old_diff_range_end)
1301 }
1302 old_cursor.next();
1303 }
1304 }
1305 }
1306 (Some(new_hunk), None) => {
1307 has_changes = true;
1308 extended_end_candidate = None;
1309 start.get_or_insert(new_hunk.buffer_range.start);
1310 base_text_start
1311 .get_or_insert(new_base_text.anchor_after(new_hunk.diff_base_byte_range.start));
1312 if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1313 {
1314 end.replace(new_hunk.buffer_range.end);
1315 }
1316 let new_base_text_end =
1317 new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1318 if base_text_end.is_none_or(|base_text_end| {
1319 new_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1320 }) {
1321 base_text_end = Some(new_base_text_end)
1322 }
1323 new_cursor.next();
1324 }
1325 (None, Some(old_hunk)) => {
1326 has_changes = true;
1327 extended_end_candidate = None;
1328 start.get_or_insert(old_hunk.buffer_range.start);
1329 base_text_start
1330 .get_or_insert(old_base_text.anchor_after(old_hunk.diff_base_byte_range.start));
1331 if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1332 {
1333 end.replace(old_hunk.buffer_range.end);
1334 }
1335 let old_base_text_end =
1336 old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1337 if base_text_end.is_none_or(|base_text_end| {
1338 old_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1339 }) {
1340 base_text_end = Some(old_base_text_end);
1341 }
1342 old_cursor.next();
1343 }
1344 (None, None) => break,
1345 }
1346 }
1347
1348 let changed_range = start.zip(end).map(|(start, end)| start..end);
1349 let base_text_changed_range = base_text_start
1350 .zip(base_text_end)
1351 .map(|(start, end)| (start..end).to_offset(new_base_text));
1352
1353 let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1354 let extended_start = *last_unchanged_new_hunk_end
1355 .unwrap_or(text::Anchor::min_for_buffer(new_snapshot.remote_id()))
1356 .min(&changed_range.start, new_snapshot);
1357 let extended_start = new_snapshot
1358 .anchored_edits_since_in_range::<usize>(
1359 &old_snapshot.version(),
1360 extended_start..changed_range.start,
1361 )
1362 .map(|(_, anchors)| anchors.start)
1363 .min_by(|a, b| a.cmp(b, new_snapshot))
1364 .unwrap_or(changed_range.start);
1365
1366 let extended_end = *extended_end_candidate
1367 .unwrap_or(text::Anchor::max_for_buffer(new_snapshot.remote_id()))
1368 .max(&changed_range.end, new_snapshot);
1369 let extended_end = new_snapshot
1370 .anchored_edits_since_in_range::<usize>(
1371 &old_snapshot.version(),
1372 changed_range.end..extended_end,
1373 )
1374 .map(|(_, anchors)| anchors.end)
1375 .max_by(|a, b| a.cmp(b, new_snapshot))
1376 .unwrap_or(changed_range.end);
1377
1378 Some(extended_start..extended_end)
1379 } else {
1380 None
1381 };
1382
1383 DiffChanged {
1384 changed_range,
1385 base_text_changed_range,
1386 extended_range,
1387 }
1388}
1389
1390fn process_patch_hunk(
1391 patch: &GitPatch<'_>,
1392 hunk_index: usize,
1393 diff_base: &Rope,
1394 buffer: &text::BufferSnapshot,
1395 buffer_row_divergence: &mut i64,
1396 diff_options: Option<&DiffOptions>,
1397) -> InternalDiffHunk {
1398 let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
1399 assert!(line_item_count > 0);
1400
1401 let mut first_deletion_buffer_row: Option<u32> = None;
1402 let mut buffer_row_range: Option<Range<u32>> = None;
1403 let mut diff_base_byte_range: Option<Range<usize>> = None;
1404 let mut first_addition_old_row: Option<u32> = None;
1405
1406 for line_index in 0..line_item_count {
1407 let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
1408 let kind = line.origin_value();
1409 let content_offset = line.content_offset() as isize;
1410 let content_len = line.content().len() as isize;
1411 match kind {
1412 GitDiffLineType::Addition => {
1413 if first_addition_old_row.is_none() {
1414 first_addition_old_row = Some(
1415 (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
1416 );
1417 }
1418 *buffer_row_divergence += 1;
1419 let row = line.new_lineno().unwrap().saturating_sub(1);
1420
1421 match &mut buffer_row_range {
1422 Some(Range { end, .. }) => *end = row + 1,
1423 None => buffer_row_range = Some(row..row + 1),
1424 }
1425 }
1426 GitDiffLineType::Deletion => {
1427 let end = content_offset + content_len;
1428
1429 match &mut diff_base_byte_range {
1430 Some(head_byte_range) => head_byte_range.end = end as usize,
1431 None => diff_base_byte_range = Some(content_offset as usize..end as usize),
1432 }
1433
1434 if first_deletion_buffer_row.is_none() {
1435 let old_row = line.old_lineno().unwrap().saturating_sub(1);
1436 let row = old_row as i64 + *buffer_row_divergence;
1437 first_deletion_buffer_row = Some(row as u32);
1438 }
1439
1440 *buffer_row_divergence -= 1;
1441 }
1442 _ => {}
1443 }
1444 }
1445
1446 let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
1447 // Pure deletion hunk without addition.
1448 let row = first_deletion_buffer_row.unwrap();
1449 row..row
1450 });
1451 let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
1452 // Pure addition hunk without deletion.
1453 let row = first_addition_old_row.unwrap();
1454 let offset = diff_base.point_to_offset(Point::new(row, 0));
1455 offset..offset
1456 });
1457
1458 let start = Point::new(buffer_row_range.start, 0);
1459 let end = Point::new(buffer_row_range.end, 0);
1460 let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
1461
1462 let (base_word_diffs, buffer_word_diffs) = if let Some(options) = diff_options {
1463 word_diffs(
1464 diff_base,
1465 diff_base_byte_range.clone(),
1466 buffer,
1467 buffer_range.clone(),
1468 options,
1469 )
1470 } else {
1471 (Vec::new(), Vec::new())
1472 };
1473
1474 InternalDiffHunk {
1475 buffer_range,
1476 diff_base_byte_range,
1477 base_word_diffs,
1478 buffer_word_diffs,
1479 }
1480}
1481
1482fn word_diffs(
1483 diff_base: &Rope,
1484 diff_base_byte_range: Range<usize>,
1485 buffer: &text::BufferSnapshot,
1486 buffer_range: Range<Anchor>,
1487 options: &DiffOptions,
1488) -> (Vec<Range<usize>>, Vec<Range<Anchor>>) {
1489 let buffer_rows = buffer.text_summary_for_range::<Point, _>(buffer_range.clone());
1490 let base_text_rows = diff_base
1491 .cursor(diff_base_byte_range.start)
1492 .summary::<Point>(diff_base_byte_range.end);
1493 if buffer_rows == Point::zero()
1494 || base_text_rows.row != buffer_rows.row
1495 || base_text_rows.row > options.max_word_diff_line_count as u32
1496 {
1497 return (Vec::default(), Vec::default());
1498 }
1499
1500 let base_text: String = diff_base
1501 .chunks_in_range(diff_base_byte_range.clone())
1502 .collect();
1503
1504 let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
1505
1506 let (base_word_diffs, buffer_word_diffs_relative) =
1507 word_diff_ranges(&base_text, &buffer_text, options.clone());
1508
1509 let buffer_start_offset = buffer_range.start.to_offset(buffer);
1510 let buffer_word_diffs = buffer_word_diffs_relative
1511 .into_iter()
1512 .map(|range| {
1513 let start = buffer.anchor_after(buffer_start_offset + range.start);
1514 let end = buffer.anchor_after(buffer_start_offset + range.end);
1515 start..end
1516 })
1517 .collect();
1518
1519 (base_word_diffs, buffer_word_diffs)
1520}
1521
1522impl std::fmt::Debug for BufferDiff {
1523 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1524 f.debug_struct("BufferChangeSet")
1525 .field("buffer_id", &self.buffer_id)
1526 .finish()
1527 }
1528}
1529
1530#[derive(Clone, Debug, Default)]
1531pub struct DiffChanged {
1532 pub changed_range: Option<Range<text::Anchor>>,
1533 pub base_text_changed_range: Option<Range<usize>>,
1534 pub extended_range: Option<Range<text::Anchor>>,
1535}
1536
1537#[derive(Clone, Debug)]
1538pub enum BufferDiffEvent {
1539 DiffChanged(DiffChanged),
1540 LanguageChanged,
1541 HunksStagedOrUnstaged(Option<Rope>),
1542}
1543
1544impl EventEmitter<BufferDiffEvent> for BufferDiff {}
1545
1546impl BufferDiff {
1547 pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1548 let base_text = cx.new(|cx| {
1549 let mut buffer = language::Buffer::local("", cx);
1550 buffer.set_capability(Capability::ReadOnly, cx);
1551 buffer
1552 });
1553
1554 BufferDiff {
1555 buffer_id: buffer.remote_id(),
1556 inner: BufferDiffInner {
1557 base_text,
1558 hunks: SumTree::new(buffer),
1559 pending_hunks: SumTree::new(buffer),
1560 base_text_exists: false,
1561 buffer_snapshot: buffer.clone(),
1562 },
1563 secondary_diff: None,
1564 }
1565 }
1566
1567 pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1568 let base_text = buffer.text();
1569 let base_text = cx.new(|cx| {
1570 let mut buffer = language::Buffer::local(base_text, cx);
1571 buffer.set_capability(Capability::ReadOnly, cx);
1572 buffer
1573 });
1574
1575 BufferDiff {
1576 buffer_id: buffer.remote_id(),
1577 inner: BufferDiffInner {
1578 base_text,
1579 hunks: SumTree::new(buffer),
1580 pending_hunks: SumTree::new(buffer),
1581 base_text_exists: true,
1582 buffer_snapshot: buffer.clone(),
1583 },
1584 secondary_diff: None,
1585 }
1586 }
1587
1588 #[cfg(any(test, feature = "test-support"))]
1589 pub fn new_with_base_text(
1590 base_text: &str,
1591 buffer: &text::BufferSnapshot,
1592 cx: &mut Context<Self>,
1593 ) -> Self {
1594 let mut this = BufferDiff::new(&buffer, cx);
1595 let mut base_text = base_text.to_owned();
1596 text::LineEnding::normalize(&mut base_text);
1597 let inner = cx.foreground_executor().block_on(this.update_diff(
1598 buffer.clone(),
1599 Some(Arc::from(base_text)),
1600 Some(false),
1601 None,
1602 cx,
1603 ));
1604 this.set_snapshot(inner, &buffer, cx).detach();
1605 this
1606 }
1607
1608 pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1609 self.secondary_diff = Some(diff);
1610 }
1611
1612 pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1613 self.secondary_diff.clone()
1614 }
1615
1616 pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1617 if self.secondary_diff.is_some() {
1618 self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1619 buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1620 diff_base_byte_range: 0..0,
1621 });
1622 let changed_range = Some(Anchor::min_max_range_for_buffer(self.buffer_id));
1623 let base_text_range = Some(0..self.base_text(cx).len());
1624 cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1625 changed_range: changed_range.clone(),
1626 base_text_changed_range: base_text_range,
1627 extended_range: changed_range,
1628 }));
1629 }
1630 }
1631
1632 pub fn stage_or_unstage_hunks(
1633 &mut self,
1634 stage: bool,
1635 hunks: &[DiffHunk],
1636 buffer: &text::BufferSnapshot,
1637 file_exists: bool,
1638 cx: &mut Context<Self>,
1639 ) -> Option<Rope> {
1640 let new_index_text = self
1641 .secondary_diff
1642 .as_ref()?
1643 .update(cx, |secondary_diff, cx| {
1644 self.inner.stage_or_unstage_hunks_impl(
1645 &secondary_diff.inner,
1646 stage,
1647 hunks,
1648 buffer,
1649 file_exists,
1650 cx,
1651 )
1652 });
1653
1654 cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1655 new_index_text.clone(),
1656 ));
1657 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1658 let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1659 let base_text_changed_range =
1660 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1661 cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1662 changed_range: changed_range.clone(),
1663 base_text_changed_range,
1664 extended_range: changed_range,
1665 }));
1666 }
1667 new_index_text
1668 }
1669
1670 pub fn stage_or_unstage_all_hunks(
1671 &mut self,
1672 stage: bool,
1673 buffer: &text::BufferSnapshot,
1674 file_exists: bool,
1675 cx: &mut Context<Self>,
1676 ) {
1677 let hunks = self
1678 .snapshot(cx)
1679 .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1680 .collect::<Vec<_>>();
1681 let Some(secondary) = self.secondary_diff.clone() else {
1682 return;
1683 };
1684 let secondary = secondary.read(cx).inner.clone();
1685 self.inner
1686 .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1687 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1688 let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1689 let base_text_changed_range =
1690 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1691 cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1692 changed_range: changed_range.clone(),
1693 base_text_changed_range,
1694 extended_range: changed_range,
1695 }));
1696 }
1697 }
1698
1699 pub fn update_diff(
1700 &self,
1701 buffer: text::BufferSnapshot,
1702 base_text: Option<Arc<str>>,
1703 base_text_change: Option<bool>,
1704 language: Option<Arc<Language>>,
1705 cx: &App,
1706 ) -> Task<BufferDiffUpdate> {
1707 let prev_base_text = self.base_text(cx).as_rope().clone();
1708 let base_text_changed = base_text_change.is_some();
1709 let compute_base_text_edits = base_text_change == Some(true);
1710 let diff_options = build_diff_options(
1711 None,
1712 language.as_ref().map(|l| l.name()),
1713 language.as_ref().map(|l| l.default_scope()),
1714 cx,
1715 );
1716 let buffer_snapshot = buffer.clone();
1717
1718 let base_text_diff_task = if base_text_changed && compute_base_text_edits {
1719 base_text
1720 .as_ref()
1721 .map(|new_text| self.inner.base_text.read(cx).diff(new_text.clone(), cx))
1722 } else {
1723 None
1724 };
1725
1726 let hunk_task = cx.background_executor().spawn({
1727 let buffer_snapshot = buffer_snapshot.clone();
1728 async move {
1729 let base_text_rope = if let Some(base_text) = &base_text {
1730 if base_text_changed {
1731 Rope::from(base_text.as_ref())
1732 } else {
1733 prev_base_text
1734 }
1735 } else {
1736 Rope::new()
1737 };
1738 let base_text_exists = base_text.is_some();
1739 let hunks = compute_hunks(
1740 base_text
1741 .clone()
1742 .map(|base_text| (base_text, base_text_rope.clone())),
1743 &buffer,
1744 diff_options,
1745 );
1746 let base_text = base_text.unwrap_or_default();
1747 BufferDiffInner {
1748 base_text,
1749 hunks,
1750 base_text_exists,
1751 pending_hunks: SumTree::new(&buffer),
1752 buffer_snapshot,
1753 }
1754 }
1755 });
1756
1757 cx.background_executor().spawn(async move {
1758 let (inner, base_text_edits) = match base_text_diff_task {
1759 Some(diff_task) => {
1760 let (inner, diff) = futures::join!(hunk_task, diff_task);
1761 (inner, Some(diff))
1762 }
1763 None => (hunk_task.await, None),
1764 };
1765
1766 BufferDiffUpdate {
1767 inner,
1768 buffer_snapshot,
1769 base_text_edits,
1770 base_text_changed,
1771 }
1772 })
1773 }
1774
1775 #[ztracing::instrument(skip_all)]
1776 pub fn language_changed(
1777 &mut self,
1778 language: Option<Arc<Language>>,
1779 language_registry: Option<Arc<LanguageRegistry>>,
1780 cx: &mut Context<Self>,
1781 ) {
1782 let fut = self.inner.base_text.update(cx, |base_text, cx| {
1783 if let Some(language_registry) = language_registry {
1784 base_text.set_language_registry(language_registry);
1785 }
1786 base_text.set_language_async(language, cx);
1787 base_text.parsing_idle()
1788 });
1789 cx.spawn(async move |this, cx| {
1790 fut.await;
1791 this.update(cx, |_, cx| {
1792 cx.emit(BufferDiffEvent::LanguageChanged);
1793 })
1794 .ok();
1795 })
1796 .detach();
1797 }
1798
1799 fn set_snapshot_with_secondary_inner(
1800 &mut self,
1801 update: BufferDiffUpdate,
1802 buffer: &text::BufferSnapshot,
1803 secondary_diff_change: Option<Range<Anchor>>,
1804 clear_pending_hunks: bool,
1805 cx: &mut Context<Self>,
1806 ) -> impl Future<Output = DiffChanged> + use<> {
1807 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1808
1809 let old_snapshot = self.snapshot(cx);
1810 let new_state = update.inner;
1811 let base_text_changed = update.base_text_changed;
1812
1813 let state = &mut self.inner;
1814 state.base_text_exists = new_state.base_text_exists;
1815 let should_compare_hunks = update.base_text_edits.is_some() || !base_text_changed;
1816 let parsing_idle = if let Some(diff) = update.base_text_edits {
1817 state.base_text.update(cx, |base_text, cx| {
1818 base_text.set_sync_parse_timeout(None);
1819 base_text.set_capability(Capability::ReadWrite, cx);
1820 base_text.apply_diff(diff, cx);
1821 base_text.set_capability(Capability::ReadOnly, cx);
1822 Some(base_text.parsing_idle())
1823 })
1824 } else if update.base_text_changed {
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.set_text(new_state.base_text.clone(), cx);
1829 base_text.set_capability(Capability::ReadOnly, cx);
1830 Some(base_text.parsing_idle())
1831 })
1832 } else {
1833 None
1834 };
1835
1836 let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1837 let old_base_snapshot = &old_snapshot.inner.base_text;
1838 let new_base_snapshot = state.base_text.read(cx).snapshot();
1839 let DiffChanged {
1840 mut changed_range,
1841 mut base_text_changed_range,
1842 mut extended_range,
1843 } = match (state.base_text_exists, new_state.base_text_exists) {
1844 (false, false) => DiffChanged::default(),
1845 (true, true) if should_compare_hunks => compare_hunks(
1846 &new_state.hunks,
1847 &old_snapshot.inner.hunks,
1848 old_buffer_snapshot,
1849 buffer,
1850 old_base_snapshot,
1851 &new_base_snapshot,
1852 ),
1853 _ => {
1854 let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
1855 let full_base_range = 0..new_state.base_text.len();
1856 DiffChanged {
1857 changed_range: Some(full_range.clone()),
1858 base_text_changed_range: Some(full_base_range),
1859 extended_range: Some(full_range),
1860 }
1861 }
1862 };
1863 state.hunks = new_state.hunks;
1864 state.buffer_snapshot = update.buffer_snapshot;
1865
1866 if base_text_changed || clear_pending_hunks {
1867 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1868 {
1869 let pending_range = first.buffer_range.start..last.buffer_range.end;
1870 if let Some(range) = &mut changed_range {
1871 range.start = *range.start.min(&pending_range.start, buffer);
1872 range.end = *range.end.max(&pending_range.end, buffer);
1873 } else {
1874 changed_range = Some(pending_range.clone());
1875 }
1876
1877 if let Some(base_text_range) = base_text_changed_range.as_mut() {
1878 base_text_range.start =
1879 base_text_range.start.min(first.diff_base_byte_range.start);
1880 base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1881 } else {
1882 base_text_changed_range =
1883 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1884 }
1885
1886 if let Some(ext) = &mut extended_range {
1887 ext.start = *ext.start.min(&pending_range.start, buffer);
1888 ext.end = *ext.end.max(&pending_range.end, buffer);
1889 } else {
1890 extended_range = Some(pending_range);
1891 }
1892 }
1893 state.pending_hunks = SumTree::new(buffer);
1894 }
1895
1896 if let Some(secondary_changed_range) = secondary_diff_change
1897 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1898 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1899 {
1900 if let Some(range) = &mut changed_range {
1901 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1902 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1903 } else {
1904 changed_range = Some(secondary_hunk_range.clone());
1905 }
1906
1907 if let Some(base_text_range) = base_text_changed_range.as_mut() {
1908 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1909 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1910 } else {
1911 base_text_changed_range = Some(secondary_base_range);
1912 }
1913
1914 if let Some(ext) = &mut extended_range {
1915 ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1916 ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1917 } else {
1918 extended_range = Some(secondary_hunk_range);
1919 }
1920 }
1921
1922 async move {
1923 if let Some(parsing_idle) = parsing_idle {
1924 parsing_idle.await;
1925 }
1926 DiffChanged {
1927 changed_range,
1928 base_text_changed_range,
1929 extended_range,
1930 }
1931 }
1932 }
1933
1934 pub fn set_snapshot(
1935 &mut self,
1936 new_state: BufferDiffUpdate,
1937 buffer: &text::BufferSnapshot,
1938 cx: &mut Context<Self>,
1939 ) -> Task<Option<Range<Anchor>>> {
1940 self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1941 }
1942
1943 pub fn set_snapshot_with_secondary(
1944 &mut self,
1945 update: BufferDiffUpdate,
1946 buffer: &text::BufferSnapshot,
1947 secondary_diff_change: Option<Range<Anchor>>,
1948 clear_pending_hunks: bool,
1949 cx: &mut Context<Self>,
1950 ) -> Task<Option<Range<Anchor>>> {
1951 let fut = self.set_snapshot_with_secondary_inner(
1952 update,
1953 buffer,
1954 secondary_diff_change,
1955 clear_pending_hunks,
1956 cx,
1957 );
1958
1959 cx.spawn(async move |this, cx| {
1960 let change = fut.await;
1961 this.update(cx, |_, cx| {
1962 cx.emit(BufferDiffEvent::DiffChanged(change.clone()));
1963 })
1964 .ok();
1965 change.changed_range
1966 })
1967 }
1968
1969 pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1970 self.inner.base_text.read(cx).snapshot()
1971 }
1972
1973 pub fn base_text_exists(&self) -> bool {
1974 self.inner.base_text_exists
1975 }
1976
1977 pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1978 BufferDiffSnapshot {
1979 inner: BufferDiffInner {
1980 hunks: self.inner.hunks.clone(),
1981 pending_hunks: self.inner.pending_hunks.clone(),
1982 base_text: self.inner.base_text.read(cx).snapshot(),
1983 base_text_exists: self.inner.base_text_exists,
1984 buffer_snapshot: self.inner.buffer_snapshot.clone(),
1985 },
1986 secondary_diff: self.secondary_diff.as_ref().map(|diff| {
1987 debug_assert!(diff.read(cx).secondary_diff.is_none());
1988 Arc::new(diff.read(cx).snapshot(cx))
1989 }),
1990 }
1991 }
1992
1993 /// Used in cases where the change set isn't derived from git.
1994 pub fn set_base_text(
1995 &mut self,
1996 base_text: Option<Arc<str>>,
1997 language: Option<Arc<Language>>,
1998 buffer: text::BufferSnapshot,
1999 cx: &mut Context<Self>,
2000 ) -> oneshot::Receiver<()> {
2001 let (tx, rx) = oneshot::channel();
2002 let complete_on_drop = util::defer(|| {
2003 tx.send(()).ok();
2004 });
2005 cx.spawn(async move |this, cx| {
2006 let Some(state) = this
2007 .update(cx, |this, cx| {
2008 this.update_diff(buffer.clone(), base_text, Some(false), language, cx)
2009 })
2010 .log_err()
2011 else {
2012 return;
2013 };
2014 let state = state.await;
2015 if let Some(task) = this
2016 .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
2017 .log_err()
2018 {
2019 task.await;
2020 }
2021 drop(complete_on_drop)
2022 })
2023 .detach();
2024 rx
2025 }
2026
2027 pub fn base_text_string(&self, cx: &App) -> Option<String> {
2028 self.inner
2029 .base_text_exists
2030 .then(|| self.inner.base_text.read(cx).text())
2031 }
2032
2033 #[cfg(any(test, feature = "test-support"))]
2034 pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
2035 let language = self.base_text(cx).language().cloned();
2036 let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
2037 let fut = self.update_diff(buffer.clone(), base_text, None, language, cx);
2038 let fg_executor = cx.foreground_executor().clone();
2039 let snapshot = fg_executor.block_on(fut);
2040 let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
2041 let change = fg_executor.block_on(fut);
2042 cx.emit(BufferDiffEvent::DiffChanged(change));
2043 }
2044
2045 pub fn base_text_buffer(&self) -> &Entity<language::Buffer> {
2046 &self.inner.base_text
2047 }
2048}
2049
2050impl DiffHunk {
2051 pub fn is_created_file(&self) -> bool {
2052 self.diff_base_byte_range == (0..0)
2053 && self.buffer_range.start.is_min()
2054 && self.buffer_range.end.is_max()
2055 }
2056
2057 pub fn status(&self) -> DiffHunkStatus {
2058 let kind = if self.buffer_range.start == self.buffer_range.end {
2059 DiffHunkStatusKind::Deleted
2060 } else if self.diff_base_byte_range.is_empty() {
2061 DiffHunkStatusKind::Added
2062 } else {
2063 DiffHunkStatusKind::Modified
2064 };
2065 DiffHunkStatus {
2066 kind,
2067 secondary: self.secondary_status,
2068 }
2069 }
2070}
2071
2072impl DiffHunkStatus {
2073 pub fn has_secondary_hunk(&self) -> bool {
2074 matches!(
2075 self.secondary,
2076 DiffHunkSecondaryStatus::HasSecondaryHunk
2077 | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2078 | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
2079 )
2080 }
2081
2082 pub fn is_pending(&self) -> bool {
2083 matches!(
2084 self.secondary,
2085 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2086 | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2087 )
2088 }
2089
2090 pub fn is_deleted(&self) -> bool {
2091 self.kind == DiffHunkStatusKind::Deleted
2092 }
2093
2094 pub fn is_added(&self) -> bool {
2095 self.kind == DiffHunkStatusKind::Added
2096 }
2097
2098 pub fn is_modified(&self) -> bool {
2099 self.kind == DiffHunkStatusKind::Modified
2100 }
2101
2102 pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
2103 Self {
2104 kind: DiffHunkStatusKind::Added,
2105 secondary,
2106 }
2107 }
2108
2109 pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
2110 Self {
2111 kind: DiffHunkStatusKind::Modified,
2112 secondary,
2113 }
2114 }
2115
2116 pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
2117 Self {
2118 kind: DiffHunkStatusKind::Deleted,
2119 secondary,
2120 }
2121 }
2122
2123 pub fn deleted_none() -> Self {
2124 Self {
2125 kind: DiffHunkStatusKind::Deleted,
2126 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2127 }
2128 }
2129
2130 pub fn added_none() -> Self {
2131 Self {
2132 kind: DiffHunkStatusKind::Added,
2133 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2134 }
2135 }
2136
2137 pub fn modified_none() -> Self {
2138 Self {
2139 kind: DiffHunkStatusKind::Modified,
2140 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2141 }
2142 }
2143}
2144
2145#[cfg(any(test, feature = "test-support"))]
2146#[track_caller]
2147pub fn assert_hunks<ExpectedText, HunkIter>(
2148 diff_hunks: HunkIter,
2149 buffer: &text::BufferSnapshot,
2150 diff_base: &str,
2151 // Line range, deleted, added, status
2152 expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
2153) where
2154 HunkIter: Iterator<Item = DiffHunk>,
2155 ExpectedText: AsRef<str>,
2156{
2157 let actual_hunks = diff_hunks
2158 .map(|hunk| {
2159 (
2160 hunk.range.clone(),
2161 &diff_base[hunk.diff_base_byte_range.clone()],
2162 buffer
2163 .text_for_range(hunk.range.clone())
2164 .collect::<String>(),
2165 hunk.status(),
2166 )
2167 })
2168 .collect::<Vec<_>>();
2169
2170 let expected_hunks: Vec<_> = expected_hunks
2171 .iter()
2172 .map(|(line_range, deleted_text, added_text, status)| {
2173 (
2174 Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
2175 deleted_text.as_ref(),
2176 added_text.as_ref().to_string(),
2177 *status,
2178 )
2179 })
2180 .collect();
2181
2182 pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
2183}
2184
2185#[cfg(test)]
2186mod tests {
2187 use std::{fmt::Write as _, sync::mpsc};
2188
2189 use super::*;
2190 use gpui::TestAppContext;
2191 use pretty_assertions::{assert_eq, assert_ne};
2192 use rand::{Rng as _, rngs::StdRng};
2193 use text::{Buffer, BufferId, ReplicaId, Rope};
2194 use unindent::Unindent as _;
2195 use util::test::marked_text_ranges;
2196
2197 #[ctor::ctor]
2198 fn init_logger() {
2199 zlog::init_test();
2200 }
2201
2202 #[gpui::test]
2203 async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
2204 let diff_base = "
2205 one
2206 two
2207 three
2208 "
2209 .unindent();
2210
2211 let buffer_text = "
2212 one
2213 HELLO
2214 three
2215 "
2216 .unindent();
2217
2218 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2219 let mut diff = BufferDiffSnapshot::new_sync(&buffer, diff_base.clone(), cx);
2220 assert_hunks(
2221 diff.hunks_intersecting_range(
2222 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2223 &buffer,
2224 ),
2225 &buffer,
2226 &diff_base,
2227 &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
2228 );
2229
2230 buffer.edit([(0..0, "point five\n")]);
2231 diff = BufferDiffSnapshot::new_sync(&buffer, diff_base.clone(), cx);
2232 assert_hunks(
2233 diff.hunks_intersecting_range(
2234 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2235 &buffer,
2236 ),
2237 &buffer,
2238 &diff_base,
2239 &[
2240 (0..1, "", "point five\n", DiffHunkStatus::added_none()),
2241 (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
2242 ],
2243 );
2244
2245 diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2246 assert_hunks::<&str, _>(
2247 diff.hunks_intersecting_range(
2248 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2249 &buffer,
2250 ),
2251 &buffer,
2252 &diff_base,
2253 &[],
2254 );
2255 }
2256
2257 #[gpui::test]
2258 async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
2259 let head_text = "
2260 zero
2261 one
2262 two
2263 three
2264 four
2265 five
2266 six
2267 seven
2268 eight
2269 nine
2270 "
2271 .unindent();
2272
2273 let index_text = "
2274 zero
2275 one
2276 TWO
2277 three
2278 FOUR
2279 five
2280 six
2281 seven
2282 eight
2283 NINE
2284 "
2285 .unindent();
2286
2287 let buffer_text = "
2288 zero
2289 one
2290 TWO
2291 three
2292 FOUR
2293 FIVE
2294 six
2295 SEVEN
2296 eight
2297 nine
2298 "
2299 .unindent();
2300
2301 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2302 let unstaged_diff = BufferDiffSnapshot::new_sync(&buffer, index_text, cx);
2303 let mut uncommitted_diff = BufferDiffSnapshot::new_sync(&buffer, head_text.clone(), cx);
2304 uncommitted_diff.secondary_diff = Some(Arc::new(unstaged_diff));
2305
2306 let expected_hunks = vec![
2307 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
2308 (
2309 4..6,
2310 "four\nfive\n",
2311 "FOUR\nFIVE\n",
2312 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2313 ),
2314 (
2315 7..8,
2316 "seven\n",
2317 "SEVEN\n",
2318 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2319 ),
2320 ];
2321
2322 assert_hunks(
2323 uncommitted_diff.hunks_intersecting_range(
2324 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2325 &buffer,
2326 ),
2327 &buffer,
2328 &head_text,
2329 &expected_hunks,
2330 );
2331 }
2332
2333 #[gpui::test]
2334 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
2335 let diff_base = "
2336 one
2337 two
2338 three
2339 four
2340 five
2341 six
2342 seven
2343 eight
2344 nine
2345 ten
2346 "
2347 .unindent();
2348
2349 let buffer_text = "
2350 A
2351 one
2352 B
2353 two
2354 C
2355 three
2356 HELLO
2357 four
2358 five
2359 SIXTEEN
2360 seven
2361 eight
2362 WORLD
2363 nine
2364
2365 ten
2366
2367 "
2368 .unindent();
2369
2370 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2371 let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
2372 assert_eq!(
2373 diff.hunks_intersecting_range(
2374 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2375 &buffer
2376 )
2377 .count(),
2378 8
2379 );
2380
2381 assert_hunks(
2382 diff.hunks_intersecting_range(
2383 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
2384 &buffer,
2385 ),
2386 &buffer,
2387 &diff_base,
2388 &[
2389 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
2390 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
2391 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
2392 ],
2393 );
2394 }
2395
2396 #[gpui::test]
2397 async fn test_stage_hunk(cx: &mut TestAppContext) {
2398 struct Example {
2399 name: &'static str,
2400 head_text: String,
2401 index_text: String,
2402 buffer_marked_text: String,
2403 final_index_text: String,
2404 }
2405
2406 let table = [
2407 Example {
2408 name: "uncommitted hunk straddles end of unstaged hunk",
2409 head_text: "
2410 one
2411 two
2412 three
2413 four
2414 five
2415 "
2416 .unindent(),
2417 index_text: "
2418 one
2419 TWO_HUNDRED
2420 three
2421 FOUR_HUNDRED
2422 five
2423 "
2424 .unindent(),
2425 buffer_marked_text: "
2426 ZERO
2427 one
2428 two
2429 «THREE_HUNDRED
2430 FOUR_HUNDRED»
2431 five
2432 SIX
2433 "
2434 .unindent(),
2435 final_index_text: "
2436 one
2437 two
2438 THREE_HUNDRED
2439 FOUR_HUNDRED
2440 five
2441 "
2442 .unindent(),
2443 },
2444 Example {
2445 name: "uncommitted hunk straddles start of unstaged hunk",
2446 head_text: "
2447 one
2448 two
2449 three
2450 four
2451 five
2452 "
2453 .unindent(),
2454 index_text: "
2455 one
2456 TWO_HUNDRED
2457 three
2458 FOUR_HUNDRED
2459 five
2460 "
2461 .unindent(),
2462 buffer_marked_text: "
2463 ZERO
2464 one
2465 «TWO_HUNDRED
2466 THREE_HUNDRED»
2467 four
2468 five
2469 SIX
2470 "
2471 .unindent(),
2472 final_index_text: "
2473 one
2474 TWO_HUNDRED
2475 THREE_HUNDRED
2476 four
2477 five
2478 "
2479 .unindent(),
2480 },
2481 Example {
2482 name: "uncommitted hunk strictly contains unstaged hunks",
2483 head_text: "
2484 one
2485 two
2486 three
2487 four
2488 five
2489 six
2490 seven
2491 "
2492 .unindent(),
2493 index_text: "
2494 one
2495 TWO
2496 THREE
2497 FOUR
2498 FIVE
2499 SIX
2500 seven
2501 "
2502 .unindent(),
2503 buffer_marked_text: "
2504 one
2505 TWO
2506 «THREE_HUNDRED
2507 FOUR
2508 FIVE_HUNDRED»
2509 SIX
2510 seven
2511 "
2512 .unindent(),
2513 final_index_text: "
2514 one
2515 TWO
2516 THREE_HUNDRED
2517 FOUR
2518 FIVE_HUNDRED
2519 SIX
2520 seven
2521 "
2522 .unindent(),
2523 },
2524 Example {
2525 name: "uncommitted deletion hunk",
2526 head_text: "
2527 one
2528 two
2529 three
2530 four
2531 five
2532 "
2533 .unindent(),
2534 index_text: "
2535 one
2536 two
2537 three
2538 four
2539 five
2540 "
2541 .unindent(),
2542 buffer_marked_text: "
2543 one
2544 ˇfive
2545 "
2546 .unindent(),
2547 final_index_text: "
2548 one
2549 five
2550 "
2551 .unindent(),
2552 },
2553 Example {
2554 name: "one unstaged hunk that contains two uncommitted hunks",
2555 head_text: "
2556 one
2557 two
2558
2559 three
2560 four
2561 "
2562 .unindent(),
2563 index_text: "
2564 one
2565 two
2566 three
2567 four
2568 "
2569 .unindent(),
2570 buffer_marked_text: "
2571 «one
2572
2573 three // modified
2574 four»
2575 "
2576 .unindent(),
2577 final_index_text: "
2578 one
2579
2580 three // modified
2581 four
2582 "
2583 .unindent(),
2584 },
2585 Example {
2586 name: "one uncommitted hunk that contains two unstaged hunks",
2587 head_text: "
2588 one
2589 two
2590 three
2591 four
2592 five
2593 "
2594 .unindent(),
2595 index_text: "
2596 ZERO
2597 one
2598 TWO
2599 THREE
2600 FOUR
2601 five
2602 "
2603 .unindent(),
2604 buffer_marked_text: "
2605 «one
2606 TWO_HUNDRED
2607 THREE
2608 FOUR_HUNDRED
2609 five»
2610 "
2611 .unindent(),
2612 final_index_text: "
2613 ZERO
2614 one
2615 TWO_HUNDRED
2616 THREE
2617 FOUR_HUNDRED
2618 five
2619 "
2620 .unindent(),
2621 },
2622 ];
2623
2624 for example in table {
2625 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2626 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2627 let hunk_range =
2628 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2629
2630 let unstaged_diff =
2631 cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2632
2633 let uncommitted_diff = cx.new(|cx| {
2634 let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2635 diff.set_secondary_diff(unstaged_diff);
2636 diff
2637 });
2638
2639 uncommitted_diff.update(cx, |diff, cx| {
2640 let hunks = diff
2641 .snapshot(cx)
2642 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2643 .collect::<Vec<_>>();
2644 for hunk in &hunks {
2645 assert_ne!(
2646 hunk.secondary_status,
2647 DiffHunkSecondaryStatus::NoSecondaryHunk
2648 )
2649 }
2650
2651 let new_index_text = diff
2652 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2653 .unwrap()
2654 .to_string();
2655
2656 let hunks = diff
2657 .snapshot(cx)
2658 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2659 .collect::<Vec<_>>();
2660 for hunk in &hunks {
2661 assert_eq!(
2662 hunk.secondary_status,
2663 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2664 )
2665 }
2666
2667 pretty_assertions::assert_eq!(
2668 new_index_text,
2669 example.final_index_text,
2670 "example: {}",
2671 example.name
2672 );
2673 });
2674 }
2675 }
2676
2677 #[gpui::test]
2678 async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2679 // This test reproduces a crash where staging all hunks would cause an underflow
2680 // when there's one large unstaged hunk containing multiple uncommitted hunks.
2681 let head_text = "
2682 aaa
2683 bbb
2684 ccc
2685 ddd
2686 eee
2687 fff
2688 ggg
2689 hhh
2690 iii
2691 jjj
2692 kkk
2693 lll
2694 "
2695 .unindent();
2696
2697 let index_text = "
2698 aaa
2699 bbb
2700 CCC-index
2701 DDD-index
2702 EEE-index
2703 FFF-index
2704 GGG-index
2705 HHH-index
2706 III-index
2707 JJJ-index
2708 kkk
2709 lll
2710 "
2711 .unindent();
2712
2713 let buffer_text = "
2714 aaa
2715 bbb
2716 ccc-modified
2717 ddd
2718 eee-modified
2719 fff
2720 ggg
2721 hhh-modified
2722 iii
2723 jjj
2724 kkk
2725 lll
2726 "
2727 .unindent();
2728
2729 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2730
2731 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2732 let uncommitted_diff = cx.new(|cx| {
2733 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2734 diff.set_secondary_diff(unstaged_diff);
2735 diff
2736 });
2737
2738 uncommitted_diff.update(cx, |diff, cx| {
2739 diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2740 });
2741 }
2742
2743 #[gpui::test]
2744 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2745 let head_text = "
2746 one
2747 two
2748 three
2749 "
2750 .unindent();
2751 let index_text = head_text.clone();
2752 let buffer_text = "
2753 one
2754 three
2755 "
2756 .unindent();
2757
2758 let buffer = Buffer::new(
2759 ReplicaId::LOCAL,
2760 BufferId::new(1).unwrap(),
2761 buffer_text.clone(),
2762 );
2763 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2764 let uncommitted_diff = cx.new(|cx| {
2765 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2766 diff.set_secondary_diff(unstaged_diff.clone());
2767 diff
2768 });
2769
2770 uncommitted_diff.update(cx, |diff, cx| {
2771 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2772
2773 let new_index_text = diff
2774 .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2775 .unwrap()
2776 .to_string();
2777 assert_eq!(new_index_text, buffer_text);
2778
2779 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2780 assert_eq!(
2781 hunk.secondary_status,
2782 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2783 );
2784
2785 let index_text = diff
2786 .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2787 .unwrap()
2788 .to_string();
2789 assert_eq!(index_text, head_text);
2790
2791 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2792 // optimistically unstaged (fine, could also be HasSecondaryHunk)
2793 assert_eq!(
2794 hunk.secondary_status,
2795 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2796 );
2797 });
2798 }
2799
2800 #[gpui::test]
2801 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2802 let base_text = "
2803 zero
2804 one
2805 two
2806 three
2807 four
2808 five
2809 six
2810 seven
2811 eight
2812 nine
2813 "
2814 .unindent();
2815
2816 let buffer_text_1 = "
2817 one
2818 three
2819 four
2820 five
2821 SIX
2822 seven
2823 eight
2824 NINE
2825 "
2826 .unindent();
2827
2828 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2829
2830 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2831 let diff_1 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2832 let DiffChanged {
2833 changed_range,
2834 base_text_changed_range,
2835 extended_range: _,
2836 } = compare_hunks(
2837 &diff_1.inner.hunks,
2838 &empty_diff.inner.hunks,
2839 &buffer,
2840 &buffer,
2841 &diff_1.base_text(),
2842 &diff_1.base_text(),
2843 );
2844 let range = changed_range.unwrap();
2845 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2846 let base_text_range = base_text_changed_range.unwrap();
2847 assert_eq!(
2848 base_text_range.to_point(diff_1.base_text()),
2849 Point::new(0, 0)..Point::new(10, 0)
2850 );
2851
2852 // Edit does affects the diff because it recalculates word diffs.
2853 buffer.edit_via_marked_text(
2854 &"
2855 one
2856 three
2857 four
2858 five
2859 «SIX.5»
2860 seven
2861 eight
2862 NINE
2863 "
2864 .unindent(),
2865 );
2866 let diff_2 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2867 let DiffChanged {
2868 changed_range,
2869 base_text_changed_range,
2870 extended_range: _,
2871 } = compare_hunks(
2872 &diff_2.inner.hunks,
2873 &diff_1.inner.hunks,
2874 &buffer,
2875 &buffer,
2876 diff_2.base_text(),
2877 diff_2.base_text(),
2878 );
2879 assert_eq!(
2880 changed_range.unwrap().to_point(&buffer),
2881 Point::new(4, 0)..Point::new(5, 0),
2882 );
2883 assert_eq!(
2884 base_text_changed_range
2885 .unwrap()
2886 .to_point(diff_2.base_text()),
2887 Point::new(6, 0)..Point::new(7, 0),
2888 );
2889
2890 // Edit turns a deletion hunk into a modification.
2891 buffer.edit_via_marked_text(
2892 &"
2893 one
2894 «THREE»
2895 four
2896 five
2897 SIX.5
2898 seven
2899 eight
2900 NINE
2901 "
2902 .unindent(),
2903 );
2904 let diff_3 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2905 let DiffChanged {
2906 changed_range,
2907 base_text_changed_range,
2908 extended_range: _,
2909 } = compare_hunks(
2910 &diff_3.inner.hunks,
2911 &diff_2.inner.hunks,
2912 &buffer,
2913 &buffer,
2914 diff_3.base_text(),
2915 diff_3.base_text(),
2916 );
2917 let range = changed_range.unwrap();
2918 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2919 let base_text_range = base_text_changed_range.unwrap();
2920 assert_eq!(
2921 base_text_range.to_point(diff_3.base_text()),
2922 Point::new(2, 0)..Point::new(4, 0)
2923 );
2924
2925 // Edit turns a modification hunk into a deletion.
2926 buffer.edit_via_marked_text(
2927 &"
2928 one
2929 THREE
2930 four
2931 five«»
2932 seven
2933 eight
2934 NINE
2935 "
2936 .unindent(),
2937 );
2938 let diff_4 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2939 let DiffChanged {
2940 changed_range,
2941 base_text_changed_range,
2942 extended_range: _,
2943 } = compare_hunks(
2944 &diff_4.inner.hunks,
2945 &diff_3.inner.hunks,
2946 &buffer,
2947 &buffer,
2948 diff_4.base_text(),
2949 diff_4.base_text(),
2950 );
2951 let range = changed_range.unwrap();
2952 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2953 let base_text_range = base_text_changed_range.unwrap();
2954 assert_eq!(
2955 base_text_range.to_point(diff_4.base_text()),
2956 Point::new(6, 0)..Point::new(7, 0)
2957 );
2958
2959 // Edit introduces a new insertion hunk.
2960 buffer.edit_via_marked_text(
2961 &"
2962 one
2963 THREE
2964 four«
2965 FOUR.5
2966 »five
2967 seven
2968 eight
2969 NINE
2970 "
2971 .unindent(),
2972 );
2973 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2974 let DiffChanged {
2975 changed_range,
2976 base_text_changed_range,
2977 extended_range: _,
2978 } = compare_hunks(
2979 &diff_5.inner.hunks,
2980 &diff_4.inner.hunks,
2981 &buffer,
2982 &buffer,
2983 diff_5.base_text(),
2984 diff_5.base_text(),
2985 );
2986 let range = changed_range.unwrap();
2987 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2988 let base_text_range = base_text_changed_range.unwrap();
2989 assert_eq!(
2990 base_text_range.to_point(diff_5.base_text()),
2991 Point::new(5, 0)..Point::new(5, 0)
2992 );
2993
2994 // Edit removes a hunk.
2995 buffer.edit_via_marked_text(
2996 &"
2997 one
2998 THREE
2999 four
3000 FOUR.5
3001 five
3002 seven
3003 eight
3004 «nine»
3005 "
3006 .unindent(),
3007 );
3008 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
3009 let DiffChanged {
3010 changed_range,
3011 base_text_changed_range,
3012 extended_range: _,
3013 } = compare_hunks(
3014 &diff_6.inner.hunks,
3015 &diff_5.inner.hunks,
3016 &buffer,
3017 &buffer,
3018 diff_6.base_text(),
3019 diff_6.base_text(),
3020 );
3021 let range = changed_range.unwrap();
3022 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
3023 let base_text_range = base_text_changed_range.unwrap();
3024 assert_eq!(
3025 base_text_range.to_point(diff_6.base_text()),
3026 Point::new(9, 0)..Point::new(10, 0)
3027 );
3028
3029 buffer.edit_via_marked_text(
3030 &"
3031 one
3032 THREE
3033 four«»
3034 five
3035 seven
3036 eight
3037 «NINE»
3038 "
3039 .unindent(),
3040 );
3041
3042 let diff_7 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
3043 let DiffChanged {
3044 changed_range,
3045 base_text_changed_range,
3046 extended_range: _,
3047 } = compare_hunks(
3048 &diff_7.inner.hunks,
3049 &diff_6.inner.hunks,
3050 &buffer,
3051 &buffer,
3052 diff_7.base_text(),
3053 diff_7.base_text(),
3054 );
3055 let range = changed_range.unwrap();
3056 assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
3057 let base_text_range = base_text_changed_range.unwrap();
3058 assert_eq!(
3059 base_text_range.to_point(diff_7.base_text()),
3060 Point::new(5, 0)..Point::new(10, 0)
3061 );
3062
3063 buffer.edit_via_marked_text(
3064 &"
3065 one
3066 THREE
3067 four
3068 five«»seven
3069 eight
3070 NINE
3071 "
3072 .unindent(),
3073 );
3074
3075 let diff_8 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
3076 let DiffChanged {
3077 changed_range,
3078 base_text_changed_range,
3079 extended_range: _,
3080 } = compare_hunks(
3081 &diff_8.inner.hunks,
3082 &diff_7.inner.hunks,
3083 &buffer,
3084 &buffer,
3085 diff_8.base_text(),
3086 diff_8.base_text(),
3087 );
3088 let range = changed_range.unwrap();
3089 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
3090 let base_text_range = base_text_changed_range.unwrap();
3091 assert_eq!(
3092 base_text_range.to_point(diff_8.base_text()),
3093 Point::new(5, 0)..Point::new(8, 0)
3094 );
3095 }
3096
3097 #[gpui::test(iterations = 100)]
3098 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
3099 fn gen_line(rng: &mut StdRng) -> String {
3100 if rng.random_bool(0.2) {
3101 "\n".to_owned()
3102 } else {
3103 let c = rng.random_range('A'..='Z');
3104 format!("{c}{c}{c}\n")
3105 }
3106 }
3107
3108 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
3109 let mut old_lines = {
3110 let mut old_lines = Vec::new();
3111 let old_lines_iter = head.lines();
3112 for line in old_lines_iter {
3113 assert!(!line.ends_with("\n"));
3114 old_lines.push(line.to_owned());
3115 }
3116 if old_lines.last().is_some_and(|line| line.is_empty()) {
3117 old_lines.pop();
3118 }
3119 old_lines.into_iter()
3120 };
3121 let mut result = String::new();
3122 let unchanged_count = rng.random_range(0..=old_lines.len());
3123 result +=
3124 &old_lines
3125 .by_ref()
3126 .take(unchanged_count)
3127 .fold(String::new(), |mut s, line| {
3128 writeln!(&mut s, "{line}").unwrap();
3129 s
3130 });
3131 while old_lines.len() > 0 {
3132 let deleted_count = rng.random_range(0..=old_lines.len());
3133 let _advance = old_lines
3134 .by_ref()
3135 .take(deleted_count)
3136 .map(|line| line.len() + 1)
3137 .sum::<usize>();
3138 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
3139 let added_count = rng.random_range(minimum_added..=5);
3140 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
3141 result += &addition;
3142
3143 if old_lines.len() > 0 {
3144 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
3145 if blank_lines == old_lines.len() {
3146 break;
3147 };
3148 let unchanged_count =
3149 rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
3150 result += &old_lines.by_ref().take(unchanged_count).fold(
3151 String::new(),
3152 |mut s, line| {
3153 writeln!(&mut s, "{line}").unwrap();
3154 s
3155 },
3156 );
3157 }
3158 }
3159 result
3160 }
3161
3162 fn uncommitted_diff(
3163 working_copy: &language::BufferSnapshot,
3164 index_text: &Rope,
3165 head_text: String,
3166 cx: &mut TestAppContext,
3167 ) -> Entity<BufferDiff> {
3168 let secondary = cx.new(|cx| {
3169 BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
3170 });
3171 cx.new(|cx| {
3172 let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
3173 diff.secondary_diff = Some(secondary);
3174 diff
3175 })
3176 }
3177
3178 let operations = std::env::var("OPERATIONS")
3179 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3180 .unwrap_or(10);
3181
3182 let rng = &mut rng;
3183 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
3184 writeln!(&mut s, "{c}{c}{c}").unwrap();
3185 s
3186 });
3187 let working_copy = gen_working_copy(rng, &head_text);
3188 let working_copy = cx.new(|cx| {
3189 language::Buffer::local_normalized(
3190 Rope::from(working_copy.as_str()),
3191 text::LineEnding::default(),
3192 cx,
3193 )
3194 });
3195 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
3196 let mut index_text = if rng.random() {
3197 Rope::from(head_text.as_str())
3198 } else {
3199 working_copy.as_rope().clone()
3200 };
3201
3202 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3203 let mut hunks = diff.update(cx, |diff, cx| {
3204 diff.snapshot(cx)
3205 .hunks_intersecting_range(
3206 Anchor::min_max_range_for_buffer(diff.buffer_id),
3207 &working_copy,
3208 )
3209 .collect::<Vec<_>>()
3210 });
3211 if hunks.is_empty() {
3212 return;
3213 }
3214
3215 for _ in 0..operations {
3216 let i = rng.random_range(0..hunks.len());
3217 let hunk = &mut hunks[i];
3218 let hunk_to_change = hunk.clone();
3219 let stage = match hunk.secondary_status {
3220 DiffHunkSecondaryStatus::HasSecondaryHunk => {
3221 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
3222 true
3223 }
3224 DiffHunkSecondaryStatus::NoSecondaryHunk => {
3225 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
3226 false
3227 }
3228 _ => unreachable!(),
3229 };
3230
3231 index_text = diff.update(cx, |diff, cx| {
3232 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
3233 .unwrap()
3234 });
3235
3236 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3237 let found_hunks = diff.update(cx, |diff, cx| {
3238 diff.snapshot(cx)
3239 .hunks_intersecting_range(
3240 Anchor::min_max_range_for_buffer(diff.buffer_id),
3241 &working_copy,
3242 )
3243 .collect::<Vec<_>>()
3244 });
3245 assert_eq!(hunks.len(), found_hunks.len());
3246
3247 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
3248 assert_eq!(
3249 expected_hunk.buffer_range.to_point(&working_copy),
3250 found_hunk.buffer_range.to_point(&working_copy)
3251 );
3252 assert_eq!(
3253 expected_hunk.diff_base_byte_range,
3254 found_hunk.diff_base_byte_range
3255 );
3256 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
3257 }
3258 hunks = found_hunks;
3259 }
3260 }
3261
3262 #[gpui::test]
3263 async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
3264 let base_text = "
3265 one
3266 two
3267 three
3268 four
3269 five
3270 six
3271 "
3272 .unindent();
3273 let buffer_text = "
3274 one
3275 TWO
3276 three
3277 four
3278 FIVE
3279 six
3280 "
3281 .unindent();
3282 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3283 let diff = cx.new(|cx| {
3284 BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
3285 });
3286 cx.run_until_parked();
3287 let (tx, rx) = mpsc::channel();
3288 let subscription =
3289 cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
3290
3291 let snapshot = buffer.update(cx, |buffer, cx| {
3292 buffer.set_text(
3293 "
3294 ONE
3295 TWO
3296 THREE
3297 FOUR
3298 FIVE
3299 SIX
3300 "
3301 .unindent(),
3302 cx,
3303 );
3304 buffer.text_snapshot()
3305 });
3306 let update = diff
3307 .update(cx, |diff, cx| {
3308 diff.update_diff(
3309 snapshot.clone(),
3310 Some(base_text.as_str().into()),
3311 None,
3312 None,
3313 cx,
3314 )
3315 })
3316 .await;
3317 diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
3318 .await;
3319 cx.run_until_parked();
3320 drop(subscription);
3321 let events = rx.into_iter().collect::<Vec<_>>();
3322 match events.as_slice() {
3323 [
3324 BufferDiffEvent::DiffChanged(DiffChanged {
3325 changed_range: _,
3326 base_text_changed_range,
3327 extended_range: _,
3328 }),
3329 ] => {
3330 // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3331 // assert_eq!(
3332 // *changed_range,
3333 // Some(Anchor::min_max_range_for_buffer(
3334 // buffer.read_with(cx, |buffer, _| buffer.remote_id())
3335 // ))
3336 // );
3337 assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3338 }
3339 _ => panic!("unexpected events: {:?}", events),
3340 }
3341 }
3342
3343 #[gpui::test]
3344 async fn test_extended_range(cx: &mut TestAppContext) {
3345 let base_text = "
3346 aaa
3347 bbb
3348
3349
3350
3351
3352
3353 ccc
3354 ddd
3355 "
3356 .unindent();
3357
3358 let buffer_text = "
3359 aaa
3360 bbb
3361
3362
3363
3364
3365
3366 CCC
3367 ddd
3368 "
3369 .unindent();
3370
3371 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3372 let old_buffer = buffer.snapshot().clone();
3373 let diff_a = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
3374
3375 buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3376 let diff_b = BufferDiffSnapshot::new_sync(&buffer, base_text, cx);
3377
3378 let DiffChanged {
3379 changed_range,
3380 base_text_changed_range: _,
3381 extended_range,
3382 } = compare_hunks(
3383 &diff_b.inner.hunks,
3384 &diff_a.inner.hunks,
3385 &old_buffer,
3386 &buffer,
3387 &diff_a.base_text(),
3388 &diff_a.base_text(),
3389 );
3390
3391 let changed_range = changed_range.unwrap();
3392 assert_eq!(
3393 changed_range.to_point(&buffer),
3394 Point::new(7, 0)..Point::new(9, 0),
3395 "changed_range should span from old hunk position to new hunk end"
3396 );
3397
3398 let extended_range = extended_range.unwrap();
3399 assert_eq!(
3400 extended_range.start.to_point(&buffer),
3401 Point::new(1, 3),
3402 "extended_range.start should extend to include the edit outside changed_range"
3403 );
3404 assert_eq!(
3405 extended_range.end.to_point(&buffer),
3406 Point::new(9, 0),
3407 "extended_range.end should collapse to changed_range.end when no edits in end margin"
3408 );
3409
3410 let base_text_2 = "
3411 one
3412 two
3413 three
3414 four
3415 five
3416 six
3417 seven
3418 eight
3419 "
3420 .unindent();
3421
3422 let buffer_text_2 = "
3423 ONE
3424 two
3425 THREE
3426 four
3427 FIVE
3428 six
3429 SEVEN
3430 eight
3431 "
3432 .unindent();
3433
3434 let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3435 let old_buffer_2 = buffer_2.snapshot().clone();
3436 let diff_2a = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2.clone(), cx);
3437
3438 buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3439 let diff_2b = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2, cx);
3440
3441 let DiffChanged {
3442 changed_range,
3443 base_text_changed_range: _,
3444 extended_range,
3445 } = compare_hunks(
3446 &diff_2b.inner.hunks,
3447 &diff_2a.inner.hunks,
3448 &old_buffer_2,
3449 &buffer_2,
3450 &diff_2a.base_text(),
3451 &diff_2a.base_text(),
3452 );
3453
3454 let changed_range = changed_range.unwrap();
3455 assert_eq!(
3456 changed_range.to_point(&buffer_2),
3457 Point::new(4, 0)..Point::new(5, 0),
3458 "changed_range should be just the hunk that changed (FIVE)"
3459 );
3460
3461 let extended_range = extended_range.unwrap();
3462 assert_eq!(
3463 extended_range.to_point(&buffer_2),
3464 Point::new(4, 0)..Point::new(5, 0),
3465 "extended_range should equal changed_range when edit is within the hunk"
3466 );
3467 }
3468
3469 #[gpui::test]
3470 async fn test_buffer_diff_compare_with_base_text_change(_cx: &mut TestAppContext) {
3471 // Use a shared base text buffer so that anchors from old and new snapshots
3472 // share the same remote_id and resolve correctly across versions.
3473 let initial_base = "aaa\nbbb\nccc\nddd\neee\n";
3474 let mut base_text_buffer = Buffer::new(
3475 ReplicaId::LOCAL,
3476 BufferId::new(99).unwrap(),
3477 initial_base.to_string(),
3478 );
3479
3480 // --- Scenario 1: Base text gains a line, producing a new deletion hunk ---
3481 //
3482 // Buffer has a modification (ccc → CCC). When the base text gains
3483 // a new line "XXX" after "aaa", the diff now also contains a
3484 // deletion for that line, and the modification hunk shifts in the
3485 // base text.
3486 let buffer_text_1 = "aaa\nbbb\nCCC\nddd\neee\n";
3487 let buffer = Buffer::new(
3488 ReplicaId::LOCAL,
3489 BufferId::new(1).unwrap(),
3490 buffer_text_1.to_string(),
3491 );
3492
3493 let old_base_snapshot_1 = base_text_buffer.snapshot().clone();
3494 let old_hunks_1 = compute_hunks(
3495 Some((Arc::from(initial_base), Rope::from(initial_base))),
3496 buffer.snapshot(),
3497 None,
3498 );
3499
3500 // Insert "XXX\n" after "aaa\n" in the base text.
3501 base_text_buffer.edit([(4..4, "XXX\n")]);
3502 let new_base_str_1: Arc<str> = Arc::from(base_text_buffer.text().as_str());
3503 let new_base_snapshot_1 = base_text_buffer.snapshot();
3504
3505 let new_hunks_1 = compute_hunks(
3506 Some((new_base_str_1.clone(), Rope::from(new_base_str_1.as_ref()))),
3507 buffer.snapshot(),
3508 None,
3509 );
3510
3511 let DiffChanged {
3512 changed_range,
3513 base_text_changed_range,
3514 extended_range: _,
3515 } = compare_hunks(
3516 &new_hunks_1,
3517 &old_hunks_1,
3518 &buffer.snapshot(),
3519 &buffer.snapshot(),
3520 &old_base_snapshot_1,
3521 &new_base_snapshot_1,
3522 );
3523
3524 // The new deletion hunk (XXX) starts at buffer row 1 and the
3525 // modification hunk (ccc → CCC) now has a different
3526 // diff_base_byte_range, so the changed range spans both.
3527 let range = changed_range.unwrap();
3528 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(3, 0),);
3529 let base_range = base_text_changed_range.unwrap();
3530 assert_eq!(
3531 base_range.to_point(&new_base_snapshot_1),
3532 Point::new(1, 0)..Point::new(4, 0),
3533 );
3534
3535 // --- Scenario 2: Base text changes to match the buffer (hunk disappears) ---
3536 //
3537 // Start fresh with a simple base text.
3538 let simple_base = "one\ntwo\nthree\n";
3539 let mut base_buf_2 = Buffer::new(
3540 ReplicaId::LOCAL,
3541 BufferId::new(100).unwrap(),
3542 simple_base.to_string(),
3543 );
3544
3545 let buffer_text_2 = "one\nTWO\nthree\n";
3546 let buffer_2 = Buffer::new(
3547 ReplicaId::LOCAL,
3548 BufferId::new(2).unwrap(),
3549 buffer_text_2.to_string(),
3550 );
3551
3552 let old_base_snapshot_2 = base_buf_2.snapshot().clone();
3553 let old_hunks_2 = compute_hunks(
3554 Some((Arc::from(simple_base), Rope::from(simple_base))),
3555 buffer_2.snapshot(),
3556 None,
3557 );
3558
3559 // The base text is edited so "two" becomes "TWO", now matching the buffer.
3560 base_buf_2.edit([(4..7, "TWO")]);
3561 let new_base_str_2: Arc<str> = Arc::from(base_buf_2.text().as_str());
3562 let new_base_snapshot_2 = base_buf_2.snapshot();
3563
3564 let new_hunks_2 = compute_hunks(
3565 Some((new_base_str_2.clone(), Rope::from(new_base_str_2.as_ref()))),
3566 buffer_2.snapshot(),
3567 None,
3568 );
3569
3570 let DiffChanged {
3571 changed_range,
3572 base_text_changed_range,
3573 extended_range: _,
3574 } = compare_hunks(
3575 &new_hunks_2,
3576 &old_hunks_2,
3577 &buffer_2.snapshot(),
3578 &buffer_2.snapshot(),
3579 &old_base_snapshot_2,
3580 &new_base_snapshot_2,
3581 );
3582
3583 // The old modification hunk (two → TWO) is now gone because the
3584 // base text matches the buffer. The changed range covers where the
3585 // old hunk used to be.
3586 let range = changed_range.unwrap();
3587 assert_eq!(
3588 range.to_point(&buffer_2),
3589 Point::new(1, 0)..Point::new(2, 0),
3590 );
3591 let base_range = base_text_changed_range.unwrap();
3592 // The old hunk's diff_base_byte_range covered "two\n" (bytes 4..8).
3593 // anchor_after(4) is right-biased at the start of the deleted "two",
3594 // so after the edit replacing "two" with "TWO" it resolves past the
3595 // insertion to Point(1, 3).
3596 assert_eq!(
3597 base_range.to_point(&new_base_snapshot_2),
3598 Point::new(1, 3)..Point::new(2, 0),
3599 );
3600
3601 // --- Scenario 3: Base text edit changes one hunk but not another ---
3602 //
3603 // Two modification hunks exist. Only one of them is resolved by
3604 // the base text change; the other remains identical.
3605 let base_3 = "aaa\nbbb\nccc\nddd\neee\n";
3606 let mut base_buf_3 = Buffer::new(
3607 ReplicaId::LOCAL,
3608 BufferId::new(101).unwrap(),
3609 base_3.to_string(),
3610 );
3611
3612 let buffer_text_3 = "aaa\nBBB\nccc\nDDD\neee\n";
3613 let buffer_3 = Buffer::new(
3614 ReplicaId::LOCAL,
3615 BufferId::new(3).unwrap(),
3616 buffer_text_3.to_string(),
3617 );
3618
3619 let old_base_snapshot_3 = base_buf_3.snapshot().clone();
3620 let old_hunks_3 = compute_hunks(
3621 Some((Arc::from(base_3), Rope::from(base_3))),
3622 buffer_3.snapshot(),
3623 None,
3624 );
3625
3626 // Change "ddd" to "DDD" in the base text so that hunk disappears,
3627 // but "bbb" stays, so its hunk remains.
3628 base_buf_3.edit([(12..15, "DDD")]);
3629 let new_base_str_3: Arc<str> = Arc::from(base_buf_3.text().as_str());
3630 let new_base_snapshot_3 = base_buf_3.snapshot();
3631
3632 let new_hunks_3 = compute_hunks(
3633 Some((new_base_str_3.clone(), Rope::from(new_base_str_3.as_ref()))),
3634 buffer_3.snapshot(),
3635 None,
3636 );
3637
3638 let DiffChanged {
3639 changed_range,
3640 base_text_changed_range,
3641 extended_range: _,
3642 } = compare_hunks(
3643 &new_hunks_3,
3644 &old_hunks_3,
3645 &buffer_3.snapshot(),
3646 &buffer_3.snapshot(),
3647 &old_base_snapshot_3,
3648 &new_base_snapshot_3,
3649 );
3650
3651 // Only the second hunk (ddd → DDD) disappeared; the first hunk
3652 // (bbb → BBB) is unchanged, so the changed range covers only line 3.
3653 let range = changed_range.unwrap();
3654 assert_eq!(
3655 range.to_point(&buffer_3),
3656 Point::new(3, 0)..Point::new(4, 0),
3657 );
3658 let base_range = base_text_changed_range.unwrap();
3659 // anchor_after(12) is right-biased at the start of deleted "ddd",
3660 // so after the edit replacing "ddd" with "DDD" it resolves past
3661 // the insertion to Point(3, 3).
3662 assert_eq!(
3663 base_range.to_point(&new_base_snapshot_3),
3664 Point::new(3, 3)..Point::new(4, 0),
3665 );
3666
3667 // --- Scenario 4: Both buffer and base text change simultaneously ---
3668 //
3669 // The buffer gains an edit that introduces a new hunk while the
3670 // base text also changes.
3671 let base_4 = "alpha\nbeta\ngamma\ndelta\n";
3672 let mut base_buf_4 = Buffer::new(
3673 ReplicaId::LOCAL,
3674 BufferId::new(102).unwrap(),
3675 base_4.to_string(),
3676 );
3677
3678 let buffer_text_4 = "alpha\nBETA\ngamma\ndelta\n";
3679 let mut buffer_4 = Buffer::new(
3680 ReplicaId::LOCAL,
3681 BufferId::new(4).unwrap(),
3682 buffer_text_4.to_string(),
3683 );
3684
3685 let old_base_snapshot_4 = base_buf_4.snapshot().clone();
3686 let old_buffer_snapshot_4 = buffer_4.snapshot().clone();
3687 let old_hunks_4 = compute_hunks(
3688 Some((Arc::from(base_4), Rope::from(base_4))),
3689 buffer_4.snapshot(),
3690 None,
3691 );
3692
3693 // Edit the buffer: change "delta" to "DELTA" (new modification hunk).
3694 buffer_4.edit_via_marked_text(
3695 &"
3696 alpha
3697 BETA
3698 gamma
3699 «DELTA»
3700 "
3701 .unindent(),
3702 );
3703
3704 // Edit the base text: change "beta" to "BETA" (resolves that hunk).
3705 base_buf_4.edit([(6..10, "BETA")]);
3706 let new_base_str_4: Arc<str> = Arc::from(base_buf_4.text().as_str());
3707 let new_base_snapshot_4 = base_buf_4.snapshot();
3708
3709 let new_hunks_4 = compute_hunks(
3710 Some((new_base_str_4.clone(), Rope::from(new_base_str_4.as_ref()))),
3711 buffer_4.snapshot(),
3712 None,
3713 );
3714
3715 let DiffChanged {
3716 changed_range,
3717 base_text_changed_range,
3718 extended_range: _,
3719 } = compare_hunks(
3720 &new_hunks_4,
3721 &old_hunks_4,
3722 &old_buffer_snapshot_4,
3723 &buffer_4.snapshot(),
3724 &old_base_snapshot_4,
3725 &new_base_snapshot_4,
3726 );
3727
3728 // The old BETA hunk (line 1) is gone and a new DELTA hunk (line 3)
3729 // appeared, so the changed range spans from line 1 through line 4.
3730 let range = changed_range.unwrap();
3731 assert_eq!(
3732 range.to_point(&buffer_4),
3733 Point::new(1, 0)..Point::new(4, 0),
3734 );
3735 let base_range = base_text_changed_range.unwrap();
3736 // The old BETA hunk's base range started at byte 6 ("beta"). After
3737 // the base text edit replacing "beta" with "BETA", anchor_after(6)
3738 // resolves past the insertion to Point(1, 4).
3739 assert_eq!(
3740 base_range.to_point(&new_base_snapshot_4),
3741 Point::new(1, 4)..Point::new(4, 0),
3742 );
3743 }
3744
3745 #[gpui::test(iterations = 100)]
3746 async fn test_patch_for_range_random(cx: &mut TestAppContext, mut rng: StdRng) {
3747 fn gen_line(rng: &mut StdRng) -> String {
3748 if rng.random_bool(0.2) {
3749 "\n".to_owned()
3750 } else {
3751 let c = rng.random_range('A'..='Z');
3752 format!("{c}{c}{c}\n")
3753 }
3754 }
3755
3756 fn gen_text(rng: &mut StdRng, line_count: usize) -> String {
3757 (0..line_count).map(|_| gen_line(rng)).collect()
3758 }
3759
3760 fn gen_edits_from(rng: &mut StdRng, base: &str) -> String {
3761 let mut old_lines: Vec<&str> = base.lines().collect();
3762 let mut result = String::new();
3763
3764 while !old_lines.is_empty() {
3765 let unchanged_count = rng.random_range(0..=old_lines.len());
3766 for _ in 0..unchanged_count {
3767 if old_lines.is_empty() {
3768 break;
3769 }
3770 result.push_str(old_lines.remove(0));
3771 result.push('\n');
3772 }
3773
3774 if old_lines.is_empty() {
3775 break;
3776 }
3777
3778 let deleted_count = rng.random_range(0..=old_lines.len().min(3));
3779 for _ in 0..deleted_count {
3780 if old_lines.is_empty() {
3781 break;
3782 }
3783 old_lines.remove(0);
3784 }
3785
3786 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
3787 let added_count = rng.random_range(minimum_added..=3);
3788 for _ in 0..added_count {
3789 result.push_str(&gen_line(rng));
3790 }
3791 }
3792
3793 result
3794 }
3795
3796 fn random_point_in_text(rng: &mut StdRng, lines: &[&str]) -> Point {
3797 if lines.is_empty() {
3798 return Point::zero();
3799 }
3800 let row = rng.random_range(0..lines.len() as u32);
3801 let line = lines[row as usize];
3802 let col = if line.is_empty() {
3803 0
3804 } else {
3805 rng.random_range(0..=line.len() as u32)
3806 };
3807 Point::new(row, col)
3808 }
3809
3810 fn random_range_in_text(rng: &mut StdRng, lines: &[&str]) -> RangeInclusive<Point> {
3811 let start = random_point_in_text(rng, lines);
3812 let end = random_point_in_text(rng, lines);
3813 if start <= end {
3814 start..=end
3815 } else {
3816 end..=start
3817 }
3818 }
3819
3820 fn points_in_range(range: &RangeInclusive<Point>, lines: &[&str]) -> Vec<Point> {
3821 let mut points = Vec::new();
3822 for row in range.start().row..=range.end().row {
3823 if row as usize >= lines.len() {
3824 points.push(Point::new(row, 0));
3825 continue;
3826 }
3827 let line = lines[row as usize];
3828 let start_col = if row == range.start().row {
3829 range.start().column
3830 } else {
3831 0
3832 };
3833 let end_col = if row == range.end().row {
3834 range.end().column
3835 } else {
3836 line.len() as u32
3837 };
3838 for col in start_col..=end_col {
3839 points.push(Point::new(row, col));
3840 }
3841 }
3842 points
3843 }
3844
3845 let rng = &mut rng;
3846
3847 let line_count = rng.random_range(5..20);
3848 let base_text = gen_text(rng, line_count);
3849 let initial_buffer_text = gen_edits_from(rng, &base_text);
3850
3851 let mut buffer = Buffer::new(
3852 ReplicaId::LOCAL,
3853 BufferId::new(1).unwrap(),
3854 initial_buffer_text.clone(),
3855 );
3856
3857 let diff = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
3858
3859 let edit_count = rng.random_range(1..=5);
3860 for _ in 0..edit_count {
3861 let buffer_text = buffer.text();
3862 if buffer_text.is_empty() {
3863 buffer.edit([(0..0, gen_line(rng))]);
3864 } else {
3865 let lines: Vec<&str> = buffer_text.lines().collect();
3866 let start_row = rng.random_range(0..lines.len());
3867 let end_row = rng.random_range(start_row..=lines.len().min(start_row + 3));
3868
3869 let start_col = if start_row < lines.len() {
3870 rng.random_range(0..=lines[start_row].len())
3871 } else {
3872 0
3873 };
3874 let end_col = if end_row < lines.len() {
3875 rng.random_range(0..=lines[end_row].len())
3876 } else {
3877 0
3878 };
3879
3880 let start_offset = buffer
3881 .point_to_offset(Point::new(start_row as u32, start_col as u32))
3882 .min(buffer.len());
3883 let end_offset = buffer
3884 .point_to_offset(Point::new(end_row as u32, end_col as u32))
3885 .min(buffer.len());
3886
3887 let (start, end) = if start_offset <= end_offset {
3888 (start_offset, end_offset)
3889 } else {
3890 (end_offset, start_offset)
3891 };
3892
3893 let new_text = if rng.random_bool(0.3) {
3894 String::new()
3895 } else {
3896 let line_count = rng.random_range(0..=2);
3897 gen_text(rng, line_count)
3898 };
3899
3900 buffer.edit([(start..end, new_text)]);
3901 }
3902 }
3903
3904 let buffer_snapshot = buffer.snapshot();
3905
3906 let buffer_text = buffer_snapshot.text();
3907 let buffer_lines: Vec<&str> = buffer_text.lines().collect();
3908 let base_lines: Vec<&str> = base_text.lines().collect();
3909
3910 let test_count = 10;
3911 for _ in 0..test_count {
3912 let range = random_range_in_text(rng, &buffer_lines);
3913 let points = points_in_range(&range, &buffer_lines);
3914
3915 let optimized_patch = diff.patch_for_buffer_range(range.clone(), &buffer_snapshot);
3916 let naive_patch = diff.patch_for_buffer_range_naive(&buffer_snapshot);
3917
3918 for point in points {
3919 let optimized_edit = optimized_patch.edit_for_old_position(point);
3920 let naive_edit = naive_patch.edit_for_old_position(point);
3921
3922 assert_eq!(
3923 optimized_edit,
3924 naive_edit,
3925 "patch_for_buffer_range mismatch at point {:?} in range {:?}\nbase_text: {:?}\ninitial_buffer: {:?}\ncurrent_buffer: {:?}",
3926 point,
3927 range,
3928 base_text,
3929 initial_buffer_text,
3930 buffer_snapshot.text()
3931 );
3932 }
3933 }
3934
3935 for _ in 0..test_count {
3936 let range = random_range_in_text(rng, &base_lines);
3937 let points = points_in_range(&range, &base_lines);
3938
3939 let optimized_patch = diff.patch_for_base_text_range(range.clone(), &buffer_snapshot);
3940 let naive_patch = diff.patch_for_base_text_range_naive(&buffer_snapshot);
3941
3942 for point in points {
3943 let optimized_edit = optimized_patch.edit_for_old_position(point);
3944 let naive_edit = naive_patch.edit_for_old_position(point);
3945
3946 assert_eq!(
3947 optimized_edit,
3948 naive_edit,
3949 "patch_for_base_text_range mismatch at point {:?} in range {:?}\nbase_text: {:?}\ninitial_buffer: {:?}\ncurrent_buffer: {:?}",
3950 point,
3951 range,
3952 base_text,
3953 initial_buffer_text,
3954 buffer_snapshot.text()
3955 );
3956 }
3957 }
3958 }
3959}