buffer_diff.rs

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