buffer_diff.rs

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