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