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