buffer_diff.rs

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