buffer_diff.rs

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