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) if state.base_text.remote_id() == new_state.base_text.remote_id() => {
1032                    (false, new_state.compare(&state, buffer))
1033                }
1034                _ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),
1035            };
1036
1037        if let Some(secondary_changed_range) = secondary_diff_change {
1038            if let Some(secondary_hunk_range) =
1039                self.range_to_hunk_range(secondary_changed_range, &buffer, cx)
1040            {
1041                if let Some(range) = &mut changed_range {
1042                    range.start = secondary_hunk_range.start.min(&range.start, &buffer);
1043                    range.end = secondary_hunk_range.end.max(&range.end, &buffer);
1044                } else {
1045                    changed_range = Some(secondary_hunk_range);
1046                }
1047            }
1048        }
1049
1050        let state = &mut self.inner;
1051        state.base_text_exists = new_state.base_text_exists;
1052        state.base_text = new_state.base_text;
1053        state.hunks = new_state.hunks;
1054        if base_text_changed || clear_pending_hunks {
1055            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1056            {
1057                if let Some(range) = &mut changed_range {
1058                    range.start = range.start.min(&first.buffer_range.start, &buffer);
1059                    range.end = range.end.max(&last.buffer_range.end, &buffer);
1060                } else {
1061                    changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1062                }
1063            }
1064            state.pending_hunks = SumTree::new(buffer);
1065        }
1066
1067        cx.emit(BufferDiffEvent::DiffChanged {
1068            changed_range: changed_range.clone(),
1069        });
1070        changed_range
1071    }
1072
1073    pub fn base_text(&self) -> &language::BufferSnapshot {
1074        &self.inner.base_text
1075    }
1076
1077    pub fn base_text_exists(&self) -> bool {
1078        self.inner.base_text_exists
1079    }
1080
1081    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1082        BufferDiffSnapshot {
1083            inner: self.inner.clone(),
1084            secondary_diff: self
1085                .secondary_diff
1086                .as_ref()
1087                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1088        }
1089    }
1090
1091    pub fn hunks<'a>(
1092        &'a self,
1093        buffer_snapshot: &'a text::BufferSnapshot,
1094        cx: &'a App,
1095    ) -> impl 'a + Iterator<Item = DiffHunk> {
1096        self.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer_snapshot, cx)
1097    }
1098
1099    pub fn hunks_intersecting_range<'a>(
1100        &'a self,
1101        range: Range<text::Anchor>,
1102        buffer_snapshot: &'a text::BufferSnapshot,
1103        cx: &'a App,
1104    ) -> impl 'a + Iterator<Item = DiffHunk> {
1105        let unstaged_counterpart = self
1106            .secondary_diff
1107            .as_ref()
1108            .map(|diff| &diff.read(cx).inner);
1109        self.inner
1110            .hunks_intersecting_range(range, buffer_snapshot, unstaged_counterpart)
1111    }
1112
1113    pub fn hunks_intersecting_range_rev<'a>(
1114        &'a self,
1115        range: Range<text::Anchor>,
1116        buffer_snapshot: &'a text::BufferSnapshot,
1117    ) -> impl 'a + Iterator<Item = DiffHunk> {
1118        self.inner
1119            .hunks_intersecting_range_rev(range, buffer_snapshot)
1120    }
1121
1122    pub fn hunks_in_row_range<'a>(
1123        &'a self,
1124        range: Range<u32>,
1125        buffer: &'a text::BufferSnapshot,
1126        cx: &'a App,
1127    ) -> impl 'a + Iterator<Item = DiffHunk> {
1128        let start = buffer.anchor_before(Point::new(range.start, 0));
1129        let end = buffer.anchor_after(Point::new(range.end, 0));
1130        self.hunks_intersecting_range(start..end, buffer, cx)
1131    }
1132
1133    pub fn set_base_text_buffer(
1134        &mut self,
1135        base_buffer: Entity<language::Buffer>,
1136        buffer: text::BufferSnapshot,
1137        cx: &mut Context<Self>,
1138    ) -> oneshot::Receiver<()> {
1139        let base_buffer = base_buffer.read(cx);
1140        let language_registry = base_buffer.language_registry();
1141        let base_buffer = base_buffer.snapshot();
1142        self.set_base_text(base_buffer, language_registry, buffer, cx)
1143    }
1144
1145    /// Used in cases where the change set isn't derived from git.
1146    pub fn set_base_text(
1147        &mut self,
1148        base_buffer: language::BufferSnapshot,
1149        language_registry: Option<Arc<LanguageRegistry>>,
1150        buffer: text::BufferSnapshot,
1151        cx: &mut Context<Self>,
1152    ) -> oneshot::Receiver<()> {
1153        let (tx, rx) = oneshot::channel();
1154        let this = cx.weak_entity();
1155        let base_text = Arc::new(base_buffer.text());
1156
1157        let snapshot = BufferDiffSnapshot::new_with_base_text(
1158            buffer.clone(),
1159            Some(base_text),
1160            base_buffer.language().cloned(),
1161            language_registry,
1162            cx,
1163        );
1164        let complete_on_drop = util::defer(|| {
1165            tx.send(()).ok();
1166        });
1167        cx.spawn(async move |_, cx| {
1168            let snapshot = snapshot.await;
1169            let Some(this) = this.upgrade() else {
1170                return;
1171            };
1172            this.update(cx, |this, cx| {
1173                this.set_snapshot(snapshot, &buffer, cx);
1174            })
1175            .log_err();
1176            drop(complete_on_drop)
1177        })
1178        .detach();
1179        rx
1180    }
1181
1182    pub fn base_text_string(&self) -> Option<String> {
1183        self.inner
1184            .base_text_exists
1185            .then(|| self.inner.base_text.text())
1186    }
1187
1188    #[cfg(any(test, feature = "test-support"))]
1189    pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
1190        let base_text = self.base_text_string().map(Arc::new);
1191        let snapshot = BufferDiffSnapshot::new_with_base_buffer(
1192            buffer.clone(),
1193            base_text,
1194            self.inner.base_text.clone(),
1195            cx,
1196        );
1197        let snapshot = cx.background_executor().block(snapshot);
1198        self.set_snapshot(snapshot, &buffer, cx);
1199    }
1200}
1201
1202impl DiffHunk {
1203    pub fn is_created_file(&self) -> bool {
1204        self.diff_base_byte_range == (0..0) && self.buffer_range == (Anchor::MIN..Anchor::MAX)
1205    }
1206
1207    pub fn status(&self) -> DiffHunkStatus {
1208        let kind = if self.buffer_range.start == self.buffer_range.end {
1209            DiffHunkStatusKind::Deleted
1210        } else if self.diff_base_byte_range.is_empty() {
1211            DiffHunkStatusKind::Added
1212        } else {
1213            DiffHunkStatusKind::Modified
1214        };
1215        DiffHunkStatus {
1216            kind,
1217            secondary: self.secondary_status,
1218        }
1219    }
1220}
1221
1222impl DiffHunkStatus {
1223    pub fn has_secondary_hunk(&self) -> bool {
1224        matches!(
1225            self.secondary,
1226            DiffHunkSecondaryStatus::HasSecondaryHunk
1227                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1228                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1229        )
1230    }
1231
1232    pub fn is_pending(&self) -> bool {
1233        matches!(
1234            self.secondary,
1235            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1236                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1237        )
1238    }
1239
1240    pub fn is_deleted(&self) -> bool {
1241        self.kind == DiffHunkStatusKind::Deleted
1242    }
1243
1244    pub fn is_added(&self) -> bool {
1245        self.kind == DiffHunkStatusKind::Added
1246    }
1247
1248    pub fn is_modified(&self) -> bool {
1249        self.kind == DiffHunkStatusKind::Modified
1250    }
1251
1252    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1253        Self {
1254            kind: DiffHunkStatusKind::Added,
1255            secondary,
1256        }
1257    }
1258
1259    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1260        Self {
1261            kind: DiffHunkStatusKind::Modified,
1262            secondary,
1263        }
1264    }
1265
1266    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1267        Self {
1268            kind: DiffHunkStatusKind::Deleted,
1269            secondary,
1270        }
1271    }
1272
1273    pub fn deleted_none() -> Self {
1274        Self {
1275            kind: DiffHunkStatusKind::Deleted,
1276            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1277        }
1278    }
1279
1280    pub fn added_none() -> Self {
1281        Self {
1282            kind: DiffHunkStatusKind::Added,
1283            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1284        }
1285    }
1286
1287    pub fn modified_none() -> Self {
1288        Self {
1289            kind: DiffHunkStatusKind::Modified,
1290            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1291        }
1292    }
1293}
1294
1295#[cfg(any(test, feature = "test-support"))]
1296#[track_caller]
1297pub fn assert_hunks<ExpectedText, HunkIter>(
1298    diff_hunks: HunkIter,
1299    buffer: &text::BufferSnapshot,
1300    diff_base: &str,
1301    // Line range, deleted, added, status
1302    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1303) where
1304    HunkIter: Iterator<Item = DiffHunk>,
1305    ExpectedText: AsRef<str>,
1306{
1307    let actual_hunks = diff_hunks
1308        .map(|hunk| {
1309            (
1310                hunk.range.clone(),
1311                &diff_base[hunk.diff_base_byte_range.clone()],
1312                buffer
1313                    .text_for_range(hunk.range.clone())
1314                    .collect::<String>(),
1315                hunk.status(),
1316            )
1317        })
1318        .collect::<Vec<_>>();
1319
1320    let expected_hunks: Vec<_> = expected_hunks
1321        .iter()
1322        .map(|(line_range, deleted_text, added_text, status)| {
1323            (
1324                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1325                deleted_text.as_ref(),
1326                added_text.as_ref().to_string(),
1327                *status,
1328            )
1329        })
1330        .collect();
1331
1332    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1333}
1334
1335#[cfg(test)]
1336mod tests {
1337    use std::fmt::Write as _;
1338
1339    use super::*;
1340    use gpui::TestAppContext;
1341    use pretty_assertions::{assert_eq, assert_ne};
1342    use rand::{Rng as _, rngs::StdRng};
1343    use text::{Buffer, BufferId, Rope};
1344    use unindent::Unindent as _;
1345    use util::test::marked_text_ranges;
1346
1347    #[ctor::ctor]
1348    fn init_logger() {
1349        zlog::init_test();
1350    }
1351
1352    #[gpui::test]
1353    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1354        let diff_base = "
1355            one
1356            two
1357            three
1358        "
1359        .unindent();
1360
1361        let buffer_text = "
1362            one
1363            HELLO
1364            three
1365        "
1366        .unindent();
1367
1368        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1369        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1370        assert_hunks(
1371            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1372            &buffer,
1373            &diff_base,
1374            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1375        );
1376
1377        buffer.edit([(0..0, "point five\n")]);
1378        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1379        assert_hunks(
1380            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1381            &buffer,
1382            &diff_base,
1383            &[
1384                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1385                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1386            ],
1387        );
1388
1389        diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
1390        assert_hunks::<&str, _>(
1391            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1392            &buffer,
1393            &diff_base,
1394            &[],
1395        );
1396    }
1397
1398    #[gpui::test]
1399    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1400        let head_text = "
1401            zero
1402            one
1403            two
1404            three
1405            four
1406            five
1407            six
1408            seven
1409            eight
1410            nine
1411        "
1412        .unindent();
1413
1414        let index_text = "
1415            zero
1416            one
1417            TWO
1418            three
1419            FOUR
1420            five
1421            six
1422            seven
1423            eight
1424            NINE
1425        "
1426        .unindent();
1427
1428        let buffer_text = "
1429            zero
1430            one
1431            TWO
1432            three
1433            FOUR
1434            FIVE
1435            six
1436            SEVEN
1437            eight
1438            nine
1439        "
1440        .unindent();
1441
1442        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1443        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text.clone(), cx);
1444        let mut uncommitted_diff =
1445            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1446        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1447
1448        let expected_hunks = vec![
1449            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1450            (
1451                4..6,
1452                "four\nfive\n",
1453                "FOUR\nFIVE\n",
1454                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1455            ),
1456            (
1457                7..8,
1458                "seven\n",
1459                "SEVEN\n",
1460                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1461            ),
1462        ];
1463
1464        assert_hunks(
1465            uncommitted_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer),
1466            &buffer,
1467            &head_text,
1468            &expected_hunks,
1469        );
1470    }
1471
1472    #[gpui::test]
1473    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1474        let diff_base = Arc::new(
1475            "
1476            one
1477            two
1478            three
1479            four
1480            five
1481            six
1482            seven
1483            eight
1484            nine
1485            ten
1486        "
1487            .unindent(),
1488        );
1489
1490        let buffer_text = "
1491            A
1492            one
1493            B
1494            two
1495            C
1496            three
1497            HELLO
1498            four
1499            five
1500            SIXTEEN
1501            seven
1502            eight
1503            WORLD
1504            nine
1505
1506            ten
1507
1508        "
1509        .unindent();
1510
1511        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1512        let diff = cx
1513            .update(|cx| {
1514                BufferDiffSnapshot::new_with_base_text(
1515                    buffer.snapshot(),
1516                    Some(diff_base.clone()),
1517                    None,
1518                    None,
1519                    cx,
1520                )
1521            })
1522            .await;
1523        assert_eq!(
1524            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer)
1525                .count(),
1526            8
1527        );
1528
1529        assert_hunks(
1530            diff.hunks_intersecting_range(
1531                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1532                &buffer,
1533            ),
1534            &buffer,
1535            &diff_base,
1536            &[
1537                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1538                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1539                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1540            ],
1541        );
1542    }
1543
1544    #[gpui::test]
1545    async fn test_stage_hunk(cx: &mut TestAppContext) {
1546        struct Example {
1547            name: &'static str,
1548            head_text: String,
1549            index_text: String,
1550            buffer_marked_text: String,
1551            final_index_text: String,
1552        }
1553
1554        let table = [
1555            Example {
1556                name: "uncommitted hunk straddles end of unstaged hunk",
1557                head_text: "
1558                    one
1559                    two
1560                    three
1561                    four
1562                    five
1563                "
1564                .unindent(),
1565                index_text: "
1566                    one
1567                    TWO_HUNDRED
1568                    three
1569                    FOUR_HUNDRED
1570                    five
1571                "
1572                .unindent(),
1573                buffer_marked_text: "
1574                    ZERO
1575                    one
1576                    two
1577                    «THREE_HUNDRED
1578                    FOUR_HUNDRED»
1579                    five
1580                    SIX
1581                "
1582                .unindent(),
1583                final_index_text: "
1584                    one
1585                    two
1586                    THREE_HUNDRED
1587                    FOUR_HUNDRED
1588                    five
1589                "
1590                .unindent(),
1591            },
1592            Example {
1593                name: "uncommitted hunk straddles start of unstaged hunk",
1594                head_text: "
1595                    one
1596                    two
1597                    three
1598                    four
1599                    five
1600                "
1601                .unindent(),
1602                index_text: "
1603                    one
1604                    TWO_HUNDRED
1605                    three
1606                    FOUR_HUNDRED
1607                    five
1608                "
1609                .unindent(),
1610                buffer_marked_text: "
1611                    ZERO
1612                    one
1613                    «TWO_HUNDRED
1614                    THREE_HUNDRED»
1615                    four
1616                    five
1617                    SIX
1618                "
1619                .unindent(),
1620                final_index_text: "
1621                    one
1622                    TWO_HUNDRED
1623                    THREE_HUNDRED
1624                    four
1625                    five
1626                "
1627                .unindent(),
1628            },
1629            Example {
1630                name: "uncommitted hunk strictly contains unstaged hunks",
1631                head_text: "
1632                    one
1633                    two
1634                    three
1635                    four
1636                    five
1637                    six
1638                    seven
1639                "
1640                .unindent(),
1641                index_text: "
1642                    one
1643                    TWO
1644                    THREE
1645                    FOUR
1646                    FIVE
1647                    SIX
1648                    seven
1649                "
1650                .unindent(),
1651                buffer_marked_text: "
1652                    one
1653                    TWO
1654                    «THREE_HUNDRED
1655                    FOUR
1656                    FIVE_HUNDRED»
1657                    SIX
1658                    seven
1659                "
1660                .unindent(),
1661                final_index_text: "
1662                    one
1663                    TWO
1664                    THREE_HUNDRED
1665                    FOUR
1666                    FIVE_HUNDRED
1667                    SIX
1668                    seven
1669                "
1670                .unindent(),
1671            },
1672            Example {
1673                name: "uncommitted deletion hunk",
1674                head_text: "
1675                    one
1676                    two
1677                    three
1678                    four
1679                    five
1680                "
1681                .unindent(),
1682                index_text: "
1683                    one
1684                    two
1685                    three
1686                    four
1687                    five
1688                "
1689                .unindent(),
1690                buffer_marked_text: "
1691                    one
1692                    ˇfive
1693                "
1694                .unindent(),
1695                final_index_text: "
1696                    one
1697                    five
1698                "
1699                .unindent(),
1700            },
1701            Example {
1702                name: "one unstaged hunk that contains two uncommitted hunks",
1703                head_text: "
1704                    one
1705                    two
1706
1707                    three
1708                    four
1709                "
1710                .unindent(),
1711                index_text: "
1712                    one
1713                    two
1714                    three
1715                    four
1716                "
1717                .unindent(),
1718                buffer_marked_text: "
1719                    «one
1720
1721                    three // modified
1722                    four»
1723                "
1724                .unindent(),
1725                final_index_text: "
1726                    one
1727
1728                    three // modified
1729                    four
1730                "
1731                .unindent(),
1732            },
1733            Example {
1734                name: "one uncommitted hunk that contains two unstaged hunks",
1735                head_text: "
1736                    one
1737                    two
1738                    three
1739                    four
1740                    five
1741                "
1742                .unindent(),
1743                index_text: "
1744                    ZERO
1745                    one
1746                    TWO
1747                    THREE
1748                    FOUR
1749                    five
1750                "
1751                .unindent(),
1752                buffer_marked_text: "
1753                    «one
1754                    TWO_HUNDRED
1755                    THREE
1756                    FOUR_HUNDRED
1757                    five»
1758                "
1759                .unindent(),
1760                final_index_text: "
1761                    ZERO
1762                    one
1763                    TWO_HUNDRED
1764                    THREE
1765                    FOUR_HUNDRED
1766                    five
1767                "
1768                .unindent(),
1769            },
1770        ];
1771
1772        for example in table {
1773            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
1774            let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1775            let hunk_range =
1776                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
1777
1778            let unstaged =
1779                BufferDiffSnapshot::new_sync(buffer.clone(), example.index_text.clone(), cx);
1780            let uncommitted =
1781                BufferDiffSnapshot::new_sync(buffer.clone(), example.head_text.clone(), cx);
1782
1783            let unstaged_diff = cx.new(|cx| {
1784                let mut diff = BufferDiff::new(&buffer, cx);
1785                diff.set_snapshot(unstaged, &buffer, cx);
1786                diff
1787            });
1788
1789            let uncommitted_diff = cx.new(|cx| {
1790                let mut diff = BufferDiff::new(&buffer, cx);
1791                diff.set_snapshot(uncommitted, &buffer, cx);
1792                diff.set_secondary_diff(unstaged_diff);
1793                diff
1794            });
1795
1796            uncommitted_diff.update(cx, |diff, cx| {
1797                let hunks = diff
1798                    .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
1799                    .collect::<Vec<_>>();
1800                for hunk in &hunks {
1801                    assert_ne!(
1802                        hunk.secondary_status,
1803                        DiffHunkSecondaryStatus::NoSecondaryHunk
1804                    )
1805                }
1806
1807                let new_index_text = diff
1808                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
1809                    .unwrap()
1810                    .to_string();
1811
1812                let hunks = diff
1813                    .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
1814                    .collect::<Vec<_>>();
1815                for hunk in &hunks {
1816                    assert_eq!(
1817                        hunk.secondary_status,
1818                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1819                    )
1820                }
1821
1822                pretty_assertions::assert_eq!(
1823                    new_index_text,
1824                    example.final_index_text,
1825                    "example: {}",
1826                    example.name
1827                );
1828            });
1829        }
1830    }
1831
1832    #[gpui::test]
1833    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
1834        let head_text = "
1835            one
1836            two
1837            three
1838        "
1839        .unindent();
1840        let index_text = head_text.clone();
1841        let buffer_text = "
1842            one
1843            three
1844        "
1845        .unindent();
1846
1847        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text.clone());
1848        let unstaged = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1849        let uncommitted = BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1850        let unstaged_diff = cx.new(|cx| {
1851            let mut diff = BufferDiff::new(&buffer, cx);
1852            diff.set_snapshot(unstaged, &buffer, cx);
1853            diff
1854        });
1855        let uncommitted_diff = cx.new(|cx| {
1856            let mut diff = BufferDiff::new(&buffer, cx);
1857            diff.set_snapshot(uncommitted, &buffer, cx);
1858            diff.set_secondary_diff(unstaged_diff.clone());
1859            diff
1860        });
1861
1862        uncommitted_diff.update(cx, |diff, cx| {
1863            let hunk = diff.hunks(&buffer, cx).next().unwrap();
1864
1865            let new_index_text = diff
1866                .stage_or_unstage_hunks(true, &[hunk.clone()], &buffer, true, cx)
1867                .unwrap()
1868                .to_string();
1869            assert_eq!(new_index_text, buffer_text);
1870
1871            let hunk = diff.hunks(&buffer, &cx).next().unwrap();
1872            assert_eq!(
1873                hunk.secondary_status,
1874                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1875            );
1876
1877            let index_text = diff
1878                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
1879                .unwrap()
1880                .to_string();
1881            assert_eq!(index_text, head_text);
1882
1883            let hunk = diff.hunks(&buffer, &cx).next().unwrap();
1884            // optimistically unstaged (fine, could also be HasSecondaryHunk)
1885            assert_eq!(
1886                hunk.secondary_status,
1887                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1888            );
1889        });
1890    }
1891
1892    #[gpui::test]
1893    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
1894        let base_text = "
1895            zero
1896            one
1897            two
1898            three
1899            four
1900            five
1901            six
1902            seven
1903            eight
1904            nine
1905        "
1906        .unindent();
1907
1908        let buffer_text_1 = "
1909            one
1910            three
1911            four
1912            five
1913            SIX
1914            seven
1915            eight
1916            NINE
1917        "
1918        .unindent();
1919
1920        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
1921
1922        let empty_diff = cx.update(|cx| BufferDiffSnapshot::empty(&buffer, cx));
1923        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1924        let range = diff_1.inner.compare(&empty_diff.inner, &buffer).unwrap();
1925        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
1926
1927        // Edit does not affect the diff.
1928        buffer.edit_via_marked_text(
1929            &"
1930                one
1931                three
1932                four
1933                five
1934                «SIX.5»
1935                seven
1936                eight
1937                NINE
1938            "
1939            .unindent(),
1940        );
1941        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1942        assert_eq!(None, diff_2.inner.compare(&diff_1.inner, &buffer));
1943
1944        // Edit turns a deletion hunk into a modification.
1945        buffer.edit_via_marked_text(
1946            &"
1947                one
1948                «THREE»
1949                four
1950                five
1951                SIX.5
1952                seven
1953                eight
1954                NINE
1955            "
1956            .unindent(),
1957        );
1958        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1959        let range = diff_3.inner.compare(&diff_2.inner, &buffer).unwrap();
1960        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
1961
1962        // Edit turns a modification hunk into a deletion.
1963        buffer.edit_via_marked_text(
1964            &"
1965                one
1966                THREE
1967                four
1968                five«»
1969                seven
1970                eight
1971                NINE
1972            "
1973            .unindent(),
1974        );
1975        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
1976        let range = diff_4.inner.compare(&diff_3.inner, &buffer).unwrap();
1977        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
1978
1979        // Edit introduces a new insertion hunk.
1980        buffer.edit_via_marked_text(
1981            &"
1982                one
1983                THREE
1984                four«
1985                FOUR.5
1986                »five
1987                seven
1988                eight
1989                NINE
1990            "
1991            .unindent(),
1992        );
1993        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
1994        let range = diff_5.inner.compare(&diff_4.inner, &buffer).unwrap();
1995        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
1996
1997        // Edit removes a hunk.
1998        buffer.edit_via_marked_text(
1999            &"
2000                one
2001                THREE
2002                four
2003                FOUR.5
2004                five
2005                seven
2006                eight
2007                «nine»
2008            "
2009            .unindent(),
2010        );
2011        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2012        let range = diff_6.inner.compare(&diff_5.inner, &buffer).unwrap();
2013        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2014    }
2015
2016    #[gpui::test(iterations = 100)]
2017    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2018        fn gen_line(rng: &mut StdRng) -> String {
2019            if rng.gen_bool(0.2) {
2020                "\n".to_owned()
2021            } else {
2022                let c = rng.gen_range('A'..='Z');
2023                format!("{c}{c}{c}\n")
2024            }
2025        }
2026
2027        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2028            let mut old_lines = {
2029                let mut old_lines = Vec::new();
2030                let mut old_lines_iter = head.lines();
2031                while let Some(line) = old_lines_iter.next() {
2032                    assert!(!line.ends_with("\n"));
2033                    old_lines.push(line.to_owned());
2034                }
2035                if old_lines.last().is_some_and(|line| line.is_empty()) {
2036                    old_lines.pop();
2037                }
2038                old_lines.into_iter()
2039            };
2040            let mut result = String::new();
2041            let unchanged_count = rng.gen_range(0..=old_lines.len());
2042            result +=
2043                &old_lines
2044                    .by_ref()
2045                    .take(unchanged_count)
2046                    .fold(String::new(), |mut s, line| {
2047                        writeln!(&mut s, "{line}").unwrap();
2048                        s
2049                    });
2050            while old_lines.len() > 0 {
2051                let deleted_count = rng.gen_range(0..=old_lines.len());
2052                let _advance = old_lines
2053                    .by_ref()
2054                    .take(deleted_count)
2055                    .map(|line| line.len() + 1)
2056                    .sum::<usize>();
2057                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2058                let added_count = rng.gen_range(minimum_added..=5);
2059                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2060                result += &addition;
2061
2062                if old_lines.len() > 0 {
2063                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2064                    if blank_lines == old_lines.len() {
2065                        break;
2066                    };
2067                    let unchanged_count = rng.gen_range((blank_lines + 1).max(1)..=old_lines.len());
2068                    result += &old_lines.by_ref().take(unchanged_count).fold(
2069                        String::new(),
2070                        |mut s, line| {
2071                            writeln!(&mut s, "{line}").unwrap();
2072                            s
2073                        },
2074                    );
2075                }
2076            }
2077            result
2078        }
2079
2080        fn uncommitted_diff(
2081            working_copy: &language::BufferSnapshot,
2082            index_text: &Rope,
2083            head_text: String,
2084            cx: &mut TestAppContext,
2085        ) -> Entity<BufferDiff> {
2086            let inner =
2087                BufferDiffSnapshot::new_sync(working_copy.text.clone(), head_text, cx).inner;
2088            let secondary = BufferDiff {
2089                buffer_id: working_copy.remote_id(),
2090                inner: BufferDiffSnapshot::new_sync(
2091                    working_copy.text.clone(),
2092                    index_text.to_string(),
2093                    cx,
2094                )
2095                .inner,
2096                secondary_diff: None,
2097            };
2098            let secondary = cx.new(|_| secondary);
2099            cx.new(|_| BufferDiff {
2100                buffer_id: working_copy.remote_id(),
2101                inner,
2102                secondary_diff: Some(secondary),
2103            })
2104        }
2105
2106        let operations = std::env::var("OPERATIONS")
2107            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2108            .unwrap_or(10);
2109
2110        let rng = &mut rng;
2111        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2112            writeln!(&mut s, "{c}{c}{c}").unwrap();
2113            s
2114        });
2115        let working_copy = gen_working_copy(rng, &head_text);
2116        let working_copy = cx.new(|cx| {
2117            language::Buffer::local_normalized(
2118                Rope::from(working_copy.as_str()),
2119                text::LineEnding::default(),
2120                cx,
2121            )
2122        });
2123        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2124        let mut index_text = if rng.r#gen() {
2125            Rope::from(head_text.as_str())
2126        } else {
2127            working_copy.as_rope().clone()
2128        };
2129
2130        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2131        let mut hunks = diff.update(cx, |diff, cx| {
2132            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
2133                .collect::<Vec<_>>()
2134        });
2135        if hunks.len() == 0 {
2136            return;
2137        }
2138
2139        for _ in 0..operations {
2140            let i = rng.gen_range(0..hunks.len());
2141            let hunk = &mut hunks[i];
2142            let hunk_to_change = hunk.clone();
2143            let stage = match hunk.secondary_status {
2144                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2145                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2146                    true
2147                }
2148                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2149                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2150                    false
2151                }
2152                _ => unreachable!(),
2153            };
2154
2155            index_text = diff.update(cx, |diff, cx| {
2156                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2157                    .unwrap()
2158            });
2159
2160            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2161            let found_hunks = diff.update(cx, |diff, cx| {
2162                diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
2163                    .collect::<Vec<_>>()
2164            });
2165            assert_eq!(hunks.len(), found_hunks.len());
2166
2167            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2168                assert_eq!(
2169                    expected_hunk.buffer_range.to_point(&working_copy),
2170                    found_hunk.buffer_range.to_point(&working_copy)
2171                );
2172                assert_eq!(
2173                    expected_hunk.diff_base_byte_range,
2174                    found_hunk.diff_base_byte_range
2175                );
2176                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2177            }
2178            hunks = found_hunks;
2179        }
2180    }
2181}