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