buffer_diff.rs

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