buffer_diff.rs

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