buffer_diff.rs

   1use futures::channel::oneshot;
   2use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
   3use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, TaskLabel};
   4use language::{Language, LanguageRegistry};
   5use rope::Rope;
   6use std::{
   7    cmp::Ordering,
   8    future::Future,
   9    iter,
  10    ops::Range,
  11    sync::{Arc, LazyLock},
  12};
  13use sum_tree::SumTree;
  14use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _};
  15use util::ResultExt;
  16
  17pub static CALCULATE_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
  18
  19pub struct BufferDiff {
  20    pub buffer_id: BufferId,
  21    inner: BufferDiffInner,
  22    secondary_diff: Option<Entity<BufferDiff>>,
  23}
  24
  25#[derive(Clone, Debug)]
  26pub struct BufferDiffSnapshot {
  27    inner: BufferDiffInner,
  28    secondary_diff: Option<Box<BufferDiffSnapshot>>,
  29}
  30
  31#[derive(Clone)]
  32struct BufferDiffInner {
  33    hunks: SumTree<InternalDiffHunk>,
  34    pending_hunks: SumTree<PendingHunk>,
  35    base_text: language::BufferSnapshot,
  36    base_text_exists: bool,
  37}
  38
  39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  40pub struct DiffHunkStatus {
  41    pub kind: DiffHunkStatusKind,
  42    pub secondary: DiffHunkSecondaryStatus,
  43}
  44
  45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  46pub enum DiffHunkStatusKind {
  47    Added,
  48    Modified,
  49    Deleted,
  50}
  51
  52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  53pub enum DiffHunkSecondaryStatus {
  54    HasSecondaryHunk,
  55    OverlapsWithSecondaryHunk,
  56    NoSecondaryHunk,
  57    SecondaryHunkAdditionPending,
  58    SecondaryHunkRemovalPending,
  59}
  60
  61/// A diff hunk resolved to rows in the buffer.
  62#[derive(Debug, Clone, PartialEq, Eq)]
  63pub struct DiffHunk {
  64    /// The buffer range as points.
  65    pub range: Range<Point>,
  66    /// The range in the buffer to which this hunk corresponds.
  67    pub buffer_range: Range<Anchor>,
  68    /// The range in the buffer's diff base text to which this hunk corresponds.
  69    pub diff_base_byte_range: Range<usize>,
  70    pub secondary_status: DiffHunkSecondaryStatus,
  71}
  72
  73/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
  74#[derive(Debug, Clone, PartialEq, Eq)]
  75struct InternalDiffHunk {
  76    buffer_range: Range<Anchor>,
  77    diff_base_byte_range: Range<usize>,
  78}
  79
  80#[derive(Debug, Clone, PartialEq, Eq)]
  81struct PendingHunk {
  82    buffer_range: Range<Anchor>,
  83    diff_base_byte_range: Range<usize>,
  84    buffer_version: clock::Global,
  85    new_status: DiffHunkSecondaryStatus,
  86}
  87
  88#[derive(Debug, Default, Clone)]
  89pub struct DiffHunkSummary {
  90    buffer_range: Range<Anchor>,
  91}
  92
  93impl sum_tree::Item for InternalDiffHunk {
  94    type Summary = DiffHunkSummary;
  95
  96    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
  97        DiffHunkSummary {
  98            buffer_range: self.buffer_range.clone(),
  99        }
 100    }
 101}
 102
 103impl sum_tree::Item for PendingHunk {
 104    type Summary = DiffHunkSummary;
 105
 106    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 107        DiffHunkSummary {
 108            buffer_range: self.buffer_range.clone(),
 109        }
 110    }
 111}
 112
 113impl sum_tree::Summary for DiffHunkSummary {
 114    type Context = text::BufferSnapshot;
 115
 116    fn zero(_cx: &Self::Context) -> Self {
 117        Default::default()
 118    }
 119
 120    fn add_summary(&mut self, other: &Self, buffer: &Self::Context) {
 121        self.buffer_range.start = self
 122            .buffer_range
 123            .start
 124            .min(&other.buffer_range.start, buffer);
 125        self.buffer_range.end = self.buffer_range.end.max(&other.buffer_range.end, buffer);
 126    }
 127}
 128
 129impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
 130    fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
 131        if self
 132            .cmp(&cursor_location.buffer_range.start, buffer)
 133            .is_lt()
 134        {
 135            Ordering::Less
 136        } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
 137            Ordering::Greater
 138        } else {
 139            Ordering::Equal
 140        }
 141    }
 142}
 143
 144impl std::fmt::Debug for BufferDiffInner {
 145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 146        f.debug_struct("BufferDiffSnapshot")
 147            .field("hunks", &self.hunks)
 148            .finish()
 149    }
 150}
 151
 152impl BufferDiffSnapshot {
 153    fn empty(buffer: &text::BufferSnapshot, cx: &mut App) -> BufferDiffSnapshot {
 154        BufferDiffSnapshot {
 155            inner: BufferDiffInner {
 156                base_text: language::Buffer::build_empty_snapshot(cx),
 157                hunks: SumTree::new(buffer),
 158                pending_hunks: SumTree::new(buffer),
 159                base_text_exists: false,
 160            },
 161            secondary_diff: None,
 162        }
 163    }
 164
 165    fn new_with_base_text(
 166        buffer: text::BufferSnapshot,
 167        base_text: Option<Arc<String>>,
 168        language: Option<Arc<Language>>,
 169        language_registry: Option<Arc<LanguageRegistry>>,
 170        cx: &mut App,
 171    ) -> impl Future<Output = Self> + use<> {
 172        let base_text_pair;
 173        let base_text_exists;
 174        let base_text_snapshot;
 175        if let Some(text) = &base_text {
 176            let base_text_rope = Rope::from(text.as_str());
 177            base_text_pair = Some((text.clone(), base_text_rope.clone()));
 178            let snapshot = language::Buffer::build_snapshot(
 179                base_text_rope,
 180                language.clone(),
 181                language_registry.clone(),
 182                cx,
 183            );
 184            base_text_snapshot = cx.background_spawn(snapshot);
 185            base_text_exists = true;
 186        } else {
 187            base_text_pair = None;
 188            base_text_snapshot = Task::ready(language::Buffer::build_empty_snapshot(cx));
 189            base_text_exists = false;
 190        };
 191
 192        let hunks = cx
 193            .background_executor()
 194            .spawn_labeled(*CALCULATE_DIFF_TASK, {
 195                let buffer = buffer.clone();
 196                async move { compute_hunks(base_text_pair, buffer) }
 197            });
 198
 199        async move {
 200            let (base_text, hunks) = futures::join!(base_text_snapshot, hunks);
 201            Self {
 202                inner: BufferDiffInner {
 203                    base_text,
 204                    hunks,
 205                    base_text_exists,
 206                    pending_hunks: SumTree::new(&buffer),
 207                },
 208                secondary_diff: None,
 209            }
 210        }
 211    }
 212
 213    pub fn new_with_base_buffer(
 214        buffer: text::BufferSnapshot,
 215        base_text: Option<Arc<String>>,
 216        base_text_snapshot: language::BufferSnapshot,
 217        cx: &App,
 218    ) -> impl Future<Output = Self> + use<> {
 219        let base_text_exists = base_text.is_some();
 220        let base_text_pair = base_text.map(|text| (text, base_text_snapshot.as_rope().clone()));
 221        cx.background_executor()
 222            .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
 223                Self {
 224                    inner: BufferDiffInner {
 225                        base_text: base_text_snapshot,
 226                        pending_hunks: SumTree::new(&buffer),
 227                        hunks: compute_hunks(base_text_pair, buffer),
 228                        base_text_exists,
 229                    },
 230                    secondary_diff: None,
 231                }
 232            })
 233    }
 234
 235    #[cfg(test)]
 236    fn new_sync(
 237        buffer: text::BufferSnapshot,
 238        diff_base: String,
 239        cx: &mut gpui::TestAppContext,
 240    ) -> BufferDiffSnapshot {
 241        cx.executor().block(cx.update(|cx| {
 242            Self::new_with_base_text(buffer, Some(Arc::new(diff_base)), None, None, cx)
 243        }))
 244    }
 245
 246    pub fn is_empty(&self) -> bool {
 247        self.inner.hunks.is_empty()
 248    }
 249
 250    pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
 251        self.secondary_diff.as_deref()
 252    }
 253
 254    pub fn hunks_intersecting_range<'a>(
 255        &'a self,
 256        range: Range<Anchor>,
 257        buffer: &'a text::BufferSnapshot,
 258    ) -> impl 'a + Iterator<Item = DiffHunk> {
 259        let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
 260        self.inner
 261            .hunks_intersecting_range(range, buffer, unstaged_counterpart)
 262    }
 263
 264    pub fn hunks_intersecting_range_rev<'a>(
 265        &'a self,
 266        range: Range<Anchor>,
 267        buffer: &'a text::BufferSnapshot,
 268    ) -> impl 'a + Iterator<Item = DiffHunk> {
 269        self.inner.hunks_intersecting_range_rev(range, buffer)
 270    }
 271
 272    pub fn base_text(&self) -> &language::BufferSnapshot {
 273        &self.inner.base_text
 274    }
 275
 276    pub fn base_texts_eq(&self, other: &Self) -> bool {
 277        if self.inner.base_text_exists != other.inner.base_text_exists {
 278            return false;
 279        }
 280        let left = &self.inner.base_text;
 281        let right = &other.inner.base_text;
 282        let (old_id, old_empty) = (left.remote_id(), left.is_empty());
 283        let (new_id, new_empty) = (right.remote_id(), right.is_empty());
 284        new_id == old_id || (new_empty && old_empty)
 285    }
 286}
 287
 288impl BufferDiffInner {
 289    /// Returns the new index text and new pending hunks.
 290    fn stage_or_unstage_hunks_impl(
 291        &mut self,
 292        unstaged_diff: &Self,
 293        stage: bool,
 294        hunks: &[DiffHunk],
 295        buffer: &text::BufferSnapshot,
 296        file_exists: bool,
 297    ) -> Option<Rope> {
 298        let head_text = self
 299            .base_text_exists
 300            .then(|| self.base_text.as_rope().clone());
 301        let index_text = unstaged_diff
 302            .base_text_exists
 303            .then(|| unstaged_diff.base_text.as_rope().clone());
 304
 305        // If the file doesn't exist in either HEAD or the index, then the
 306        // entire file must be either created or deleted in the index.
 307        let (index_text, head_text) = match (index_text, head_text) {
 308            (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
 309            (index_text, head_text) => {
 310                let (new_index_text, new_status) = if stage {
 311                    log::debug!("stage all");
 312                    (
 313                        file_exists.then(|| buffer.as_rope().clone()),
 314                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
 315                    )
 316                } else {
 317                    log::debug!("unstage all");
 318                    (
 319                        head_text,
 320                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
 321                    )
 322                };
 323
 324                let hunk = PendingHunk {
 325                    buffer_range: Anchor::MIN..Anchor::MAX,
 326                    diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
 327                    buffer_version: buffer.version().clone(),
 328                    new_status,
 329                };
 330                self.pending_hunks = SumTree::from_item(hunk, buffer);
 331                return new_index_text;
 332            }
 333        };
 334
 335        let mut pending_hunks = SumTree::new(buffer);
 336        let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 337
 338        // first, merge new hunks into pending_hunks
 339        for DiffHunk {
 340            buffer_range,
 341            diff_base_byte_range,
 342            secondary_status,
 343            ..
 344        } in hunks.iter().cloned()
 345        {
 346            let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
 347            pending_hunks.append(preceding_pending_hunks, buffer);
 348
 349            // Skip all overlapping or adjacent old pending hunks
 350            while old_pending_hunks.item().is_some_and(|old_hunk| {
 351                old_hunk
 352                    .buffer_range
 353                    .start
 354                    .cmp(&buffer_range.end, buffer)
 355                    .is_le()
 356            }) {
 357                old_pending_hunks.next();
 358            }
 359
 360            if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
 361                || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
 362            {
 363                continue;
 364            }
 365
 366            pending_hunks.push(
 367                PendingHunk {
 368                    buffer_range,
 369                    diff_base_byte_range,
 370                    buffer_version: buffer.version().clone(),
 371                    new_status: if stage {
 372                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
 373                    } else {
 374                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
 375                    },
 376                },
 377                buffer,
 378            );
 379        }
 380        // append the remainder
 381        pending_hunks.append(old_pending_hunks.suffix(), buffer);
 382
 383        let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
 384        unstaged_hunk_cursor.next();
 385
 386        // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
 387        let mut prev_unstaged_hunk_buffer_end = 0;
 388        let mut prev_unstaged_hunk_base_text_end = 0;
 389        let mut edits = Vec::<(Range<usize>, String)>::new();
 390        let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
 391        while let Some(PendingHunk {
 392            buffer_range,
 393            diff_base_byte_range,
 394            new_status,
 395            ..
 396        }) = pending_hunks_iter.next()
 397        {
 398            // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
 399            let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
 400
 401            if let Some(unstaged_hunk) = skipped_unstaged.last() {
 402                prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 403                prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
 404            }
 405
 406            // Find where this hunk is in the index if it doesn't overlap
 407            let mut buffer_offset_range = buffer_range.to_offset(buffer);
 408            let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
 409            let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
 410
 411            loop {
 412                // Merge this hunk with any overlapping unstaged hunks.
 413                if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
 414                    let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
 415                    if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
 416                        prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 417                        prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
 418
 419                        index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
 420                        buffer_offset_range.start = buffer_offset_range
 421                            .start
 422                            .min(unstaged_hunk_offset_range.start);
 423                        buffer_offset_range.end =
 424                            buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
 425
 426                        unstaged_hunk_cursor.next();
 427                        continue;
 428                    }
 429                }
 430
 431                // If any unstaged hunks were merged, then subsequent pending hunks may
 432                // now overlap this hunk. Merge them.
 433                if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
 434                    let next_pending_hunk_offset_range =
 435                        next_pending_hunk.buffer_range.to_offset(buffer);
 436                    if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
 437                        buffer_offset_range.end = next_pending_hunk_offset_range.end;
 438                        pending_hunks_iter.next();
 439                        continue;
 440                    }
 441                }
 442
 443                break;
 444            }
 445
 446            let end_overshoot = buffer_offset_range
 447                .end
 448                .saturating_sub(prev_unstaged_hunk_buffer_end);
 449            let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
 450            let index_byte_range = index_start..index_end;
 451
 452            let replacement_text = match new_status {
 453                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
 454                    log::debug!("staging hunk {:?}", buffer_offset_range);
 455                    buffer
 456                        .text_for_range(buffer_offset_range)
 457                        .collect::<String>()
 458                }
 459                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
 460                    log::debug!("unstaging hunk {:?}", buffer_offset_range);
 461                    head_text
 462                        .chunks_in_range(diff_base_byte_range.clone())
 463                        .collect::<String>()
 464                }
 465                _ => {
 466                    debug_assert!(false);
 467                    continue;
 468                }
 469            };
 470
 471            edits.push((index_byte_range, replacement_text));
 472        }
 473        drop(pending_hunks_iter);
 474        drop(old_pending_hunks);
 475        self.pending_hunks = pending_hunks;
 476
 477        #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
 478        {
 479            for window in edits.windows(2) {
 480                let (range_a, range_b) = (&window[0].0, &window[1].0);
 481                debug_assert!(range_a.end < range_b.start);
 482            }
 483        }
 484
 485        let mut new_index_text = Rope::new();
 486        let mut index_cursor = index_text.cursor(0);
 487
 488        for (old_range, replacement_text) in edits {
 489            new_index_text.append(index_cursor.slice(old_range.start));
 490            index_cursor.seek_forward(old_range.end);
 491            new_index_text.push(&replacement_text);
 492        }
 493        new_index_text.append(index_cursor.suffix());
 494        Some(new_index_text)
 495    }
 496
 497    fn hunks_intersecting_range<'a>(
 498        &'a self,
 499        range: Range<Anchor>,
 500        buffer: &'a text::BufferSnapshot,
 501        secondary: Option<&'a Self>,
 502    ) -> impl 'a + Iterator<Item = DiffHunk> {
 503        let range = range.to_offset(buffer);
 504
 505        let mut cursor = self
 506            .hunks
 507            .filter::<_, DiffHunkSummary>(buffer, move |summary| {
 508                let summary_range = summary.buffer_range.to_offset(buffer);
 509                let before_start = summary_range.end < range.start;
 510                let after_end = summary_range.start > range.end;
 511                !before_start && !after_end
 512            });
 513
 514        let anchor_iter = iter::from_fn(move || {
 515            cursor.next();
 516            cursor.item()
 517        })
 518        .flat_map(move |hunk| {
 519            [
 520                (
 521                    &hunk.buffer_range.start,
 522                    (hunk.buffer_range.start, hunk.diff_base_byte_range.start),
 523                ),
 524                (
 525                    &hunk.buffer_range.end,
 526                    (hunk.buffer_range.end, hunk.diff_base_byte_range.end),
 527                ),
 528            ]
 529        });
 530
 531        let mut pending_hunks_cursor = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 532        pending_hunks_cursor.next();
 533
 534        let mut secondary_cursor = None;
 535        if let Some(secondary) = secondary.as_ref() {
 536            let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
 537            cursor.next();
 538            secondary_cursor = Some(cursor);
 539        }
 540
 541        let max_point = buffer.max_point();
 542        let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
 543        iter::from_fn(move || {
 544            loop {
 545                let (start_point, (start_anchor, start_base)) = summaries.next()?;
 546                let (mut end_point, (mut end_anchor, end_base)) = summaries.next()?;
 547
 548                if !start_anchor.is_valid(buffer) {
 549                    continue;
 550                }
 551
 552                if end_point.column > 0 && end_point < max_point {
 553                    end_point.row += 1;
 554                    end_point.column = 0;
 555                    end_anchor = buffer.anchor_before(end_point);
 556                }
 557
 558                let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
 559
 560                let mut has_pending = false;
 561                if start_anchor
 562                    .cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
 563                    .is_gt()
 564                {
 565                    pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
 566                }
 567
 568                if let Some(pending_hunk) = pending_hunks_cursor.item() {
 569                    let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
 570                    if pending_range.end.column > 0 {
 571                        pending_range.end.row += 1;
 572                        pending_range.end.column = 0;
 573                    }
 574
 575                    if pending_range == (start_point..end_point) {
 576                        if !buffer.has_edits_since_in_range(
 577                            &pending_hunk.buffer_version,
 578                            start_anchor..end_anchor,
 579                        ) {
 580                            has_pending = true;
 581                            secondary_status = pending_hunk.new_status;
 582                        }
 583                    }
 584                }
 585
 586                if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
 587                    if start_anchor
 588                        .cmp(&secondary_cursor.start().buffer_range.start, buffer)
 589                        .is_gt()
 590                    {
 591                        secondary_cursor.seek_forward(&start_anchor, Bias::Left);
 592                    }
 593
 594                    if let Some(secondary_hunk) = secondary_cursor.item() {
 595                        let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
 596                        if secondary_range.end.column > 0 {
 597                            secondary_range.end.row += 1;
 598                            secondary_range.end.column = 0;
 599                        }
 600                        if secondary_range.is_empty()
 601                            && secondary_hunk.diff_base_byte_range.is_empty()
 602                        {
 603                            // ignore
 604                        } else if secondary_range == (start_point..end_point) {
 605                            secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
 606                        } else if secondary_range.start <= end_point {
 607                            secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
 608                        }
 609                    }
 610                }
 611
 612                return Some(DiffHunk {
 613                    range: start_point..end_point,
 614                    diff_base_byte_range: start_base..end_base,
 615                    buffer_range: start_anchor..end_anchor,
 616                    secondary_status,
 617                });
 618            }
 619        })
 620    }
 621
 622    fn hunks_intersecting_range_rev<'a>(
 623        &'a self,
 624        range: Range<Anchor>,
 625        buffer: &'a text::BufferSnapshot,
 626    ) -> impl 'a + Iterator<Item = DiffHunk> {
 627        let mut cursor = self
 628            .hunks
 629            .filter::<_, DiffHunkSummary>(buffer, move |summary| {
 630                let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
 631                let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
 632                !before_start && !after_end
 633            });
 634
 635        iter::from_fn(move || {
 636            cursor.prev();
 637
 638            let hunk = cursor.item()?;
 639            let range = hunk.buffer_range.to_point(buffer);
 640
 641            Some(DiffHunk {
 642                range,
 643                diff_base_byte_range: hunk.diff_base_byte_range.clone(),
 644                buffer_range: hunk.buffer_range.clone(),
 645                // The secondary status is not used by callers of this method.
 646                secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
 647            })
 648        })
 649    }
 650
 651    fn compare(&self, old: &Self, new_snapshot: &text::BufferSnapshot) -> Option<Range<Anchor>> {
 652        let mut new_cursor = self.hunks.cursor::<()>(new_snapshot);
 653        let mut old_cursor = old.hunks.cursor::<()>(new_snapshot);
 654        old_cursor.next();
 655        new_cursor.next();
 656        let mut start = None;
 657        let mut end = None;
 658
 659        loop {
 660            match (new_cursor.item(), old_cursor.item()) {
 661                (Some(new_hunk), Some(old_hunk)) => {
 662                    match new_hunk
 663                        .buffer_range
 664                        .start
 665                        .cmp(&old_hunk.buffer_range.start, new_snapshot)
 666                    {
 667                        Ordering::Less => {
 668                            start.get_or_insert(new_hunk.buffer_range.start);
 669                            end.replace(new_hunk.buffer_range.end);
 670                            new_cursor.next();
 671                        }
 672                        Ordering::Equal => {
 673                            if new_hunk != old_hunk {
 674                                start.get_or_insert(new_hunk.buffer_range.start);
 675                                if old_hunk
 676                                    .buffer_range
 677                                    .end
 678                                    .cmp(&new_hunk.buffer_range.end, new_snapshot)
 679                                    .is_ge()
 680                                {
 681                                    end.replace(old_hunk.buffer_range.end);
 682                                } else {
 683                                    end.replace(new_hunk.buffer_range.end);
 684                                }
 685                            }
 686
 687                            new_cursor.next();
 688                            old_cursor.next();
 689                        }
 690                        Ordering::Greater => {
 691                            start.get_or_insert(old_hunk.buffer_range.start);
 692                            end.replace(old_hunk.buffer_range.end);
 693                            old_cursor.next();
 694                        }
 695                    }
 696                }
 697                (Some(new_hunk), None) => {
 698                    start.get_or_insert(new_hunk.buffer_range.start);
 699                    end.replace(new_hunk.buffer_range.end);
 700                    new_cursor.next();
 701                }
 702                (None, Some(old_hunk)) => {
 703                    start.get_or_insert(old_hunk.buffer_range.start);
 704                    end.replace(old_hunk.buffer_range.end);
 705                    old_cursor.next();
 706                }
 707                (None, None) => break,
 708            }
 709        }
 710
 711        start.zip(end).map(|(start, end)| start..end)
 712    }
 713}
 714
 715fn compute_hunks(
 716    diff_base: Option<(Arc<String>, Rope)>,
 717    buffer: text::BufferSnapshot,
 718) -> SumTree<InternalDiffHunk> {
 719    let mut tree = SumTree::new(&buffer);
 720
 721    if let Some((diff_base, diff_base_rope)) = diff_base {
 722        let buffer_text = buffer.as_rope().to_string();
 723
 724        let mut options = GitOptions::default();
 725        options.context_lines(0);
 726        let patch = GitPatch::from_buffers(
 727            diff_base.as_bytes(),
 728            None,
 729            buffer_text.as_bytes(),
 730            None,
 731            Some(&mut options),
 732        )
 733        .log_err();
 734
 735        // A common case in Zed is that the empty buffer is represented as just a newline,
 736        // but if we just compute a naive diff you get a "preserved" line in the middle,
 737        // which is a bit odd.
 738        if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
 739            tree.push(
 740                InternalDiffHunk {
 741                    buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
 742                    diff_base_byte_range: 0..diff_base.len() - 1,
 743                },
 744                &buffer,
 745            );
 746            return tree;
 747        }
 748
 749        if let Some(patch) = patch {
 750            let mut divergence = 0;
 751            for hunk_index in 0..patch.num_hunks() {
 752                let hunk = process_patch_hunk(
 753                    &patch,
 754                    hunk_index,
 755                    &diff_base_rope,
 756                    &buffer,
 757                    &mut divergence,
 758                );
 759                tree.push(hunk, &buffer);
 760            }
 761        }
 762    } else {
 763        tree.push(
 764            InternalDiffHunk {
 765                buffer_range: Anchor::MIN..Anchor::MAX,
 766                diff_base_byte_range: 0..0,
 767            },
 768            &buffer,
 769        );
 770    }
 771
 772    tree
 773}
 774
 775fn process_patch_hunk(
 776    patch: &GitPatch<'_>,
 777    hunk_index: usize,
 778    diff_base: &Rope,
 779    buffer: &text::BufferSnapshot,
 780    buffer_row_divergence: &mut i64,
 781) -> InternalDiffHunk {
 782    let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
 783    assert!(line_item_count > 0);
 784
 785    let mut first_deletion_buffer_row: Option<u32> = None;
 786    let mut buffer_row_range: Option<Range<u32>> = None;
 787    let mut diff_base_byte_range: Option<Range<usize>> = None;
 788    let mut first_addition_old_row: Option<u32> = None;
 789
 790    for line_index in 0..line_item_count {
 791        let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
 792        let kind = line.origin_value();
 793        let content_offset = line.content_offset() as isize;
 794        let content_len = line.content().len() as isize;
 795        match kind {
 796            GitDiffLineType::Addition => {
 797                if first_addition_old_row.is_none() {
 798                    first_addition_old_row = Some(
 799                        (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
 800                    );
 801                }
 802                *buffer_row_divergence += 1;
 803                let row = line.new_lineno().unwrap().saturating_sub(1);
 804
 805                match &mut buffer_row_range {
 806                    Some(Range { end, .. }) => *end = row + 1,
 807                    None => buffer_row_range = Some(row..row + 1),
 808                }
 809            }
 810            GitDiffLineType::Deletion => {
 811                let end = content_offset + content_len;
 812
 813                match &mut diff_base_byte_range {
 814                    Some(head_byte_range) => head_byte_range.end = end as usize,
 815                    None => diff_base_byte_range = Some(content_offset as usize..end as usize),
 816                }
 817
 818                if first_deletion_buffer_row.is_none() {
 819                    let old_row = line.old_lineno().unwrap().saturating_sub(1);
 820                    let row = old_row as i64 + *buffer_row_divergence;
 821                    first_deletion_buffer_row = Some(row as u32);
 822                }
 823
 824                *buffer_row_divergence -= 1;
 825            }
 826            _ => {}
 827        }
 828    }
 829
 830    let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
 831        // Pure deletion hunk without addition.
 832        let row = first_deletion_buffer_row.unwrap();
 833        row..row
 834    });
 835    let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
 836        // Pure addition hunk without deletion.
 837        let row = first_addition_old_row.unwrap();
 838        let offset = diff_base.point_to_offset(Point::new(row, 0));
 839        offset..offset
 840    });
 841
 842    let start = Point::new(buffer_row_range.start, 0);
 843    let end = Point::new(buffer_row_range.end, 0);
 844    let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
 845    InternalDiffHunk {
 846        buffer_range,
 847        diff_base_byte_range,
 848    }
 849}
 850
 851impl std::fmt::Debug for BufferDiff {
 852    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 853        f.debug_struct("BufferChangeSet")
 854            .field("buffer_id", &self.buffer_id)
 855            .field("snapshot", &self.inner)
 856            .finish()
 857    }
 858}
 859
 860#[derive(Clone, Debug)]
 861pub enum BufferDiffEvent {
 862    DiffChanged {
 863        changed_range: Option<Range<text::Anchor>>,
 864    },
 865    LanguageChanged,
 866    HunksStagedOrUnstaged(Option<Rope>),
 867}
 868
 869impl EventEmitter<BufferDiffEvent> for BufferDiff {}
 870
 871impl BufferDiff {
 872    pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
 873        BufferDiff {
 874            buffer_id: buffer.remote_id(),
 875            inner: BufferDiffSnapshot::empty(buffer, cx).inner,
 876            secondary_diff: None,
 877        }
 878    }
 879
 880    #[cfg(any(test, feature = "test-support"))]
 881    pub fn new_with_base_text(
 882        base_text: &str,
 883        buffer: &Entity<language::Buffer>,
 884        cx: &mut App,
 885    ) -> Self {
 886        let mut base_text = base_text.to_owned();
 887        text::LineEnding::normalize(&mut base_text);
 888        let snapshot = BufferDiffSnapshot::new_with_base_text(
 889            buffer.read(cx).text_snapshot(),
 890            Some(base_text.into()),
 891            None,
 892            None,
 893            cx,
 894        );
 895        let snapshot = cx.background_executor().block(snapshot);
 896        Self {
 897            buffer_id: buffer.read(cx).remote_id(),
 898            inner: snapshot.inner,
 899            secondary_diff: None,
 900        }
 901    }
 902
 903    pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
 904        self.secondary_diff = Some(diff);
 905    }
 906
 907    pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
 908        self.secondary_diff.clone()
 909    }
 910
 911    pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
 912        if self.secondary_diff.is_some() {
 913            self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary::default());
 914            cx.emit(BufferDiffEvent::DiffChanged {
 915                changed_range: Some(Anchor::MIN..Anchor::MAX),
 916            });
 917        }
 918    }
 919
 920    pub fn stage_or_unstage_hunks(
 921        &mut self,
 922        stage: bool,
 923        hunks: &[DiffHunk],
 924        buffer: &text::BufferSnapshot,
 925        file_exists: bool,
 926        cx: &mut Context<Self>,
 927    ) -> Option<Rope> {
 928        let new_index_text = self.inner.stage_or_unstage_hunks_impl(
 929            &self.secondary_diff.as_ref()?.read(cx).inner,
 930            stage,
 931            hunks,
 932            buffer,
 933            file_exists,
 934        );
 935
 936        cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
 937            new_index_text.clone(),
 938        ));
 939        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
 940            let changed_range = first.buffer_range.start..last.buffer_range.end;
 941            cx.emit(BufferDiffEvent::DiffChanged {
 942                changed_range: Some(changed_range),
 943            });
 944        }
 945        new_index_text
 946    }
 947
 948    pub fn range_to_hunk_range(
 949        &self,
 950        range: Range<Anchor>,
 951        buffer: &text::BufferSnapshot,
 952        cx: &App,
 953    ) -> Option<Range<Anchor>> {
 954        let start = self
 955            .hunks_intersecting_range(range.clone(), buffer, cx)
 956            .next()?
 957            .buffer_range
 958            .start;
 959        let end = self
 960            .hunks_intersecting_range_rev(range.clone(), buffer)
 961            .next()?
 962            .buffer_range
 963            .end;
 964        Some(start..end)
 965    }
 966
 967    pub async fn update_diff(
 968        this: Entity<BufferDiff>,
 969        buffer: text::BufferSnapshot,
 970        base_text: Option<Arc<String>>,
 971        base_text_changed: bool,
 972        language_changed: bool,
 973        language: Option<Arc<Language>>,
 974        language_registry: Option<Arc<LanguageRegistry>>,
 975        cx: &mut AsyncApp,
 976    ) -> anyhow::Result<BufferDiffSnapshot> {
 977        Ok(if base_text_changed || language_changed {
 978            cx.update(|cx| {
 979                BufferDiffSnapshot::new_with_base_text(
 980                    buffer.clone(),
 981                    base_text,
 982                    language.clone(),
 983                    language_registry.clone(),
 984                    cx,
 985                )
 986            })?
 987            .await
 988        } else {
 989            this.read_with(cx, |this, cx| {
 990                BufferDiffSnapshot::new_with_base_buffer(
 991                    buffer.clone(),
 992                    base_text,
 993                    this.base_text().clone(),
 994                    cx,
 995                )
 996            })?
 997            .await
 998        })
 999    }
1000
1001    pub fn language_changed(&mut self, cx: &mut Context<Self>) {
1002        cx.emit(BufferDiffEvent::LanguageChanged);
1003    }
1004
1005    pub fn set_snapshot(
1006        &mut self,
1007        new_snapshot: BufferDiffSnapshot,
1008        buffer: &text::BufferSnapshot,
1009        cx: &mut Context<Self>,
1010    ) -> Option<Range<Anchor>> {
1011        self.set_snapshot_with_secondary(new_snapshot, buffer, None, false, cx)
1012    }
1013
1014    pub fn set_snapshot_with_secondary(
1015        &mut self,
1016        new_snapshot: BufferDiffSnapshot,
1017        buffer: &text::BufferSnapshot,
1018        secondary_diff_change: Option<Range<Anchor>>,
1019        clear_pending_hunks: bool,
1020        cx: &mut Context<Self>,
1021    ) -> Option<Range<Anchor>> {
1022        log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1023
1024        let state = &mut self.inner;
1025        let new_state = new_snapshot.inner;
1026        let (base_text_changed, mut changed_range) =
1027            match (state.base_text_exists, new_state.base_text_exists) {
1028                (false, false) => (true, None),
1029                (true, true)
1030                    if state.base_text.remote_id() == new_state.base_text.remote_id()
1031                        && state.base_text.syntax_update_count()
1032                            == new_state.base_text.syntax_update_count() =>
1033                {
1034                    (false, new_state.compare(state, buffer))
1035                }
1036                _ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),
1037            };
1038
1039        if let Some(secondary_changed_range) = secondary_diff_change {
1040            if let Some(secondary_hunk_range) =
1041                self.range_to_hunk_range(secondary_changed_range, buffer, cx)
1042            {
1043                if let Some(range) = &mut changed_range {
1044                    range.start = secondary_hunk_range.start.min(&range.start, buffer);
1045                    range.end = secondary_hunk_range.end.max(&range.end, buffer);
1046                } else {
1047                    changed_range = Some(secondary_hunk_range);
1048                }
1049            }
1050        }
1051
1052        let state = &mut self.inner;
1053        state.base_text_exists = new_state.base_text_exists;
1054        state.base_text = new_state.base_text;
1055        state.hunks = new_state.hunks;
1056        if base_text_changed || clear_pending_hunks {
1057            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1058            {
1059                if let Some(range) = &mut changed_range {
1060                    range.start = range.start.min(&first.buffer_range.start, buffer);
1061                    range.end = range.end.max(&last.buffer_range.end, buffer);
1062                } else {
1063                    changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1064                }
1065            }
1066            state.pending_hunks = SumTree::new(buffer);
1067        }
1068
1069        cx.emit(BufferDiffEvent::DiffChanged {
1070            changed_range: changed_range.clone(),
1071        });
1072        changed_range
1073    }
1074
1075    pub fn base_text(&self) -> &language::BufferSnapshot {
1076        &self.inner.base_text
1077    }
1078
1079    pub fn base_text_exists(&self) -> bool {
1080        self.inner.base_text_exists
1081    }
1082
1083    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1084        BufferDiffSnapshot {
1085            inner: self.inner.clone(),
1086            secondary_diff: self
1087                .secondary_diff
1088                .as_ref()
1089                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1090        }
1091    }
1092
1093    pub fn hunks<'a>(
1094        &'a self,
1095        buffer_snapshot: &'a text::BufferSnapshot,
1096        cx: &'a App,
1097    ) -> impl 'a + Iterator<Item = DiffHunk> {
1098        self.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer_snapshot, cx)
1099    }
1100
1101    pub fn hunks_intersecting_range<'a>(
1102        &'a self,
1103        range: Range<text::Anchor>,
1104        buffer_snapshot: &'a text::BufferSnapshot,
1105        cx: &'a App,
1106    ) -> impl 'a + Iterator<Item = DiffHunk> {
1107        let unstaged_counterpart = self
1108            .secondary_diff
1109            .as_ref()
1110            .map(|diff| &diff.read(cx).inner);
1111        self.inner
1112            .hunks_intersecting_range(range, buffer_snapshot, unstaged_counterpart)
1113    }
1114
1115    pub fn hunks_intersecting_range_rev<'a>(
1116        &'a self,
1117        range: Range<text::Anchor>,
1118        buffer_snapshot: &'a text::BufferSnapshot,
1119    ) -> impl 'a + Iterator<Item = DiffHunk> {
1120        self.inner
1121            .hunks_intersecting_range_rev(range, buffer_snapshot)
1122    }
1123
1124    pub fn hunks_in_row_range<'a>(
1125        &'a self,
1126        range: Range<u32>,
1127        buffer: &'a text::BufferSnapshot,
1128        cx: &'a App,
1129    ) -> impl 'a + Iterator<Item = DiffHunk> {
1130        let start = buffer.anchor_before(Point::new(range.start, 0));
1131        let end = buffer.anchor_after(Point::new(range.end, 0));
1132        self.hunks_intersecting_range(start..end, buffer, cx)
1133    }
1134
1135    pub fn set_base_text_buffer(
1136        &mut self,
1137        base_buffer: Entity<language::Buffer>,
1138        buffer: text::BufferSnapshot,
1139        cx: &mut Context<Self>,
1140    ) -> oneshot::Receiver<()> {
1141        let base_buffer = base_buffer.read(cx);
1142        let language_registry = base_buffer.language_registry();
1143        let base_buffer = base_buffer.snapshot();
1144        self.set_base_text(base_buffer, language_registry, buffer, cx)
1145    }
1146
1147    /// Used in cases where the change set isn't derived from git.
1148    pub fn set_base_text(
1149        &mut self,
1150        base_buffer: language::BufferSnapshot,
1151        language_registry: Option<Arc<LanguageRegistry>>,
1152        buffer: text::BufferSnapshot,
1153        cx: &mut Context<Self>,
1154    ) -> oneshot::Receiver<()> {
1155        let (tx, rx) = oneshot::channel();
1156        let this = cx.weak_entity();
1157        let base_text = Arc::new(base_buffer.text());
1158
1159        let snapshot = BufferDiffSnapshot::new_with_base_text(
1160            buffer.clone(),
1161            Some(base_text),
1162            base_buffer.language().cloned(),
1163            language_registry,
1164            cx,
1165        );
1166        let complete_on_drop = util::defer(|| {
1167            tx.send(()).ok();
1168        });
1169        cx.spawn(async move |_, cx| {
1170            let snapshot = snapshot.await;
1171            let Some(this) = this.upgrade() else {
1172                return;
1173            };
1174            this.update(cx, |this, cx| {
1175                this.set_snapshot(snapshot, &buffer, cx);
1176            })
1177            .log_err();
1178            drop(complete_on_drop)
1179        })
1180        .detach();
1181        rx
1182    }
1183
1184    pub fn base_text_string(&self) -> Option<String> {
1185        self.inner
1186            .base_text_exists
1187            .then(|| self.inner.base_text.text())
1188    }
1189
1190    #[cfg(any(test, feature = "test-support"))]
1191    pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
1192        let base_text = self.base_text_string().map(Arc::new);
1193        let snapshot = BufferDiffSnapshot::new_with_base_buffer(
1194            buffer.clone(),
1195            base_text,
1196            self.inner.base_text.clone(),
1197            cx,
1198        );
1199        let snapshot = cx.background_executor().block(snapshot);
1200        self.set_snapshot(snapshot, &buffer, cx);
1201    }
1202}
1203
1204impl DiffHunk {
1205    pub fn is_created_file(&self) -> bool {
1206        self.diff_base_byte_range == (0..0) && self.buffer_range == (Anchor::MIN..Anchor::MAX)
1207    }
1208
1209    pub fn status(&self) -> DiffHunkStatus {
1210        let kind = if self.buffer_range.start == self.buffer_range.end {
1211            DiffHunkStatusKind::Deleted
1212        } else if self.diff_base_byte_range.is_empty() {
1213            DiffHunkStatusKind::Added
1214        } else {
1215            DiffHunkStatusKind::Modified
1216        };
1217        DiffHunkStatus {
1218            kind,
1219            secondary: self.secondary_status,
1220        }
1221    }
1222}
1223
1224impl DiffHunkStatus {
1225    pub fn has_secondary_hunk(&self) -> bool {
1226        matches!(
1227            self.secondary,
1228            DiffHunkSecondaryStatus::HasSecondaryHunk
1229                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1230                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1231        )
1232    }
1233
1234    pub fn is_pending(&self) -> bool {
1235        matches!(
1236            self.secondary,
1237            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1238                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1239        )
1240    }
1241
1242    pub fn is_deleted(&self) -> bool {
1243        self.kind == DiffHunkStatusKind::Deleted
1244    }
1245
1246    pub fn is_added(&self) -> bool {
1247        self.kind == DiffHunkStatusKind::Added
1248    }
1249
1250    pub fn is_modified(&self) -> bool {
1251        self.kind == DiffHunkStatusKind::Modified
1252    }
1253
1254    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1255        Self {
1256            kind: DiffHunkStatusKind::Added,
1257            secondary,
1258        }
1259    }
1260
1261    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1262        Self {
1263            kind: DiffHunkStatusKind::Modified,
1264            secondary,
1265        }
1266    }
1267
1268    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1269        Self {
1270            kind: DiffHunkStatusKind::Deleted,
1271            secondary,
1272        }
1273    }
1274
1275    pub fn deleted_none() -> Self {
1276        Self {
1277            kind: DiffHunkStatusKind::Deleted,
1278            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1279        }
1280    }
1281
1282    pub fn added_none() -> Self {
1283        Self {
1284            kind: DiffHunkStatusKind::Added,
1285            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1286        }
1287    }
1288
1289    pub fn modified_none() -> Self {
1290        Self {
1291            kind: DiffHunkStatusKind::Modified,
1292            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1293        }
1294    }
1295}
1296
1297#[cfg(any(test, feature = "test-support"))]
1298#[track_caller]
1299pub fn assert_hunks<ExpectedText, HunkIter>(
1300    diff_hunks: HunkIter,
1301    buffer: &text::BufferSnapshot,
1302    diff_base: &str,
1303    // Line range, deleted, added, status
1304    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1305) where
1306    HunkIter: Iterator<Item = DiffHunk>,
1307    ExpectedText: AsRef<str>,
1308{
1309    let actual_hunks = diff_hunks
1310        .map(|hunk| {
1311            (
1312                hunk.range.clone(),
1313                &diff_base[hunk.diff_base_byte_range.clone()],
1314                buffer
1315                    .text_for_range(hunk.range.clone())
1316                    .collect::<String>(),
1317                hunk.status(),
1318            )
1319        })
1320        .collect::<Vec<_>>();
1321
1322    let expected_hunks: Vec<_> = expected_hunks
1323        .iter()
1324        .map(|(line_range, deleted_text, added_text, status)| {
1325            (
1326                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1327                deleted_text.as_ref(),
1328                added_text.as_ref().to_string(),
1329                *status,
1330            )
1331        })
1332        .collect();
1333
1334    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1335}
1336
1337#[cfg(test)]
1338mod tests {
1339    use std::fmt::Write as _;
1340
1341    use super::*;
1342    use gpui::TestAppContext;
1343    use pretty_assertions::{assert_eq, assert_ne};
1344    use rand::{Rng as _, rngs::StdRng};
1345    use text::{Buffer, BufferId, Rope};
1346    use unindent::Unindent as _;
1347    use util::test::marked_text_ranges;
1348
1349    #[ctor::ctor]
1350    fn init_logger() {
1351        zlog::init_test();
1352    }
1353
1354    #[gpui::test]
1355    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1356        let diff_base = "
1357            one
1358            two
1359            three
1360        "
1361        .unindent();
1362
1363        let buffer_text = "
1364            one
1365            HELLO
1366            three
1367        "
1368        .unindent();
1369
1370        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1371        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1372        assert_hunks(
1373            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1374            &buffer,
1375            &diff_base,
1376            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1377        );
1378
1379        buffer.edit([(0..0, "point five\n")]);
1380        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1381        assert_hunks(
1382            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1383            &buffer,
1384            &diff_base,
1385            &[
1386                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1387                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1388            ],
1389        );
1390
1391        diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
1392        assert_hunks::<&str, _>(
1393            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1394            &buffer,
1395            &diff_base,
1396            &[],
1397        );
1398    }
1399
1400    #[gpui::test]
1401    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1402        let head_text = "
1403            zero
1404            one
1405            two
1406            three
1407            four
1408            five
1409            six
1410            seven
1411            eight
1412            nine
1413        "
1414        .unindent();
1415
1416        let index_text = "
1417            zero
1418            one
1419            TWO
1420            three
1421            FOUR
1422            five
1423            six
1424            seven
1425            eight
1426            NINE
1427        "
1428        .unindent();
1429
1430        let buffer_text = "
1431            zero
1432            one
1433            TWO
1434            three
1435            FOUR
1436            FIVE
1437            six
1438            SEVEN
1439            eight
1440            nine
1441        "
1442        .unindent();
1443
1444        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1445        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text.clone(), cx);
1446        let mut uncommitted_diff =
1447            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1448        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1449
1450        let expected_hunks = vec![
1451            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1452            (
1453                4..6,
1454                "four\nfive\n",
1455                "FOUR\nFIVE\n",
1456                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1457            ),
1458            (
1459                7..8,
1460                "seven\n",
1461                "SEVEN\n",
1462                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1463            ),
1464        ];
1465
1466        assert_hunks(
1467            uncommitted_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1468            &buffer,
1469            &head_text,
1470            &expected_hunks,
1471        );
1472    }
1473
1474    #[gpui::test]
1475    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1476        let diff_base = Arc::new(
1477            "
1478            one
1479            two
1480            three
1481            four
1482            five
1483            six
1484            seven
1485            eight
1486            nine
1487            ten
1488        "
1489            .unindent(),
1490        );
1491
1492        let buffer_text = "
1493            A
1494            one
1495            B
1496            two
1497            C
1498            three
1499            HELLO
1500            four
1501            five
1502            SIXTEEN
1503            seven
1504            eight
1505            WORLD
1506            nine
1507
1508            ten
1509
1510        "
1511        .unindent();
1512
1513        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1514        let diff = cx
1515            .update(|cx| {
1516                BufferDiffSnapshot::new_with_base_text(
1517                    buffer.snapshot(),
1518                    Some(diff_base.clone()),
1519                    None,
1520                    None,
1521                    cx,
1522                )
1523            })
1524            .await;
1525        assert_eq!(
1526            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer)
1527                .count(),
1528            8
1529        );
1530
1531        assert_hunks(
1532            diff.hunks_intersecting_range(
1533                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1534                &buffer,
1535            ),
1536            &buffer,
1537            &diff_base,
1538            &[
1539                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1540                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1541                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1542            ],
1543        );
1544    }
1545
1546    #[gpui::test]
1547    async fn test_stage_hunk(cx: &mut TestAppContext) {
1548        struct Example {
1549            name: &'static str,
1550            head_text: String,
1551            index_text: String,
1552            buffer_marked_text: String,
1553            final_index_text: String,
1554        }
1555
1556        let table = [
1557            Example {
1558                name: "uncommitted hunk straddles end of unstaged hunk",
1559                head_text: "
1560                    one
1561                    two
1562                    three
1563                    four
1564                    five
1565                "
1566                .unindent(),
1567                index_text: "
1568                    one
1569                    TWO_HUNDRED
1570                    three
1571                    FOUR_HUNDRED
1572                    five
1573                "
1574                .unindent(),
1575                buffer_marked_text: "
1576                    ZERO
1577                    one
1578                    two
1579                    «THREE_HUNDRED
1580                    FOUR_HUNDRED»
1581                    five
1582                    SIX
1583                "
1584                .unindent(),
1585                final_index_text: "
1586                    one
1587                    two
1588                    THREE_HUNDRED
1589                    FOUR_HUNDRED
1590                    five
1591                "
1592                .unindent(),
1593            },
1594            Example {
1595                name: "uncommitted hunk straddles start of unstaged hunk",
1596                head_text: "
1597                    one
1598                    two
1599                    three
1600                    four
1601                    five
1602                "
1603                .unindent(),
1604                index_text: "
1605                    one
1606                    TWO_HUNDRED
1607                    three
1608                    FOUR_HUNDRED
1609                    five
1610                "
1611                .unindent(),
1612                buffer_marked_text: "
1613                    ZERO
1614                    one
1615                    «TWO_HUNDRED
1616                    THREE_HUNDRED»
1617                    four
1618                    five
1619                    SIX
1620                "
1621                .unindent(),
1622                final_index_text: "
1623                    one
1624                    TWO_HUNDRED
1625                    THREE_HUNDRED
1626                    four
1627                    five
1628                "
1629                .unindent(),
1630            },
1631            Example {
1632                name: "uncommitted hunk strictly contains unstaged hunks",
1633                head_text: "
1634                    one
1635                    two
1636                    three
1637                    four
1638                    five
1639                    six
1640                    seven
1641                "
1642                .unindent(),
1643                index_text: "
1644                    one
1645                    TWO
1646                    THREE
1647                    FOUR
1648                    FIVE
1649                    SIX
1650                    seven
1651                "
1652                .unindent(),
1653                buffer_marked_text: "
1654                    one
1655                    TWO
1656                    «THREE_HUNDRED
1657                    FOUR
1658                    FIVE_HUNDRED»
1659                    SIX
1660                    seven
1661                "
1662                .unindent(),
1663                final_index_text: "
1664                    one
1665                    TWO
1666                    THREE_HUNDRED
1667                    FOUR
1668                    FIVE_HUNDRED
1669                    SIX
1670                    seven
1671                "
1672                .unindent(),
1673            },
1674            Example {
1675                name: "uncommitted deletion hunk",
1676                head_text: "
1677                    one
1678                    two
1679                    three
1680                    four
1681                    five
1682                "
1683                .unindent(),
1684                index_text: "
1685                    one
1686                    two
1687                    three
1688                    four
1689                    five
1690                "
1691                .unindent(),
1692                buffer_marked_text: "
1693                    one
1694                    ˇfive
1695                "
1696                .unindent(),
1697                final_index_text: "
1698                    one
1699                    five
1700                "
1701                .unindent(),
1702            },
1703            Example {
1704                name: "one unstaged hunk that contains two uncommitted hunks",
1705                head_text: "
1706                    one
1707                    two
1708
1709                    three
1710                    four
1711                "
1712                .unindent(),
1713                index_text: "
1714                    one
1715                    two
1716                    three
1717                    four
1718                "
1719                .unindent(),
1720                buffer_marked_text: "
1721                    «one
1722
1723                    three // modified
1724                    four»
1725                "
1726                .unindent(),
1727                final_index_text: "
1728                    one
1729
1730                    three // modified
1731                    four
1732                "
1733                .unindent(),
1734            },
1735            Example {
1736                name: "one uncommitted hunk that contains two unstaged hunks",
1737                head_text: "
1738                    one
1739                    two
1740                    three
1741                    four
1742                    five
1743                "
1744                .unindent(),
1745                index_text: "
1746                    ZERO
1747                    one
1748                    TWO
1749                    THREE
1750                    FOUR
1751                    five
1752                "
1753                .unindent(),
1754                buffer_marked_text: "
1755                    «one
1756                    TWO_HUNDRED
1757                    THREE
1758                    FOUR_HUNDRED
1759                    five»
1760                "
1761                .unindent(),
1762                final_index_text: "
1763                    ZERO
1764                    one
1765                    TWO_HUNDRED
1766                    THREE
1767                    FOUR_HUNDRED
1768                    five
1769                "
1770                .unindent(),
1771            },
1772        ];
1773
1774        for example in table {
1775            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
1776            let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1777            let hunk_range =
1778                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
1779
1780            let unstaged =
1781                BufferDiffSnapshot::new_sync(buffer.clone(), example.index_text.clone(), cx);
1782            let uncommitted =
1783                BufferDiffSnapshot::new_sync(buffer.clone(), example.head_text.clone(), cx);
1784
1785            let unstaged_diff = cx.new(|cx| {
1786                let mut diff = BufferDiff::new(&buffer, cx);
1787                diff.set_snapshot(unstaged, &buffer, cx);
1788                diff
1789            });
1790
1791            let uncommitted_diff = cx.new(|cx| {
1792                let mut diff = BufferDiff::new(&buffer, cx);
1793                diff.set_snapshot(uncommitted, &buffer, cx);
1794                diff.set_secondary_diff(unstaged_diff);
1795                diff
1796            });
1797
1798            uncommitted_diff.update(cx, |diff, cx| {
1799                let hunks = diff
1800                    .hunks_intersecting_range(hunk_range.clone(), &buffer, cx)
1801                    .collect::<Vec<_>>();
1802                for hunk in &hunks {
1803                    assert_ne!(
1804                        hunk.secondary_status,
1805                        DiffHunkSecondaryStatus::NoSecondaryHunk
1806                    )
1807                }
1808
1809                let new_index_text = diff
1810                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
1811                    .unwrap()
1812                    .to_string();
1813
1814                let hunks = diff
1815                    .hunks_intersecting_range(hunk_range.clone(), &buffer, cx)
1816                    .collect::<Vec<_>>();
1817                for hunk in &hunks {
1818                    assert_eq!(
1819                        hunk.secondary_status,
1820                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1821                    )
1822                }
1823
1824                pretty_assertions::assert_eq!(
1825                    new_index_text,
1826                    example.final_index_text,
1827                    "example: {}",
1828                    example.name
1829                );
1830            });
1831        }
1832    }
1833
1834    #[gpui::test]
1835    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
1836        let head_text = "
1837            one
1838            two
1839            three
1840        "
1841        .unindent();
1842        let index_text = head_text.clone();
1843        let buffer_text = "
1844            one
1845            three
1846        "
1847        .unindent();
1848
1849        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text.clone());
1850        let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1851        let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1852        let unstaged_diff = cx.new(|cx| {
1853            let mut diff = BufferDiff::new(&buffer, cx);
1854            diff.set_snapshot(unstaged, &buffer, cx);
1855            diff
1856        });
1857        let uncommitted_diff = cx.new(|cx| {
1858            let mut diff = BufferDiff::new(&buffer, cx);
1859            diff.set_snapshot(uncommitted, &buffer, cx);
1860            diff.set_secondary_diff(unstaged_diff.clone());
1861            diff
1862        });
1863
1864        uncommitted_diff.update(cx, |diff, cx| {
1865            let hunk = diff.hunks(&buffer, cx).next().unwrap();
1866
1867            let new_index_text = diff
1868                .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
1869                .unwrap()
1870                .to_string();
1871            assert_eq!(new_index_text, buffer_text);
1872
1873            let hunk = diff.hunks(&buffer, cx).next().unwrap();
1874            assert_eq!(
1875                hunk.secondary_status,
1876                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1877            );
1878
1879            let index_text = diff
1880                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
1881                .unwrap()
1882                .to_string();
1883            assert_eq!(index_text, head_text);
1884
1885            let hunk = diff.hunks(&buffer, cx).next().unwrap();
1886            // optimistically unstaged (fine, could also be HasSecondaryHunk)
1887            assert_eq!(
1888                hunk.secondary_status,
1889                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1890            );
1891        });
1892    }
1893
1894    #[gpui::test]
1895    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
1896        let base_text = "
1897            zero
1898            one
1899            two
1900            three
1901            four
1902            five
1903            six
1904            seven
1905            eight
1906            nine
1907        "
1908        .unindent();
1909
1910        let buffer_text_1 = "
1911            one
1912            three
1913            four
1914            five
1915            SIX
1916            seven
1917            eight
1918            NINE
1919        "
1920        .unindent();
1921
1922        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
1923
1924        let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
1925        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1926        let range = diff_1.inner.compare(&empty_diff.inner, &buffer).unwrap();
1927        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
1928
1929        // Edit does not affect the diff.
1930        buffer.edit_via_marked_text(
1931            &"
1932                one
1933                three
1934                four
1935                five
1936                «SIX.5»
1937                seven
1938                eight
1939                NINE
1940            "
1941            .unindent(),
1942        );
1943        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1944        assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer));
1945
1946        // Edit turns a deletion hunk into a modification.
1947        buffer.edit_via_marked_text(
1948            &"
1949                one
1950                «THREE»
1951                four
1952                five
1953                SIX.5
1954                seven
1955                eight
1956                NINE
1957            "
1958            .unindent(),
1959        );
1960        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1961        let range = diff_3.inner.compare(&diff_2.inner, &buffer).unwrap();
1962        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
1963
1964        // Edit turns a modification hunk into a deletion.
1965        buffer.edit_via_marked_text(
1966            &"
1967                one
1968                THREE
1969                four
1970                five«»
1971                seven
1972                eight
1973                NINE
1974            "
1975            .unindent(),
1976        );
1977        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1978        let range = diff_4.inner.compare(&diff_3.inner, &buffer).unwrap();
1979        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
1980
1981        // Edit introduces a new insertion hunk.
1982        buffer.edit_via_marked_text(
1983            &"
1984                one
1985                THREE
1986                four«
1987                FOUR.5
1988                »five
1989                seven
1990                eight
1991                NINE
1992            "
1993            .unindent(),
1994        );
1995        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
1996        let range = diff_5.inner.compare(&diff_4.inner, &buffer).unwrap();
1997        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
1998
1999        // Edit removes a hunk.
2000        buffer.edit_via_marked_text(
2001            &"
2002                one
2003                THREE
2004                four
2005                FOUR.5
2006                five
2007                seven
2008                eight
2009                «nine»
2010            "
2011            .unindent(),
2012        );
2013        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2014        let range = diff_6.inner.compare(&diff_5.inner, &buffer).unwrap();
2015        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2016    }
2017
2018    #[gpui::test(iterations = 100)]
2019    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2020        fn gen_line(rng: &mut StdRng) -> String {
2021            if rng.gen_bool(0.2) {
2022                "\n".to_owned()
2023            } else {
2024                let c = rng.gen_range('A'..='Z');
2025                format!("{c}{c}{c}\n")
2026            }
2027        }
2028
2029        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2030            let mut old_lines = {
2031                let mut old_lines = Vec::new();
2032                let mut old_lines_iter = head.lines();
2033                while let Some(line) = old_lines_iter.next() {
2034                    assert!(!line.ends_with("\n"));
2035                    old_lines.push(line.to_owned());
2036                }
2037                if old_lines.last().is_some_and(|line| line.is_empty()) {
2038                    old_lines.pop();
2039                }
2040                old_lines.into_iter()
2041            };
2042            let mut result = String::new();
2043            let unchanged_count = rng.gen_range(0..=old_lines.len());
2044            result +=
2045                &old_lines
2046                    .by_ref()
2047                    .take(unchanged_count)
2048                    .fold(String::new(), |mut s, line| {
2049                        writeln!(&mut s, "{line}").unwrap();
2050                        s
2051                    });
2052            while old_lines.len() > 0 {
2053                let deleted_count = rng.gen_range(0..=old_lines.len());
2054                let _advance = old_lines
2055                    .by_ref()
2056                    .take(deleted_count)
2057                    .map(|line| line.len() + 1)
2058                    .sum::<usize>();
2059                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2060                let added_count = rng.gen_range(minimum_added..=5);
2061                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2062                result += &addition;
2063
2064                if old_lines.len() > 0 {
2065                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2066                    if blank_lines == old_lines.len() {
2067                        break;
2068                    };
2069                    let unchanged_count = rng.gen_range((blank_lines + 1).max(1)..=old_lines.len());
2070                    result += &old_lines.by_ref().take(unchanged_count).fold(
2071                        String::new(),
2072                        |mut s, line| {
2073                            writeln!(&mut s, "{line}").unwrap();
2074                            s
2075                        },
2076                    );
2077                }
2078            }
2079            result
2080        }
2081
2082        fn uncommitted_diff(
2083            working_copy: &language::BufferSnapshot,
2084            index_text: &Rope,
2085            head_text: String,
2086            cx: &mut TestAppContext,
2087        ) -> Entity<BufferDiff> {
2088            let inner =
2089                BufferDiffSnapshot::new_sync(working_copy.text.clone(), head_text, cx).inner;
2090            let secondary = BufferDiff {
2091                buffer_id: working_copy.remote_id(),
2092                inner: BufferDiffSnapshot::new_sync(
2093                    working_copy.text.clone(),
2094                    index_text.to_string(),
2095                    cx,
2096                )
2097                .inner,
2098                secondary_diff: None,
2099            };
2100            let secondary = cx.new(|_| secondary);
2101            cx.new(|_| BufferDiff {
2102                buffer_id: working_copy.remote_id(),
2103                inner,
2104                secondary_diff: Some(secondary),
2105            })
2106        }
2107
2108        let operations = std::env::var("OPERATIONS")
2109            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2110            .unwrap_or(10);
2111
2112        let rng = &mut rng;
2113        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2114            writeln!(&mut s, "{c}{c}{c}").unwrap();
2115            s
2116        });
2117        let working_copy = gen_working_copy(rng, &head_text);
2118        let working_copy = cx.new(|cx| {
2119            language::Buffer::local_normalized(
2120                Rope::from(working_copy.as_str()),
2121                text::LineEnding::default(),
2122                cx,
2123            )
2124        });
2125        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2126        let mut index_text = if rng.r#gen() {
2127            Rope::from(head_text.as_str())
2128        } else {
2129            working_copy.as_rope().clone()
2130        };
2131
2132        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2133        let mut hunks = diff.update(cx, |diff, cx| {
2134            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
2135                .collect::<Vec<_>>()
2136        });
2137        if hunks.len() == 0 {
2138            return;
2139        }
2140
2141        for _ in 0..operations {
2142            let i = rng.gen_range(0..hunks.len());
2143            let hunk = &mut hunks[i];
2144            let hunk_to_change = hunk.clone();
2145            let stage = match hunk.secondary_status {
2146                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2147                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2148                    true
2149                }
2150                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2151                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2152                    false
2153                }
2154                _ => unreachable!(),
2155            };
2156
2157            index_text = diff.update(cx, |diff, cx| {
2158                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2159                    .unwrap()
2160            });
2161
2162            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2163            let found_hunks = diff.update(cx, |diff, cx| {
2164                diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
2165                    .collect::<Vec<_>>()
2166            });
2167            assert_eq!(hunks.len(), found_hunks.len());
2168
2169            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2170                assert_eq!(
2171                    expected_hunk.buffer_range.to_point(&working_copy),
2172                    found_hunk.buffer_range.to_point(&working_copy)
2173                );
2174                assert_eq!(
2175                    expected_hunk.diff_base_byte_range,
2176                    found_hunk.diff_base_byte_range
2177                );
2178                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2179            }
2180            hunks = found_hunks;
2181        }
2182    }
2183}