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