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