buffer_diff.rs

   1use futures::channel::oneshot;
   2use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
   3use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
   4use language::{
   5    Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry,
   6    language_settings::language_settings, text_diff, word_diff_ranges,
   7};
   8use rope::Rope;
   9use std::{
  10    cmp::Ordering,
  11    future::Future,
  12    iter,
  13    ops::{Range, RangeInclusive},
  14    sync::Arc,
  15};
  16use sum_tree::SumTree;
  17use text::{
  18    Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,
  19};
  20use util::ResultExt;
  21
  22pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
  23
  24pub struct BufferDiff {
  25    pub buffer_id: BufferId,
  26    inner: BufferDiffInner<Entity<language::Buffer>>,
  27    secondary_diff: Option<Entity<BufferDiff>>,
  28}
  29
  30#[derive(Clone)]
  31pub struct BufferDiffSnapshot {
  32    inner: BufferDiffInner<language::BufferSnapshot>,
  33    secondary_diff: Option<Box<BufferDiffSnapshot>>,
  34}
  35
  36impl std::fmt::Debug for BufferDiffSnapshot {
  37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  38        f.debug_struct("BufferDiffSnapshot")
  39            .field("inner", &self.inner)
  40            .field("secondary_diff", &self.secondary_diff)
  41            .finish()
  42    }
  43}
  44
  45#[derive(Clone)]
  46pub struct BufferDiffUpdate {
  47    inner: BufferDiffInner<Arc<str>>,
  48    buffer_snapshot: text::BufferSnapshot,
  49    base_text_edits: Option<Diff>,
  50    base_text_changed: bool,
  51}
  52
  53#[derive(Clone)]
  54struct BufferDiffInner<BaseText> {
  55    hunks: SumTree<InternalDiffHunk>,
  56    pending_hunks: SumTree<PendingHunk>,
  57    base_text: BaseText,
  58    base_text_exists: bool,
  59    buffer_snapshot: text::BufferSnapshot,
  60}
  61
  62impl<BaseText> BufferDiffInner<BaseText> {
  63    fn buffer_version(&self) -> &clock::Global {
  64        self.buffer_snapshot.version()
  65    }
  66}
  67
  68#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  69pub struct DiffHunkStatus {
  70    pub kind: DiffHunkStatusKind,
  71    pub secondary: DiffHunkSecondaryStatus,
  72}
  73
  74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  75pub enum DiffHunkStatusKind {
  76    Added,
  77    Modified,
  78    Deleted,
  79}
  80
  81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  82/// Diff of Working Copy vs Index
  83/// aka 'is this hunk staged or not'
  84pub enum DiffHunkSecondaryStatus {
  85    /// Unstaged
  86    HasSecondaryHunk,
  87    /// Partially staged
  88    OverlapsWithSecondaryHunk,
  89    /// Staged
  90    NoSecondaryHunk,
  91    /// We are unstaging
  92    SecondaryHunkAdditionPending,
  93    /// We are stagind
  94    SecondaryHunkRemovalPending,
  95}
  96
  97/// A diff hunk resolved to rows in the buffer.
  98#[derive(Debug, Clone, PartialEq, Eq)]
  99pub struct DiffHunk {
 100    /// The buffer range as points.
 101    pub range: Range<Point>,
 102    /// The range in the buffer to which this hunk corresponds.
 103    pub buffer_range: Range<Anchor>,
 104    /// The range in the buffer's diff base text to which this hunk corresponds.
 105    pub diff_base_byte_range: Range<usize>,
 106    pub secondary_status: DiffHunkSecondaryStatus,
 107    // Anchors representing the word diff locations in the active buffer
 108    pub buffer_word_diffs: Vec<Range<Anchor>>,
 109    // Offsets relative to the start of the deleted diff that represent word diff locations
 110    pub base_word_diffs: Vec<Range<usize>>,
 111    // These fields are nonempty only if the secondary status is OverlapsWithSecondaryHunk
 112    pub buffer_staged_lines: Vec<Range<Anchor>>,
 113    pub base_staged_lines: Vec<Range<Point>>,
 114}
 115
 116/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
 117#[derive(Debug, Clone, PartialEq, Eq)]
 118struct InternalDiffHunk {
 119    // Range of text that has been added to the main buffer
 120    buffer_range: Range<Anchor>,
 121    // Range of text that has been deleted from the diff base
 122    diff_base_byte_range: Range<usize>,
 123    base_word_diffs: Vec<Range<usize>>,
 124    buffer_word_diffs: Vec<Range<Anchor>>,
 125}
 126
 127#[derive(Debug, Clone, PartialEq, Eq)]
 128struct PendingHunk {
 129    buffer_range: Range<Anchor>,
 130    diff_base_byte_range: Range<usize>,
 131    buffer_version: clock::Global,
 132    new_status: DiffHunkSecondaryStatus,
 133}
 134
 135#[derive(Debug, Clone)]
 136pub struct DiffHunkSummary {
 137    buffer_range: Range<Anchor>,
 138    diff_base_byte_range: Range<usize>,
 139}
 140
 141impl sum_tree::Item for InternalDiffHunk {
 142    type Summary = DiffHunkSummary;
 143
 144    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 145        DiffHunkSummary {
 146            buffer_range: self.buffer_range.clone(),
 147            diff_base_byte_range: self.diff_base_byte_range.clone(),
 148        }
 149    }
 150}
 151
 152impl sum_tree::Item for PendingHunk {
 153    type Summary = DiffHunkSummary;
 154
 155    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 156        DiffHunkSummary {
 157            buffer_range: self.buffer_range.clone(),
 158            diff_base_byte_range: self.diff_base_byte_range.clone(),
 159        }
 160    }
 161}
 162
 163impl sum_tree::Summary for DiffHunkSummary {
 164    type Context<'a> = &'a text::BufferSnapshot;
 165
 166    fn zero(_cx: Self::Context<'_>) -> Self {
 167        DiffHunkSummary {
 168            buffer_range: Anchor::MIN..Anchor::MIN,
 169            diff_base_byte_range: 0..0,
 170        }
 171    }
 172
 173    fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
 174        self.buffer_range.start = *self
 175            .buffer_range
 176            .start
 177            .min(&other.buffer_range.start, buffer);
 178        self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
 179
 180        self.diff_base_byte_range.start = self
 181            .diff_base_byte_range
 182            .start
 183            .min(other.diff_base_byte_range.start);
 184        self.diff_base_byte_range.end = self
 185            .diff_base_byte_range
 186            .end
 187            .max(other.diff_base_byte_range.end);
 188    }
 189}
 190
 191impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
 192    fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
 193        if self
 194            .cmp(&cursor_location.buffer_range.start, buffer)
 195            .is_lt()
 196        {
 197            Ordering::Less
 198        } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
 199            Ordering::Greater
 200        } else {
 201            Ordering::Equal
 202        }
 203    }
 204}
 205
 206impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize {
 207    fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering {
 208        if *self < cursor_location.diff_base_byte_range.start {
 209            Ordering::Less
 210        } else if *self > cursor_location.diff_base_byte_range.end {
 211            Ordering::Greater
 212        } else {
 213            Ordering::Equal
 214        }
 215    }
 216}
 217
 218impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
 219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 220        f.debug_struct("BufferDiffSnapshot")
 221            .field("hunks", &self.hunks)
 222            .field("remote_id", &self.base_text.remote_id())
 223            .finish()
 224    }
 225}
 226
 227impl BufferDiffSnapshot {
 228    #[cfg(test)]
 229    fn new_sync(
 230        buffer: text::BufferSnapshot,
 231        diff_base: String,
 232        cx: &mut gpui::TestAppContext,
 233    ) -> BufferDiffSnapshot {
 234        let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
 235        buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
 236    }
 237
 238    pub fn is_empty(&self) -> bool {
 239        self.inner.hunks.is_empty()
 240    }
 241
 242    pub fn base_text_string(&self) -> Option<String> {
 243        self.inner
 244            .base_text_exists
 245            .then(|| self.inner.base_text.text())
 246    }
 247
 248    pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
 249        self.secondary_diff.as_deref()
 250    }
 251
 252    pub fn buffer_version(&self) -> &clock::Global {
 253        self.inner.buffer_version()
 254    }
 255
 256    pub fn original_buffer_snapshot(&self) -> &text::BufferSnapshot {
 257        &self.inner.buffer_snapshot
 258    }
 259
 260    #[ztracing::instrument(skip_all)]
 261    pub fn hunks_intersecting_range<'a>(
 262        &'a self,
 263        range: Range<Anchor>,
 264        buffer: &'a text::BufferSnapshot,
 265    ) -> impl 'a + Iterator<Item = DiffHunk> {
 266        let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
 267        self.inner
 268            .hunks_intersecting_range(range, buffer, unstaged_counterpart)
 269    }
 270
 271    pub fn hunks_intersecting_range_rev<'a>(
 272        &'a self,
 273        range: Range<Anchor>,
 274        buffer: &'a text::BufferSnapshot,
 275    ) -> impl 'a + Iterator<Item = DiffHunk> {
 276        let filter = move |summary: &DiffHunkSummary| {
 277            let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
 278            let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
 279            !before_start && !after_end
 280        };
 281        self.inner.hunks_intersecting_range_rev_impl(filter, buffer)
 282    }
 283
 284    pub fn hunks_intersecting_base_text_range<'a>(
 285        &'a self,
 286        range: Range<usize>,
 287        main_buffer: &'a text::BufferSnapshot,
 288    ) -> impl 'a + Iterator<Item = DiffHunk> {
 289        let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
 290        let filter = move |summary: &DiffHunkSummary| {
 291            let before_start = summary.diff_base_byte_range.end < range.start;
 292            let after_end = summary.diff_base_byte_range.start > range.end;
 293            !before_start && !after_end
 294        };
 295        self.inner
 296            .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart)
 297    }
 298
 299    pub fn hunks_intersecting_base_text_range_rev<'a>(
 300        &'a self,
 301        range: Range<usize>,
 302        main_buffer: &'a text::BufferSnapshot,
 303    ) -> impl 'a + Iterator<Item = DiffHunk> {
 304        let filter = move |summary: &DiffHunkSummary| {
 305            let before_start = summary.diff_base_byte_range.end.cmp(&range.start).is_lt();
 306            let after_end = summary.diff_base_byte_range.start.cmp(&range.end).is_gt();
 307            !before_start && !after_end
 308        };
 309        self.inner
 310            .hunks_intersecting_range_rev_impl(filter, main_buffer)
 311    }
 312
 313    pub fn hunks<'a>(
 314        &'a self,
 315        buffer_snapshot: &'a text::BufferSnapshot,
 316    ) -> impl 'a + Iterator<Item = DiffHunk> {
 317        self.hunks_intersecting_range(
 318            Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()),
 319            buffer_snapshot,
 320        )
 321    }
 322
 323    pub fn hunks_in_row_range<'a>(
 324        &'a self,
 325        range: Range<u32>,
 326        buffer: &'a text::BufferSnapshot,
 327    ) -> impl 'a + Iterator<Item = DiffHunk> {
 328        let start = buffer.anchor_before(Point::new(range.start, 0));
 329        let end = buffer.anchor_after(Point::new(range.end, 0));
 330        self.hunks_intersecting_range(start..end, buffer)
 331    }
 332
 333    pub fn range_to_hunk_range(
 334        &self,
 335        range: Range<Anchor>,
 336        buffer: &text::BufferSnapshot,
 337    ) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
 338        let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next();
 339        let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next();
 340        let range = first_hunk
 341            .as_ref()
 342            .zip(last_hunk.as_ref())
 343            .map(|(first, last)| first.buffer_range.start..last.buffer_range.end);
 344        let base_text_range = first_hunk
 345            .zip(last_hunk)
 346            .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end);
 347        (range, base_text_range)
 348    }
 349
 350    pub fn base_text(&self) -> &language::BufferSnapshot {
 351        &self.inner.base_text
 352    }
 353
 354    /// If this function returns `true`, the base texts are equal. If this
 355    /// function returns `false`, they might be equal, but might not. This
 356    /// result is used to avoid recalculating diffs in situations where we know
 357    /// nothing has changed.
 358    pub fn base_texts_definitely_eq(&self, other: &Self) -> bool {
 359        if self.inner.base_text_exists != other.inner.base_text_exists {
 360            return false;
 361        }
 362        let left = &self.inner.base_text;
 363        let right = &other.inner.base_text;
 364        let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty());
 365        let (new_id, new_version, new_empty) =
 366            (right.remote_id(), right.version(), right.is_empty());
 367        (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
 368    }
 369
 370    #[allow(unused)]
 371    fn hunk_before_base_text_offset<'a>(
 372        &self,
 373        target: usize,
 374        cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
 375    ) -> Option<&'a InternalDiffHunk> {
 376        cursor.seek_forward(&target, Bias::Left);
 377        if cursor
 378            .item()
 379            .is_none_or(|hunk| target < hunk.diff_base_byte_range.start)
 380        {
 381            cursor.prev();
 382        }
 383        let result = cursor
 384            .item()
 385            .filter(|hunk| target >= hunk.diff_base_byte_range.start);
 386        if cursor.item().is_none() {
 387            cursor.reset();
 388        }
 389        result
 390    }
 391
 392    #[allow(unused)]
 393    fn hunk_before_buffer_anchor<'a>(
 394        &self,
 395        target: Anchor,
 396        cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
 397        buffer: &text::BufferSnapshot,
 398    ) -> Option<&'a InternalDiffHunk> {
 399        cursor.seek_forward(&target, Bias::Left);
 400        if cursor
 401            .item()
 402            .is_none_or(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_lt())
 403        {
 404            cursor.prev();
 405        }
 406        let result = cursor
 407            .item()
 408            .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge());
 409        if cursor.item().is_none() {
 410            cursor.reset();
 411        }
 412        result
 413    }
 414
 415    /// Returns a patch mapping the provided main buffer snapshot to the base text of this diff.
 416    ///
 417    /// The returned patch is guaranteed to be accurate for all main buffer points in the provided range,
 418    /// but not necessarily for points outside that range.
 419    pub fn patch_for_buffer_range<'a>(
 420        &'a self,
 421        _range: RangeInclusive<Point>,
 422        buffer: &'a text::BufferSnapshot,
 423    ) -> Patch<Point> {
 424        let original_snapshot = self.original_buffer_snapshot();
 425
 426        let edits_since: Vec<Edit<Point>> = buffer
 427            .edits_since::<Point>(original_snapshot.version())
 428            .collect();
 429        let mut inverted_edits_since = Patch::new(edits_since);
 430        inverted_edits_since.invert();
 431
 432        inverted_edits_since.compose(
 433            self.inner
 434                .hunks
 435                .iter()
 436                .map(|hunk| {
 437                    let old_start = hunk.buffer_range.start.to_point(original_snapshot);
 438                    let old_end = hunk.buffer_range.end.to_point(original_snapshot);
 439                    let new_start = self
 440                        .base_text()
 441                        .offset_to_point(hunk.diff_base_byte_range.start);
 442                    let new_end = self
 443                        .base_text()
 444                        .offset_to_point(hunk.diff_base_byte_range.end);
 445                    Edit {
 446                        old: old_start..old_end,
 447                        new: new_start..new_end,
 448                    }
 449                })
 450                .chain(
 451                    if !self.inner.base_text_exists && self.inner.hunks.is_empty() {
 452                        Some(Edit {
 453                            old: Point::zero()..original_snapshot.max_point(),
 454                            new: Point::zero()..Point::zero(),
 455                        })
 456                    } else {
 457                        None
 458                    },
 459                ),
 460        )
 461    }
 462
 463    /// Returns a patch mapping the base text of this diff to the provided buffer snapshot.
 464    ///
 465    /// The returned patch is guaranteed to be accurate for all base text points in the provided range,
 466    /// but not necessarily for points outside that range.
 467    pub fn patch_for_base_text_range<'a>(
 468        &'a self,
 469        _range: RangeInclusive<Point>,
 470        buffer: &'a text::BufferSnapshot,
 471    ) -> Patch<Point> {
 472        let original_snapshot = self.original_buffer_snapshot();
 473
 474        let mut hunk_edits: Vec<Edit<Point>> = Vec::new();
 475        for hunk in self.inner.hunks.iter() {
 476            let old_start = self
 477                .base_text()
 478                .offset_to_point(hunk.diff_base_byte_range.start);
 479            let old_end = self
 480                .base_text()
 481                .offset_to_point(hunk.diff_base_byte_range.end);
 482            let new_start = hunk.buffer_range.start.to_point(original_snapshot);
 483            let new_end = hunk.buffer_range.end.to_point(original_snapshot);
 484            hunk_edits.push(Edit {
 485                old: old_start..old_end,
 486                new: new_start..new_end,
 487            });
 488        }
 489        if !self.inner.base_text_exists && hunk_edits.is_empty() {
 490            hunk_edits.push(Edit {
 491                old: Point::zero()..Point::zero(),
 492                new: Point::zero()..original_snapshot.max_point(),
 493            })
 494        }
 495        let hunk_patch = Patch::new(hunk_edits);
 496
 497        hunk_patch.compose(buffer.edits_since::<Point>(original_snapshot.version()))
 498    }
 499
 500    pub fn buffer_point_to_base_text_range(
 501        &self,
 502        point: Point,
 503        buffer: &text::BufferSnapshot,
 504    ) -> Range<Point> {
 505        let patch = self.patch_for_buffer_range(point..=point, buffer);
 506        let edit = patch.edit_for_old_position(point);
 507        edit.new
 508    }
 509
 510    pub fn base_text_point_to_buffer_range(
 511        &self,
 512        point: Point,
 513        buffer: &text::BufferSnapshot,
 514    ) -> Range<Point> {
 515        let patch = self.patch_for_base_text_range(point..=point, buffer);
 516        let edit = patch.edit_for_old_position(point);
 517        edit.new
 518    }
 519
 520    pub fn buffer_point_to_base_text_point(
 521        &self,
 522        point: Point,
 523        buffer: &text::BufferSnapshot,
 524    ) -> Point {
 525        let patch = self.patch_for_buffer_range(point..=point, buffer);
 526        let edit = patch.edit_for_old_position(point);
 527        if point == edit.old.end {
 528            edit.new.end
 529        } else {
 530            edit.new.start
 531        }
 532    }
 533
 534    pub fn base_text_point_to_buffer_point(
 535        &self,
 536        point: Point,
 537        buffer: &text::BufferSnapshot,
 538    ) -> Point {
 539        let patch = self.patch_for_base_text_range(point..=point, buffer);
 540        let edit = patch.edit_for_old_position(point);
 541        if point == edit.old.end {
 542            edit.new.end
 543        } else {
 544            edit.new.start
 545        }
 546    }
 547}
 548
 549impl BufferDiffInner<Entity<language::Buffer>> {
 550    /// Returns the new index text and new pending hunks.
 551    fn stage_or_unstage_hunks_impl(
 552        &mut self,
 553        unstaged_diff: &Self,
 554        stage: bool,
 555        hunks: &[DiffHunk],
 556        buffer: &text::BufferSnapshot,
 557        file_exists: bool,
 558        cx: &mut Context<BufferDiff>,
 559    ) -> Option<Rope> {
 560        let head_text = self
 561            .base_text_exists
 562            .then(|| self.base_text.read(cx).as_rope().clone());
 563        let index_text = unstaged_diff
 564            .base_text_exists
 565            .then(|| unstaged_diff.base_text.read(cx).as_rope().clone());
 566
 567        // If the file doesn't exist in either HEAD or the index, then the
 568        // entire file must be either created or deleted in the index.
 569        let (index_text, head_text) = match (index_text, head_text) {
 570            (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
 571            (index_text, head_text) => {
 572                let (new_index_text, new_status) = if stage {
 573                    log::debug!("stage all");
 574                    (
 575                        file_exists.then(|| buffer.as_rope().clone()),
 576                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
 577                    )
 578                } else {
 579                    log::debug!("unstage all");
 580                    (
 581                        head_text,
 582                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
 583                    )
 584                };
 585
 586                let hunk = PendingHunk {
 587                    buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
 588                    diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
 589                    buffer_version: buffer.version().clone(),
 590                    new_status,
 591                };
 592                self.pending_hunks = SumTree::from_item(hunk, buffer);
 593                return new_index_text;
 594            }
 595        };
 596
 597        let mut pending_hunks = SumTree::new(buffer);
 598        let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 599
 600        // first, merge new hunks into pending_hunks
 601        for DiffHunk {
 602            buffer_range,
 603            diff_base_byte_range,
 604            secondary_status,
 605            ..
 606        } in hunks.iter().cloned()
 607        {
 608            let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
 609            pending_hunks.append(preceding_pending_hunks, buffer);
 610
 611            // Skip all overlapping or adjacent old pending hunks
 612            while old_pending_hunks.item().is_some_and(|old_hunk| {
 613                old_hunk
 614                    .buffer_range
 615                    .start
 616                    .cmp(&buffer_range.end, buffer)
 617                    .is_le()
 618            }) {
 619                old_pending_hunks.next();
 620            }
 621
 622            if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
 623                || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
 624            {
 625                continue;
 626            }
 627
 628            pending_hunks.push(
 629                PendingHunk {
 630                    buffer_range,
 631                    diff_base_byte_range,
 632                    buffer_version: buffer.version().clone(),
 633                    new_status: if stage {
 634                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
 635                    } else {
 636                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
 637                    },
 638                },
 639                buffer,
 640            );
 641        }
 642        // append the remainder
 643        pending_hunks.append(old_pending_hunks.suffix(), buffer);
 644
 645        let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
 646        unstaged_hunk_cursor.next();
 647
 648        // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
 649        let mut prev_unstaged_hunk_buffer_end = 0;
 650        let mut prev_unstaged_hunk_base_text_end = 0;
 651        let mut edits = Vec::<(Range<usize>, String)>::new();
 652        let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
 653        while let Some(PendingHunk {
 654            buffer_range,
 655            diff_base_byte_range,
 656            new_status,
 657            ..
 658        }) = pending_hunks_iter.next()
 659        {
 660            // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
 661            let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
 662
 663            if let Some(unstaged_hunk) = skipped_unstaged.last() {
 664                prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 665                prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
 666            }
 667
 668            // Find where this hunk is in the index if it doesn't overlap
 669            let mut buffer_offset_range = buffer_range.to_offset(buffer);
 670            let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
 671            let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
 672
 673            loop {
 674                // Merge this hunk with any overlapping unstaged hunks.
 675                if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
 676                    let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
 677                    if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
 678                        prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 679                        prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
 680
 681                        index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
 682                        buffer_offset_range.start = buffer_offset_range
 683                            .start
 684                            .min(unstaged_hunk_offset_range.start);
 685                        buffer_offset_range.end =
 686                            buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
 687
 688                        unstaged_hunk_cursor.next();
 689                        continue;
 690                    }
 691                }
 692
 693                // If any unstaged hunks were merged, then subsequent pending hunks may
 694                // now overlap this hunk. Merge them.
 695                if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
 696                    let next_pending_hunk_offset_range =
 697                        next_pending_hunk.buffer_range.to_offset(buffer);
 698                    if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
 699                        buffer_offset_range.end = buffer_offset_range
 700                            .end
 701                            .max(next_pending_hunk_offset_range.end);
 702                        pending_hunks_iter.next();
 703                        continue;
 704                    }
 705                }
 706
 707                break;
 708            }
 709
 710            let end_overshoot = buffer_offset_range
 711                .end
 712                .saturating_sub(prev_unstaged_hunk_buffer_end);
 713            let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
 714            let index_byte_range = index_start..index_end;
 715
 716            let replacement_text = match new_status {
 717                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
 718                    log::debug!("staging hunk {:?}", buffer_offset_range);
 719                    buffer
 720                        .text_for_range(buffer_offset_range)
 721                        .collect::<String>()
 722                }
 723                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
 724                    log::debug!("unstaging hunk {:?}", buffer_offset_range);
 725                    head_text
 726                        .chunks_in_range(diff_base_byte_range.clone())
 727                        .collect::<String>()
 728                }
 729                _ => {
 730                    debug_assert!(false);
 731                    continue;
 732                }
 733            };
 734
 735            edits.push((index_byte_range, replacement_text));
 736        }
 737        drop(pending_hunks_iter);
 738        drop(old_pending_hunks);
 739        self.pending_hunks = pending_hunks;
 740
 741        #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
 742        {
 743            for window in edits.windows(2) {
 744                let (range_a, range_b) = (&window[0].0, &window[1].0);
 745                debug_assert!(range_a.end < range_b.start);
 746            }
 747        }
 748
 749        let mut new_index_text = Rope::new();
 750        let mut index_cursor = index_text.cursor(0);
 751
 752        for (old_range, replacement_text) in edits {
 753            new_index_text.append(index_cursor.slice(old_range.start));
 754            index_cursor.seek_forward(old_range.end);
 755            new_index_text.push(&replacement_text);
 756        }
 757        new_index_text.append(index_cursor.suffix());
 758        Some(new_index_text)
 759    }
 760}
 761
 762impl BufferDiffInner<language::BufferSnapshot> {
 763    fn hunks_intersecting_range<'a>(
 764        &'a self,
 765        range: Range<Anchor>,
 766        buffer: &'a text::BufferSnapshot,
 767        secondary: Option<&'a Self>,
 768    ) -> impl 'a + Iterator<Item = DiffHunk> {
 769        let range = range.to_offset(buffer);
 770        let filter = move |summary: &DiffHunkSummary| {
 771            let summary_range = summary.buffer_range.to_offset(buffer);
 772            let before_start = summary_range.end < range.start;
 773            let after_end = summary_range.start > range.end;
 774            !before_start && !after_end
 775        };
 776        self.hunks_intersecting_range_impl(filter, buffer, secondary)
 777    }
 778
 779    fn hunks_intersecting_range_impl<'a>(
 780        &'a self,
 781        filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
 782        buffer: &'a text::BufferSnapshot,
 783        secondary: Option<&'a Self>,
 784    ) -> impl 'a + Iterator<Item = DiffHunk> {
 785        let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
 786
 787        let anchor_iter = iter::from_fn(move || {
 788            cursor.next();
 789            cursor.item()
 790        })
 791        .flat_map(move |hunk| {
 792            [
 793                (
 794                    &hunk.buffer_range.start,
 795                    (
 796                        hunk.buffer_range.start,
 797                        hunk.diff_base_byte_range.start,
 798                        hunk,
 799                    ),
 800                ),
 801                (
 802                    &hunk.buffer_range.end,
 803                    (hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
 804                ),
 805            ]
 806        });
 807
 808        let mut pending_hunks_cursor = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 809        pending_hunks_cursor.next();
 810
 811        let mut secondary_cursor = None;
 812        if let Some(secondary) = secondary.as_ref() {
 813            let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
 814            cursor.next();
 815            secondary_cursor = Some(cursor);
 816        }
 817
 818        let max_point = buffer.max_point();
 819        let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
 820        iter::from_fn(move || {
 821            loop {
 822                let (start_point, (start_anchor, start_base, hunk)) = summaries.next()?;
 823                let (mut end_point, (mut end_anchor, end_base, _)) = summaries.next()?;
 824
 825                let base_word_diffs = hunk.base_word_diffs.clone();
 826                let buffer_word_diffs = hunk.buffer_word_diffs.clone();
 827
 828                let mut buffer_staged_lines = Vec::new();
 829                let mut base_staged_lines = Vec::new();
 830
 831                if !start_anchor.is_valid(buffer) {
 832                    continue;
 833                }
 834
 835                if end_point.column > 0 && end_point < max_point {
 836                    end_point.row += 1;
 837                    end_point.column = 0;
 838                    end_anchor = buffer.anchor_before(end_point);
 839                }
 840
 841                let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
 842
 843                let mut has_pending = false;
 844                if start_anchor
 845                    .cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
 846                    .is_gt()
 847                {
 848                    pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
 849                }
 850
 851                if let Some(pending_hunk) = pending_hunks_cursor.item() {
 852                    let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
 853                    if pending_range.end.column > 0 {
 854                        pending_range.end.row += 1;
 855                        pending_range.end.column = 0;
 856                    }
 857
 858                    if pending_range == (start_point..end_point)
 859                        && !buffer.has_edits_since_in_range(
 860                            &pending_hunk.buffer_version,
 861                            start_anchor..end_anchor,
 862                        )
 863                    {
 864                        has_pending = true;
 865                        secondary_status = pending_hunk.new_status;
 866                    }
 867                }
 868
 869                if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
 870                    if start_anchor
 871                        .cmp(&secondary_cursor.start().buffer_range.start, buffer)
 872                        .is_gt()
 873                    {
 874                        secondary_cursor.seek_forward(&start_anchor, Bias::Left);
 875                    }
 876
 877                    if let Some(secondary_hunk) = secondary_cursor.item() {
 878                        let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
 879                        if secondary_range.end.column > 0 {
 880                            secondary_range.end.row += 1;
 881                            secondary_range.end.column = 0;
 882                        }
 883                        if secondary_range.is_empty()
 884                            && secondary_hunk.diff_base_byte_range.is_empty()
 885                        {
 886                            // ignore
 887                        } else if secondary_range == (start_point..end_point) {
 888                            secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
 889                        } else if secondary_range.start <= end_point {
 890                            // FIXME this should be a background computation that only happens when either the diff or the secondary diff changes
 891                            let (buffer, base) = compute_staged_lines(
 892                                &hunk,
 893                                &secondary_hunk,
 894                                buffer,
 895                                &self.base_text,
 896                                &secondary.unwrap().base_text,
 897                            );
 898                            buffer_staged_lines = buffer;
 899                            base_staged_lines = base;
 900
 901                            secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
 902                        }
 903                    }
 904                }
 905
 906                return Some(DiffHunk {
 907                    range: start_point..end_point,
 908                    diff_base_byte_range: start_base..end_base,
 909                    buffer_range: start_anchor..end_anchor,
 910                    base_word_diffs,
 911                    buffer_word_diffs,
 912                    secondary_status,
 913                    buffer_staged_lines,
 914                    base_staged_lines,
 915                });
 916            }
 917        })
 918    }
 919
 920    fn hunks_intersecting_range_rev_impl<'a>(
 921        &'a self,
 922        filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
 923        buffer: &'a text::BufferSnapshot,
 924    ) -> impl 'a + Iterator<Item = DiffHunk> {
 925        let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
 926
 927        iter::from_fn(move || {
 928            cursor.prev();
 929
 930            let hunk = cursor.item()?;
 931            let range = hunk.buffer_range.to_point(buffer);
 932
 933            Some(DiffHunk {
 934                range,
 935                diff_base_byte_range: hunk.diff_base_byte_range.clone(),
 936                buffer_range: hunk.buffer_range.clone(),
 937                // The secondary status is not used by callers of this method.
 938                secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
 939                base_word_diffs: hunk.base_word_diffs.clone(),
 940                buffer_word_diffs: hunk.buffer_word_diffs.clone(),
 941                base_staged_lines: Vec::new(),
 942                buffer_staged_lines: Vec::new(),
 943            })
 944        })
 945    }
 946}
 947
 948fn compute_staged_lines(
 949    hunk: &InternalDiffHunk,
 950    secondary_hunk: &InternalDiffHunk,
 951    buffer: &text::BufferSnapshot,
 952    base_text: &language::BufferSnapshot,
 953    secondary_base_text: &language::BufferSnapshot,
 954) -> (Vec<Range<Anchor>>, Vec<Range<Point>>) {
 955    let primary_hunk_buffer_text = buffer
 956        .text_for_range(hunk.buffer_range.clone())
 957        .collect::<String>();
 958    let secondary_hunk_buffer_text = buffer
 959        .text_for_range(secondary_hunk.buffer_range.clone())
 960        .collect::<String>();
 961    let primary_hunk_base_text = base_text
 962        .text_for_range(hunk.diff_base_byte_range.clone())
 963        .collect::<String>();
 964    let secondary_hunk_base_text = secondary_base_text
 965        .text_for_range(secondary_hunk.diff_base_byte_range.clone())
 966        .collect::<String>();
 967
 968    let primary_hunk_start_in_buffer = hunk.buffer_range.start.to_offset(buffer);
 969    // No word diffs
 970    let buffer_staged_lines = language::text_diff_with_options_internal(
 971        dbg!(&secondary_hunk_buffer_text),
 972        dbg!(&primary_hunk_buffer_text),
 973        DiffOptions {
 974            language_scope: None,
 975            max_word_diff_len: 0,
 976            max_word_diff_line_count: 0,
 977        },
 978    )
 979    .into_iter()
 980    .map(|edit| {
 981        buffer.anchor_before(edit.new.start + primary_hunk_start_in_buffer)
 982            ..buffer.anchor_after(edit.new.end + primary_hunk_start_in_buffer)
 983    })
 984    .collect::<Vec<_>>();
 985
 986    // FIXME
 987    if !buffer_staged_lines.is_empty() {
 988        eprintln!(
 989            "staged lines: {:?}",
 990            buffer_staged_lines
 991                .iter()
 992                .map(|range| buffer.text_for_range(range.clone()).collect::<String>())
 993                .collect::<Vec<_>>()
 994        );
 995    }
 996
 997    // FIXME
 998    (buffer_staged_lines, Vec::new())
 999}
1000
1001fn build_diff_options(
1002    file: Option<&Arc<dyn File>>,
1003    language: Option<LanguageName>,
1004    language_scope: Option<language::LanguageScope>,
1005    cx: &App,
1006) -> Option<DiffOptions> {
1007    #[cfg(any(test, feature = "test-support"))]
1008    {
1009        if !cx.has_global::<settings::SettingsStore>() {
1010            return Some(DiffOptions {
1011                language_scope,
1012                max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
1013                ..Default::default()
1014            });
1015        }
1016    }
1017
1018    language_settings(language, file, cx)
1019        .word_diff_enabled
1020        .then_some(DiffOptions {
1021            language_scope,
1022            max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
1023            ..Default::default()
1024        })
1025}
1026
1027fn compute_hunks(
1028    diff_base: Option<(Arc<str>, Rope)>,
1029    buffer: text::BufferSnapshot,
1030    diff_options: Option<DiffOptions>,
1031) -> SumTree<InternalDiffHunk> {
1032    let mut tree = SumTree::new(&buffer);
1033
1034    if let Some((diff_base, diff_base_rope)) = diff_base {
1035        let buffer_text = buffer.as_rope().to_string();
1036
1037        let mut options = GitOptions::default();
1038        options.context_lines(0);
1039        let patch = GitPatch::from_buffers(
1040            diff_base.as_bytes(),
1041            None,
1042            buffer_text.as_bytes(),
1043            None,
1044            Some(&mut options),
1045        )
1046        .log_err();
1047
1048        // A common case in Zed is that the empty buffer is represented as just a newline,
1049        // but if we just compute a naive diff you get a "preserved" line in the middle,
1050        // which is a bit odd.
1051        if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
1052            tree.push(
1053                InternalDiffHunk {
1054                    buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
1055                    diff_base_byte_range: 0..diff_base.len() - 1,
1056                    base_word_diffs: Vec::default(),
1057                    buffer_word_diffs: Vec::default(),
1058                },
1059                &buffer,
1060            );
1061            return tree;
1062        }
1063
1064        if let Some(patch) = patch {
1065            let mut divergence = 0;
1066            for hunk_index in 0..patch.num_hunks() {
1067                let hunk = process_patch_hunk(
1068                    &patch,
1069                    hunk_index,
1070                    &diff_base_rope,
1071                    &buffer,
1072                    &mut divergence,
1073                    diff_options.as_ref(),
1074                );
1075                tree.push(hunk, &buffer);
1076            }
1077        }
1078    } else {
1079        tree.push(
1080            InternalDiffHunk {
1081                buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
1082                diff_base_byte_range: 0..0,
1083                base_word_diffs: Vec::default(),
1084                buffer_word_diffs: Vec::default(),
1085            },
1086            &buffer,
1087        );
1088    }
1089
1090    tree
1091}
1092
1093fn compare_hunks(
1094    new_hunks: &SumTree<InternalDiffHunk>,
1095    old_hunks: &SumTree<InternalDiffHunk>,
1096    old_snapshot: &text::BufferSnapshot,
1097    new_snapshot: &text::BufferSnapshot,
1098    old_base_text: &text::BufferSnapshot,
1099    new_base_text: &text::BufferSnapshot,
1100) -> DiffChanged {
1101    let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
1102    let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
1103    old_cursor.next();
1104    new_cursor.next();
1105    let mut start = None;
1106    let mut end = None;
1107    let mut base_text_start: Option<Anchor> = None;
1108    let mut base_text_end: Option<Anchor> = None;
1109
1110    let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1111    let mut has_changes = false;
1112    let mut extended_end_candidate: Option<text::Anchor> = None;
1113
1114    loop {
1115        match (new_cursor.item(), old_cursor.item()) {
1116            (Some(new_hunk), Some(old_hunk)) => {
1117                match new_hunk
1118                    .buffer_range
1119                    .start
1120                    .cmp(&old_hunk.buffer_range.start, new_snapshot)
1121                {
1122                    Ordering::Less => {
1123                        has_changes = true;
1124                        extended_end_candidate = None;
1125                        start.get_or_insert(new_hunk.buffer_range.start);
1126                        base_text_start.get_or_insert(
1127                            new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1128                        );
1129                        end.replace(new_hunk.buffer_range.end);
1130                        let new_diff_range_end =
1131                            new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1132                        if base_text_end.is_none_or(|base_text_end| {
1133                            new_diff_range_end
1134                                .cmp(&base_text_end, &new_base_text)
1135                                .is_gt()
1136                        }) {
1137                            base_text_end = Some(new_diff_range_end)
1138                        }
1139                        new_cursor.next();
1140                    }
1141                    Ordering::Equal => {
1142                        if new_hunk != old_hunk {
1143                            has_changes = true;
1144                            extended_end_candidate = None;
1145                            start.get_or_insert(new_hunk.buffer_range.start);
1146                            base_text_start.get_or_insert(
1147                                new_base_text.anchor_before(new_hunk.diff_base_byte_range.start),
1148                            );
1149                            if old_hunk
1150                                .buffer_range
1151                                .end
1152                                .cmp(&new_hunk.buffer_range.end, new_snapshot)
1153                                .is_ge()
1154                            {
1155                                end.replace(old_hunk.buffer_range.end);
1156                            } else {
1157                                end.replace(new_hunk.buffer_range.end);
1158                            }
1159
1160                            let old_hunk_diff_base_range_end =
1161                                old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1162                            let new_hunk_diff_base_range_end =
1163                                new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1164
1165                            base_text_end.replace(
1166                                *old_hunk_diff_base_range_end
1167                                    .max(&new_hunk_diff_base_range_end, new_base_text),
1168                            );
1169                        } else {
1170                            if !has_changes {
1171                                last_unchanged_new_hunk_end = Some(new_hunk.buffer_range.end);
1172                            } else if extended_end_candidate.is_none() {
1173                                extended_end_candidate = Some(new_hunk.buffer_range.start);
1174                            }
1175                        }
1176
1177                        new_cursor.next();
1178                        old_cursor.next();
1179                    }
1180                    Ordering::Greater => {
1181                        has_changes = true;
1182                        extended_end_candidate = None;
1183                        start.get_or_insert(old_hunk.buffer_range.start);
1184                        base_text_start.get_or_insert(
1185                            old_base_text.anchor_after(old_hunk.diff_base_byte_range.start),
1186                        );
1187                        end.replace(old_hunk.buffer_range.end);
1188                        let old_diff_range_end =
1189                            old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1190                        if base_text_end.is_none_or(|base_text_end| {
1191                            old_diff_range_end
1192                                .cmp(&base_text_end, new_base_text)
1193                                .is_gt()
1194                        }) {
1195                            base_text_end = Some(old_diff_range_end)
1196                        }
1197                        old_cursor.next();
1198                    }
1199                }
1200            }
1201            (Some(new_hunk), None) => {
1202                has_changes = true;
1203                extended_end_candidate = None;
1204                start.get_or_insert(new_hunk.buffer_range.start);
1205                base_text_start
1206                    .get_or_insert(new_base_text.anchor_after(new_hunk.diff_base_byte_range.start));
1207                if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1208                {
1209                    end.replace(new_hunk.buffer_range.end);
1210                }
1211                let new_base_text_end =
1212                    new_base_text.anchor_after(new_hunk.diff_base_byte_range.end);
1213                if base_text_end.is_none_or(|base_text_end| {
1214                    new_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1215                }) {
1216                    base_text_end = Some(new_base_text_end)
1217                }
1218                new_cursor.next();
1219            }
1220            (None, Some(old_hunk)) => {
1221                has_changes = true;
1222                extended_end_candidate = None;
1223                start.get_or_insert(old_hunk.buffer_range.start);
1224                base_text_start
1225                    .get_or_insert(old_base_text.anchor_after(old_hunk.diff_base_byte_range.start));
1226                if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1227                {
1228                    end.replace(old_hunk.buffer_range.end);
1229                }
1230                let old_base_text_end =
1231                    old_base_text.anchor_after(old_hunk.diff_base_byte_range.end);
1232                if base_text_end.is_none_or(|base_text_end| {
1233                    old_base_text_end.cmp(&base_text_end, new_base_text).is_gt()
1234                }) {
1235                    base_text_end = Some(old_base_text_end);
1236                }
1237                old_cursor.next();
1238            }
1239            (None, None) => break,
1240        }
1241    }
1242
1243    let changed_range = start.zip(end).map(|(start, end)| start..end);
1244    let base_text_changed_range = base_text_start
1245        .zip(base_text_end)
1246        .map(|(start, end)| (start..end).to_offset(new_base_text));
1247
1248    let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1249        let extended_start = *last_unchanged_new_hunk_end
1250            .unwrap_or(text::Anchor::min_for_buffer(new_snapshot.remote_id()))
1251            .min(&changed_range.start, new_snapshot);
1252        let extended_start = new_snapshot
1253            .anchored_edits_since_in_range::<usize>(
1254                &old_snapshot.version(),
1255                extended_start..changed_range.start,
1256            )
1257            .map(|(_, anchors)| anchors.start)
1258            .min_by(|a, b| a.cmp(b, new_snapshot))
1259            .unwrap_or(changed_range.start);
1260
1261        let extended_end = *extended_end_candidate
1262            .unwrap_or(text::Anchor::max_for_buffer(new_snapshot.remote_id()))
1263            .max(&changed_range.end, new_snapshot);
1264        let extended_end = new_snapshot
1265            .anchored_edits_since_in_range::<usize>(
1266                &old_snapshot.version(),
1267                changed_range.end..extended_end,
1268            )
1269            .map(|(_, anchors)| anchors.end)
1270            .max_by(|a, b| a.cmp(b, new_snapshot))
1271            .unwrap_or(changed_range.end);
1272
1273        Some(extended_start..extended_end)
1274    } else {
1275        None
1276    };
1277
1278    DiffChanged {
1279        changed_range,
1280        base_text_changed_range,
1281        extended_range,
1282    }
1283}
1284
1285fn process_patch_hunk(
1286    patch: &GitPatch<'_>,
1287    hunk_index: usize,
1288    diff_base: &Rope,
1289    buffer: &text::BufferSnapshot,
1290    buffer_row_divergence: &mut i64,
1291    diff_options: Option<&DiffOptions>,
1292) -> InternalDiffHunk {
1293    let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
1294    assert!(line_item_count > 0);
1295
1296    let mut first_deletion_buffer_row: Option<u32> = None;
1297    let mut buffer_row_range: Option<Range<u32>> = None;
1298    let mut diff_base_byte_range: Option<Range<usize>> = None;
1299    let mut first_addition_old_row: Option<u32> = None;
1300
1301    for line_index in 0..line_item_count {
1302        let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
1303        let kind = line.origin_value();
1304        let content_offset = line.content_offset() as isize;
1305        let content_len = line.content().len() as isize;
1306        match kind {
1307            GitDiffLineType::Addition => {
1308                if first_addition_old_row.is_none() {
1309                    first_addition_old_row = Some(
1310                        (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
1311                    );
1312                }
1313                *buffer_row_divergence += 1;
1314                let row = line.new_lineno().unwrap().saturating_sub(1);
1315
1316                match &mut buffer_row_range {
1317                    Some(Range { end, .. }) => *end = row + 1,
1318                    None => buffer_row_range = Some(row..row + 1),
1319                }
1320            }
1321            GitDiffLineType::Deletion => {
1322                let end = content_offset + content_len;
1323
1324                match &mut diff_base_byte_range {
1325                    Some(head_byte_range) => head_byte_range.end = end as usize,
1326                    None => diff_base_byte_range = Some(content_offset as usize..end as usize),
1327                }
1328
1329                if first_deletion_buffer_row.is_none() {
1330                    let old_row = line.old_lineno().unwrap().saturating_sub(1);
1331                    let row = old_row as i64 + *buffer_row_divergence;
1332                    first_deletion_buffer_row = Some(row as u32);
1333                }
1334
1335                *buffer_row_divergence -= 1;
1336            }
1337            _ => {}
1338        }
1339    }
1340
1341    let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
1342        // Pure deletion hunk without addition.
1343        let row = first_deletion_buffer_row.unwrap();
1344        row..row
1345    });
1346    let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
1347        // Pure addition hunk without deletion.
1348        let row = first_addition_old_row.unwrap();
1349        let offset = diff_base.point_to_offset(Point::new(row, 0));
1350        offset..offset
1351    });
1352
1353    let start = Point::new(buffer_row_range.start, 0);
1354    let end = Point::new(buffer_row_range.end, 0);
1355    let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
1356
1357    let base_line_count = line_item_count.saturating_sub(buffer_row_range.len());
1358
1359    let (base_word_diffs, buffer_word_diffs) = if let Some(diff_options) = diff_options
1360        && !buffer_row_range.is_empty()
1361        && base_line_count == buffer_row_range.len()
1362        && diff_options.max_word_diff_line_count >= base_line_count
1363    {
1364        let base_text: String = diff_base
1365            .chunks_in_range(diff_base_byte_range.clone())
1366            .collect();
1367
1368        let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
1369
1370        let (base_word_diffs, buffer_word_diffs_relative) = word_diff_ranges(
1371            &base_text,
1372            &buffer_text,
1373            DiffOptions {
1374                language_scope: diff_options.language_scope.clone(),
1375                ..*diff_options
1376            },
1377        );
1378
1379        let buffer_start_offset = buffer_range.start.to_offset(buffer);
1380        let buffer_word_diffs = buffer_word_diffs_relative
1381            .into_iter()
1382            .map(|range| {
1383                let start = buffer.anchor_after(buffer_start_offset + range.start);
1384                let end = buffer.anchor_after(buffer_start_offset + range.end);
1385                start..end
1386            })
1387            .collect();
1388
1389        (base_word_diffs, buffer_word_diffs)
1390    } else {
1391        (Vec::default(), Vec::default())
1392    };
1393
1394    InternalDiffHunk {
1395        buffer_range,
1396        diff_base_byte_range,
1397        base_word_diffs,
1398        buffer_word_diffs,
1399    }
1400}
1401
1402impl std::fmt::Debug for BufferDiff {
1403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1404        f.debug_struct("BufferChangeSet")
1405            .field("buffer_id", &self.buffer_id)
1406            .finish()
1407    }
1408}
1409
1410#[derive(Clone, Debug, Default)]
1411pub struct DiffChanged {
1412    pub changed_range: Option<Range<text::Anchor>>,
1413    pub base_text_changed_range: Option<Range<usize>>,
1414    pub extended_range: Option<Range<text::Anchor>>,
1415}
1416
1417#[derive(Clone, Debug)]
1418pub enum BufferDiffEvent {
1419    DiffChanged(DiffChanged),
1420    LanguageChanged,
1421    HunksStagedOrUnstaged(Option<Rope>),
1422}
1423
1424impl EventEmitter<BufferDiffEvent> for BufferDiff {}
1425
1426impl BufferDiff {
1427    pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1428        let base_text = cx.new(|cx| {
1429            let mut buffer = language::Buffer::local("", cx);
1430            buffer.set_capability(Capability::ReadOnly, cx);
1431            buffer
1432        });
1433
1434        BufferDiff {
1435            buffer_id: buffer.remote_id(),
1436            inner: BufferDiffInner {
1437                base_text,
1438                hunks: SumTree::new(buffer),
1439                pending_hunks: SumTree::new(buffer),
1440                base_text_exists: false,
1441                buffer_snapshot: buffer.clone(),
1442            },
1443            secondary_diff: None,
1444        }
1445    }
1446
1447    pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1448        let base_text = buffer.text();
1449        let base_text = cx.new(|cx| {
1450            let mut buffer = language::Buffer::local(base_text, cx);
1451            buffer.set_capability(Capability::ReadOnly, cx);
1452            buffer
1453        });
1454
1455        BufferDiff {
1456            buffer_id: buffer.remote_id(),
1457            inner: BufferDiffInner {
1458                base_text,
1459                hunks: SumTree::new(buffer),
1460                pending_hunks: SumTree::new(buffer),
1461                base_text_exists: true,
1462                buffer_snapshot: buffer.clone(),
1463            },
1464            secondary_diff: None,
1465        }
1466    }
1467
1468    #[cfg(any(test, feature = "test-support"))]
1469    pub fn new_with_base_text(
1470        base_text: &str,
1471        buffer: &text::BufferSnapshot,
1472        cx: &mut Context<Self>,
1473    ) -> Self {
1474        let mut this = BufferDiff::new(&buffer, cx);
1475        let mut base_text = base_text.to_owned();
1476        text::LineEnding::normalize(&mut base_text);
1477        let inner = cx.foreground_executor().block_on(this.update_diff(
1478            buffer.clone(),
1479            Some(Arc::from(base_text)),
1480            Some(false),
1481            None,
1482            cx,
1483        ));
1484        this.set_snapshot(inner, &buffer, cx).detach();
1485        this
1486    }
1487
1488    pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1489        self.secondary_diff = Some(diff);
1490    }
1491
1492    pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1493        self.secondary_diff.clone()
1494    }
1495
1496    pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1497        if self.secondary_diff.is_some() {
1498            self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1499                buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1500                diff_base_byte_range: 0..0,
1501            });
1502            let changed_range = Some(Anchor::min_max_range_for_buffer(self.buffer_id));
1503            let base_text_range = Some(0..self.base_text(cx).len());
1504            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1505                changed_range: changed_range.clone(),
1506                base_text_changed_range: base_text_range,
1507                extended_range: changed_range,
1508            }));
1509        }
1510    }
1511
1512    pub fn stage_or_unstage_hunks(
1513        &mut self,
1514        stage: bool,
1515        hunks: &[DiffHunk],
1516        buffer: &text::BufferSnapshot,
1517        file_exists: bool,
1518        cx: &mut Context<Self>,
1519    ) -> Option<Rope> {
1520        let new_index_text = self
1521            .secondary_diff
1522            .as_ref()?
1523            .update(cx, |secondary_diff, cx| {
1524                self.inner.stage_or_unstage_hunks_impl(
1525                    &secondary_diff.inner,
1526                    stage,
1527                    hunks,
1528                    buffer,
1529                    file_exists,
1530                    cx,
1531                )
1532            });
1533
1534        cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1535            new_index_text.clone(),
1536        ));
1537        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1538            let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1539            let base_text_changed_range =
1540                Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1541            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1542                changed_range: changed_range.clone(),
1543                base_text_changed_range,
1544                extended_range: changed_range,
1545            }));
1546        }
1547        new_index_text
1548    }
1549
1550    pub fn stage_or_unstage_all_hunks(
1551        &mut self,
1552        stage: bool,
1553        buffer: &text::BufferSnapshot,
1554        file_exists: bool,
1555        cx: &mut Context<Self>,
1556    ) {
1557        let hunks = self
1558            .snapshot(cx)
1559            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1560            .collect::<Vec<_>>();
1561        let Some(secondary) = self.secondary_diff.clone() else {
1562            return;
1563        };
1564        let secondary = secondary.read(cx).inner.clone();
1565        self.inner
1566            .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1567        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1568            let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1569            let base_text_changed_range =
1570                Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1571            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1572                changed_range: changed_range.clone(),
1573                base_text_changed_range,
1574                extended_range: changed_range,
1575            }));
1576        }
1577    }
1578
1579    pub fn update_diff(
1580        &self,
1581        buffer: text::BufferSnapshot,
1582        base_text: Option<Arc<str>>,
1583        base_text_change: Option<bool>,
1584        language: Option<Arc<Language>>,
1585        cx: &App,
1586    ) -> Task<BufferDiffUpdate> {
1587        let prev_base_text = self.base_text(cx).as_rope().clone();
1588        let base_text_changed = base_text_change.is_some();
1589        let compute_base_text_edits = base_text_change == Some(true);
1590        let diff_options = build_diff_options(
1591            None,
1592            language.as_ref().map(|l| l.name()),
1593            language.as_ref().map(|l| l.default_scope()),
1594            cx,
1595        );
1596        let buffer_snapshot = buffer.clone();
1597
1598        let base_text_diff_task = if base_text_changed && compute_base_text_edits {
1599            base_text
1600                .as_ref()
1601                .map(|new_text| self.inner.base_text.read(cx).diff(new_text.clone(), cx))
1602        } else {
1603            None
1604        };
1605
1606        let hunk_task = cx.background_executor().spawn({
1607            let buffer_snapshot = buffer_snapshot.clone();
1608            async move {
1609                let base_text_rope = if let Some(base_text) = &base_text {
1610                    if base_text_changed {
1611                        Rope::from(base_text.as_ref())
1612                    } else {
1613                        prev_base_text
1614                    }
1615                } else {
1616                    Rope::new()
1617                };
1618                let base_text_exists = base_text.is_some();
1619                let hunks = compute_hunks(
1620                    base_text
1621                        .clone()
1622                        .map(|base_text| (base_text, base_text_rope.clone())),
1623                    buffer.clone(),
1624                    diff_options,
1625                );
1626                let base_text = base_text.unwrap_or_default();
1627                BufferDiffInner {
1628                    base_text,
1629                    hunks,
1630                    base_text_exists,
1631                    pending_hunks: SumTree::new(&buffer),
1632                    buffer_snapshot,
1633                }
1634            }
1635        });
1636
1637        cx.background_executor().spawn(async move {
1638            let (inner, base_text_edits) = match base_text_diff_task {
1639                Some(diff_task) => {
1640                    let (inner, diff) = futures::join!(hunk_task, diff_task);
1641                    (inner, Some(diff))
1642                }
1643                None => (hunk_task.await, None),
1644            };
1645
1646            BufferDiffUpdate {
1647                inner,
1648                buffer_snapshot,
1649                base_text_edits,
1650                base_text_changed,
1651            }
1652        })
1653    }
1654
1655    #[ztracing::instrument(skip_all)]
1656    pub fn language_changed(
1657        &mut self,
1658        language: Option<Arc<Language>>,
1659        language_registry: Option<Arc<LanguageRegistry>>,
1660        cx: &mut Context<Self>,
1661    ) {
1662        let fut = self.inner.base_text.update(cx, |base_text, cx| {
1663            if let Some(language_registry) = language_registry {
1664                base_text.set_language_registry(language_registry);
1665            }
1666            base_text.set_language(language, cx);
1667            base_text.parsing_idle()
1668        });
1669        cx.spawn(async move |this, cx| {
1670            fut.await;
1671            this.update(cx, |_, cx| {
1672                cx.emit(BufferDiffEvent::LanguageChanged);
1673            })
1674            .ok();
1675        })
1676        .detach();
1677    }
1678
1679    fn set_snapshot_with_secondary_inner(
1680        &mut self,
1681        update: BufferDiffUpdate,
1682        buffer: &text::BufferSnapshot,
1683        secondary_diff_change: Option<Range<Anchor>>,
1684        clear_pending_hunks: bool,
1685        cx: &mut Context<Self>,
1686    ) -> impl Future<Output = DiffChanged> + use<> {
1687        log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1688
1689        let old_snapshot = self.snapshot(cx);
1690        let new_state = update.inner;
1691        let base_text_changed = update.base_text_changed;
1692
1693        let state = &mut self.inner;
1694        state.base_text_exists = new_state.base_text_exists;
1695        let should_compare_hunks = update.base_text_edits.is_some() || !base_text_changed;
1696        let parsing_idle = if let Some(diff) = update.base_text_edits {
1697            state.base_text.update(cx, |base_text, cx| {
1698                base_text.set_capability(Capability::ReadWrite, cx);
1699                base_text.apply_diff(diff, cx);
1700                base_text.set_capability(Capability::ReadOnly, cx);
1701                Some(base_text.parsing_idle())
1702            })
1703        } else if update.base_text_changed {
1704            state.base_text.update(cx, |base_text, cx| {
1705                base_text.set_capability(Capability::ReadWrite, cx);
1706                base_text.set_text(new_state.base_text.clone(), cx);
1707                base_text.set_capability(Capability::ReadOnly, cx);
1708                Some(base_text.parsing_idle())
1709            })
1710        } else {
1711            None
1712        };
1713
1714        let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1715        let old_base_snapshot = &old_snapshot.inner.base_text;
1716        let new_base_snapshot = state.base_text.read(cx).snapshot();
1717        let DiffChanged {
1718            mut changed_range,
1719            mut base_text_changed_range,
1720            mut extended_range,
1721        } = match (state.base_text_exists, new_state.base_text_exists) {
1722            (false, false) => DiffChanged::default(),
1723            (true, true) if should_compare_hunks => compare_hunks(
1724                &new_state.hunks,
1725                &old_snapshot.inner.hunks,
1726                old_buffer_snapshot,
1727                buffer,
1728                old_base_snapshot,
1729                &new_base_snapshot,
1730            ),
1731            _ => {
1732                let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
1733                let full_base_range = 0..new_state.base_text.len();
1734                DiffChanged {
1735                    changed_range: Some(full_range.clone()),
1736                    base_text_changed_range: Some(full_base_range),
1737                    extended_range: Some(full_range),
1738                }
1739            }
1740        };
1741        state.hunks = new_state.hunks;
1742        state.buffer_snapshot = update.buffer_snapshot;
1743
1744        if base_text_changed || clear_pending_hunks {
1745            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1746            {
1747                let pending_range = first.buffer_range.start..last.buffer_range.end;
1748                if let Some(range) = &mut changed_range {
1749                    range.start = *range.start.min(&pending_range.start, buffer);
1750                    range.end = *range.end.max(&pending_range.end, buffer);
1751                } else {
1752                    changed_range = Some(pending_range.clone());
1753                }
1754
1755                if let Some(base_text_range) = base_text_changed_range.as_mut() {
1756                    base_text_range.start =
1757                        base_text_range.start.min(first.diff_base_byte_range.start);
1758                    base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1759                } else {
1760                    base_text_changed_range =
1761                        Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1762                }
1763
1764                if let Some(ext) = &mut extended_range {
1765                    ext.start = *ext.start.min(&pending_range.start, buffer);
1766                    ext.end = *ext.end.max(&pending_range.end, buffer);
1767                } else {
1768                    extended_range = Some(pending_range);
1769                }
1770            }
1771            state.pending_hunks = SumTree::new(buffer);
1772        }
1773
1774        if let Some(secondary_changed_range) = secondary_diff_change
1775            && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1776                old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1777        {
1778            if let Some(range) = &mut changed_range {
1779                range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1780                range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1781            } else {
1782                changed_range = Some(secondary_hunk_range.clone());
1783            }
1784
1785            if let Some(base_text_range) = base_text_changed_range.as_mut() {
1786                base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1787                base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1788            } else {
1789                base_text_changed_range = Some(secondary_base_range);
1790            }
1791
1792            if let Some(ext) = &mut extended_range {
1793                ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1794                ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1795            } else {
1796                extended_range = Some(secondary_hunk_range);
1797            }
1798        }
1799
1800        async move {
1801            if let Some(parsing_idle) = parsing_idle {
1802                parsing_idle.await;
1803            }
1804            DiffChanged {
1805                changed_range,
1806                base_text_changed_range,
1807                extended_range,
1808            }
1809        }
1810    }
1811
1812    pub fn set_snapshot(
1813        &mut self,
1814        new_state: BufferDiffUpdate,
1815        buffer: &text::BufferSnapshot,
1816        cx: &mut Context<Self>,
1817    ) -> Task<Option<Range<Anchor>>> {
1818        self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1819    }
1820
1821    pub fn set_snapshot_with_secondary(
1822        &mut self,
1823        update: BufferDiffUpdate,
1824        buffer: &text::BufferSnapshot,
1825        secondary_diff_change: Option<Range<Anchor>>,
1826        clear_pending_hunks: bool,
1827        cx: &mut Context<Self>,
1828    ) -> Task<Option<Range<Anchor>>> {
1829        let fut = self.set_snapshot_with_secondary_inner(
1830            update,
1831            buffer,
1832            secondary_diff_change,
1833            clear_pending_hunks,
1834            cx,
1835        );
1836
1837        cx.spawn(async move |this, cx| {
1838            let change = fut.await;
1839            this.update(cx, |_, cx| {
1840                cx.emit(BufferDiffEvent::DiffChanged(change.clone()));
1841            })
1842            .ok();
1843            change.changed_range
1844        })
1845    }
1846
1847    pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1848        self.inner.base_text.read(cx).snapshot()
1849    }
1850
1851    pub fn base_text_exists(&self) -> bool {
1852        self.inner.base_text_exists
1853    }
1854
1855    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1856        BufferDiffSnapshot {
1857            inner: BufferDiffInner {
1858                hunks: self.inner.hunks.clone(),
1859                pending_hunks: self.inner.pending_hunks.clone(),
1860                base_text: self.inner.base_text.read(cx).snapshot(),
1861                base_text_exists: self.inner.base_text_exists,
1862                buffer_snapshot: self.inner.buffer_snapshot.clone(),
1863            },
1864            secondary_diff: self
1865                .secondary_diff
1866                .as_ref()
1867                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1868        }
1869    }
1870
1871    /// Used in cases where the change set isn't derived from git.
1872    pub fn set_base_text(
1873        &mut self,
1874        base_text: Option<Arc<str>>,
1875        language: Option<Arc<Language>>,
1876        buffer: text::BufferSnapshot,
1877        cx: &mut Context<Self>,
1878    ) -> oneshot::Receiver<()> {
1879        let (tx, rx) = oneshot::channel();
1880        let complete_on_drop = util::defer(|| {
1881            tx.send(()).ok();
1882        });
1883        cx.spawn(async move |this, cx| {
1884            let Some(state) = this
1885                .update(cx, |this, cx| {
1886                    this.update_diff(buffer.clone(), base_text, Some(false), language, cx)
1887                })
1888                .log_err()
1889            else {
1890                return;
1891            };
1892            let state = state.await;
1893            if let Some(task) = this
1894                .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
1895                .log_err()
1896            {
1897                task.await;
1898            }
1899            drop(complete_on_drop)
1900        })
1901        .detach();
1902        rx
1903    }
1904
1905    pub fn base_text_string(&self, cx: &App) -> Option<String> {
1906        self.inner
1907            .base_text_exists
1908            .then(|| self.inner.base_text.read(cx).text())
1909    }
1910
1911    #[cfg(any(test, feature = "test-support"))]
1912    pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1913        let language = self.base_text(cx).language().cloned();
1914        let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1915        let fut = self.update_diff(buffer.clone(), base_text, None, language, cx);
1916        let fg_executor = cx.foreground_executor().clone();
1917        let snapshot = fg_executor.block_on(fut);
1918        let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
1919        let change = fg_executor.block_on(fut);
1920        cx.emit(BufferDiffEvent::DiffChanged(change));
1921    }
1922
1923    pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1924        self.inner.base_text.clone()
1925    }
1926}
1927
1928impl DiffHunk {
1929    pub fn is_created_file(&self) -> bool {
1930        self.diff_base_byte_range == (0..0)
1931            && self.buffer_range.start.is_min()
1932            && self.buffer_range.end.is_max()
1933    }
1934
1935    pub fn status(&self) -> DiffHunkStatus {
1936        let kind = if self.buffer_range.start == self.buffer_range.end {
1937            DiffHunkStatusKind::Deleted
1938        } else if self.diff_base_byte_range.is_empty() {
1939            DiffHunkStatusKind::Added
1940        } else {
1941            DiffHunkStatusKind::Modified
1942        };
1943        DiffHunkStatus {
1944            kind,
1945            secondary: self.secondary_status,
1946        }
1947    }
1948}
1949
1950impl DiffHunkStatus {
1951    pub fn has_secondary_hunk(&self) -> bool {
1952        matches!(
1953            self.secondary,
1954            DiffHunkSecondaryStatus::HasSecondaryHunk
1955                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1956                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1957        )
1958    }
1959
1960    pub fn is_pending(&self) -> bool {
1961        matches!(
1962            self.secondary,
1963            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1964                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1965        )
1966    }
1967
1968    pub fn is_deleted(&self) -> bool {
1969        self.kind == DiffHunkStatusKind::Deleted
1970    }
1971
1972    pub fn is_added(&self) -> bool {
1973        self.kind == DiffHunkStatusKind::Added
1974    }
1975
1976    pub fn is_modified(&self) -> bool {
1977        self.kind == DiffHunkStatusKind::Modified
1978    }
1979
1980    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1981        Self {
1982            kind: DiffHunkStatusKind::Added,
1983            secondary,
1984        }
1985    }
1986
1987    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1988        Self {
1989            kind: DiffHunkStatusKind::Modified,
1990            secondary,
1991        }
1992    }
1993
1994    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1995        Self {
1996            kind: DiffHunkStatusKind::Deleted,
1997            secondary,
1998        }
1999    }
2000
2001    pub fn deleted_none() -> Self {
2002        Self {
2003            kind: DiffHunkStatusKind::Deleted,
2004            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2005        }
2006    }
2007
2008    pub fn added_none() -> Self {
2009        Self {
2010            kind: DiffHunkStatusKind::Added,
2011            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2012        }
2013    }
2014
2015    pub fn modified_none() -> Self {
2016        Self {
2017            kind: DiffHunkStatusKind::Modified,
2018            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
2019        }
2020    }
2021}
2022
2023#[cfg(any(test, feature = "test-support"))]
2024#[track_caller]
2025pub fn assert_hunks<ExpectedText, HunkIter>(
2026    diff_hunks: HunkIter,
2027    buffer: &text::BufferSnapshot,
2028    diff_base: &str,
2029    // Line range, deleted, added, status
2030    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
2031) where
2032    HunkIter: Iterator<Item = DiffHunk>,
2033    ExpectedText: AsRef<str>,
2034{
2035    let actual_hunks = diff_hunks
2036        .map(|hunk| {
2037            (
2038                hunk.range.clone(),
2039                &diff_base[hunk.diff_base_byte_range.clone()],
2040                buffer
2041                    .text_for_range(hunk.range.clone())
2042                    .collect::<String>(),
2043                hunk.status(),
2044            )
2045        })
2046        .collect::<Vec<_>>();
2047
2048    let expected_hunks: Vec<_> = expected_hunks
2049        .iter()
2050        .map(|(line_range, deleted_text, added_text, status)| {
2051            (
2052                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
2053                deleted_text.as_ref(),
2054                added_text.as_ref().to_string(),
2055                *status,
2056            )
2057        })
2058        .collect();
2059
2060    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
2061}
2062
2063#[cfg(test)]
2064mod tests {
2065    use std::{fmt::Write as _, sync::mpsc};
2066
2067    use super::*;
2068    use gpui::TestAppContext;
2069    use pretty_assertions::{assert_eq, assert_ne};
2070    use rand::{Rng as _, rngs::StdRng};
2071    use text::{Buffer, BufferId, ReplicaId, Rope};
2072    use unindent::Unindent as _;
2073    use util::test::marked_text_ranges;
2074
2075    #[ctor::ctor]
2076    fn init_logger() {
2077        zlog::init_test();
2078    }
2079
2080    #[gpui::test]
2081    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
2082        let diff_base = "
2083            one
2084            two
2085            three
2086        "
2087        .unindent();
2088
2089        let buffer_text = "
2090            one
2091            HELLO
2092            three
2093        "
2094        .unindent();
2095
2096        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2097        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
2098        assert_hunks(
2099            diff.hunks_intersecting_range(
2100                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2101                &buffer,
2102            ),
2103            &buffer,
2104            &diff_base,
2105            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
2106        );
2107
2108        buffer.edit([(0..0, "point five\n")]);
2109        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
2110        assert_hunks(
2111            diff.hunks_intersecting_range(
2112                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2113                &buffer,
2114            ),
2115            &buffer,
2116            &diff_base,
2117            &[
2118                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
2119                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
2120            ],
2121        );
2122
2123        diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2124        assert_hunks::<&str, _>(
2125            diff.hunks_intersecting_range(
2126                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2127                &buffer,
2128            ),
2129            &buffer,
2130            &diff_base,
2131            &[],
2132        );
2133    }
2134
2135    #[gpui::test]
2136    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
2137        let head_text = "
2138            zero
2139            one
2140            two
2141            three
2142            four
2143            five
2144            six
2145            seven
2146            eight
2147            nine
2148        "
2149        .unindent();
2150
2151        let index_text = "
2152            zero
2153            one
2154            TWO
2155            three
2156            FOUR
2157            five
2158            six
2159            seven
2160            eight
2161            NINE
2162        "
2163        .unindent();
2164
2165        let buffer_text = "
2166            zero
2167            one
2168            TWO
2169            three
2170            FOUR
2171            FIVE
2172            six
2173            SEVEN
2174            eight
2175            nine
2176        "
2177        .unindent();
2178
2179        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2180        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
2181        let mut uncommitted_diff =
2182            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
2183        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
2184
2185        let expected_hunks = vec![
2186            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
2187            (
2188                4..6,
2189                "four\nfive\n",
2190                "FOUR\nFIVE\n",
2191                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2192            ),
2193            (
2194                7..8,
2195                "seven\n",
2196                "SEVEN\n",
2197                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2198            ),
2199        ];
2200
2201        assert_hunks(
2202            uncommitted_diff.hunks_intersecting_range(
2203                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2204                &buffer,
2205            ),
2206            &buffer,
2207            &head_text,
2208            &expected_hunks,
2209        );
2210    }
2211
2212    #[gpui::test]
2213    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
2214        let diff_base = "
2215            one
2216            two
2217            three
2218            four
2219            five
2220            six
2221            seven
2222            eight
2223            nine
2224            ten
2225        "
2226        .unindent();
2227
2228        let buffer_text = "
2229            A
2230            one
2231            B
2232            two
2233            C
2234            three
2235            HELLO
2236            four
2237            five
2238            SIXTEEN
2239            seven
2240            eight
2241            WORLD
2242            nine
2243
2244            ten
2245
2246        "
2247        .unindent();
2248
2249        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2250        let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
2251        assert_eq!(
2252            diff.hunks_intersecting_range(
2253                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2254                &buffer
2255            )
2256            .count(),
2257            8
2258        );
2259
2260        assert_hunks(
2261            diff.hunks_intersecting_range(
2262                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
2263                &buffer,
2264            ),
2265            &buffer,
2266            &diff_base,
2267            &[
2268                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
2269                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
2270                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
2271            ],
2272        );
2273    }
2274
2275    #[gpui::test]
2276    async fn test_stage_hunk(cx: &mut TestAppContext) {
2277        struct Example {
2278            name: &'static str,
2279            head_text: String,
2280            index_text: String,
2281            buffer_marked_text: String,
2282            final_index_text: String,
2283        }
2284
2285        let table = [
2286            Example {
2287                name: "uncommitted hunk straddles end of unstaged hunk",
2288                head_text: "
2289                    one
2290                    two
2291                    three
2292                    four
2293                    five
2294                "
2295                .unindent(),
2296                index_text: "
2297                    one
2298                    TWO_HUNDRED
2299                    three
2300                    FOUR_HUNDRED
2301                    five
2302                "
2303                .unindent(),
2304                buffer_marked_text: "
2305                    ZERO
2306                    one
2307                    two
2308                    «THREE_HUNDRED
2309                    FOUR_HUNDRED»
2310                    five
2311                    SIX
2312                "
2313                .unindent(),
2314                final_index_text: "
2315                    one
2316                    two
2317                    THREE_HUNDRED
2318                    FOUR_HUNDRED
2319                    five
2320                "
2321                .unindent(),
2322            },
2323            Example {
2324                name: "uncommitted hunk straddles start of unstaged hunk",
2325                head_text: "
2326                    one
2327                    two
2328                    three
2329                    four
2330                    five
2331                "
2332                .unindent(),
2333                index_text: "
2334                    one
2335                    TWO_HUNDRED
2336                    three
2337                    FOUR_HUNDRED
2338                    five
2339                "
2340                .unindent(),
2341                buffer_marked_text: "
2342                    ZERO
2343                    one
2344                    «TWO_HUNDRED
2345                    THREE_HUNDRED»
2346                    four
2347                    five
2348                    SIX
2349                "
2350                .unindent(),
2351                final_index_text: "
2352                    one
2353                    TWO_HUNDRED
2354                    THREE_HUNDRED
2355                    four
2356                    five
2357                "
2358                .unindent(),
2359            },
2360            Example {
2361                name: "uncommitted hunk strictly contains unstaged hunks",
2362                head_text: "
2363                    one
2364                    two
2365                    three
2366                    four
2367                    five
2368                    six
2369                    seven
2370                "
2371                .unindent(),
2372                index_text: "
2373                    one
2374                    TWO
2375                    THREE
2376                    FOUR
2377                    FIVE
2378                    SIX
2379                    seven
2380                "
2381                .unindent(),
2382                buffer_marked_text: "
2383                    one
2384                    TWO
2385                    «THREE_HUNDRED
2386                    FOUR
2387                    FIVE_HUNDRED»
2388                    SIX
2389                    seven
2390                "
2391                .unindent(),
2392                final_index_text: "
2393                    one
2394                    TWO
2395                    THREE_HUNDRED
2396                    FOUR
2397                    FIVE_HUNDRED
2398                    SIX
2399                    seven
2400                "
2401                .unindent(),
2402            },
2403            Example {
2404                name: "uncommitted deletion hunk",
2405                head_text: "
2406                    one
2407                    two
2408                    three
2409                    four
2410                    five
2411                "
2412                .unindent(),
2413                index_text: "
2414                    one
2415                    two
2416                    three
2417                    four
2418                    five
2419                "
2420                .unindent(),
2421                buffer_marked_text: "
2422                    one
2423                    ˇfive
2424                "
2425                .unindent(),
2426                final_index_text: "
2427                    one
2428                    five
2429                "
2430                .unindent(),
2431            },
2432            Example {
2433                name: "one unstaged hunk that contains two uncommitted hunks",
2434                head_text: "
2435                    one
2436                    two
2437
2438                    three
2439                    four
2440                "
2441                .unindent(),
2442                index_text: "
2443                    one
2444                    two
2445                    three
2446                    four
2447                "
2448                .unindent(),
2449                buffer_marked_text: "
2450                    «one
2451
2452                    three // modified
2453                    four»
2454                "
2455                .unindent(),
2456                final_index_text: "
2457                    one
2458
2459                    three // modified
2460                    four
2461                "
2462                .unindent(),
2463            },
2464            Example {
2465                name: "one uncommitted hunk that contains two unstaged hunks",
2466                head_text: "
2467                    one
2468                    two
2469                    three
2470                    four
2471                    five
2472                "
2473                .unindent(),
2474                index_text: "
2475                    ZERO
2476                    one
2477                    TWO
2478                    THREE
2479                    FOUR
2480                    five
2481                "
2482                .unindent(),
2483                buffer_marked_text: "
2484                    «one
2485                    TWO_HUNDRED
2486                    THREE
2487                    FOUR_HUNDRED
2488                    five»
2489                "
2490                .unindent(),
2491                final_index_text: "
2492                    ZERO
2493                    one
2494                    TWO_HUNDRED
2495                    THREE
2496                    FOUR_HUNDRED
2497                    five
2498                "
2499                .unindent(),
2500            },
2501        ];
2502
2503        for example in table {
2504            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2505            let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2506            let hunk_range =
2507                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2508
2509            let unstaged_diff =
2510                cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2511
2512            let uncommitted_diff = cx.new(|cx| {
2513                let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2514                diff.set_secondary_diff(unstaged_diff);
2515                diff
2516            });
2517
2518            uncommitted_diff.update(cx, |diff, cx| {
2519                let hunks = diff
2520                    .snapshot(cx)
2521                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2522                    .collect::<Vec<_>>();
2523                for hunk in &hunks {
2524                    assert_ne!(
2525                        hunk.secondary_status,
2526                        DiffHunkSecondaryStatus::NoSecondaryHunk
2527                    )
2528                }
2529
2530                let new_index_text = diff
2531                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2532                    .unwrap()
2533                    .to_string();
2534
2535                let hunks = diff
2536                    .snapshot(cx)
2537                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2538                    .collect::<Vec<_>>();
2539                for hunk in &hunks {
2540                    assert_eq!(
2541                        hunk.secondary_status,
2542                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2543                    )
2544                }
2545
2546                pretty_assertions::assert_eq!(
2547                    new_index_text,
2548                    example.final_index_text,
2549                    "example: {}",
2550                    example.name
2551                );
2552            });
2553        }
2554    }
2555
2556    #[gpui::test]
2557    async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2558        // This test reproduces a crash where staging all hunks would cause an underflow
2559        // when there's one large unstaged hunk containing multiple uncommitted hunks.
2560        let head_text = "
2561            aaa
2562            bbb
2563            ccc
2564            ddd
2565            eee
2566            fff
2567            ggg
2568            hhh
2569            iii
2570            jjj
2571            kkk
2572            lll
2573        "
2574        .unindent();
2575
2576        let index_text = "
2577            aaa
2578            bbb
2579            CCC-index
2580            DDD-index
2581            EEE-index
2582            FFF-index
2583            GGG-index
2584            HHH-index
2585            III-index
2586            JJJ-index
2587            kkk
2588            lll
2589        "
2590        .unindent();
2591
2592        let buffer_text = "
2593            aaa
2594            bbb
2595            ccc-modified
2596            ddd
2597            eee-modified
2598            fff
2599            ggg
2600            hhh-modified
2601            iii
2602            jjj
2603            kkk
2604            lll
2605        "
2606        .unindent();
2607
2608        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2609
2610        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2611        let uncommitted_diff = cx.new(|cx| {
2612            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2613            diff.set_secondary_diff(unstaged_diff);
2614            diff
2615        });
2616
2617        uncommitted_diff.update(cx, |diff, cx| {
2618            diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2619        });
2620    }
2621
2622    #[gpui::test]
2623    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2624        let head_text = "
2625            one
2626            two
2627            three
2628        "
2629        .unindent();
2630        let index_text = head_text.clone();
2631        let buffer_text = "
2632            one
2633            three
2634        "
2635        .unindent();
2636
2637        let buffer = Buffer::new(
2638            ReplicaId::LOCAL,
2639            BufferId::new(1).unwrap(),
2640            buffer_text.clone(),
2641        );
2642        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2643        let uncommitted_diff = cx.new(|cx| {
2644            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2645            diff.set_secondary_diff(unstaged_diff.clone());
2646            diff
2647        });
2648
2649        uncommitted_diff.update(cx, |diff, cx| {
2650            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2651
2652            let new_index_text = diff
2653                .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2654                .unwrap()
2655                .to_string();
2656            assert_eq!(new_index_text, buffer_text);
2657
2658            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2659            assert_eq!(
2660                hunk.secondary_status,
2661                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2662            );
2663
2664            let index_text = diff
2665                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2666                .unwrap()
2667                .to_string();
2668            assert_eq!(index_text, head_text);
2669
2670            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2671            // optimistically unstaged (fine, could also be HasSecondaryHunk)
2672            assert_eq!(
2673                hunk.secondary_status,
2674                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2675            );
2676        });
2677    }
2678
2679    #[gpui::test]
2680    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2681        let base_text = "
2682            zero
2683            one
2684            two
2685            three
2686            four
2687            five
2688            six
2689            seven
2690            eight
2691            nine
2692        "
2693        .unindent();
2694
2695        let buffer_text_1 = "
2696            one
2697            three
2698            four
2699            five
2700            SIX
2701            seven
2702            eight
2703            NINE
2704        "
2705        .unindent();
2706
2707        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2708
2709        let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2710        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2711        let DiffChanged {
2712            changed_range,
2713            base_text_changed_range,
2714            extended_range: _,
2715        } = compare_hunks(
2716            &diff_1.inner.hunks,
2717            &empty_diff.inner.hunks,
2718            &buffer,
2719            &buffer,
2720            &diff_1.base_text(),
2721            &diff_1.base_text(),
2722        );
2723        let range = changed_range.unwrap();
2724        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2725        let base_text_range = base_text_changed_range.unwrap();
2726        assert_eq!(
2727            base_text_range.to_point(diff_1.base_text()),
2728            Point::new(0, 0)..Point::new(10, 0)
2729        );
2730
2731        // Edit does affects the diff because it recalculates word diffs.
2732        buffer.edit_via_marked_text(
2733            &"
2734                one
2735                three
2736                four
2737                five
2738                «SIX.5»
2739                seven
2740                eight
2741                NINE
2742            "
2743            .unindent(),
2744        );
2745        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2746        let DiffChanged {
2747            changed_range,
2748            base_text_changed_range,
2749            extended_range: _,
2750        } = compare_hunks(
2751            &diff_2.inner.hunks,
2752            &diff_1.inner.hunks,
2753            &buffer,
2754            &buffer,
2755            diff_2.base_text(),
2756            diff_2.base_text(),
2757        );
2758        assert_eq!(
2759            changed_range.unwrap().to_point(&buffer),
2760            Point::new(4, 0)..Point::new(5, 0),
2761        );
2762        assert_eq!(
2763            base_text_changed_range
2764                .unwrap()
2765                .to_point(diff_2.base_text()),
2766            Point::new(6, 0)..Point::new(7, 0),
2767        );
2768
2769        // Edit turns a deletion hunk into a modification.
2770        buffer.edit_via_marked_text(
2771            &"
2772                one
2773                «THREE»
2774                four
2775                five
2776                SIX.5
2777                seven
2778                eight
2779                NINE
2780            "
2781            .unindent(),
2782        );
2783        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2784        let DiffChanged {
2785            changed_range,
2786            base_text_changed_range,
2787            extended_range: _,
2788        } = compare_hunks(
2789            &diff_3.inner.hunks,
2790            &diff_2.inner.hunks,
2791            &buffer,
2792            &buffer,
2793            diff_3.base_text(),
2794            diff_3.base_text(),
2795        );
2796        let range = changed_range.unwrap();
2797        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2798        let base_text_range = base_text_changed_range.unwrap();
2799        assert_eq!(
2800            base_text_range.to_point(diff_3.base_text()),
2801            Point::new(2, 0)..Point::new(4, 0)
2802        );
2803
2804        // Edit turns a modification hunk into a deletion.
2805        buffer.edit_via_marked_text(
2806            &"
2807                one
2808                THREE
2809                four
2810                five«»
2811                seven
2812                eight
2813                NINE
2814            "
2815            .unindent(),
2816        );
2817        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2818        let DiffChanged {
2819            changed_range,
2820            base_text_changed_range,
2821            extended_range: _,
2822        } = compare_hunks(
2823            &diff_4.inner.hunks,
2824            &diff_3.inner.hunks,
2825            &buffer,
2826            &buffer,
2827            diff_4.base_text(),
2828            diff_4.base_text(),
2829        );
2830        let range = changed_range.unwrap();
2831        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2832        let base_text_range = base_text_changed_range.unwrap();
2833        assert_eq!(
2834            base_text_range.to_point(diff_4.base_text()),
2835            Point::new(6, 0)..Point::new(7, 0)
2836        );
2837
2838        // Edit introduces a new insertion hunk.
2839        buffer.edit_via_marked_text(
2840            &"
2841                one
2842                THREE
2843                four«
2844                FOUR.5
2845                »five
2846                seven
2847                eight
2848                NINE
2849            "
2850            .unindent(),
2851        );
2852        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2853        let DiffChanged {
2854            changed_range,
2855            base_text_changed_range,
2856            extended_range: _,
2857        } = compare_hunks(
2858            &diff_5.inner.hunks,
2859            &diff_4.inner.hunks,
2860            &buffer,
2861            &buffer,
2862            diff_5.base_text(),
2863            diff_5.base_text(),
2864        );
2865        let range = changed_range.unwrap();
2866        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2867        let base_text_range = base_text_changed_range.unwrap();
2868        assert_eq!(
2869            base_text_range.to_point(diff_5.base_text()),
2870            Point::new(5, 0)..Point::new(5, 0)
2871        );
2872
2873        // Edit removes a hunk.
2874        buffer.edit_via_marked_text(
2875            &"
2876                one
2877                THREE
2878                four
2879                FOUR.5
2880                five
2881                seven
2882                eight
2883                «nine»
2884            "
2885            .unindent(),
2886        );
2887        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2888        let DiffChanged {
2889            changed_range,
2890            base_text_changed_range,
2891            extended_range: _,
2892        } = compare_hunks(
2893            &diff_6.inner.hunks,
2894            &diff_5.inner.hunks,
2895            &buffer,
2896            &buffer,
2897            diff_6.base_text(),
2898            diff_6.base_text(),
2899        );
2900        let range = changed_range.unwrap();
2901        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2902        let base_text_range = base_text_changed_range.unwrap();
2903        assert_eq!(
2904            base_text_range.to_point(diff_6.base_text()),
2905            Point::new(9, 0)..Point::new(10, 0)
2906        );
2907
2908        buffer.edit_via_marked_text(
2909            &"
2910                one
2911                THREE
2912                four«»
2913                five
2914                seven
2915                eight
2916                «NINE»
2917            "
2918            .unindent(),
2919        );
2920
2921        let diff_7 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2922        let DiffChanged {
2923            changed_range,
2924            base_text_changed_range,
2925            extended_range: _,
2926        } = compare_hunks(
2927            &diff_7.inner.hunks,
2928            &diff_6.inner.hunks,
2929            &buffer,
2930            &buffer,
2931            diff_7.base_text(),
2932            diff_7.base_text(),
2933        );
2934        let range = changed_range.unwrap();
2935        assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
2936        let base_text_range = base_text_changed_range.unwrap();
2937        assert_eq!(
2938            base_text_range.to_point(diff_7.base_text()),
2939            Point::new(5, 0)..Point::new(10, 0)
2940        );
2941
2942        buffer.edit_via_marked_text(
2943            &"
2944                one
2945                THREE
2946                four
2947                five«»seven
2948                eight
2949                NINE
2950            "
2951            .unindent(),
2952        );
2953
2954        let diff_8 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2955        let DiffChanged {
2956            changed_range,
2957            base_text_changed_range,
2958            extended_range: _,
2959        } = compare_hunks(
2960            &diff_8.inner.hunks,
2961            &diff_7.inner.hunks,
2962            &buffer,
2963            &buffer,
2964            diff_8.base_text(),
2965            diff_8.base_text(),
2966        );
2967        let range = changed_range.unwrap();
2968        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
2969        let base_text_range = base_text_changed_range.unwrap();
2970        assert_eq!(
2971            base_text_range.to_point(diff_8.base_text()),
2972            Point::new(5, 0)..Point::new(8, 0)
2973        );
2974    }
2975
2976    #[gpui::test(iterations = 100)]
2977    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2978        fn gen_line(rng: &mut StdRng) -> String {
2979            if rng.random_bool(0.2) {
2980                "\n".to_owned()
2981            } else {
2982                let c = rng.random_range('A'..='Z');
2983                format!("{c}{c}{c}\n")
2984            }
2985        }
2986
2987        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2988            let mut old_lines = {
2989                let mut old_lines = Vec::new();
2990                let old_lines_iter = head.lines();
2991                for line in old_lines_iter {
2992                    assert!(!line.ends_with("\n"));
2993                    old_lines.push(line.to_owned());
2994                }
2995                if old_lines.last().is_some_and(|line| line.is_empty()) {
2996                    old_lines.pop();
2997                }
2998                old_lines.into_iter()
2999            };
3000            let mut result = String::new();
3001            let unchanged_count = rng.random_range(0..=old_lines.len());
3002            result +=
3003                &old_lines
3004                    .by_ref()
3005                    .take(unchanged_count)
3006                    .fold(String::new(), |mut s, line| {
3007                        writeln!(&mut s, "{line}").unwrap();
3008                        s
3009                    });
3010            while old_lines.len() > 0 {
3011                let deleted_count = rng.random_range(0..=old_lines.len());
3012                let _advance = old_lines
3013                    .by_ref()
3014                    .take(deleted_count)
3015                    .map(|line| line.len() + 1)
3016                    .sum::<usize>();
3017                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
3018                let added_count = rng.random_range(minimum_added..=5);
3019                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
3020                result += &addition;
3021
3022                if old_lines.len() > 0 {
3023                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
3024                    if blank_lines == old_lines.len() {
3025                        break;
3026                    };
3027                    let unchanged_count =
3028                        rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
3029                    result += &old_lines.by_ref().take(unchanged_count).fold(
3030                        String::new(),
3031                        |mut s, line| {
3032                            writeln!(&mut s, "{line}").unwrap();
3033                            s
3034                        },
3035                    );
3036                }
3037            }
3038            result
3039        }
3040
3041        fn uncommitted_diff(
3042            working_copy: &language::BufferSnapshot,
3043            index_text: &Rope,
3044            head_text: String,
3045            cx: &mut TestAppContext,
3046        ) -> Entity<BufferDiff> {
3047            let secondary = cx.new(|cx| {
3048                BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
3049            });
3050            cx.new(|cx| {
3051                let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
3052                diff.secondary_diff = Some(secondary);
3053                diff
3054            })
3055        }
3056
3057        let operations = std::env::var("OPERATIONS")
3058            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
3059            .unwrap_or(10);
3060
3061        let rng = &mut rng;
3062        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
3063            writeln!(&mut s, "{c}{c}{c}").unwrap();
3064            s
3065        });
3066        let working_copy = gen_working_copy(rng, &head_text);
3067        let working_copy = cx.new(|cx| {
3068            language::Buffer::local_normalized(
3069                Rope::from(working_copy.as_str()),
3070                text::LineEnding::default(),
3071                cx,
3072            )
3073        });
3074        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
3075        let mut index_text = if rng.random() {
3076            Rope::from(head_text.as_str())
3077        } else {
3078            working_copy.as_rope().clone()
3079        };
3080
3081        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3082        let mut hunks = diff.update(cx, |diff, cx| {
3083            diff.snapshot(cx)
3084                .hunks_intersecting_range(
3085                    Anchor::min_max_range_for_buffer(diff.buffer_id),
3086                    &working_copy,
3087                )
3088                .collect::<Vec<_>>()
3089        });
3090        if hunks.is_empty() {
3091            return;
3092        }
3093
3094        for _ in 0..operations {
3095            let i = rng.random_range(0..hunks.len());
3096            let hunk = &mut hunks[i];
3097            let hunk_to_change = hunk.clone();
3098            let stage = match hunk.secondary_status {
3099                DiffHunkSecondaryStatus::HasSecondaryHunk => {
3100                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
3101                    true
3102                }
3103                DiffHunkSecondaryStatus::NoSecondaryHunk => {
3104                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
3105                    false
3106                }
3107                _ => unreachable!(),
3108            };
3109
3110            index_text = diff.update(cx, |diff, cx| {
3111                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
3112                    .unwrap()
3113            });
3114
3115            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
3116            let found_hunks = diff.update(cx, |diff, cx| {
3117                diff.snapshot(cx)
3118                    .hunks_intersecting_range(
3119                        Anchor::min_max_range_for_buffer(diff.buffer_id),
3120                        &working_copy,
3121                    )
3122                    .collect::<Vec<_>>()
3123            });
3124            assert_eq!(hunks.len(), found_hunks.len());
3125
3126            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
3127                assert_eq!(
3128                    expected_hunk.buffer_range.to_point(&working_copy),
3129                    found_hunk.buffer_range.to_point(&working_copy)
3130                );
3131                assert_eq!(
3132                    expected_hunk.diff_base_byte_range,
3133                    found_hunk.diff_base_byte_range
3134                );
3135                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
3136            }
3137            hunks = found_hunks;
3138        }
3139    }
3140
3141    #[gpui::test]
3142    async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
3143        let base_text = "
3144            one
3145            two
3146            three
3147            four
3148            five
3149            six
3150        "
3151        .unindent();
3152        let buffer_text = "
3153            one
3154            TWO
3155            three
3156            four
3157            FIVE
3158            six
3159        "
3160        .unindent();
3161        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3162        let diff = cx.new(|cx| {
3163            BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
3164        });
3165        cx.run_until_parked();
3166        let (tx, rx) = mpsc::channel();
3167        let subscription =
3168            cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
3169
3170        let snapshot = buffer.update(cx, |buffer, cx| {
3171            buffer.set_text(
3172                "
3173                ONE
3174                TWO
3175                THREE
3176                FOUR
3177                FIVE
3178                SIX
3179            "
3180                .unindent(),
3181                cx,
3182            );
3183            buffer.text_snapshot()
3184        });
3185        let update = diff
3186            .update(cx, |diff, cx| {
3187                diff.update_diff(
3188                    snapshot.clone(),
3189                    Some(base_text.as_str().into()),
3190                    None,
3191                    None,
3192                    cx,
3193                )
3194            })
3195            .await;
3196        diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
3197            .await;
3198        cx.run_until_parked();
3199        drop(subscription);
3200        let events = rx.into_iter().collect::<Vec<_>>();
3201        match events.as_slice() {
3202            [
3203                BufferDiffEvent::DiffChanged(DiffChanged {
3204                    changed_range: _,
3205                    base_text_changed_range,
3206                    extended_range: _,
3207                }),
3208            ] => {
3209                // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3210                // assert_eq!(
3211                //     *changed_range,
3212                //     Some(Anchor::min_max_range_for_buffer(
3213                //         buffer.read_with(cx, |buffer, _| buffer.remote_id())
3214                //     ))
3215                // );
3216                assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3217            }
3218            _ => panic!("unexpected events: {:?}", events),
3219        }
3220    }
3221
3222    #[gpui::test]
3223    async fn test_extended_range(cx: &mut TestAppContext) {
3224        let base_text = "
3225            aaa
3226            bbb
3227
3228
3229
3230
3231
3232            ccc
3233            ddd
3234        "
3235        .unindent();
3236
3237        let buffer_text = "
3238            aaa
3239            bbb
3240
3241
3242
3243
3244
3245            CCC
3246            ddd
3247        "
3248        .unindent();
3249
3250        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3251        let old_buffer = buffer.snapshot();
3252        let diff_a = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
3253
3254        buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3255        let diff_b = BufferDiffSnapshot::new_sync(buffer.clone(), base_text, cx);
3256
3257        let DiffChanged {
3258            changed_range,
3259            base_text_changed_range: _,
3260            extended_range,
3261        } = compare_hunks(
3262            &diff_b.inner.hunks,
3263            &diff_a.inner.hunks,
3264            &old_buffer,
3265            &buffer,
3266            &diff_a.base_text(),
3267            &diff_a.base_text(),
3268        );
3269
3270        let changed_range = changed_range.unwrap();
3271        assert_eq!(
3272            changed_range.to_point(&buffer),
3273            Point::new(7, 0)..Point::new(9, 0),
3274            "changed_range should span from old hunk position to new hunk end"
3275        );
3276
3277        let extended_range = extended_range.unwrap();
3278        assert_eq!(
3279            extended_range.start.to_point(&buffer),
3280            Point::new(1, 3),
3281            "extended_range.start should extend to include the edit outside changed_range"
3282        );
3283        assert_eq!(
3284            extended_range.end.to_point(&buffer),
3285            Point::new(9, 0),
3286            "extended_range.end should collapse to changed_range.end when no edits in end margin"
3287        );
3288
3289        let base_text_2 = "
3290            one
3291            two
3292            three
3293            four
3294            five
3295            six
3296            seven
3297            eight
3298        "
3299        .unindent();
3300
3301        let buffer_text_2 = "
3302            ONE
3303            two
3304            THREE
3305            four
3306            FIVE
3307            six
3308            SEVEN
3309            eight
3310        "
3311        .unindent();
3312
3313        let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3314        let old_buffer_2 = buffer_2.snapshot();
3315        let diff_2a = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2.clone(), cx);
3316
3317        buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3318        let diff_2b = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2, cx);
3319
3320        let DiffChanged {
3321            changed_range,
3322            base_text_changed_range: _,
3323            extended_range,
3324        } = compare_hunks(
3325            &diff_2b.inner.hunks,
3326            &diff_2a.inner.hunks,
3327            &old_buffer_2,
3328            &buffer_2,
3329            &diff_2a.base_text(),
3330            &diff_2a.base_text(),
3331        );
3332
3333        let changed_range = changed_range.unwrap();
3334        assert_eq!(
3335            changed_range.to_point(&buffer_2),
3336            Point::new(4, 0)..Point::new(5, 0),
3337            "changed_range should be just the hunk that changed (FIVE)"
3338        );
3339
3340        let extended_range = extended_range.unwrap();
3341        assert_eq!(
3342            extended_range.to_point(&buffer_2),
3343            Point::new(4, 0)..Point::new(5, 0),
3344            "extended_range should equal changed_range when edit is within the hunk"
3345        );
3346    }
3347
3348    #[gpui::test]
3349    async fn test_buffer_diff_compare_with_base_text_change(_cx: &mut TestAppContext) {
3350        // Use a shared base text buffer so that anchors from old and new snapshots
3351        // share the same remote_id and resolve correctly across versions.
3352        let initial_base = "aaa\nbbb\nccc\nddd\neee\n";
3353        let mut base_text_buffer = Buffer::new(
3354            ReplicaId::LOCAL,
3355            BufferId::new(99).unwrap(),
3356            initial_base.to_string(),
3357        );
3358
3359        // --- Scenario 1: Base text gains a line, producing a new deletion hunk ---
3360        //
3361        // Buffer has a modification (ccc → CCC). When the base text gains
3362        // a new line "XXX" after "aaa", the diff now also contains a
3363        // deletion for that line, and the modification hunk shifts in the
3364        // base text.
3365        let buffer_text_1 = "aaa\nbbb\nCCC\nddd\neee\n";
3366        let buffer = Buffer::new(
3367            ReplicaId::LOCAL,
3368            BufferId::new(1).unwrap(),
3369            buffer_text_1.to_string(),
3370        );
3371
3372        let old_base_snapshot_1 = base_text_buffer.snapshot();
3373        let old_hunks_1 = compute_hunks(
3374            Some((Arc::from(initial_base), Rope::from(initial_base))),
3375            buffer.snapshot(),
3376            None,
3377        );
3378
3379        // Insert "XXX\n" after "aaa\n" in the base text.
3380        base_text_buffer.edit([(4..4, "XXX\n")]);
3381        let new_base_str_1: Arc<str> = Arc::from(base_text_buffer.text().as_str());
3382        let new_base_snapshot_1 = base_text_buffer.snapshot();
3383
3384        let new_hunks_1 = compute_hunks(
3385            Some((new_base_str_1.clone(), Rope::from(new_base_str_1.as_ref()))),
3386            buffer.snapshot(),
3387            None,
3388        );
3389
3390        let DiffChanged {
3391            changed_range,
3392            base_text_changed_range,
3393            extended_range: _,
3394        } = compare_hunks(
3395            &new_hunks_1,
3396            &old_hunks_1,
3397            &buffer.snapshot(),
3398            &buffer.snapshot(),
3399            &old_base_snapshot_1,
3400            &new_base_snapshot_1,
3401        );
3402
3403        // The new deletion hunk (XXX) starts at buffer row 1 and the
3404        // modification hunk (ccc → CCC) now has a different
3405        // diff_base_byte_range, so the changed range spans both.
3406        let range = changed_range.unwrap();
3407        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(3, 0),);
3408        let base_range = base_text_changed_range.unwrap();
3409        assert_eq!(
3410            base_range.to_point(&new_base_snapshot_1),
3411            Point::new(1, 0)..Point::new(4, 0),
3412        );
3413
3414        // --- Scenario 2: Base text changes to match the buffer (hunk disappears) ---
3415        //
3416        // Start fresh with a simple base text.
3417        let simple_base = "one\ntwo\nthree\n";
3418        let mut base_buf_2 = Buffer::new(
3419            ReplicaId::LOCAL,
3420            BufferId::new(100).unwrap(),
3421            simple_base.to_string(),
3422        );
3423
3424        let buffer_text_2 = "one\nTWO\nthree\n";
3425        let buffer_2 = Buffer::new(
3426            ReplicaId::LOCAL,
3427            BufferId::new(2).unwrap(),
3428            buffer_text_2.to_string(),
3429        );
3430
3431        let old_base_snapshot_2 = base_buf_2.snapshot();
3432        let old_hunks_2 = compute_hunks(
3433            Some((Arc::from(simple_base), Rope::from(simple_base))),
3434            buffer_2.snapshot(),
3435            None,
3436        );
3437
3438        // The base text is edited so "two" becomes "TWO", now matching the buffer.
3439        base_buf_2.edit([(4..7, "TWO")]);
3440        let new_base_str_2: Arc<str> = Arc::from(base_buf_2.text().as_str());
3441        let new_base_snapshot_2 = base_buf_2.snapshot();
3442
3443        let new_hunks_2 = compute_hunks(
3444            Some((new_base_str_2.clone(), Rope::from(new_base_str_2.as_ref()))),
3445            buffer_2.snapshot(),
3446            None,
3447        );
3448
3449        let DiffChanged {
3450            changed_range,
3451            base_text_changed_range,
3452            extended_range: _,
3453        } = compare_hunks(
3454            &new_hunks_2,
3455            &old_hunks_2,
3456            &buffer_2.snapshot(),
3457            &buffer_2.snapshot(),
3458            &old_base_snapshot_2,
3459            &new_base_snapshot_2,
3460        );
3461
3462        // The old modification hunk (two → TWO) is now gone because the
3463        // base text matches the buffer. The changed range covers where the
3464        // old hunk used to be.
3465        let range = changed_range.unwrap();
3466        assert_eq!(
3467            range.to_point(&buffer_2),
3468            Point::new(1, 0)..Point::new(2, 0),
3469        );
3470        let base_range = base_text_changed_range.unwrap();
3471        // The old hunk's diff_base_byte_range covered "two\n" (bytes 4..8).
3472        // anchor_after(4) is right-biased at the start of the deleted "two",
3473        // so after the edit replacing "two" with "TWO" it resolves past the
3474        // insertion to Point(1, 3).
3475        assert_eq!(
3476            base_range.to_point(&new_base_snapshot_2),
3477            Point::new(1, 3)..Point::new(2, 0),
3478        );
3479
3480        // --- Scenario 3: Base text edit changes one hunk but not another ---
3481        //
3482        // Two modification hunks exist. Only one of them is resolved by
3483        // the base text change; the other remains identical.
3484        let base_3 = "aaa\nbbb\nccc\nddd\neee\n";
3485        let mut base_buf_3 = Buffer::new(
3486            ReplicaId::LOCAL,
3487            BufferId::new(101).unwrap(),
3488            base_3.to_string(),
3489        );
3490
3491        let buffer_text_3 = "aaa\nBBB\nccc\nDDD\neee\n";
3492        let buffer_3 = Buffer::new(
3493            ReplicaId::LOCAL,
3494            BufferId::new(3).unwrap(),
3495            buffer_text_3.to_string(),
3496        );
3497
3498        let old_base_snapshot_3 = base_buf_3.snapshot();
3499        let old_hunks_3 = compute_hunks(
3500            Some((Arc::from(base_3), Rope::from(base_3))),
3501            buffer_3.snapshot(),
3502            None,
3503        );
3504
3505        // Change "ddd" to "DDD" in the base text so that hunk disappears,
3506        // but "bbb" stays, so its hunk remains.
3507        base_buf_3.edit([(12..15, "DDD")]);
3508        let new_base_str_3: Arc<str> = Arc::from(base_buf_3.text().as_str());
3509        let new_base_snapshot_3 = base_buf_3.snapshot();
3510
3511        let new_hunks_3 = compute_hunks(
3512            Some((new_base_str_3.clone(), Rope::from(new_base_str_3.as_ref()))),
3513            buffer_3.snapshot(),
3514            None,
3515        );
3516
3517        let DiffChanged {
3518            changed_range,
3519            base_text_changed_range,
3520            extended_range: _,
3521        } = compare_hunks(
3522            &new_hunks_3,
3523            &old_hunks_3,
3524            &buffer_3.snapshot(),
3525            &buffer_3.snapshot(),
3526            &old_base_snapshot_3,
3527            &new_base_snapshot_3,
3528        );
3529
3530        // Only the second hunk (ddd → DDD) disappeared; the first hunk
3531        // (bbb → BBB) is unchanged, so the changed range covers only line 3.
3532        let range = changed_range.unwrap();
3533        assert_eq!(
3534            range.to_point(&buffer_3),
3535            Point::new(3, 0)..Point::new(4, 0),
3536        );
3537        let base_range = base_text_changed_range.unwrap();
3538        // anchor_after(12) is right-biased at the start of deleted "ddd",
3539        // so after the edit replacing "ddd" with "DDD" it resolves past
3540        // the insertion to Point(3, 3).
3541        assert_eq!(
3542            base_range.to_point(&new_base_snapshot_3),
3543            Point::new(3, 3)..Point::new(4, 0),
3544        );
3545
3546        // --- Scenario 4: Both buffer and base text change simultaneously ---
3547        //
3548        // The buffer gains an edit that introduces a new hunk while the
3549        // base text also changes.
3550        let base_4 = "alpha\nbeta\ngamma\ndelta\n";
3551        let mut base_buf_4 = Buffer::new(
3552            ReplicaId::LOCAL,
3553            BufferId::new(102).unwrap(),
3554            base_4.to_string(),
3555        );
3556
3557        let buffer_text_4 = "alpha\nBETA\ngamma\ndelta\n";
3558        let mut buffer_4 = Buffer::new(
3559            ReplicaId::LOCAL,
3560            BufferId::new(4).unwrap(),
3561            buffer_text_4.to_string(),
3562        );
3563
3564        let old_base_snapshot_4 = base_buf_4.snapshot();
3565        let old_buffer_snapshot_4 = buffer_4.snapshot();
3566        let old_hunks_4 = compute_hunks(
3567            Some((Arc::from(base_4), Rope::from(base_4))),
3568            buffer_4.snapshot(),
3569            None,
3570        );
3571
3572        // Edit the buffer: change "delta" to "DELTA" (new modification hunk).
3573        buffer_4.edit_via_marked_text(
3574            &"
3575                alpha
3576                BETA
3577                gamma
3578                «DELTA»
3579            "
3580            .unindent(),
3581        );
3582
3583        // Edit the base text: change "beta" to "BETA" (resolves that hunk).
3584        base_buf_4.edit([(6..10, "BETA")]);
3585        let new_base_str_4: Arc<str> = Arc::from(base_buf_4.text().as_str());
3586        let new_base_snapshot_4 = base_buf_4.snapshot();
3587
3588        let new_hunks_4 = compute_hunks(
3589            Some((new_base_str_4.clone(), Rope::from(new_base_str_4.as_ref()))),
3590            buffer_4.snapshot(),
3591            None,
3592        );
3593
3594        let DiffChanged {
3595            changed_range,
3596            base_text_changed_range,
3597            extended_range: _,
3598        } = compare_hunks(
3599            &new_hunks_4,
3600            &old_hunks_4,
3601            &old_buffer_snapshot_4,
3602            &buffer_4.snapshot(),
3603            &old_base_snapshot_4,
3604            &new_base_snapshot_4,
3605        );
3606
3607        // The old BETA hunk (line 1) is gone and a new DELTA hunk (line 3)
3608        // appeared, so the changed range spans from line 1 through line 4.
3609        let range = changed_range.unwrap();
3610        assert_eq!(
3611            range.to_point(&buffer_4),
3612            Point::new(1, 0)..Point::new(4, 0),
3613        );
3614        let base_range = base_text_changed_range.unwrap();
3615        // The old BETA hunk's base range started at byte 6 ("beta"). After
3616        // the base text edit replacing "beta" with "BETA", anchor_after(6)
3617        // resolves past the insertion to Point(1, 4).
3618        assert_eq!(
3619            base_range.to_point(&new_base_snapshot_4),
3620            Point::new(1, 4)..Point::new(4, 0),
3621        );
3622    }
3623}