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