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