buffer_diff.rs

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