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