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