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