buffer_diff.rs

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