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