buffer_diff.rs

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