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