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