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