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