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