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