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