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