1use futures::channel::oneshot;
2use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
3use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
4use language::{
5 Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry,
6 language_settings::language_settings, word_diff_ranges,
7};
8use rope::Rope;
9use std::{
10 cmp::Ordering,
11 future::Future,
12 iter,
13 ops::{Range, RangeInclusive},
14 sync::Arc,
15};
16use sum_tree::SumTree;
17use text::{
18 Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,
19};
20use util::ResultExt;
21
22pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
23
24pub struct BufferDiff {
25 pub buffer_id: BufferId,
26 inner: BufferDiffInner<Entity<language::Buffer>>,
27 secondary_diff: Option<Entity<BufferDiff>>,
28}
29
30#[derive(Clone)]
31pub struct BufferDiffSnapshot {
32 inner: BufferDiffInner<language::BufferSnapshot>,
33 secondary_diff: Option<Arc<BufferDiffSnapshot>>,
34}
35
36impl std::fmt::Debug for BufferDiffSnapshot {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("BufferDiffSnapshot")
39 .field("inner", &self.inner)
40 .field("secondary_diff", &self.secondary_diff)
41 .finish()
42 }
43}
44
45#[derive(Clone)]
46pub struct BufferDiffUpdate {
47 inner: BufferDiffInner<Arc<str>>,
48 buffer_snapshot: text::BufferSnapshot,
49 base_text_edits: Option<Diff>,
50 base_text_changed: bool,
51}
52
53#[derive(Clone)]
54struct BufferDiffInner<BaseText> {
55 hunks: SumTree<InternalDiffHunk>,
56 pending_hunks: SumTree<PendingHunk>,
57 base_text: BaseText,
58 base_text_exists: bool,
59 buffer_snapshot: text::BufferSnapshot,
60}
61
62impl<BaseText> BufferDiffInner<BaseText> {
63 fn buffer_version(&self) -> &clock::Global {
64 self.buffer_snapshot.version()
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
69pub struct DiffHunkStatus {
70 pub kind: DiffHunkStatusKind,
71 pub secondary: DiffHunkSecondaryStatus,
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum DiffHunkStatusKind {
76 Added,
77 Modified,
78 Deleted,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82/// Diff of Working Copy vs Index
83/// aka 'is this hunk staged or not'
84pub enum DiffHunkSecondaryStatus {
85 /// Unstaged
86 HasSecondaryHunk,
87 /// Partially staged
88 OverlapsWithSecondaryHunk,
89 /// Staged
90 NoSecondaryHunk,
91 /// We are unstaging
92 SecondaryHunkAdditionPending,
93 /// We are stagind
94 SecondaryHunkRemovalPending,
95}
96
97/// A diff hunk resolved to rows in the buffer.
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub struct DiffHunk {
100 /// The buffer range as points.
101 pub range: Range<Point>,
102 /// The range in the buffer to which this hunk corresponds.
103 pub buffer_range: Range<Anchor>,
104 /// The range in the buffer's diff base text to which this hunk corresponds.
105 pub diff_base_byte_range: Range<usize>,
106 pub secondary_status: DiffHunkSecondaryStatus,
107 // Anchors representing the word diff locations in the active buffer
108 pub buffer_word_diffs: Vec<Range<Anchor>>,
109 // Offsets relative to the start of the deleted diff that represent word diff locations
110 pub base_word_diffs: Vec<Range<usize>>,
111}
112
113/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
114#[derive(Debug, Clone, PartialEq, Eq)]
115struct InternalDiffHunk {
116 buffer_range: Range<Anchor>,
117 diff_base_byte_range: Range<usize>,
118 base_word_diffs: Vec<Range<usize>>,
119 buffer_word_diffs: Vec<Range<Anchor>>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq)]
123struct PendingHunk {
124 buffer_range: Range<Anchor>,
125 diff_base_byte_range: Range<usize>,
126 buffer_version: clock::Global,
127 new_status: DiffHunkSecondaryStatus,
128}
129
130#[derive(Debug, Clone)]
131pub struct DiffHunkSummary {
132 buffer_range: Range<Anchor>,
133 diff_base_byte_range: Range<usize>,
134}
135
136impl sum_tree::Item for InternalDiffHunk {
137 type Summary = DiffHunkSummary;
138
139 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
140 DiffHunkSummary {
141 buffer_range: self.buffer_range.clone(),
142 diff_base_byte_range: self.diff_base_byte_range.clone(),
143 }
144 }
145}
146
147impl sum_tree::Item for PendingHunk {
148 type Summary = DiffHunkSummary;
149
150 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
151 DiffHunkSummary {
152 buffer_range: self.buffer_range.clone(),
153 diff_base_byte_range: self.diff_base_byte_range.clone(),
154 }
155 }
156}
157
158impl sum_tree::Summary for DiffHunkSummary {
159 type Context<'a> = &'a text::BufferSnapshot;
160
161 fn zero(_cx: Self::Context<'_>) -> Self {
162 DiffHunkSummary {
163 buffer_range: Anchor::MIN..Anchor::MIN,
164 diff_base_byte_range: 0..0,
165 }
166 }
167
168 fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
169 self.buffer_range.start = *self
170 .buffer_range
171 .start
172 .min(&other.buffer_range.start, buffer);
173 self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
174
175 self.diff_base_byte_range.start = self
176 .diff_base_byte_range
177 .start
178 .min(other.diff_base_byte_range.start);
179 self.diff_base_byte_range.end = self
180 .diff_base_byte_range
181 .end
182 .max(other.diff_base_byte_range.end);
183 }
184}
185
186impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
187 fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
188 if self
189 .cmp(&cursor_location.buffer_range.start, buffer)
190 .is_lt()
191 {
192 Ordering::Less
193 } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
194 Ordering::Greater
195 } else {
196 Ordering::Equal
197 }
198 }
199}
200
201impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize {
202 fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering {
203 if *self < cursor_location.diff_base_byte_range.start {
204 Ordering::Less
205 } else if *self > cursor_location.diff_base_byte_range.end {
206 Ordering::Greater
207 } else {
208 Ordering::Equal
209 }
210 }
211}
212
213impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 f.debug_struct("BufferDiffSnapshot")
216 .field("hunks", &self.hunks)
217 .field("remote_id", &self.base_text.remote_id())
218 .finish()
219 }
220}
221
222impl BufferDiffSnapshot {
223 #[cfg(test)]
224 fn new_sync(
225 buffer: &text::BufferSnapshot,
226 diff_base: String,
227 cx: &mut gpui::TestAppContext,
228 ) -> BufferDiffSnapshot {
229 let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, buffer, cx));
230 buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
231 }
232
233 pub fn is_empty(&self) -> bool {
234 self.inner.hunks.is_empty()
235 }
236
237 pub fn base_text_string(&self) -> Option<String> {
238 self.inner
239 .base_text_exists
240 .then(|| self.inner.base_text.text())
241 }
242
243 pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
244 self.secondary_diff.as_deref()
245 }
246
247 pub fn buffer_version(&self) -> &clock::Global {
248 self.inner.buffer_version()
249 }
250
251 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,
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.secondary_diff.as_ref().map(|diff| {
1789 debug_assert!(diff.read(cx).secondary_diff.is_none());
1790 Arc::new(diff.read(cx).snapshot(cx))
1791 }),
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, 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, 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, index_text, cx);
2105 let mut uncommitted_diff = BufferDiffSnapshot::new_sync(&buffer, head_text.clone(), cx);
2106 uncommitted_diff.secondary_diff = Some(Arc::new(unstaged_diff));
2107
2108 let expected_hunks = vec![
2109 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
2110 (
2111 4..6,
2112 "four\nfive\n",
2113 "FOUR\nFIVE\n",
2114 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2115 ),
2116 (
2117 7..8,
2118 "seven\n",
2119 "SEVEN\n",
2120 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2121 ),
2122 ];
2123
2124 assert_hunks(
2125 uncommitted_diff.hunks_intersecting_range(
2126 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2127 &buffer,
2128 ),
2129 &buffer,
2130 &head_text,
2131 &expected_hunks,
2132 );
2133 }
2134
2135 #[gpui::test]
2136 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
2137 let diff_base = "
2138 one
2139 two
2140 three
2141 four
2142 five
2143 six
2144 seven
2145 eight
2146 nine
2147 ten
2148 "
2149 .unindent();
2150
2151 let buffer_text = "
2152 A
2153 one
2154 B
2155 two
2156 C
2157 three
2158 HELLO
2159 four
2160 five
2161 SIXTEEN
2162 seven
2163 eight
2164 WORLD
2165 nine
2166
2167 ten
2168
2169 "
2170 .unindent();
2171
2172 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2173 let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
2174 assert_eq!(
2175 diff.hunks_intersecting_range(
2176 Anchor::min_max_range_for_buffer(buffer.remote_id()),
2177 &buffer
2178 )
2179 .count(),
2180 8
2181 );
2182
2183 assert_hunks(
2184 diff.hunks_intersecting_range(
2185 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
2186 &buffer,
2187 ),
2188 &buffer,
2189 &diff_base,
2190 &[
2191 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
2192 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
2193 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
2194 ],
2195 );
2196 }
2197
2198 #[gpui::test]
2199 async fn test_stage_hunk(cx: &mut TestAppContext) {
2200 struct Example {
2201 name: &'static str,
2202 head_text: String,
2203 index_text: String,
2204 buffer_marked_text: String,
2205 final_index_text: String,
2206 }
2207
2208 let table = [
2209 Example {
2210 name: "uncommitted hunk straddles end of unstaged hunk",
2211 head_text: "
2212 one
2213 two
2214 three
2215 four
2216 five
2217 "
2218 .unindent(),
2219 index_text: "
2220 one
2221 TWO_HUNDRED
2222 three
2223 FOUR_HUNDRED
2224 five
2225 "
2226 .unindent(),
2227 buffer_marked_text: "
2228 ZERO
2229 one
2230 two
2231 «THREE_HUNDRED
2232 FOUR_HUNDRED»
2233 five
2234 SIX
2235 "
2236 .unindent(),
2237 final_index_text: "
2238 one
2239 two
2240 THREE_HUNDRED
2241 FOUR_HUNDRED
2242 five
2243 "
2244 .unindent(),
2245 },
2246 Example {
2247 name: "uncommitted hunk straddles start of unstaged hunk",
2248 head_text: "
2249 one
2250 two
2251 three
2252 four
2253 five
2254 "
2255 .unindent(),
2256 index_text: "
2257 one
2258 TWO_HUNDRED
2259 three
2260 FOUR_HUNDRED
2261 five
2262 "
2263 .unindent(),
2264 buffer_marked_text: "
2265 ZERO
2266 one
2267 «TWO_HUNDRED
2268 THREE_HUNDRED»
2269 four
2270 five
2271 SIX
2272 "
2273 .unindent(),
2274 final_index_text: "
2275 one
2276 TWO_HUNDRED
2277 THREE_HUNDRED
2278 four
2279 five
2280 "
2281 .unindent(),
2282 },
2283 Example {
2284 name: "uncommitted hunk strictly contains unstaged hunks",
2285 head_text: "
2286 one
2287 two
2288 three
2289 four
2290 five
2291 six
2292 seven
2293 "
2294 .unindent(),
2295 index_text: "
2296 one
2297 TWO
2298 THREE
2299 FOUR
2300 FIVE
2301 SIX
2302 seven
2303 "
2304 .unindent(),
2305 buffer_marked_text: "
2306 one
2307 TWO
2308 «THREE_HUNDRED
2309 FOUR
2310 FIVE_HUNDRED»
2311 SIX
2312 seven
2313 "
2314 .unindent(),
2315 final_index_text: "
2316 one
2317 TWO
2318 THREE_HUNDRED
2319 FOUR
2320 FIVE_HUNDRED
2321 SIX
2322 seven
2323 "
2324 .unindent(),
2325 },
2326 Example {
2327 name: "uncommitted deletion hunk",
2328 head_text: "
2329 one
2330 two
2331 three
2332 four
2333 five
2334 "
2335 .unindent(),
2336 index_text: "
2337 one
2338 two
2339 three
2340 four
2341 five
2342 "
2343 .unindent(),
2344 buffer_marked_text: "
2345 one
2346 ˇfive
2347 "
2348 .unindent(),
2349 final_index_text: "
2350 one
2351 five
2352 "
2353 .unindent(),
2354 },
2355 Example {
2356 name: "one unstaged hunk that contains two uncommitted hunks",
2357 head_text: "
2358 one
2359 two
2360
2361 three
2362 four
2363 "
2364 .unindent(),
2365 index_text: "
2366 one
2367 two
2368 three
2369 four
2370 "
2371 .unindent(),
2372 buffer_marked_text: "
2373 «one
2374
2375 three // modified
2376 four»
2377 "
2378 .unindent(),
2379 final_index_text: "
2380 one
2381
2382 three // modified
2383 four
2384 "
2385 .unindent(),
2386 },
2387 Example {
2388 name: "one uncommitted hunk that contains two unstaged hunks",
2389 head_text: "
2390 one
2391 two
2392 three
2393 four
2394 five
2395 "
2396 .unindent(),
2397 index_text: "
2398 ZERO
2399 one
2400 TWO
2401 THREE
2402 FOUR
2403 five
2404 "
2405 .unindent(),
2406 buffer_marked_text: "
2407 «one
2408 TWO_HUNDRED
2409 THREE
2410 FOUR_HUNDRED
2411 five»
2412 "
2413 .unindent(),
2414 final_index_text: "
2415 ZERO
2416 one
2417 TWO_HUNDRED
2418 THREE
2419 FOUR_HUNDRED
2420 five
2421 "
2422 .unindent(),
2423 },
2424 ];
2425
2426 for example in table {
2427 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2428 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2429 let hunk_range =
2430 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2431
2432 let unstaged_diff =
2433 cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2434
2435 let uncommitted_diff = cx.new(|cx| {
2436 let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2437 diff.set_secondary_diff(unstaged_diff);
2438 diff
2439 });
2440
2441 uncommitted_diff.update(cx, |diff, cx| {
2442 let hunks = diff
2443 .snapshot(cx)
2444 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2445 .collect::<Vec<_>>();
2446 for hunk in &hunks {
2447 assert_ne!(
2448 hunk.secondary_status,
2449 DiffHunkSecondaryStatus::NoSecondaryHunk
2450 )
2451 }
2452
2453 let new_index_text = diff
2454 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2455 .unwrap()
2456 .to_string();
2457
2458 let hunks = diff
2459 .snapshot(cx)
2460 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2461 .collect::<Vec<_>>();
2462 for hunk in &hunks {
2463 assert_eq!(
2464 hunk.secondary_status,
2465 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2466 )
2467 }
2468
2469 pretty_assertions::assert_eq!(
2470 new_index_text,
2471 example.final_index_text,
2472 "example: {}",
2473 example.name
2474 );
2475 });
2476 }
2477 }
2478
2479 #[gpui::test]
2480 async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2481 // This test reproduces a crash where staging all hunks would cause an underflow
2482 // when there's one large unstaged hunk containing multiple uncommitted hunks.
2483 let head_text = "
2484 aaa
2485 bbb
2486 ccc
2487 ddd
2488 eee
2489 fff
2490 ggg
2491 hhh
2492 iii
2493 jjj
2494 kkk
2495 lll
2496 "
2497 .unindent();
2498
2499 let index_text = "
2500 aaa
2501 bbb
2502 CCC-index
2503 DDD-index
2504 EEE-index
2505 FFF-index
2506 GGG-index
2507 HHH-index
2508 III-index
2509 JJJ-index
2510 kkk
2511 lll
2512 "
2513 .unindent();
2514
2515 let buffer_text = "
2516 aaa
2517 bbb
2518 ccc-modified
2519 ddd
2520 eee-modified
2521 fff
2522 ggg
2523 hhh-modified
2524 iii
2525 jjj
2526 kkk
2527 lll
2528 "
2529 .unindent();
2530
2531 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2532
2533 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2534 let uncommitted_diff = cx.new(|cx| {
2535 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2536 diff.set_secondary_diff(unstaged_diff);
2537 diff
2538 });
2539
2540 uncommitted_diff.update(cx, |diff, cx| {
2541 diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2542 });
2543 }
2544
2545 #[gpui::test]
2546 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2547 let head_text = "
2548 one
2549 two
2550 three
2551 "
2552 .unindent();
2553 let index_text = head_text.clone();
2554 let buffer_text = "
2555 one
2556 three
2557 "
2558 .unindent();
2559
2560 let buffer = Buffer::new(
2561 ReplicaId::LOCAL,
2562 BufferId::new(1).unwrap(),
2563 buffer_text.clone(),
2564 );
2565 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2566 let uncommitted_diff = cx.new(|cx| {
2567 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2568 diff.set_secondary_diff(unstaged_diff.clone());
2569 diff
2570 });
2571
2572 uncommitted_diff.update(cx, |diff, cx| {
2573 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2574
2575 let new_index_text = diff
2576 .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2577 .unwrap()
2578 .to_string();
2579 assert_eq!(new_index_text, buffer_text);
2580
2581 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2582 assert_eq!(
2583 hunk.secondary_status,
2584 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2585 );
2586
2587 let index_text = diff
2588 .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2589 .unwrap()
2590 .to_string();
2591 assert_eq!(index_text, head_text);
2592
2593 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2594 // optimistically unstaged (fine, could also be HasSecondaryHunk)
2595 assert_eq!(
2596 hunk.secondary_status,
2597 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2598 );
2599 });
2600 }
2601
2602 #[gpui::test]
2603 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2604 let base_text = "
2605 zero
2606 one
2607 two
2608 three
2609 four
2610 five
2611 six
2612 seven
2613 eight
2614 nine
2615 "
2616 .unindent();
2617
2618 let buffer_text_1 = "
2619 one
2620 three
2621 four
2622 five
2623 SIX
2624 seven
2625 eight
2626 NINE
2627 "
2628 .unindent();
2629
2630 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2631
2632 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2633 let diff_1 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2634 let DiffChanged {
2635 changed_range,
2636 base_text_changed_range,
2637 extended_range: _,
2638 } = compare_hunks(
2639 &diff_1.inner.hunks,
2640 &empty_diff.inner.hunks,
2641 &buffer,
2642 &buffer,
2643 &diff_1.base_text(),
2644 &diff_1.base_text(),
2645 );
2646 let range = changed_range.unwrap();
2647 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2648 let base_text_range = base_text_changed_range.unwrap();
2649 assert_eq!(
2650 base_text_range.to_point(diff_1.base_text()),
2651 Point::new(0, 0)..Point::new(10, 0)
2652 );
2653
2654 // Edit does affects the diff because it recalculates word diffs.
2655 buffer.edit_via_marked_text(
2656 &"
2657 one
2658 three
2659 four
2660 five
2661 «SIX.5»
2662 seven
2663 eight
2664 NINE
2665 "
2666 .unindent(),
2667 );
2668 let diff_2 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2669 let DiffChanged {
2670 changed_range,
2671 base_text_changed_range,
2672 extended_range: _,
2673 } = compare_hunks(
2674 &diff_2.inner.hunks,
2675 &diff_1.inner.hunks,
2676 &buffer,
2677 &buffer,
2678 diff_2.base_text(),
2679 diff_2.base_text(),
2680 );
2681 assert_eq!(
2682 changed_range.unwrap().to_point(&buffer),
2683 Point::new(4, 0)..Point::new(5, 0),
2684 );
2685 assert_eq!(
2686 base_text_changed_range
2687 .unwrap()
2688 .to_point(diff_2.base_text()),
2689 Point::new(6, 0)..Point::new(7, 0),
2690 );
2691
2692 // Edit turns a deletion hunk into a modification.
2693 buffer.edit_via_marked_text(
2694 &"
2695 one
2696 «THREE»
2697 four
2698 five
2699 SIX.5
2700 seven
2701 eight
2702 NINE
2703 "
2704 .unindent(),
2705 );
2706 let diff_3 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2707 let DiffChanged {
2708 changed_range,
2709 base_text_changed_range,
2710 extended_range: _,
2711 } = compare_hunks(
2712 &diff_3.inner.hunks,
2713 &diff_2.inner.hunks,
2714 &buffer,
2715 &buffer,
2716 diff_3.base_text(),
2717 diff_3.base_text(),
2718 );
2719 let range = changed_range.unwrap();
2720 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2721 let base_text_range = base_text_changed_range.unwrap();
2722 assert_eq!(
2723 base_text_range.to_point(diff_3.base_text()),
2724 Point::new(2, 0)..Point::new(4, 0)
2725 );
2726
2727 // Edit turns a modification hunk into a deletion.
2728 buffer.edit_via_marked_text(
2729 &"
2730 one
2731 THREE
2732 four
2733 five«»
2734 seven
2735 eight
2736 NINE
2737 "
2738 .unindent(),
2739 );
2740 let diff_4 = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
2741 let DiffChanged {
2742 changed_range,
2743 base_text_changed_range,
2744 extended_range: _,
2745 } = compare_hunks(
2746 &diff_4.inner.hunks,
2747 &diff_3.inner.hunks,
2748 &buffer,
2749 &buffer,
2750 diff_4.base_text(),
2751 diff_4.base_text(),
2752 );
2753 let range = changed_range.unwrap();
2754 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2755 let base_text_range = base_text_changed_range.unwrap();
2756 assert_eq!(
2757 base_text_range.to_point(diff_4.base_text()),
2758 Point::new(6, 0)..Point::new(7, 0)
2759 );
2760
2761 // Edit introduces a new insertion hunk.
2762 buffer.edit_via_marked_text(
2763 &"
2764 one
2765 THREE
2766 four«
2767 FOUR.5
2768 »five
2769 seven
2770 eight
2771 NINE
2772 "
2773 .unindent(),
2774 );
2775 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2776 let DiffChanged {
2777 changed_range,
2778 base_text_changed_range,
2779 extended_range: _,
2780 } = compare_hunks(
2781 &diff_5.inner.hunks,
2782 &diff_4.inner.hunks,
2783 &buffer,
2784 &buffer,
2785 diff_5.base_text(),
2786 diff_5.base_text(),
2787 );
2788 let range = changed_range.unwrap();
2789 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2790 let base_text_range = base_text_changed_range.unwrap();
2791 assert_eq!(
2792 base_text_range.to_point(diff_5.base_text()),
2793 Point::new(5, 0)..Point::new(5, 0)
2794 );
2795
2796 // Edit removes a hunk.
2797 buffer.edit_via_marked_text(
2798 &"
2799 one
2800 THREE
2801 four
2802 FOUR.5
2803 five
2804 seven
2805 eight
2806 «nine»
2807 "
2808 .unindent(),
2809 );
2810 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2811 let DiffChanged {
2812 changed_range,
2813 base_text_changed_range,
2814 extended_range: _,
2815 } = compare_hunks(
2816 &diff_6.inner.hunks,
2817 &diff_5.inner.hunks,
2818 &buffer,
2819 &buffer,
2820 diff_6.base_text(),
2821 diff_6.base_text(),
2822 );
2823 let range = changed_range.unwrap();
2824 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2825 let base_text_range = base_text_changed_range.unwrap();
2826 assert_eq!(
2827 base_text_range.to_point(diff_6.base_text()),
2828 Point::new(9, 0)..Point::new(10, 0)
2829 );
2830
2831 buffer.edit_via_marked_text(
2832 &"
2833 one
2834 THREE
2835 four«»
2836 five
2837 seven
2838 eight
2839 «NINE»
2840 "
2841 .unindent(),
2842 );
2843
2844 let diff_7 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2845 let DiffChanged {
2846 changed_range,
2847 base_text_changed_range,
2848 extended_range: _,
2849 } = compare_hunks(
2850 &diff_7.inner.hunks,
2851 &diff_6.inner.hunks,
2852 &buffer,
2853 &buffer,
2854 diff_7.base_text(),
2855 diff_7.base_text(),
2856 );
2857 let range = changed_range.unwrap();
2858 assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
2859 let base_text_range = base_text_changed_range.unwrap();
2860 assert_eq!(
2861 base_text_range.to_point(diff_7.base_text()),
2862 Point::new(5, 0)..Point::new(10, 0)
2863 );
2864
2865 buffer.edit_via_marked_text(
2866 &"
2867 one
2868 THREE
2869 four
2870 five«»seven
2871 eight
2872 NINE
2873 "
2874 .unindent(),
2875 );
2876
2877 let diff_8 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2878 let DiffChanged {
2879 changed_range,
2880 base_text_changed_range,
2881 extended_range: _,
2882 } = compare_hunks(
2883 &diff_8.inner.hunks,
2884 &diff_7.inner.hunks,
2885 &buffer,
2886 &buffer,
2887 diff_8.base_text(),
2888 diff_8.base_text(),
2889 );
2890 let range = changed_range.unwrap();
2891 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
2892 let base_text_range = base_text_changed_range.unwrap();
2893 assert_eq!(
2894 base_text_range.to_point(diff_8.base_text()),
2895 Point::new(5, 0)..Point::new(8, 0)
2896 );
2897 }
2898
2899 #[gpui::test(iterations = 100)]
2900 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2901 fn gen_line(rng: &mut StdRng) -> String {
2902 if rng.random_bool(0.2) {
2903 "\n".to_owned()
2904 } else {
2905 let c = rng.random_range('A'..='Z');
2906 format!("{c}{c}{c}\n")
2907 }
2908 }
2909
2910 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2911 let mut old_lines = {
2912 let mut old_lines = Vec::new();
2913 let old_lines_iter = head.lines();
2914 for line in old_lines_iter {
2915 assert!(!line.ends_with("\n"));
2916 old_lines.push(line.to_owned());
2917 }
2918 if old_lines.last().is_some_and(|line| line.is_empty()) {
2919 old_lines.pop();
2920 }
2921 old_lines.into_iter()
2922 };
2923 let mut result = String::new();
2924 let unchanged_count = rng.random_range(0..=old_lines.len());
2925 result +=
2926 &old_lines
2927 .by_ref()
2928 .take(unchanged_count)
2929 .fold(String::new(), |mut s, line| {
2930 writeln!(&mut s, "{line}").unwrap();
2931 s
2932 });
2933 while old_lines.len() > 0 {
2934 let deleted_count = rng.random_range(0..=old_lines.len());
2935 let _advance = old_lines
2936 .by_ref()
2937 .take(deleted_count)
2938 .map(|line| line.len() + 1)
2939 .sum::<usize>();
2940 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2941 let added_count = rng.random_range(minimum_added..=5);
2942 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2943 result += &addition;
2944
2945 if old_lines.len() > 0 {
2946 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2947 if blank_lines == old_lines.len() {
2948 break;
2949 };
2950 let unchanged_count =
2951 rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2952 result += &old_lines.by_ref().take(unchanged_count).fold(
2953 String::new(),
2954 |mut s, line| {
2955 writeln!(&mut s, "{line}").unwrap();
2956 s
2957 },
2958 );
2959 }
2960 }
2961 result
2962 }
2963
2964 fn uncommitted_diff(
2965 working_copy: &language::BufferSnapshot,
2966 index_text: &Rope,
2967 head_text: String,
2968 cx: &mut TestAppContext,
2969 ) -> Entity<BufferDiff> {
2970 let secondary = cx.new(|cx| {
2971 BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2972 });
2973 cx.new(|cx| {
2974 let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2975 diff.secondary_diff = Some(secondary);
2976 diff
2977 })
2978 }
2979
2980 let operations = std::env::var("OPERATIONS")
2981 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2982 .unwrap_or(10);
2983
2984 let rng = &mut rng;
2985 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2986 writeln!(&mut s, "{c}{c}{c}").unwrap();
2987 s
2988 });
2989 let working_copy = gen_working_copy(rng, &head_text);
2990 let working_copy = cx.new(|cx| {
2991 language::Buffer::local_normalized(
2992 Rope::from(working_copy.as_str()),
2993 text::LineEnding::default(),
2994 cx,
2995 )
2996 });
2997 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2998 let mut index_text = if rng.random() {
2999 Rope::from(head_text.as_str())
3000 } else {
3001 working_copy.as_rope().clone()
3002 };
3003
3004 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3005 let mut hunks = diff.update(cx, |diff, cx| {
3006 diff.snapshot(cx)
3007 .hunks_intersecting_range(
3008 Anchor::min_max_range_for_buffer(diff.buffer_id),
3009 &working_copy,
3010 )
3011 .collect::<Vec<_>>()
3012 });
3013 if hunks.is_empty() {
3014 return;
3015 }
3016
3017 for _ in 0..operations {
3018 let i = rng.random_range(0..hunks.len());
3019 let hunk = &mut hunks[i];
3020 let hunk_to_change = hunk.clone();
3021 let stage = match hunk.secondary_status {
3022 DiffHunkSecondaryStatus::HasSecondaryHunk => {
3023 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
3024 true
3025 }
3026 DiffHunkSecondaryStatus::NoSecondaryHunk => {
3027 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
3028 false
3029 }
3030 _ => unreachable!(),
3031 };
3032
3033 index_text = diff.update(cx, |diff, cx| {
3034 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
3035 .unwrap()
3036 });
3037
3038 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3039 let found_hunks = diff.update(cx, |diff, cx| {
3040 diff.snapshot(cx)
3041 .hunks_intersecting_range(
3042 Anchor::min_max_range_for_buffer(diff.buffer_id),
3043 &working_copy,
3044 )
3045 .collect::<Vec<_>>()
3046 });
3047 assert_eq!(hunks.len(), found_hunks.len());
3048
3049 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
3050 assert_eq!(
3051 expected_hunk.buffer_range.to_point(&working_copy),
3052 found_hunk.buffer_range.to_point(&working_copy)
3053 );
3054 assert_eq!(
3055 expected_hunk.diff_base_byte_range,
3056 found_hunk.diff_base_byte_range
3057 );
3058 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
3059 }
3060 hunks = found_hunks;
3061 }
3062 }
3063
3064 #[gpui::test]
3065 async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
3066 let base_text = "
3067 one
3068 two
3069 three
3070 four
3071 five
3072 six
3073 "
3074 .unindent();
3075 let buffer_text = "
3076 one
3077 TWO
3078 three
3079 four
3080 FIVE
3081 six
3082 "
3083 .unindent();
3084 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3085 let diff = cx.new(|cx| {
3086 BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
3087 });
3088 cx.run_until_parked();
3089 let (tx, rx) = mpsc::channel();
3090 let subscription =
3091 cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
3092
3093 let snapshot = buffer.update(cx, |buffer, cx| {
3094 buffer.set_text(
3095 "
3096 ONE
3097 TWO
3098 THREE
3099 FOUR
3100 FIVE
3101 SIX
3102 "
3103 .unindent(),
3104 cx,
3105 );
3106 buffer.text_snapshot()
3107 });
3108 let update = diff
3109 .update(cx, |diff, cx| {
3110 diff.update_diff(
3111 snapshot.clone(),
3112 Some(base_text.as_str().into()),
3113 None,
3114 None,
3115 cx,
3116 )
3117 })
3118 .await;
3119 diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
3120 .await;
3121 cx.run_until_parked();
3122 drop(subscription);
3123 let events = rx.into_iter().collect::<Vec<_>>();
3124 match events.as_slice() {
3125 [
3126 BufferDiffEvent::DiffChanged(DiffChanged {
3127 changed_range: _,
3128 base_text_changed_range,
3129 extended_range: _,
3130 }),
3131 ] => {
3132 // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3133 // assert_eq!(
3134 // *changed_range,
3135 // Some(Anchor::min_max_range_for_buffer(
3136 // buffer.read_with(cx, |buffer, _| buffer.remote_id())
3137 // ))
3138 // );
3139 assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3140 }
3141 _ => panic!("unexpected events: {:?}", events),
3142 }
3143 }
3144
3145 #[gpui::test]
3146 async fn test_extended_range(cx: &mut TestAppContext) {
3147 let base_text = "
3148 aaa
3149 bbb
3150
3151
3152
3153
3154
3155 ccc
3156 ddd
3157 "
3158 .unindent();
3159
3160 let buffer_text = "
3161 aaa
3162 bbb
3163
3164
3165
3166
3167
3168 CCC
3169 ddd
3170 "
3171 .unindent();
3172
3173 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3174 let old_buffer = buffer.snapshot().clone();
3175 let diff_a = BufferDiffSnapshot::new_sync(&buffer, base_text.clone(), cx);
3176
3177 buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3178 let diff_b = BufferDiffSnapshot::new_sync(&buffer, base_text, cx);
3179
3180 let DiffChanged {
3181 changed_range,
3182 base_text_changed_range: _,
3183 extended_range,
3184 } = compare_hunks(
3185 &diff_b.inner.hunks,
3186 &diff_a.inner.hunks,
3187 &old_buffer,
3188 &buffer,
3189 &diff_a.base_text(),
3190 &diff_a.base_text(),
3191 );
3192
3193 let changed_range = changed_range.unwrap();
3194 assert_eq!(
3195 changed_range.to_point(&buffer),
3196 Point::new(7, 0)..Point::new(9, 0),
3197 "changed_range should span from old hunk position to new hunk end"
3198 );
3199
3200 let extended_range = extended_range.unwrap();
3201 assert_eq!(
3202 extended_range.start.to_point(&buffer),
3203 Point::new(1, 3),
3204 "extended_range.start should extend to include the edit outside changed_range"
3205 );
3206 assert_eq!(
3207 extended_range.end.to_point(&buffer),
3208 Point::new(9, 0),
3209 "extended_range.end should collapse to changed_range.end when no edits in end margin"
3210 );
3211
3212 let base_text_2 = "
3213 one
3214 two
3215 three
3216 four
3217 five
3218 six
3219 seven
3220 eight
3221 "
3222 .unindent();
3223
3224 let buffer_text_2 = "
3225 ONE
3226 two
3227 THREE
3228 four
3229 FIVE
3230 six
3231 SEVEN
3232 eight
3233 "
3234 .unindent();
3235
3236 let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3237 let old_buffer_2 = buffer_2.snapshot().clone();
3238 let diff_2a = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2.clone(), cx);
3239
3240 buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3241 let diff_2b = BufferDiffSnapshot::new_sync(&buffer_2, base_text_2, cx);
3242
3243 let DiffChanged {
3244 changed_range,
3245 base_text_changed_range: _,
3246 extended_range,
3247 } = compare_hunks(
3248 &diff_2b.inner.hunks,
3249 &diff_2a.inner.hunks,
3250 &old_buffer_2,
3251 &buffer_2,
3252 &diff_2a.base_text(),
3253 &diff_2a.base_text(),
3254 );
3255
3256 let changed_range = changed_range.unwrap();
3257 assert_eq!(
3258 changed_range.to_point(&buffer_2),
3259 Point::new(4, 0)..Point::new(5, 0),
3260 "changed_range should be just the hunk that changed (FIVE)"
3261 );
3262
3263 let extended_range = extended_range.unwrap();
3264 assert_eq!(
3265 extended_range.to_point(&buffer_2),
3266 Point::new(4, 0)..Point::new(5, 0),
3267 "extended_range should equal changed_range when edit is within the hunk"
3268 );
3269 }
3270
3271 #[gpui::test]
3272 async fn test_buffer_diff_compare_with_base_text_change(_cx: &mut TestAppContext) {
3273 // Use a shared base text buffer so that anchors from old and new snapshots
3274 // share the same remote_id and resolve correctly across versions.
3275 let initial_base = "aaa\nbbb\nccc\nddd\neee\n";
3276 let mut base_text_buffer = Buffer::new(
3277 ReplicaId::LOCAL,
3278 BufferId::new(99).unwrap(),
3279 initial_base.to_string(),
3280 );
3281
3282 // --- Scenario 1: Base text gains a line, producing a new deletion hunk ---
3283 //
3284 // Buffer has a modification (ccc → CCC). When the base text gains
3285 // a new line "XXX" after "aaa", the diff now also contains a
3286 // deletion for that line, and the modification hunk shifts in the
3287 // base text.
3288 let buffer_text_1 = "aaa\nbbb\nCCC\nddd\neee\n";
3289 let buffer = Buffer::new(
3290 ReplicaId::LOCAL,
3291 BufferId::new(1).unwrap(),
3292 buffer_text_1.to_string(),
3293 );
3294
3295 let old_base_snapshot_1 = base_text_buffer.snapshot().clone();
3296 let old_hunks_1 = compute_hunks(
3297 Some((Arc::from(initial_base), Rope::from(initial_base))),
3298 buffer.snapshot(),
3299 None,
3300 );
3301
3302 // Insert "XXX\n" after "aaa\n" in the base text.
3303 base_text_buffer.edit([(4..4, "XXX\n")]);
3304 let new_base_str_1: Arc<str> = Arc::from(base_text_buffer.text().as_str());
3305 let new_base_snapshot_1 = base_text_buffer.snapshot();
3306
3307 let new_hunks_1 = compute_hunks(
3308 Some((new_base_str_1.clone(), Rope::from(new_base_str_1.as_ref()))),
3309 buffer.snapshot(),
3310 None,
3311 );
3312
3313 let DiffChanged {
3314 changed_range,
3315 base_text_changed_range,
3316 extended_range: _,
3317 } = compare_hunks(
3318 &new_hunks_1,
3319 &old_hunks_1,
3320 &buffer.snapshot(),
3321 &buffer.snapshot(),
3322 &old_base_snapshot_1,
3323 &new_base_snapshot_1,
3324 );
3325
3326 // The new deletion hunk (XXX) starts at buffer row 1 and the
3327 // modification hunk (ccc → CCC) now has a different
3328 // diff_base_byte_range, so the changed range spans both.
3329 let range = changed_range.unwrap();
3330 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(3, 0),);
3331 let base_range = base_text_changed_range.unwrap();
3332 assert_eq!(
3333 base_range.to_point(&new_base_snapshot_1),
3334 Point::new(1, 0)..Point::new(4, 0),
3335 );
3336
3337 // --- Scenario 2: Base text changes to match the buffer (hunk disappears) ---
3338 //
3339 // Start fresh with a simple base text.
3340 let simple_base = "one\ntwo\nthree\n";
3341 let mut base_buf_2 = Buffer::new(
3342 ReplicaId::LOCAL,
3343 BufferId::new(100).unwrap(),
3344 simple_base.to_string(),
3345 );
3346
3347 let buffer_text_2 = "one\nTWO\nthree\n";
3348 let buffer_2 = Buffer::new(
3349 ReplicaId::LOCAL,
3350 BufferId::new(2).unwrap(),
3351 buffer_text_2.to_string(),
3352 );
3353
3354 let old_base_snapshot_2 = base_buf_2.snapshot().clone();
3355 let old_hunks_2 = compute_hunks(
3356 Some((Arc::from(simple_base), Rope::from(simple_base))),
3357 buffer_2.snapshot(),
3358 None,
3359 );
3360
3361 // The base text is edited so "two" becomes "TWO", now matching the buffer.
3362 base_buf_2.edit([(4..7, "TWO")]);
3363 let new_base_str_2: Arc<str> = Arc::from(base_buf_2.text().as_str());
3364 let new_base_snapshot_2 = base_buf_2.snapshot();
3365
3366 let new_hunks_2 = compute_hunks(
3367 Some((new_base_str_2.clone(), Rope::from(new_base_str_2.as_ref()))),
3368 buffer_2.snapshot(),
3369 None,
3370 );
3371
3372 let DiffChanged {
3373 changed_range,
3374 base_text_changed_range,
3375 extended_range: _,
3376 } = compare_hunks(
3377 &new_hunks_2,
3378 &old_hunks_2,
3379 &buffer_2.snapshot(),
3380 &buffer_2.snapshot(),
3381 &old_base_snapshot_2,
3382 &new_base_snapshot_2,
3383 );
3384
3385 // The old modification hunk (two → TWO) is now gone because the
3386 // base text matches the buffer. The changed range covers where the
3387 // old hunk used to be.
3388 let range = changed_range.unwrap();
3389 assert_eq!(
3390 range.to_point(&buffer_2),
3391 Point::new(1, 0)..Point::new(2, 0),
3392 );
3393 let base_range = base_text_changed_range.unwrap();
3394 // The old hunk's diff_base_byte_range covered "two\n" (bytes 4..8).
3395 // anchor_after(4) is right-biased at the start of the deleted "two",
3396 // so after the edit replacing "two" with "TWO" it resolves past the
3397 // insertion to Point(1, 3).
3398 assert_eq!(
3399 base_range.to_point(&new_base_snapshot_2),
3400 Point::new(1, 3)..Point::new(2, 0),
3401 );
3402
3403 // --- Scenario 3: Base text edit changes one hunk but not another ---
3404 //
3405 // Two modification hunks exist. Only one of them is resolved by
3406 // the base text change; the other remains identical.
3407 let base_3 = "aaa\nbbb\nccc\nddd\neee\n";
3408 let mut base_buf_3 = Buffer::new(
3409 ReplicaId::LOCAL,
3410 BufferId::new(101).unwrap(),
3411 base_3.to_string(),
3412 );
3413
3414 let buffer_text_3 = "aaa\nBBB\nccc\nDDD\neee\n";
3415 let buffer_3 = Buffer::new(
3416 ReplicaId::LOCAL,
3417 BufferId::new(3).unwrap(),
3418 buffer_text_3.to_string(),
3419 );
3420
3421 let old_base_snapshot_3 = base_buf_3.snapshot().clone();
3422 let old_hunks_3 = compute_hunks(
3423 Some((Arc::from(base_3), Rope::from(base_3))),
3424 buffer_3.snapshot(),
3425 None,
3426 );
3427
3428 // Change "ddd" to "DDD" in the base text so that hunk disappears,
3429 // but "bbb" stays, so its hunk remains.
3430 base_buf_3.edit([(12..15, "DDD")]);
3431 let new_base_str_3: Arc<str> = Arc::from(base_buf_3.text().as_str());
3432 let new_base_snapshot_3 = base_buf_3.snapshot();
3433
3434 let new_hunks_3 = compute_hunks(
3435 Some((new_base_str_3.clone(), Rope::from(new_base_str_3.as_ref()))),
3436 buffer_3.snapshot(),
3437 None,
3438 );
3439
3440 let DiffChanged {
3441 changed_range,
3442 base_text_changed_range,
3443 extended_range: _,
3444 } = compare_hunks(
3445 &new_hunks_3,
3446 &old_hunks_3,
3447 &buffer_3.snapshot(),
3448 &buffer_3.snapshot(),
3449 &old_base_snapshot_3,
3450 &new_base_snapshot_3,
3451 );
3452
3453 // Only the second hunk (ddd → DDD) disappeared; the first hunk
3454 // (bbb → BBB) is unchanged, so the changed range covers only line 3.
3455 let range = changed_range.unwrap();
3456 assert_eq!(
3457 range.to_point(&buffer_3),
3458 Point::new(3, 0)..Point::new(4, 0),
3459 );
3460 let base_range = base_text_changed_range.unwrap();
3461 // anchor_after(12) is right-biased at the start of deleted "ddd",
3462 // so after the edit replacing "ddd" with "DDD" it resolves past
3463 // the insertion to Point(3, 3).
3464 assert_eq!(
3465 base_range.to_point(&new_base_snapshot_3),
3466 Point::new(3, 3)..Point::new(4, 0),
3467 );
3468
3469 // --- Scenario 4: Both buffer and base text change simultaneously ---
3470 //
3471 // The buffer gains an edit that introduces a new hunk while the
3472 // base text also changes.
3473 let base_4 = "alpha\nbeta\ngamma\ndelta\n";
3474 let mut base_buf_4 = Buffer::new(
3475 ReplicaId::LOCAL,
3476 BufferId::new(102).unwrap(),
3477 base_4.to_string(),
3478 );
3479
3480 let buffer_text_4 = "alpha\nBETA\ngamma\ndelta\n";
3481 let mut buffer_4 = Buffer::new(
3482 ReplicaId::LOCAL,
3483 BufferId::new(4).unwrap(),
3484 buffer_text_4.to_string(),
3485 );
3486
3487 let old_base_snapshot_4 = base_buf_4.snapshot().clone();
3488 let old_buffer_snapshot_4 = buffer_4.snapshot().clone();
3489 let old_hunks_4 = compute_hunks(
3490 Some((Arc::from(base_4), Rope::from(base_4))),
3491 buffer_4.snapshot(),
3492 None,
3493 );
3494
3495 // Edit the buffer: change "delta" to "DELTA" (new modification hunk).
3496 buffer_4.edit_via_marked_text(
3497 &"
3498 alpha
3499 BETA
3500 gamma
3501 «DELTA»
3502 "
3503 .unindent(),
3504 );
3505
3506 // Edit the base text: change "beta" to "BETA" (resolves that hunk).
3507 base_buf_4.edit([(6..10, "BETA")]);
3508 let new_base_str_4: Arc<str> = Arc::from(base_buf_4.text().as_str());
3509 let new_base_snapshot_4 = base_buf_4.snapshot();
3510
3511 let new_hunks_4 = compute_hunks(
3512 Some((new_base_str_4.clone(), Rope::from(new_base_str_4.as_ref()))),
3513 buffer_4.snapshot(),
3514 None,
3515 );
3516
3517 let DiffChanged {
3518 changed_range,
3519 base_text_changed_range,
3520 extended_range: _,
3521 } = compare_hunks(
3522 &new_hunks_4,
3523 &old_hunks_4,
3524 &old_buffer_snapshot_4,
3525 &buffer_4.snapshot(),
3526 &old_base_snapshot_4,
3527 &new_base_snapshot_4,
3528 );
3529
3530 // The old BETA hunk (line 1) is gone and a new DELTA hunk (line 3)
3531 // appeared, so the changed range spans from line 1 through line 4.
3532 let range = changed_range.unwrap();
3533 assert_eq!(
3534 range.to_point(&buffer_4),
3535 Point::new(1, 0)..Point::new(4, 0),
3536 );
3537 let base_range = base_text_changed_range.unwrap();
3538 // The old BETA hunk's base range started at byte 6 ("beta"). After
3539 // the base text edit replacing "beta" with "BETA", anchor_after(6)
3540 // resolves past the insertion to Point(1, 4).
3541 assert_eq!(
3542 base_range.to_point(&new_base_snapshot_4),
3543 Point::new(1, 4)..Point::new(4, 0),
3544 );
3545 }
3546}