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