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