buffer_diff.rs

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