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