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