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