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        let base_text = self.inner.base_text.downgrade();
1297        cx.spawn(async move |this, cx| {
1298            let fut = base_text
1299                .update(cx, |base_text, cx| {
1300                    if let Some(language_registry) = language_registry {
1301                        base_text.set_language_registry(language_registry);
1302                    }
1303                    base_text.set_language(language, cx);
1304                    base_text.parsing_idle()
1305                })
1306                .ok()?;
1307
1308            fut.await;
1309
1310            this.update(cx, |_, cx| {
1311                cx.emit(BufferDiffEvent::LanguageChanged);
1312            })
1313            .ok()?;
1314
1315            Some(())
1316        })
1317        .detach();
1318    }
1319
1320    pub fn set_snapshot(
1321        &mut self,
1322        new_state: BufferDiffUpdate,
1323        buffer: &text::BufferSnapshot,
1324        cx: &mut Context<Self>,
1325    ) -> Option<Range<Anchor>> {
1326        self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1327    }
1328
1329    pub fn set_snapshot_with_secondary(
1330        &mut self,
1331        update: BufferDiffUpdate,
1332        buffer: &text::BufferSnapshot,
1333        secondary_diff_change: Option<Range<Anchor>>,
1334        clear_pending_hunks: bool,
1335        cx: &mut Context<Self>,
1336    ) -> Option<Range<Anchor>> {
1337        log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1338
1339        let old_snapshot = self.snapshot(cx);
1340        let state = &mut self.inner;
1341        let new_state = update.inner;
1342        let (mut changed_range, mut base_text_changed_range) =
1343            match (state.base_text_exists, new_state.base_text_exists) {
1344                (false, false) => (None, None),
1345                (true, true) if !update.base_text_changed => {
1346                    compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
1347                }
1348                _ => (
1349                    Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
1350                    Some(0..new_state.base_text.len()),
1351                ),
1352            };
1353
1354        if let Some(secondary_changed_range) = secondary_diff_change
1355            && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1356                old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1357        {
1358            if let Some(range) = &mut changed_range {
1359                range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1360                range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1361            } else {
1362                changed_range = Some(secondary_hunk_range);
1363            }
1364
1365            if let Some(base_text_range) = &mut base_text_changed_range {
1366                base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1367                base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1368            } else {
1369                base_text_changed_range = Some(secondary_base_range);
1370            }
1371        }
1372
1373        let state = &mut self.inner;
1374        state.base_text_exists = new_state.base_text_exists;
1375        if update.base_text_changed {
1376            state.base_text.update(cx, |base_text, cx| {
1377                base_text.set_capability(Capability::ReadWrite, cx);
1378                base_text.set_text(new_state.base_text.clone(), cx);
1379                base_text.set_capability(Capability::ReadOnly, cx);
1380            })
1381        }
1382        state.hunks = new_state.hunks;
1383        if update.base_text_changed || clear_pending_hunks {
1384            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1385            {
1386                if let Some(range) = &mut changed_range {
1387                    range.start = *range.start.min(&first.buffer_range.start, buffer);
1388                    range.end = *range.end.max(&last.buffer_range.end, buffer);
1389                } else {
1390                    changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1391                }
1392
1393                if let Some(base_text_range) = &mut base_text_changed_range {
1394                    base_text_range.start =
1395                        base_text_range.start.min(first.diff_base_byte_range.start);
1396                    base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1397                } else {
1398                    base_text_changed_range =
1399                        Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1400                }
1401            }
1402            state.pending_hunks = SumTree::new(buffer);
1403        }
1404
1405        cx.emit(BufferDiffEvent::DiffChanged {
1406            changed_range: changed_range.clone(),
1407            base_text_changed_range,
1408        });
1409        changed_range
1410    }
1411
1412    pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1413        self.inner.base_text.read(cx).snapshot()
1414    }
1415
1416    pub fn base_text_exists(&self) -> bool {
1417        self.inner.base_text_exists
1418    }
1419
1420    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1421        BufferDiffSnapshot {
1422            inner: BufferDiffInner {
1423                hunks: self.inner.hunks.clone(),
1424                pending_hunks: self.inner.pending_hunks.clone(),
1425                base_text: self.inner.base_text.read(cx).snapshot(),
1426                base_text_exists: self.inner.base_text_exists,
1427            },
1428            secondary_diff: self
1429                .secondary_diff
1430                .as_ref()
1431                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1432        }
1433    }
1434
1435    /// Used in cases where the change set isn't derived from git.
1436    pub fn set_base_text(
1437        &mut self,
1438        base_text: Option<Arc<str>>,
1439        language: Option<Arc<Language>>,
1440        buffer: text::BufferSnapshot,
1441        cx: &mut Context<Self>,
1442    ) -> oneshot::Receiver<()> {
1443        let (tx, rx) = oneshot::channel();
1444        let complete_on_drop = util::defer(|| {
1445            tx.send(()).ok();
1446        });
1447        cx.spawn(async move |this, cx| {
1448            let Some(state) = this
1449                .update(cx, |this, cx| {
1450                    this.update_diff(buffer.clone(), base_text, true, language, cx)
1451                })
1452                .log_err()
1453            else {
1454                return;
1455            };
1456            let state = state.await;
1457            this.update(cx, |this, cx| {
1458                this.set_snapshot(state, &buffer, cx);
1459            })
1460            .log_err();
1461            drop(complete_on_drop)
1462        })
1463        .detach();
1464        rx
1465    }
1466
1467    pub fn base_text_string(&self, cx: &App) -> Option<String> {
1468        self.inner
1469            .base_text_exists
1470            .then(|| self.inner.base_text.read(cx).text())
1471    }
1472
1473    #[cfg(any(test, feature = "test-support"))]
1474    pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1475        let language = self.base_text(cx).language().cloned();
1476        let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1477        let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
1478        let snapshot = cx.background_executor().block(fut);
1479        self.set_snapshot(snapshot, &buffer, cx);
1480    }
1481
1482    pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1483        self.inner.base_text.clone()
1484    }
1485}
1486
1487impl DiffHunk {
1488    pub fn is_created_file(&self) -> bool {
1489        self.diff_base_byte_range == (0..0)
1490            && self.buffer_range.start.is_min()
1491            && self.buffer_range.end.is_max()
1492    }
1493
1494    pub fn status(&self) -> DiffHunkStatus {
1495        let kind = if self.buffer_range.start == self.buffer_range.end {
1496            DiffHunkStatusKind::Deleted
1497        } else if self.diff_base_byte_range.is_empty() {
1498            DiffHunkStatusKind::Added
1499        } else {
1500            DiffHunkStatusKind::Modified
1501        };
1502        DiffHunkStatus {
1503            kind,
1504            secondary: self.secondary_status,
1505        }
1506    }
1507}
1508
1509impl DiffHunkStatus {
1510    pub fn has_secondary_hunk(&self) -> bool {
1511        matches!(
1512            self.secondary,
1513            DiffHunkSecondaryStatus::HasSecondaryHunk
1514                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1515                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1516        )
1517    }
1518
1519    pub fn is_pending(&self) -> bool {
1520        matches!(
1521            self.secondary,
1522            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1523                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1524        )
1525    }
1526
1527    pub fn is_deleted(&self) -> bool {
1528        self.kind == DiffHunkStatusKind::Deleted
1529    }
1530
1531    pub fn is_added(&self) -> bool {
1532        self.kind == DiffHunkStatusKind::Added
1533    }
1534
1535    pub fn is_modified(&self) -> bool {
1536        self.kind == DiffHunkStatusKind::Modified
1537    }
1538
1539    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1540        Self {
1541            kind: DiffHunkStatusKind::Added,
1542            secondary,
1543        }
1544    }
1545
1546    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1547        Self {
1548            kind: DiffHunkStatusKind::Modified,
1549            secondary,
1550        }
1551    }
1552
1553    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1554        Self {
1555            kind: DiffHunkStatusKind::Deleted,
1556            secondary,
1557        }
1558    }
1559
1560    pub fn deleted_none() -> Self {
1561        Self {
1562            kind: DiffHunkStatusKind::Deleted,
1563            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1564        }
1565    }
1566
1567    pub fn added_none() -> Self {
1568        Self {
1569            kind: DiffHunkStatusKind::Added,
1570            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1571        }
1572    }
1573
1574    pub fn modified_none() -> Self {
1575        Self {
1576            kind: DiffHunkStatusKind::Modified,
1577            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1578        }
1579    }
1580}
1581
1582#[cfg(any(test, feature = "test-support"))]
1583#[track_caller]
1584pub fn assert_hunks<ExpectedText, HunkIter>(
1585    diff_hunks: HunkIter,
1586    buffer: &text::BufferSnapshot,
1587    diff_base: &str,
1588    // Line range, deleted, added, status
1589    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1590) where
1591    HunkIter: Iterator<Item = DiffHunk>,
1592    ExpectedText: AsRef<str>,
1593{
1594    let actual_hunks = diff_hunks
1595        .map(|hunk| {
1596            (
1597                hunk.range.clone(),
1598                &diff_base[hunk.diff_base_byte_range.clone()],
1599                buffer
1600                    .text_for_range(hunk.range.clone())
1601                    .collect::<String>(),
1602                hunk.status(),
1603            )
1604        })
1605        .collect::<Vec<_>>();
1606
1607    let expected_hunks: Vec<_> = expected_hunks
1608        .iter()
1609        .map(|(line_range, deleted_text, added_text, status)| {
1610            (
1611                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1612                deleted_text.as_ref(),
1613                added_text.as_ref().to_string(),
1614                *status,
1615            )
1616        })
1617        .collect();
1618
1619    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1620}
1621
1622#[cfg(test)]
1623mod tests {
1624    use std::{fmt::Write as _, sync::mpsc};
1625
1626    use super::*;
1627    use gpui::TestAppContext;
1628    use pretty_assertions::{assert_eq, assert_ne};
1629    use rand::{Rng as _, rngs::StdRng};
1630    use text::{Buffer, BufferId, ReplicaId, Rope};
1631    use unindent::Unindent as _;
1632    use util::test::marked_text_ranges;
1633
1634    #[ctor::ctor]
1635    fn init_logger() {
1636        zlog::init_test();
1637    }
1638
1639    #[gpui::test]
1640    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1641        let diff_base = "
1642            one
1643            two
1644            three
1645        "
1646        .unindent();
1647
1648        let buffer_text = "
1649            one
1650            HELLO
1651            three
1652        "
1653        .unindent();
1654
1655        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1656        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1657        assert_hunks(
1658            diff.hunks_intersecting_range(
1659                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1660                &buffer,
1661            ),
1662            &buffer,
1663            &diff_base,
1664            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1665        );
1666
1667        buffer.edit([(0..0, "point five\n")]);
1668        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1669        assert_hunks(
1670            diff.hunks_intersecting_range(
1671                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1672                &buffer,
1673            ),
1674            &buffer,
1675            &diff_base,
1676            &[
1677                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1678                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1679            ],
1680        );
1681
1682        diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
1683        assert_hunks::<&str, _>(
1684            diff.hunks_intersecting_range(
1685                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1686                &buffer,
1687            ),
1688            &buffer,
1689            &diff_base,
1690            &[],
1691        );
1692    }
1693
1694    #[gpui::test]
1695    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1696        let head_text = "
1697            zero
1698            one
1699            two
1700            three
1701            four
1702            five
1703            six
1704            seven
1705            eight
1706            nine
1707        "
1708        .unindent();
1709
1710        let index_text = "
1711            zero
1712            one
1713            TWO
1714            three
1715            FOUR
1716            five
1717            six
1718            seven
1719            eight
1720            NINE
1721        "
1722        .unindent();
1723
1724        let buffer_text = "
1725            zero
1726            one
1727            TWO
1728            three
1729            FOUR
1730            FIVE
1731            six
1732            SEVEN
1733            eight
1734            nine
1735        "
1736        .unindent();
1737
1738        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1739        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1740        let mut uncommitted_diff =
1741            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1742        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1743
1744        let expected_hunks = vec![
1745            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1746            (
1747                4..6,
1748                "four\nfive\n",
1749                "FOUR\nFIVE\n",
1750                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1751            ),
1752            (
1753                7..8,
1754                "seven\n",
1755                "SEVEN\n",
1756                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1757            ),
1758        ];
1759
1760        assert_hunks(
1761            uncommitted_diff.hunks_intersecting_range(
1762                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1763                &buffer,
1764            ),
1765            &buffer,
1766            &head_text,
1767            &expected_hunks,
1768        );
1769    }
1770
1771    #[gpui::test]
1772    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1773        let diff_base = "
1774            one
1775            two
1776            three
1777            four
1778            five
1779            six
1780            seven
1781            eight
1782            nine
1783            ten
1784        "
1785        .unindent();
1786
1787        let buffer_text = "
1788            A
1789            one
1790            B
1791            two
1792            C
1793            three
1794            HELLO
1795            four
1796            five
1797            SIXTEEN
1798            seven
1799            eight
1800            WORLD
1801            nine
1802
1803            ten
1804
1805        "
1806        .unindent();
1807
1808        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1809        let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
1810        assert_eq!(
1811            diff.hunks_intersecting_range(
1812                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1813                &buffer
1814            )
1815            .count(),
1816            8
1817        );
1818
1819        assert_hunks(
1820            diff.hunks_intersecting_range(
1821                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1822                &buffer,
1823            ),
1824            &buffer,
1825            &diff_base,
1826            &[
1827                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1828                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1829                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1830            ],
1831        );
1832    }
1833
1834    #[gpui::test]
1835    async fn test_stage_hunk(cx: &mut TestAppContext) {
1836        struct Example {
1837            name: &'static str,
1838            head_text: String,
1839            index_text: String,
1840            buffer_marked_text: String,
1841            final_index_text: String,
1842        }
1843
1844        let table = [
1845            Example {
1846                name: "uncommitted hunk straddles end of unstaged hunk",
1847                head_text: "
1848                    one
1849                    two
1850                    three
1851                    four
1852                    five
1853                "
1854                .unindent(),
1855                index_text: "
1856                    one
1857                    TWO_HUNDRED
1858                    three
1859                    FOUR_HUNDRED
1860                    five
1861                "
1862                .unindent(),
1863                buffer_marked_text: "
1864                    ZERO
1865                    one
1866                    two
1867                    «THREE_HUNDRED
1868                    FOUR_HUNDRED»
1869                    five
1870                    SIX
1871                "
1872                .unindent(),
1873                final_index_text: "
1874                    one
1875                    two
1876                    THREE_HUNDRED
1877                    FOUR_HUNDRED
1878                    five
1879                "
1880                .unindent(),
1881            },
1882            Example {
1883                name: "uncommitted hunk straddles start of unstaged hunk",
1884                head_text: "
1885                    one
1886                    two
1887                    three
1888                    four
1889                    five
1890                "
1891                .unindent(),
1892                index_text: "
1893                    one
1894                    TWO_HUNDRED
1895                    three
1896                    FOUR_HUNDRED
1897                    five
1898                "
1899                .unindent(),
1900                buffer_marked_text: "
1901                    ZERO
1902                    one
1903                    «TWO_HUNDRED
1904                    THREE_HUNDRED»
1905                    four
1906                    five
1907                    SIX
1908                "
1909                .unindent(),
1910                final_index_text: "
1911                    one
1912                    TWO_HUNDRED
1913                    THREE_HUNDRED
1914                    four
1915                    five
1916                "
1917                .unindent(),
1918            },
1919            Example {
1920                name: "uncommitted hunk strictly contains unstaged hunks",
1921                head_text: "
1922                    one
1923                    two
1924                    three
1925                    four
1926                    five
1927                    six
1928                    seven
1929                "
1930                .unindent(),
1931                index_text: "
1932                    one
1933                    TWO
1934                    THREE
1935                    FOUR
1936                    FIVE
1937                    SIX
1938                    seven
1939                "
1940                .unindent(),
1941                buffer_marked_text: "
1942                    one
1943                    TWO
1944                    «THREE_HUNDRED
1945                    FOUR
1946                    FIVE_HUNDRED»
1947                    SIX
1948                    seven
1949                "
1950                .unindent(),
1951                final_index_text: "
1952                    one
1953                    TWO
1954                    THREE_HUNDRED
1955                    FOUR
1956                    FIVE_HUNDRED
1957                    SIX
1958                    seven
1959                "
1960                .unindent(),
1961            },
1962            Example {
1963                name: "uncommitted deletion hunk",
1964                head_text: "
1965                    one
1966                    two
1967                    three
1968                    four
1969                    five
1970                "
1971                .unindent(),
1972                index_text: "
1973                    one
1974                    two
1975                    three
1976                    four
1977                    five
1978                "
1979                .unindent(),
1980                buffer_marked_text: "
1981                    one
1982                    ˇfive
1983                "
1984                .unindent(),
1985                final_index_text: "
1986                    one
1987                    five
1988                "
1989                .unindent(),
1990            },
1991            Example {
1992                name: "one unstaged hunk that contains two uncommitted hunks",
1993                head_text: "
1994                    one
1995                    two
1996
1997                    three
1998                    four
1999                "
2000                .unindent(),
2001                index_text: "
2002                    one
2003                    two
2004                    three
2005                    four
2006                "
2007                .unindent(),
2008                buffer_marked_text: "
2009                    «one
2010
2011                    three // modified
2012                    four»
2013                "
2014                .unindent(),
2015                final_index_text: "
2016                    one
2017
2018                    three // modified
2019                    four
2020                "
2021                .unindent(),
2022            },
2023            Example {
2024                name: "one uncommitted hunk that contains two unstaged hunks",
2025                head_text: "
2026                    one
2027                    two
2028                    three
2029                    four
2030                    five
2031                "
2032                .unindent(),
2033                index_text: "
2034                    ZERO
2035                    one
2036                    TWO
2037                    THREE
2038                    FOUR
2039                    five
2040                "
2041                .unindent(),
2042                buffer_marked_text: "
2043                    «one
2044                    TWO_HUNDRED
2045                    THREE
2046                    FOUR_HUNDRED
2047                    five»
2048                "
2049                .unindent(),
2050                final_index_text: "
2051                    ZERO
2052                    one
2053                    TWO_HUNDRED
2054                    THREE
2055                    FOUR_HUNDRED
2056                    five
2057                "
2058                .unindent(),
2059            },
2060        ];
2061
2062        for example in table {
2063            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2064            let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2065            let hunk_range =
2066                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2067
2068            let unstaged_diff =
2069                cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2070
2071            let uncommitted_diff = cx.new(|cx| {
2072                let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2073                diff.set_secondary_diff(unstaged_diff);
2074                diff
2075            });
2076
2077            uncommitted_diff.update(cx, |diff, cx| {
2078                let hunks = diff
2079                    .snapshot(cx)
2080                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2081                    .collect::<Vec<_>>();
2082                for hunk in &hunks {
2083                    assert_ne!(
2084                        hunk.secondary_status,
2085                        DiffHunkSecondaryStatus::NoSecondaryHunk
2086                    )
2087                }
2088
2089                let new_index_text = diff
2090                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2091                    .unwrap()
2092                    .to_string();
2093
2094                let hunks = diff
2095                    .snapshot(cx)
2096                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2097                    .collect::<Vec<_>>();
2098                for hunk in &hunks {
2099                    assert_eq!(
2100                        hunk.secondary_status,
2101                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2102                    )
2103                }
2104
2105                pretty_assertions::assert_eq!(
2106                    new_index_text,
2107                    example.final_index_text,
2108                    "example: {}",
2109                    example.name
2110                );
2111            });
2112        }
2113    }
2114
2115    #[gpui::test]
2116    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2117        let head_text = "
2118            one
2119            two
2120            three
2121        "
2122        .unindent();
2123        let index_text = head_text.clone();
2124        let buffer_text = "
2125            one
2126            three
2127        "
2128        .unindent();
2129
2130        let buffer = Buffer::new(
2131            ReplicaId::LOCAL,
2132            BufferId::new(1).unwrap(),
2133            buffer_text.clone(),
2134        );
2135        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2136        let uncommitted_diff = cx.new(|cx| {
2137            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2138            diff.set_secondary_diff(unstaged_diff.clone());
2139            diff
2140        });
2141
2142        uncommitted_diff.update(cx, |diff, cx| {
2143            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2144
2145            let new_index_text = diff
2146                .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2147                .unwrap()
2148                .to_string();
2149            assert_eq!(new_index_text, buffer_text);
2150
2151            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2152            assert_eq!(
2153                hunk.secondary_status,
2154                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2155            );
2156
2157            let index_text = diff
2158                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2159                .unwrap()
2160                .to_string();
2161            assert_eq!(index_text, head_text);
2162
2163            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2164            // optimistically unstaged (fine, could also be HasSecondaryHunk)
2165            assert_eq!(
2166                hunk.secondary_status,
2167                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2168            );
2169        });
2170    }
2171
2172    #[gpui::test]
2173    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2174        let base_text = "
2175            zero
2176            one
2177            two
2178            three
2179            four
2180            five
2181            six
2182            seven
2183            eight
2184            nine
2185        "
2186        .unindent();
2187
2188        let buffer_text_1 = "
2189            one
2190            three
2191            four
2192            five
2193            SIX
2194            seven
2195            eight
2196            NINE
2197        "
2198        .unindent();
2199
2200        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2201
2202        let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2203        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2204        let (range, base_text_range) =
2205            compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
2206        let range = range.unwrap();
2207        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2208        let base_text_range = base_text_range.unwrap();
2209        assert_eq!(
2210            base_text_range.to_point(diff_1.base_text()),
2211            Point::new(0, 0)..Point::new(10, 0)
2212        );
2213
2214        // Edit does affects the diff because it recalculates word diffs.
2215        buffer.edit_via_marked_text(
2216            &"
2217                one
2218                three
2219                four
2220                five
2221                «SIX.5»
2222                seven
2223                eight
2224                NINE
2225            "
2226            .unindent(),
2227        );
2228        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2229        let (range, base_text_range) =
2230            compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer);
2231        assert_eq!(
2232            range.unwrap().to_point(&buffer),
2233            Point::new(4, 0)..Point::new(5, 0),
2234        );
2235        assert_eq!(
2236            base_text_range.unwrap().to_point(diff_2.base_text()),
2237            Point::new(6, 0)..Point::new(7, 0),
2238        );
2239
2240        // Edit turns a deletion hunk into a modification.
2241        buffer.edit_via_marked_text(
2242            &"
2243                one
2244                «THREE»
2245                four
2246                five
2247                SIX.5
2248                seven
2249                eight
2250                NINE
2251            "
2252            .unindent(),
2253        );
2254        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2255        let (range, base_text_range) =
2256            compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
2257        let range = range.unwrap();
2258        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2259        let base_text_range = base_text_range.unwrap();
2260        assert_eq!(
2261            base_text_range.to_point(diff_3.base_text()),
2262            Point::new(2, 0)..Point::new(4, 0)
2263        );
2264
2265        // Edit turns a modification hunk into a deletion.
2266        buffer.edit_via_marked_text(
2267            &"
2268                one
2269                THREE
2270                four
2271                five«»
2272                seven
2273                eight
2274                NINE
2275            "
2276            .unindent(),
2277        );
2278        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2279        let (range, base_text_range) =
2280            compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
2281        let range = range.unwrap();
2282        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2283        let base_text_range = base_text_range.unwrap();
2284        assert_eq!(
2285            base_text_range.to_point(diff_4.base_text()),
2286            Point::new(6, 0)..Point::new(7, 0)
2287        );
2288
2289        // Edit introduces a new insertion hunk.
2290        buffer.edit_via_marked_text(
2291            &"
2292                one
2293                THREE
2294                four«
2295                FOUR.5
2296                »five
2297                seven
2298                eight
2299                NINE
2300            "
2301            .unindent(),
2302        );
2303        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2304        let (range, base_text_range) =
2305            compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
2306        let range = range.unwrap();
2307        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2308        let base_text_range = base_text_range.unwrap();
2309        assert_eq!(
2310            base_text_range.to_point(diff_5.base_text()),
2311            Point::new(5, 0)..Point::new(5, 0)
2312        );
2313
2314        // Edit removes a hunk.
2315        buffer.edit_via_marked_text(
2316            &"
2317                one
2318                THREE
2319                four
2320                FOUR.5
2321                five
2322                seven
2323                eight
2324                «nine»
2325            "
2326            .unindent(),
2327        );
2328        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2329        let (range, base_text_range) =
2330            compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
2331        let range = range.unwrap();
2332        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2333        let base_text_range = base_text_range.unwrap();
2334        assert_eq!(
2335            base_text_range.to_point(diff_6.base_text()),
2336            Point::new(9, 0)..Point::new(10, 0)
2337        );
2338    }
2339
2340    #[gpui::test(iterations = 100)]
2341    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2342        fn gen_line(rng: &mut StdRng) -> String {
2343            if rng.random_bool(0.2) {
2344                "\n".to_owned()
2345            } else {
2346                let c = rng.random_range('A'..='Z');
2347                format!("{c}{c}{c}\n")
2348            }
2349        }
2350
2351        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2352            let mut old_lines = {
2353                let mut old_lines = Vec::new();
2354                let old_lines_iter = head.lines();
2355                for line in old_lines_iter {
2356                    assert!(!line.ends_with("\n"));
2357                    old_lines.push(line.to_owned());
2358                }
2359                if old_lines.last().is_some_and(|line| line.is_empty()) {
2360                    old_lines.pop();
2361                }
2362                old_lines.into_iter()
2363            };
2364            let mut result = String::new();
2365            let unchanged_count = rng.random_range(0..=old_lines.len());
2366            result +=
2367                &old_lines
2368                    .by_ref()
2369                    .take(unchanged_count)
2370                    .fold(String::new(), |mut s, line| {
2371                        writeln!(&mut s, "{line}").unwrap();
2372                        s
2373                    });
2374            while old_lines.len() > 0 {
2375                let deleted_count = rng.random_range(0..=old_lines.len());
2376                let _advance = old_lines
2377                    .by_ref()
2378                    .take(deleted_count)
2379                    .map(|line| line.len() + 1)
2380                    .sum::<usize>();
2381                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2382                let added_count = rng.random_range(minimum_added..=5);
2383                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2384                result += &addition;
2385
2386                if old_lines.len() > 0 {
2387                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2388                    if blank_lines == old_lines.len() {
2389                        break;
2390                    };
2391                    let unchanged_count =
2392                        rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2393                    result += &old_lines.by_ref().take(unchanged_count).fold(
2394                        String::new(),
2395                        |mut s, line| {
2396                            writeln!(&mut s, "{line}").unwrap();
2397                            s
2398                        },
2399                    );
2400                }
2401            }
2402            result
2403        }
2404
2405        fn uncommitted_diff(
2406            working_copy: &language::BufferSnapshot,
2407            index_text: &Rope,
2408            head_text: String,
2409            cx: &mut TestAppContext,
2410        ) -> Entity<BufferDiff> {
2411            let secondary = cx.new(|cx| {
2412                BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2413            });
2414            cx.new(|cx| {
2415                let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2416                diff.secondary_diff = Some(secondary);
2417                diff
2418            })
2419        }
2420
2421        let operations = std::env::var("OPERATIONS")
2422            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2423            .unwrap_or(10);
2424
2425        let rng = &mut rng;
2426        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2427            writeln!(&mut s, "{c}{c}{c}").unwrap();
2428            s
2429        });
2430        let working_copy = gen_working_copy(rng, &head_text);
2431        let working_copy = cx.new(|cx| {
2432            language::Buffer::local_normalized(
2433                Rope::from(working_copy.as_str()),
2434                text::LineEnding::default(),
2435                cx,
2436            )
2437        });
2438        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2439        let mut index_text = if rng.random() {
2440            Rope::from(head_text.as_str())
2441        } else {
2442            working_copy.as_rope().clone()
2443        };
2444
2445        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2446        let mut hunks = diff.update(cx, |diff, cx| {
2447            diff.snapshot(cx)
2448                .hunks_intersecting_range(
2449                    Anchor::min_max_range_for_buffer(diff.buffer_id),
2450                    &working_copy,
2451                )
2452                .collect::<Vec<_>>()
2453        });
2454        if hunks.is_empty() {
2455            return;
2456        }
2457
2458        for _ in 0..operations {
2459            let i = rng.random_range(0..hunks.len());
2460            let hunk = &mut hunks[i];
2461            let hunk_to_change = hunk.clone();
2462            let stage = match hunk.secondary_status {
2463                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2464                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2465                    true
2466                }
2467                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2468                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2469                    false
2470                }
2471                _ => unreachable!(),
2472            };
2473
2474            index_text = diff.update(cx, |diff, cx| {
2475                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2476                    .unwrap()
2477            });
2478
2479            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2480            let found_hunks = diff.update(cx, |diff, cx| {
2481                diff.snapshot(cx)
2482                    .hunks_intersecting_range(
2483                        Anchor::min_max_range_for_buffer(diff.buffer_id),
2484                        &working_copy,
2485                    )
2486                    .collect::<Vec<_>>()
2487            });
2488            assert_eq!(hunks.len(), found_hunks.len());
2489
2490            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2491                assert_eq!(
2492                    expected_hunk.buffer_range.to_point(&working_copy),
2493                    found_hunk.buffer_range.to_point(&working_copy)
2494                );
2495                assert_eq!(
2496                    expected_hunk.diff_base_byte_range,
2497                    found_hunk.diff_base_byte_range
2498                );
2499                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2500            }
2501            hunks = found_hunks;
2502        }
2503    }
2504
2505    #[gpui::test]
2506    async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
2507        let base_text = "
2508            zero
2509            one
2510            two
2511            three
2512            four
2513            five
2514            six
2515            seven
2516            eight
2517        "
2518        .unindent();
2519        let buffer_text = "
2520            zero
2521            ONE
2522            two
2523            NINE
2524            five
2525            seven
2526        "
2527        .unindent();
2528
2529        //   zero
2530        // - one
2531        // + ONE
2532        //   two
2533        // - three
2534        // - four
2535        // + NINE
2536        //   five
2537        // - six
2538        //   seven
2539        // + eight
2540
2541        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2542        let buffer_snapshot = buffer.snapshot();
2543        let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
2544        let expected_results = [
2545            // main buffer row, base text row (right bias), base text row (left bias)
2546            (0, 0, 0),
2547            (1, 2, 1),
2548            (2, 2, 2),
2549            (3, 5, 3),
2550            (4, 5, 5),
2551            (5, 7, 7),
2552            (6, 9, 9),
2553        ];
2554        for (buffer_row, expected_right, expected_left) in expected_results {
2555            assert_eq!(
2556                diff.row_to_base_text_row(buffer_row, Bias::Right, &buffer_snapshot),
2557                expected_right,
2558                "{buffer_row}"
2559            );
2560            assert_eq!(
2561                diff.row_to_base_text_row(buffer_row, Bias::Left, &buffer_snapshot),
2562                expected_left,
2563                "{buffer_row}"
2564            );
2565        }
2566    }
2567
2568    #[gpui::test]
2569    async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2570        let base_text = "
2571            one
2572            two
2573            three
2574            four
2575            five
2576            six
2577        "
2578        .unindent();
2579        let buffer_text = "
2580            one
2581            TWO
2582            three
2583            four
2584            FIVE
2585            six
2586        "
2587        .unindent();
2588        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
2589        let diff = cx.new(|cx| {
2590            BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
2591        });
2592        let (tx, rx) = mpsc::channel();
2593        let subscription =
2594            cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
2595
2596        let snapshot = buffer.update(cx, |buffer, cx| {
2597            buffer.set_text(
2598                "
2599                ONE
2600                TWO
2601                THREE
2602                FOUR
2603                FIVE
2604                SIX
2605            "
2606                .unindent(),
2607                cx,
2608            );
2609            buffer.text_snapshot()
2610        });
2611        let update = diff
2612            .update(cx, |diff, cx| {
2613                diff.update_diff(
2614                    snapshot.clone(),
2615                    Some(base_text.as_str().into()),
2616                    false,
2617                    None,
2618                    cx,
2619                )
2620            })
2621            .await;
2622        diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx));
2623        cx.run_until_parked();
2624        drop(subscription);
2625        let events = rx.into_iter().collect::<Vec<_>>();
2626        match events.as_slice() {
2627            [
2628                BufferDiffEvent::DiffChanged {
2629                    changed_range: _,
2630                    base_text_changed_range,
2631                },
2632            ] => {
2633                // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
2634                // assert_eq!(
2635                //     *changed_range,
2636                //     Some(Anchor::min_max_range_for_buffer(
2637                //         buffer.read_with(cx, |buffer, _| buffer.remote_id())
2638                //     ))
2639                // );
2640                assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
2641            }
2642            _ => panic!("unexpected events: {:?}", events),
2643        }
2644    }
2645}