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