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