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