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