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