buffer_diff.rs

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