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