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