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};
   4use language::{
   5    BufferRow, Capability, DiffOptions, File, Language, LanguageName, LanguageRegistry,
   6    language_settings::language_settings, word_diff_ranges,
   7};
   8use rope::Rope;
   9use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc};
  10use sum_tree::SumTree;
  11use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _, ToPoint as _};
  12use util::ResultExt;
  13
  14pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
  15
  16pub struct BufferDiff {
  17    pub buffer_id: BufferId,
  18    inner: BufferDiffInner<Entity<language::Buffer>>,
  19    // diff of the index vs head
  20    secondary_diff: Option<Entity<BufferDiff>>,
  21}
  22
  23#[derive(Clone)]
  24pub struct BufferDiffSnapshot {
  25    inner: BufferDiffInner<language::BufferSnapshot>,
  26    secondary_diff: Option<Box<BufferDiffSnapshot>>,
  27}
  28
  29impl std::fmt::Debug for BufferDiffSnapshot {
  30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  31        f.debug_struct("BufferDiffSnapshot")
  32            .field("inner", &self.inner)
  33            .field("secondary_diff", &self.secondary_diff)
  34            .finish()
  35    }
  36}
  37
  38#[derive(Clone)]
  39pub struct BufferDiffUpdate {
  40    base_text_changed: bool,
  41    inner: BufferDiffInner<Arc<str>>,
  42}
  43
  44#[derive(Clone)]
  45struct BufferDiffInner<BaseText> {
  46    hunks: SumTree<InternalDiffHunk>,
  47    pending_hunks: SumTree<PendingHunk>,
  48    base_text: BaseText,
  49    base_text_exists: bool,
  50}
  51
  52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  53pub struct DiffHunkStatus {
  54    pub kind: DiffHunkStatusKind,
  55    pub secondary: DiffHunkSecondaryStatus,
  56}
  57
  58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  59pub enum DiffHunkStatusKind {
  60    Added,
  61    Modified,
  62    Deleted,
  63}
  64
  65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  66/// Diff of Working Copy vs Index
  67/// aka 'is this hunk staged or not'
  68pub enum DiffHunkSecondaryStatus {
  69    /// Unstaged
  70    HasSecondaryHunk,
  71    /// Partially staged
  72    OverlapsWithSecondaryHunk,
  73    /// Staged
  74    NoSecondaryHunk,
  75    /// We are unstaging
  76    SecondaryHunkAdditionPending,
  77    /// We are stagind
  78    SecondaryHunkRemovalPending,
  79}
  80
  81/// A diff hunk resolved to rows in the buffer.
  82#[derive(Debug, Clone, PartialEq, Eq)]
  83pub struct DiffHunk {
  84    /// The buffer range as points.
  85    pub range: Range<Point>,
  86    /// The range in the buffer to which this hunk corresponds.
  87    pub buffer_range: Range<Anchor>,
  88    /// The range in the buffer's diff base text to which this hunk corresponds.
  89    pub diff_base_byte_range: Range<usize>,
  90    pub secondary_status: DiffHunkSecondaryStatus,
  91    // Anchors representing the word diff locations in the active buffer
  92    pub buffer_word_diffs: Vec<Range<Anchor>>,
  93    // Offsets relative to the start of the deleted diff that represent word diff locations
  94    pub base_word_diffs: Vec<Range<usize>>,
  95}
  96
  97/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
  98#[derive(Debug, Clone, PartialEq, Eq)]
  99struct InternalDiffHunk {
 100    buffer_range: Range<Anchor>,
 101    diff_base_byte_range: Range<usize>,
 102    base_word_diffs: Vec<Range<usize>>,
 103    buffer_word_diffs: Vec<Range<Anchor>>,
 104}
 105
 106#[derive(Debug, Clone, PartialEq, Eq)]
 107struct PendingHunk {
 108    buffer_range: Range<Anchor>,
 109    diff_base_byte_range: Range<usize>,
 110    buffer_version: clock::Global,
 111    new_status: DiffHunkSecondaryStatus,
 112}
 113
 114#[derive(Debug, Clone)]
 115pub struct DiffHunkSummary {
 116    buffer_range: Range<Anchor>,
 117    diff_base_byte_range: Range<usize>,
 118}
 119
 120impl sum_tree::Item for InternalDiffHunk {
 121    type Summary = DiffHunkSummary;
 122
 123    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 124        DiffHunkSummary {
 125            buffer_range: self.buffer_range.clone(),
 126            diff_base_byte_range: self.diff_base_byte_range.clone(),
 127        }
 128    }
 129}
 130
 131impl sum_tree::Item for PendingHunk {
 132    type Summary = DiffHunkSummary;
 133
 134    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 135        DiffHunkSummary {
 136            buffer_range: self.buffer_range.clone(),
 137            diff_base_byte_range: self.diff_base_byte_range.clone(),
 138        }
 139    }
 140}
 141
 142impl sum_tree::Summary for DiffHunkSummary {
 143    type Context<'a> = &'a text::BufferSnapshot;
 144
 145    fn zero(_cx: Self::Context<'_>) -> Self {
 146        DiffHunkSummary {
 147            buffer_range: Anchor::MIN..Anchor::MIN,
 148            diff_base_byte_range: 0..0,
 149        }
 150    }
 151
 152    fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
 153        self.buffer_range.start = *self
 154            .buffer_range
 155            .start
 156            .min(&other.buffer_range.start, buffer);
 157        self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
 158
 159        self.diff_base_byte_range.start = self
 160            .diff_base_byte_range
 161            .start
 162            .min(other.diff_base_byte_range.start);
 163        self.diff_base_byte_range.end = self
 164            .diff_base_byte_range
 165            .end
 166            .max(other.diff_base_byte_range.end);
 167    }
 168}
 169
 170impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
 171    fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
 172        if self
 173            .cmp(&cursor_location.buffer_range.start, buffer)
 174            .is_lt()
 175        {
 176            Ordering::Less
 177        } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
 178            Ordering::Greater
 179        } else {
 180            Ordering::Equal
 181        }
 182    }
 183}
 184
 185impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
 186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 187        f.debug_struct("BufferDiffSnapshot")
 188            .field("hunks", &self.hunks)
 189            .field("remote_id", &self.base_text.remote_id())
 190            .finish()
 191    }
 192}
 193
 194impl BufferDiffSnapshot {
 195    #[cfg(test)]
 196    fn new_sync(
 197        buffer: text::BufferSnapshot,
 198        diff_base: String,
 199        cx: &mut gpui::TestAppContext,
 200    ) -> BufferDiffSnapshot {
 201        let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
 202        buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
 203    }
 204
 205    pub fn is_empty(&self) -> bool {
 206        self.inner.hunks.is_empty()
 207    }
 208
 209    pub fn base_text_string(&self) -> Option<String> {
 210        self.inner
 211            .base_text_exists
 212            .then(|| self.inner.base_text.text())
 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    pub fn base_texts_definitely_eq(&self, other: &Self) -> bool {
 317        if self.inner.base_text_exists != other.inner.base_text_exists {
 318            return false;
 319        }
 320        let left = &self.inner.base_text;
 321        let right = &other.inner.base_text;
 322        let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty());
 323        let (new_id, new_version, new_empty) =
 324            (right.remote_id(), right.version(), right.is_empty());
 325        (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
 326    }
 327
 328    pub fn row_to_base_text_row(
 329        &self,
 330        row: BufferRow,
 331        bias: Bias,
 332        buffer: &text::BufferSnapshot,
 333    ) -> u32 {
 334        // TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start
 335        let target = buffer.anchor_before(Point::new(row, 0));
 336        // Find the last hunk that starts before the target.
 337        let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);
 338        cursor.seek(&target, Bias::Left);
 339        if cursor
 340            .item()
 341            .is_none_or(|hunk| hunk.buffer_range.start.cmp(&target, buffer).is_gt())
 342        {
 343            cursor.prev();
 344        }
 345
 346        let unclipped_point = if let Some(hunk) = cursor.item()
 347            && hunk.buffer_range.start.cmp(&target, buffer).is_le()
 348        {
 349            // Found a hunk that starts before the target.
 350            let hunk_base_text_end = cursor.end().diff_base_byte_range.end;
 351            let unclipped_point = if target.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {
 352                // Target falls strictly between two hunks.
 353                let mut unclipped_point = hunk_base_text_end.to_point(self.base_text());
 354                unclipped_point +=
 355                    Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);
 356                unclipped_point
 357            } else if bias == Bias::Right {
 358                hunk_base_text_end.to_point(self.base_text())
 359            } else {
 360                hunk.diff_base_byte_range.start.to_point(self.base_text())
 361            };
 362            // Move the cursor so that at the next step we can clip with the start of the next hunk.
 363            cursor.next();
 364            unclipped_point
 365        } else {
 366            // Target is before the added region for the first hunk.
 367            debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {
 368                target.cmp(&first_hunk.buffer_range.start, buffer).is_le()
 369            }));
 370            Point::new(row, 0)
 371        };
 372
 373        // If the target falls in the region between two hunks, we added an overshoot above.
 374        // There may be changes in the main buffer that are not reflected in the hunks,
 375        // so we need to ensure this overshoot keeps us in the corresponding base text region.
 376        let max_point = if let Some(next_hunk) = cursor.item() {
 377            next_hunk
 378                .diff_base_byte_range
 379                .start
 380                .to_point(self.base_text())
 381        } else {
 382            self.base_text().max_point()
 383        };
 384        unclipped_point.min(max_point).row
 385    }
 386}
 387
 388impl BufferDiffInner<Entity<language::Buffer>> {
 389    /// Returns the new index text and new pending hunks.
 390    fn stage_or_unstage_hunks_impl(
 391        &mut self,
 392        unstaged_diff: &Self,
 393        stage: bool,
 394        hunks: &[DiffHunk],
 395        buffer: &text::BufferSnapshot,
 396        file_exists: bool,
 397        cx: &mut Context<BufferDiff>,
 398    ) -> Option<Rope> {
 399        let head_text = self
 400            .base_text_exists
 401            .then(|| self.base_text.read(cx).as_rope().clone());
 402        let index_text = unstaged_diff
 403            .base_text_exists
 404            .then(|| unstaged_diff.base_text.read(cx).as_rope().clone());
 405
 406        // If the file doesn't exist in either HEAD or the index, then the
 407        // entire file must be either created or deleted in the index.
 408        let (index_text, head_text) = match (index_text, head_text) {
 409            (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
 410            (index_text, head_text) => {
 411                let (new_index_text, new_status) = if stage {
 412                    log::debug!("stage all");
 413                    (
 414                        file_exists.then(|| buffer.as_rope().clone()),
 415                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
 416                    )
 417                } else {
 418                    log::debug!("unstage all");
 419                    (
 420                        head_text,
 421                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
 422                    )
 423                };
 424
 425                let hunk = PendingHunk {
 426                    buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
 427                    diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
 428                    buffer_version: buffer.version().clone(),
 429                    new_status,
 430                };
 431                self.pending_hunks = SumTree::from_item(hunk, buffer);
 432                return new_index_text;
 433            }
 434        };
 435
 436        let mut pending_hunks = SumTree::new(buffer);
 437        let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 438
 439        // first, merge new hunks into pending_hunks
 440        for DiffHunk {
 441            buffer_range,
 442            diff_base_byte_range,
 443            secondary_status,
 444            ..
 445        } in hunks.iter().cloned()
 446        {
 447            let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
 448            pending_hunks.append(preceding_pending_hunks, buffer);
 449
 450            // Skip all overlapping or adjacent old pending hunks
 451            while old_pending_hunks.item().is_some_and(|old_hunk| {
 452                old_hunk
 453                    .buffer_range
 454                    .start
 455                    .cmp(&buffer_range.end, buffer)
 456                    .is_le()
 457            }) {
 458                old_pending_hunks.next();
 459            }
 460
 461            if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
 462                || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
 463            {
 464                continue;
 465            }
 466
 467            pending_hunks.push(
 468                PendingHunk {
 469                    buffer_range,
 470                    diff_base_byte_range,
 471                    buffer_version: buffer.version().clone(),
 472                    new_status: if stage {
 473                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
 474                    } else {
 475                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
 476                    },
 477                },
 478                buffer,
 479            );
 480        }
 481        // append the remainder
 482        pending_hunks.append(old_pending_hunks.suffix(), buffer);
 483
 484        let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
 485        unstaged_hunk_cursor.next();
 486
 487        // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
 488        let mut prev_unstaged_hunk_buffer_end = 0;
 489        let mut prev_unstaged_hunk_base_text_end = 0;
 490        let mut edits = Vec::<(Range<usize>, String)>::new();
 491        let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
 492        while let Some(PendingHunk {
 493            buffer_range,
 494            diff_base_byte_range,
 495            new_status,
 496            ..
 497        }) = pending_hunks_iter.next()
 498        {
 499            // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
 500            let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
 501
 502            if let Some(unstaged_hunk) = skipped_unstaged.last() {
 503                prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 504                prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
 505            }
 506
 507            // Find where this hunk is in the index if it doesn't overlap
 508            let mut buffer_offset_range = buffer_range.to_offset(buffer);
 509            let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
 510            let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
 511
 512            loop {
 513                // Merge this hunk with any overlapping unstaged hunks.
 514                if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
 515                    let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
 516                    if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
 517                        prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 518                        prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
 519
 520                        index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
 521                        buffer_offset_range.start = buffer_offset_range
 522                            .start
 523                            .min(unstaged_hunk_offset_range.start);
 524                        buffer_offset_range.end =
 525                            buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
 526
 527                        unstaged_hunk_cursor.next();
 528                        continue;
 529                    }
 530                }
 531
 532                // If any unstaged hunks were merged, then subsequent pending hunks may
 533                // now overlap this hunk. Merge them.
 534                if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
 535                    let next_pending_hunk_offset_range =
 536                        next_pending_hunk.buffer_range.to_offset(buffer);
 537                    if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
 538                        buffer_offset_range.end = buffer_offset_range
 539                            .end
 540                            .max(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 mut base_text = base_text.to_owned();
1138        text::LineEnding::normalize(&mut base_text);
1139        let inner = cx.foreground_executor().block_on(this.update_diff(
1140            buffer.clone(),
1141            Some(Arc::from(base_text)),
1142            true,
1143            None,
1144            cx,
1145        ));
1146        this.set_snapshot(inner, &buffer, cx).detach();
1147        this
1148    }
1149
1150    pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1151        self.secondary_diff = Some(diff);
1152    }
1153
1154    pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1155        self.secondary_diff.clone()
1156    }
1157
1158    pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1159        if self.secondary_diff.is_some() {
1160            self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1161                buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1162                diff_base_byte_range: 0..0,
1163            });
1164            cx.emit(BufferDiffEvent::DiffChanged {
1165                changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
1166                base_text_changed_range: Some(0..self.base_text(cx).len()),
1167            });
1168        }
1169    }
1170
1171    pub fn stage_or_unstage_hunks(
1172        &mut self,
1173        stage: bool,
1174        hunks: &[DiffHunk],
1175        buffer: &text::BufferSnapshot,
1176        file_exists: bool,
1177        cx: &mut Context<Self>,
1178    ) -> Option<Rope> {
1179        let new_index_text = self
1180            .secondary_diff
1181            .as_ref()?
1182            .update(cx, |secondary_diff, cx| {
1183                self.inner.stage_or_unstage_hunks_impl(
1184                    &secondary_diff.inner,
1185                    stage,
1186                    hunks,
1187                    buffer,
1188                    file_exists,
1189                    cx,
1190                )
1191            });
1192
1193        cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1194            new_index_text.clone(),
1195        ));
1196        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1197            let changed_range = first.buffer_range.start..last.buffer_range.end;
1198            let base_text_changed_range =
1199                first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1200            cx.emit(BufferDiffEvent::DiffChanged {
1201                changed_range: Some(changed_range),
1202                base_text_changed_range: Some(base_text_changed_range),
1203            });
1204        }
1205        new_index_text
1206    }
1207
1208    pub fn stage_or_unstage_all_hunks(
1209        &mut self,
1210        stage: bool,
1211        buffer: &text::BufferSnapshot,
1212        file_exists: bool,
1213        cx: &mut Context<Self>,
1214    ) {
1215        let hunks = self
1216            .snapshot(cx)
1217            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1218            .collect::<Vec<_>>();
1219        let Some(secondary) = self.secondary_diff.clone() else {
1220            return;
1221        };
1222        let secondary = secondary.read(cx).inner.clone();
1223        self.inner
1224            .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1225        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1226            let changed_range = first.buffer_range.start..last.buffer_range.end;
1227            let base_text_changed_range =
1228                first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1229            cx.emit(BufferDiffEvent::DiffChanged {
1230                changed_range: Some(changed_range),
1231                base_text_changed_range: Some(base_text_changed_range),
1232            });
1233        }
1234    }
1235
1236    pub fn update_diff(
1237        &self,
1238        buffer: text::BufferSnapshot,
1239        base_text: Option<Arc<str>>,
1240        base_text_changed: bool,
1241        language: Option<Arc<Language>>,
1242        cx: &App,
1243    ) -> Task<BufferDiffUpdate> {
1244        let prev_base_text = self.base_text(cx).as_rope().clone();
1245        let diff_options = build_diff_options(
1246            None,
1247            language.as_ref().map(|l| l.name()),
1248            language.as_ref().map(|l| l.default_scope()),
1249            cx,
1250        );
1251
1252        cx.background_executor().spawn(async move {
1253            let base_text_rope = if let Some(base_text) = &base_text {
1254                if base_text_changed {
1255                    Rope::from(base_text.as_ref())
1256                } else {
1257                    prev_base_text
1258                }
1259            } else {
1260                Rope::new()
1261            };
1262            let base_text_exists = base_text.is_some();
1263            let hunks = compute_hunks(
1264                base_text
1265                    .clone()
1266                    .map(|base_text| (base_text, base_text_rope.clone())),
1267                buffer.clone(),
1268                diff_options,
1269            );
1270            let base_text = base_text.unwrap_or_default();
1271            let inner = BufferDiffInner {
1272                base_text,
1273                hunks,
1274                base_text_exists,
1275                pending_hunks: SumTree::new(&buffer),
1276            };
1277            BufferDiffUpdate {
1278                inner,
1279                base_text_changed,
1280            }
1281        })
1282    }
1283
1284    pub fn language_changed(
1285        &mut self,
1286        language: Option<Arc<Language>>,
1287        language_registry: Option<Arc<LanguageRegistry>>,
1288        cx: &mut Context<Self>,
1289    ) {
1290        let fut = self.inner.base_text.update(cx, |base_text, cx| {
1291            if let Some(language_registry) = language_registry {
1292                base_text.set_language_registry(language_registry);
1293            }
1294            base_text.set_language(language, cx);
1295            base_text.parsing_idle()
1296        });
1297        cx.spawn(async move |this, cx| {
1298            fut.await;
1299            this.update(cx, |_, cx| {
1300                cx.emit(BufferDiffEvent::LanguageChanged);
1301            })
1302            .ok();
1303        })
1304        .detach();
1305    }
1306
1307    fn set_snapshot_with_secondary_inner(
1308        &mut self,
1309        update: BufferDiffUpdate,
1310        buffer: &text::BufferSnapshot,
1311        secondary_diff_change: Option<Range<Anchor>>,
1312        clear_pending_hunks: bool,
1313        cx: &mut Context<Self>,
1314    ) -> impl Future<Output = (Option<Range<Anchor>>, Option<Range<usize>>)> + use<> {
1315        log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1316
1317        let old_snapshot = self.snapshot(cx);
1318        let state = &mut self.inner;
1319        let new_state = update.inner;
1320        let (mut changed_range, mut base_text_changed_range) =
1321            match (state.base_text_exists, new_state.base_text_exists) {
1322                (false, false) => (None, None),
1323                (true, true) if !update.base_text_changed => {
1324                    compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
1325                }
1326                _ => (
1327                    Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
1328                    Some(0..new_state.base_text.len()),
1329                ),
1330            };
1331
1332        if let Some(secondary_changed_range) = secondary_diff_change
1333            && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1334                old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1335        {
1336            if let Some(range) = &mut changed_range {
1337                range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1338                range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1339            } else {
1340                changed_range = Some(secondary_hunk_range);
1341            }
1342
1343            if let Some(base_text_range) = &mut base_text_changed_range {
1344                base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1345                base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1346            } else {
1347                base_text_changed_range = Some(secondary_base_range);
1348            }
1349        }
1350
1351        let state = &mut self.inner;
1352        state.base_text_exists = new_state.base_text_exists;
1353        let parsing_idle = if update.base_text_changed {
1354            state.base_text.update(cx, |base_text, cx| {
1355                base_text.set_capability(Capability::ReadWrite, cx);
1356                base_text.set_text(new_state.base_text.clone(), cx);
1357                base_text.set_capability(Capability::ReadOnly, cx);
1358                Some(base_text.parsing_idle())
1359            })
1360        } else {
1361            None
1362        };
1363        state.hunks = new_state.hunks;
1364        if update.base_text_changed || clear_pending_hunks {
1365            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1366            {
1367                if let Some(range) = &mut changed_range {
1368                    range.start = *range.start.min(&first.buffer_range.start, buffer);
1369                    range.end = *range.end.max(&last.buffer_range.end, buffer);
1370                } else {
1371                    changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1372                }
1373
1374                if let Some(base_text_range) = &mut base_text_changed_range {
1375                    base_text_range.start =
1376                        base_text_range.start.min(first.diff_base_byte_range.start);
1377                    base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1378                } else {
1379                    base_text_changed_range =
1380                        Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1381                }
1382            }
1383            state.pending_hunks = SumTree::new(buffer);
1384        }
1385
1386        async move {
1387            if let Some(parsing_idle) = parsing_idle {
1388                parsing_idle.await;
1389            }
1390            (changed_range, base_text_changed_range)
1391        }
1392    }
1393
1394    pub fn set_snapshot(
1395        &mut self,
1396        new_state: BufferDiffUpdate,
1397        buffer: &text::BufferSnapshot,
1398        cx: &mut Context<Self>,
1399    ) -> Task<Option<Range<Anchor>>> {
1400        self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1401    }
1402
1403    pub fn set_snapshot_with_secondary(
1404        &mut self,
1405        update: BufferDiffUpdate,
1406        buffer: &text::BufferSnapshot,
1407        secondary_diff_change: Option<Range<Anchor>>,
1408        clear_pending_hunks: bool,
1409        cx: &mut Context<Self>,
1410    ) -> Task<Option<Range<Anchor>>> {
1411        let fut = self.set_snapshot_with_secondary_inner(
1412            update,
1413            buffer,
1414            secondary_diff_change,
1415            clear_pending_hunks,
1416            cx,
1417        );
1418
1419        cx.spawn(async move |this, cx| {
1420            let (changed_range, base_text_changed_range) = fut.await;
1421            this.update(cx, |_, cx| {
1422                cx.emit(BufferDiffEvent::DiffChanged {
1423                    changed_range: changed_range.clone(),
1424                    base_text_changed_range,
1425                });
1426            })
1427            .ok();
1428            changed_range
1429        })
1430    }
1431
1432    pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1433        self.inner.base_text.read(cx).snapshot()
1434    }
1435
1436    pub fn base_text_exists(&self) -> bool {
1437        self.inner.base_text_exists
1438    }
1439
1440    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1441        BufferDiffSnapshot {
1442            inner: BufferDiffInner {
1443                hunks: self.inner.hunks.clone(),
1444                pending_hunks: self.inner.pending_hunks.clone(),
1445                base_text: self.inner.base_text.read(cx).snapshot(),
1446                base_text_exists: self.inner.base_text_exists,
1447            },
1448            secondary_diff: self
1449                .secondary_diff
1450                .as_ref()
1451                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1452        }
1453    }
1454
1455    /// Used in cases where the change set isn't derived from git.
1456    pub fn set_base_text(
1457        &mut self,
1458        base_text: Option<Arc<str>>,
1459        language: Option<Arc<Language>>,
1460        buffer: text::BufferSnapshot,
1461        cx: &mut Context<Self>,
1462    ) -> oneshot::Receiver<()> {
1463        let (tx, rx) = oneshot::channel();
1464        let complete_on_drop = util::defer(|| {
1465            tx.send(()).ok();
1466        });
1467        cx.spawn(async move |this, cx| {
1468            let Some(state) = this
1469                .update(cx, |this, cx| {
1470                    this.update_diff(buffer.clone(), base_text, true, language, cx)
1471                })
1472                .log_err()
1473            else {
1474                return;
1475            };
1476            let state = state.await;
1477            if let Some(task) = this
1478                .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
1479                .log_err()
1480            {
1481                task.await;
1482            }
1483            drop(complete_on_drop)
1484        })
1485        .detach();
1486        rx
1487    }
1488
1489    pub fn base_text_string(&self, cx: &App) -> Option<String> {
1490        self.inner
1491            .base_text_exists
1492            .then(|| self.inner.base_text.read(cx).text())
1493    }
1494
1495    #[cfg(any(test, feature = "test-support"))]
1496    pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1497        let language = self.base_text(cx).language().cloned();
1498        let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1499        let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
1500        let fg_executor = cx.foreground_executor().clone();
1501        let snapshot = fg_executor.block_on(fut);
1502        let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
1503        let (changed_range, base_text_changed_range) = fg_executor.block_on(fut);
1504        cx.emit(BufferDiffEvent::DiffChanged {
1505            changed_range,
1506            base_text_changed_range,
1507        })
1508    }
1509
1510    pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1511        self.inner.base_text.clone()
1512    }
1513}
1514
1515impl DiffHunk {
1516    pub fn is_created_file(&self) -> bool {
1517        self.diff_base_byte_range == (0..0)
1518            && self.buffer_range.start.is_min()
1519            && self.buffer_range.end.is_max()
1520    }
1521
1522    pub fn status(&self) -> DiffHunkStatus {
1523        let kind = if self.buffer_range.start == self.buffer_range.end {
1524            DiffHunkStatusKind::Deleted
1525        } else if self.diff_base_byte_range.is_empty() {
1526            DiffHunkStatusKind::Added
1527        } else {
1528            DiffHunkStatusKind::Modified
1529        };
1530        DiffHunkStatus {
1531            kind,
1532            secondary: self.secondary_status,
1533        }
1534    }
1535}
1536
1537impl DiffHunkStatus {
1538    pub fn has_secondary_hunk(&self) -> bool {
1539        matches!(
1540            self.secondary,
1541            DiffHunkSecondaryStatus::HasSecondaryHunk
1542                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1543                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1544        )
1545    }
1546
1547    pub fn is_pending(&self) -> bool {
1548        matches!(
1549            self.secondary,
1550            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1551                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1552        )
1553    }
1554
1555    pub fn is_deleted(&self) -> bool {
1556        self.kind == DiffHunkStatusKind::Deleted
1557    }
1558
1559    pub fn is_added(&self) -> bool {
1560        self.kind == DiffHunkStatusKind::Added
1561    }
1562
1563    pub fn is_modified(&self) -> bool {
1564        self.kind == DiffHunkStatusKind::Modified
1565    }
1566
1567    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1568        Self {
1569            kind: DiffHunkStatusKind::Added,
1570            secondary,
1571        }
1572    }
1573
1574    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1575        Self {
1576            kind: DiffHunkStatusKind::Modified,
1577            secondary,
1578        }
1579    }
1580
1581    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1582        Self {
1583            kind: DiffHunkStatusKind::Deleted,
1584            secondary,
1585        }
1586    }
1587
1588    pub fn deleted_none() -> Self {
1589        Self {
1590            kind: DiffHunkStatusKind::Deleted,
1591            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1592        }
1593    }
1594
1595    pub fn added_none() -> Self {
1596        Self {
1597            kind: DiffHunkStatusKind::Added,
1598            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1599        }
1600    }
1601
1602    pub fn modified_none() -> Self {
1603        Self {
1604            kind: DiffHunkStatusKind::Modified,
1605            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1606        }
1607    }
1608}
1609
1610#[cfg(any(test, feature = "test-support"))]
1611#[track_caller]
1612pub fn assert_hunks<ExpectedText, HunkIter>(
1613    diff_hunks: HunkIter,
1614    buffer: &text::BufferSnapshot,
1615    diff_base: &str,
1616    // Line range, deleted, added, status
1617    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1618) where
1619    HunkIter: Iterator<Item = DiffHunk>,
1620    ExpectedText: AsRef<str>,
1621{
1622    let actual_hunks = diff_hunks
1623        .map(|hunk| {
1624            (
1625                hunk.range.clone(),
1626                &diff_base[hunk.diff_base_byte_range.clone()],
1627                buffer
1628                    .text_for_range(hunk.range.clone())
1629                    .collect::<String>(),
1630                hunk.status(),
1631            )
1632        })
1633        .collect::<Vec<_>>();
1634
1635    let expected_hunks: Vec<_> = expected_hunks
1636        .iter()
1637        .map(|(line_range, deleted_text, added_text, status)| {
1638            (
1639                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1640                deleted_text.as_ref(),
1641                added_text.as_ref().to_string(),
1642                *status,
1643            )
1644        })
1645        .collect();
1646
1647    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1648}
1649
1650#[cfg(test)]
1651mod tests {
1652    use std::{fmt::Write as _, sync::mpsc};
1653
1654    use super::*;
1655    use gpui::TestAppContext;
1656    use pretty_assertions::{assert_eq, assert_ne};
1657    use rand::{Rng as _, rngs::StdRng};
1658    use text::{Buffer, BufferId, ReplicaId, Rope};
1659    use unindent::Unindent as _;
1660    use util::test::marked_text_ranges;
1661
1662    #[ctor::ctor]
1663    fn init_logger() {
1664        zlog::init_test();
1665    }
1666
1667    #[gpui::test]
1668    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1669        let diff_base = "
1670            one
1671            two
1672            three
1673        "
1674        .unindent();
1675
1676        let buffer_text = "
1677            one
1678            HELLO
1679            three
1680        "
1681        .unindent();
1682
1683        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1684        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1685        assert_hunks(
1686            diff.hunks_intersecting_range(
1687                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1688                &buffer,
1689            ),
1690            &buffer,
1691            &diff_base,
1692            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1693        );
1694
1695        buffer.edit([(0..0, "point five\n")]);
1696        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1697        assert_hunks(
1698            diff.hunks_intersecting_range(
1699                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1700                &buffer,
1701            ),
1702            &buffer,
1703            &diff_base,
1704            &[
1705                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1706                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1707            ],
1708        );
1709
1710        diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
1711        assert_hunks::<&str, _>(
1712            diff.hunks_intersecting_range(
1713                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1714                &buffer,
1715            ),
1716            &buffer,
1717            &diff_base,
1718            &[],
1719        );
1720    }
1721
1722    #[gpui::test]
1723    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1724        let head_text = "
1725            zero
1726            one
1727            two
1728            three
1729            four
1730            five
1731            six
1732            seven
1733            eight
1734            nine
1735        "
1736        .unindent();
1737
1738        let index_text = "
1739            zero
1740            one
1741            TWO
1742            three
1743            FOUR
1744            five
1745            six
1746            seven
1747            eight
1748            NINE
1749        "
1750        .unindent();
1751
1752        let buffer_text = "
1753            zero
1754            one
1755            TWO
1756            three
1757            FOUR
1758            FIVE
1759            six
1760            SEVEN
1761            eight
1762            nine
1763        "
1764        .unindent();
1765
1766        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1767        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1768        let mut uncommitted_diff =
1769            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1770        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1771
1772        let expected_hunks = vec![
1773            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1774            (
1775                4..6,
1776                "four\nfive\n",
1777                "FOUR\nFIVE\n",
1778                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1779            ),
1780            (
1781                7..8,
1782                "seven\n",
1783                "SEVEN\n",
1784                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1785            ),
1786        ];
1787
1788        assert_hunks(
1789            uncommitted_diff.hunks_intersecting_range(
1790                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1791                &buffer,
1792            ),
1793            &buffer,
1794            &head_text,
1795            &expected_hunks,
1796        );
1797    }
1798
1799    #[gpui::test]
1800    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1801        let diff_base = "
1802            one
1803            two
1804            three
1805            four
1806            five
1807            six
1808            seven
1809            eight
1810            nine
1811            ten
1812        "
1813        .unindent();
1814
1815        let buffer_text = "
1816            A
1817            one
1818            B
1819            two
1820            C
1821            three
1822            HELLO
1823            four
1824            five
1825            SIXTEEN
1826            seven
1827            eight
1828            WORLD
1829            nine
1830
1831            ten
1832
1833        "
1834        .unindent();
1835
1836        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1837        let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
1838        assert_eq!(
1839            diff.hunks_intersecting_range(
1840                Anchor::min_max_range_for_buffer(buffer.remote_id()),
1841                &buffer
1842            )
1843            .count(),
1844            8
1845        );
1846
1847        assert_hunks(
1848            diff.hunks_intersecting_range(
1849                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1850                &buffer,
1851            ),
1852            &buffer,
1853            &diff_base,
1854            &[
1855                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1856                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1857                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1858            ],
1859        );
1860    }
1861
1862    #[gpui::test]
1863    async fn test_stage_hunk(cx: &mut TestAppContext) {
1864        struct Example {
1865            name: &'static str,
1866            head_text: String,
1867            index_text: String,
1868            buffer_marked_text: String,
1869            final_index_text: String,
1870        }
1871
1872        let table = [
1873            Example {
1874                name: "uncommitted hunk straddles end of unstaged hunk",
1875                head_text: "
1876                    one
1877                    two
1878                    three
1879                    four
1880                    five
1881                "
1882                .unindent(),
1883                index_text: "
1884                    one
1885                    TWO_HUNDRED
1886                    three
1887                    FOUR_HUNDRED
1888                    five
1889                "
1890                .unindent(),
1891                buffer_marked_text: "
1892                    ZERO
1893                    one
1894                    two
1895                    «THREE_HUNDRED
1896                    FOUR_HUNDRED»
1897                    five
1898                    SIX
1899                "
1900                .unindent(),
1901                final_index_text: "
1902                    one
1903                    two
1904                    THREE_HUNDRED
1905                    FOUR_HUNDRED
1906                    five
1907                "
1908                .unindent(),
1909            },
1910            Example {
1911                name: "uncommitted hunk straddles start of unstaged hunk",
1912                head_text: "
1913                    one
1914                    two
1915                    three
1916                    four
1917                    five
1918                "
1919                .unindent(),
1920                index_text: "
1921                    one
1922                    TWO_HUNDRED
1923                    three
1924                    FOUR_HUNDRED
1925                    five
1926                "
1927                .unindent(),
1928                buffer_marked_text: "
1929                    ZERO
1930                    one
1931                    «TWO_HUNDRED
1932                    THREE_HUNDRED»
1933                    four
1934                    five
1935                    SIX
1936                "
1937                .unindent(),
1938                final_index_text: "
1939                    one
1940                    TWO_HUNDRED
1941                    THREE_HUNDRED
1942                    four
1943                    five
1944                "
1945                .unindent(),
1946            },
1947            Example {
1948                name: "uncommitted hunk strictly contains unstaged hunks",
1949                head_text: "
1950                    one
1951                    two
1952                    three
1953                    four
1954                    five
1955                    six
1956                    seven
1957                "
1958                .unindent(),
1959                index_text: "
1960                    one
1961                    TWO
1962                    THREE
1963                    FOUR
1964                    FIVE
1965                    SIX
1966                    seven
1967                "
1968                .unindent(),
1969                buffer_marked_text: "
1970                    one
1971                    TWO
1972                    «THREE_HUNDRED
1973                    FOUR
1974                    FIVE_HUNDRED»
1975                    SIX
1976                    seven
1977                "
1978                .unindent(),
1979                final_index_text: "
1980                    one
1981                    TWO
1982                    THREE_HUNDRED
1983                    FOUR
1984                    FIVE_HUNDRED
1985                    SIX
1986                    seven
1987                "
1988                .unindent(),
1989            },
1990            Example {
1991                name: "uncommitted deletion hunk",
1992                head_text: "
1993                    one
1994                    two
1995                    three
1996                    four
1997                    five
1998                "
1999                .unindent(),
2000                index_text: "
2001                    one
2002                    two
2003                    three
2004                    four
2005                    five
2006                "
2007                .unindent(),
2008                buffer_marked_text: "
2009                    one
2010                    ˇfive
2011                "
2012                .unindent(),
2013                final_index_text: "
2014                    one
2015                    five
2016                "
2017                .unindent(),
2018            },
2019            Example {
2020                name: "one unstaged hunk that contains two uncommitted hunks",
2021                head_text: "
2022                    one
2023                    two
2024
2025                    three
2026                    four
2027                "
2028                .unindent(),
2029                index_text: "
2030                    one
2031                    two
2032                    three
2033                    four
2034                "
2035                .unindent(),
2036                buffer_marked_text: "
2037                    «one
2038
2039                    three // modified
2040                    four»
2041                "
2042                .unindent(),
2043                final_index_text: "
2044                    one
2045
2046                    three // modified
2047                    four
2048                "
2049                .unindent(),
2050            },
2051            Example {
2052                name: "one uncommitted hunk that contains two unstaged hunks",
2053                head_text: "
2054                    one
2055                    two
2056                    three
2057                    four
2058                    five
2059                "
2060                .unindent(),
2061                index_text: "
2062                    ZERO
2063                    one
2064                    TWO
2065                    THREE
2066                    FOUR
2067                    five
2068                "
2069                .unindent(),
2070                buffer_marked_text: "
2071                    «one
2072                    TWO_HUNDRED
2073                    THREE
2074                    FOUR_HUNDRED
2075                    five»
2076                "
2077                .unindent(),
2078                final_index_text: "
2079                    ZERO
2080                    one
2081                    TWO_HUNDRED
2082                    THREE
2083                    FOUR_HUNDRED
2084                    five
2085                "
2086                .unindent(),
2087            },
2088        ];
2089
2090        for example in table {
2091            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2092            let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2093            let hunk_range =
2094                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2095
2096            let unstaged_diff =
2097                cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2098
2099            let uncommitted_diff = cx.new(|cx| {
2100                let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2101                diff.set_secondary_diff(unstaged_diff);
2102                diff
2103            });
2104
2105            uncommitted_diff.update(cx, |diff, cx| {
2106                let hunks = diff
2107                    .snapshot(cx)
2108                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2109                    .collect::<Vec<_>>();
2110                for hunk in &hunks {
2111                    assert_ne!(
2112                        hunk.secondary_status,
2113                        DiffHunkSecondaryStatus::NoSecondaryHunk
2114                    )
2115                }
2116
2117                let new_index_text = diff
2118                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2119                    .unwrap()
2120                    .to_string();
2121
2122                let hunks = diff
2123                    .snapshot(cx)
2124                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2125                    .collect::<Vec<_>>();
2126                for hunk in &hunks {
2127                    assert_eq!(
2128                        hunk.secondary_status,
2129                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2130                    )
2131                }
2132
2133                pretty_assertions::assert_eq!(
2134                    new_index_text,
2135                    example.final_index_text,
2136                    "example: {}",
2137                    example.name
2138                );
2139            });
2140        }
2141    }
2142
2143    #[gpui::test]
2144    async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2145        // This test reproduces a crash where staging all hunks would cause an underflow
2146        // when there's one large unstaged hunk containing multiple uncommitted hunks.
2147        let head_text = "
2148            aaa
2149            bbb
2150            ccc
2151            ddd
2152            eee
2153            fff
2154            ggg
2155            hhh
2156            iii
2157            jjj
2158            kkk
2159            lll
2160        "
2161        .unindent();
2162
2163        let index_text = "
2164            aaa
2165            bbb
2166            CCC-index
2167            DDD-index
2168            EEE-index
2169            FFF-index
2170            GGG-index
2171            HHH-index
2172            III-index
2173            JJJ-index
2174            kkk
2175            lll
2176        "
2177        .unindent();
2178
2179        let buffer_text = "
2180            aaa
2181            bbb
2182            ccc-modified
2183            ddd
2184            eee-modified
2185            fff
2186            ggg
2187            hhh-modified
2188            iii
2189            jjj
2190            kkk
2191            lll
2192        "
2193        .unindent();
2194
2195        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2196
2197        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2198        let uncommitted_diff = cx.new(|cx| {
2199            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2200            diff.set_secondary_diff(unstaged_diff);
2201            diff
2202        });
2203
2204        uncommitted_diff.update(cx, |diff, cx| {
2205            diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2206        });
2207    }
2208
2209    #[gpui::test]
2210    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2211        let head_text = "
2212            one
2213            two
2214            three
2215        "
2216        .unindent();
2217        let index_text = head_text.clone();
2218        let buffer_text = "
2219            one
2220            three
2221        "
2222        .unindent();
2223
2224        let buffer = Buffer::new(
2225            ReplicaId::LOCAL,
2226            BufferId::new(1).unwrap(),
2227            buffer_text.clone(),
2228        );
2229        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2230        let uncommitted_diff = cx.new(|cx| {
2231            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2232            diff.set_secondary_diff(unstaged_diff.clone());
2233            diff
2234        });
2235
2236        uncommitted_diff.update(cx, |diff, cx| {
2237            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2238
2239            let new_index_text = diff
2240                .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2241                .unwrap()
2242                .to_string();
2243            assert_eq!(new_index_text, buffer_text);
2244
2245            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2246            assert_eq!(
2247                hunk.secondary_status,
2248                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2249            );
2250
2251            let index_text = diff
2252                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2253                .unwrap()
2254                .to_string();
2255            assert_eq!(index_text, head_text);
2256
2257            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2258            // optimistically unstaged (fine, could also be HasSecondaryHunk)
2259            assert_eq!(
2260                hunk.secondary_status,
2261                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2262            );
2263        });
2264    }
2265
2266    #[gpui::test]
2267    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2268        let base_text = "
2269            zero
2270            one
2271            two
2272            three
2273            four
2274            five
2275            six
2276            seven
2277            eight
2278            nine
2279        "
2280        .unindent();
2281
2282        let buffer_text_1 = "
2283            one
2284            three
2285            four
2286            five
2287            SIX
2288            seven
2289            eight
2290            NINE
2291        "
2292        .unindent();
2293
2294        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2295
2296        let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2297        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2298        let (range, base_text_range) =
2299            compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
2300        let range = range.unwrap();
2301        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2302        let base_text_range = base_text_range.unwrap();
2303        assert_eq!(
2304            base_text_range.to_point(diff_1.base_text()),
2305            Point::new(0, 0)..Point::new(10, 0)
2306        );
2307
2308        // Edit does affects the diff because it recalculates word diffs.
2309        buffer.edit_via_marked_text(
2310            &"
2311                one
2312                three
2313                four
2314                five
2315                «SIX.5»
2316                seven
2317                eight
2318                NINE
2319            "
2320            .unindent(),
2321        );
2322        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2323        let (range, base_text_range) =
2324            compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer);
2325        assert_eq!(
2326            range.unwrap().to_point(&buffer),
2327            Point::new(4, 0)..Point::new(5, 0),
2328        );
2329        assert_eq!(
2330            base_text_range.unwrap().to_point(diff_2.base_text()),
2331            Point::new(6, 0)..Point::new(7, 0),
2332        );
2333
2334        // Edit turns a deletion hunk into a modification.
2335        buffer.edit_via_marked_text(
2336            &"
2337                one
2338                «THREE»
2339                four
2340                five
2341                SIX.5
2342                seven
2343                eight
2344                NINE
2345            "
2346            .unindent(),
2347        );
2348        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2349        let (range, base_text_range) =
2350            compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
2351        let range = range.unwrap();
2352        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2353        let base_text_range = base_text_range.unwrap();
2354        assert_eq!(
2355            base_text_range.to_point(diff_3.base_text()),
2356            Point::new(2, 0)..Point::new(4, 0)
2357        );
2358
2359        // Edit turns a modification hunk into a deletion.
2360        buffer.edit_via_marked_text(
2361            &"
2362                one
2363                THREE
2364                four
2365                five«»
2366                seven
2367                eight
2368                NINE
2369            "
2370            .unindent(),
2371        );
2372        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2373        let (range, base_text_range) =
2374            compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
2375        let range = range.unwrap();
2376        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2377        let base_text_range = base_text_range.unwrap();
2378        assert_eq!(
2379            base_text_range.to_point(diff_4.base_text()),
2380            Point::new(6, 0)..Point::new(7, 0)
2381        );
2382
2383        // Edit introduces a new insertion hunk.
2384        buffer.edit_via_marked_text(
2385            &"
2386                one
2387                THREE
2388                four«
2389                FOUR.5
2390                »five
2391                seven
2392                eight
2393                NINE
2394            "
2395            .unindent(),
2396        );
2397        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2398        let (range, base_text_range) =
2399            compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
2400        let range = range.unwrap();
2401        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2402        let base_text_range = base_text_range.unwrap();
2403        assert_eq!(
2404            base_text_range.to_point(diff_5.base_text()),
2405            Point::new(5, 0)..Point::new(5, 0)
2406        );
2407
2408        // Edit removes a hunk.
2409        buffer.edit_via_marked_text(
2410            &"
2411                one
2412                THREE
2413                four
2414                FOUR.5
2415                five
2416                seven
2417                eight
2418                «nine»
2419            "
2420            .unindent(),
2421        );
2422        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2423        let (range, base_text_range) =
2424            compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
2425        let range = range.unwrap();
2426        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2427        let base_text_range = base_text_range.unwrap();
2428        assert_eq!(
2429            base_text_range.to_point(diff_6.base_text()),
2430            Point::new(9, 0)..Point::new(10, 0)
2431        );
2432    }
2433
2434    #[gpui::test(iterations = 100)]
2435    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2436        fn gen_line(rng: &mut StdRng) -> String {
2437            if rng.random_bool(0.2) {
2438                "\n".to_owned()
2439            } else {
2440                let c = rng.random_range('A'..='Z');
2441                format!("{c}{c}{c}\n")
2442            }
2443        }
2444
2445        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2446            let mut old_lines = {
2447                let mut old_lines = Vec::new();
2448                let old_lines_iter = head.lines();
2449                for line in old_lines_iter {
2450                    assert!(!line.ends_with("\n"));
2451                    old_lines.push(line.to_owned());
2452                }
2453                if old_lines.last().is_some_and(|line| line.is_empty()) {
2454                    old_lines.pop();
2455                }
2456                old_lines.into_iter()
2457            };
2458            let mut result = String::new();
2459            let unchanged_count = rng.random_range(0..=old_lines.len());
2460            result +=
2461                &old_lines
2462                    .by_ref()
2463                    .take(unchanged_count)
2464                    .fold(String::new(), |mut s, line| {
2465                        writeln!(&mut s, "{line}").unwrap();
2466                        s
2467                    });
2468            while old_lines.len() > 0 {
2469                let deleted_count = rng.random_range(0..=old_lines.len());
2470                let _advance = old_lines
2471                    .by_ref()
2472                    .take(deleted_count)
2473                    .map(|line| line.len() + 1)
2474                    .sum::<usize>();
2475                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2476                let added_count = rng.random_range(minimum_added..=5);
2477                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2478                result += &addition;
2479
2480                if old_lines.len() > 0 {
2481                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2482                    if blank_lines == old_lines.len() {
2483                        break;
2484                    };
2485                    let unchanged_count =
2486                        rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2487                    result += &old_lines.by_ref().take(unchanged_count).fold(
2488                        String::new(),
2489                        |mut s, line| {
2490                            writeln!(&mut s, "{line}").unwrap();
2491                            s
2492                        },
2493                    );
2494                }
2495            }
2496            result
2497        }
2498
2499        fn uncommitted_diff(
2500            working_copy: &language::BufferSnapshot,
2501            index_text: &Rope,
2502            head_text: String,
2503            cx: &mut TestAppContext,
2504        ) -> Entity<BufferDiff> {
2505            let secondary = cx.new(|cx| {
2506                BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2507            });
2508            cx.new(|cx| {
2509                let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2510                diff.secondary_diff = Some(secondary);
2511                diff
2512            })
2513        }
2514
2515        let operations = std::env::var("OPERATIONS")
2516            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2517            .unwrap_or(10);
2518
2519        let rng = &mut rng;
2520        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2521            writeln!(&mut s, "{c}{c}{c}").unwrap();
2522            s
2523        });
2524        let working_copy = gen_working_copy(rng, &head_text);
2525        let working_copy = cx.new(|cx| {
2526            language::Buffer::local_normalized(
2527                Rope::from(working_copy.as_str()),
2528                text::LineEnding::default(),
2529                cx,
2530            )
2531        });
2532        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2533        let mut index_text = if rng.random() {
2534            Rope::from(head_text.as_str())
2535        } else {
2536            working_copy.as_rope().clone()
2537        };
2538
2539        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2540        let mut hunks = diff.update(cx, |diff, cx| {
2541            diff.snapshot(cx)
2542                .hunks_intersecting_range(
2543                    Anchor::min_max_range_for_buffer(diff.buffer_id),
2544                    &working_copy,
2545                )
2546                .collect::<Vec<_>>()
2547        });
2548        if hunks.is_empty() {
2549            return;
2550        }
2551
2552        for _ in 0..operations {
2553            let i = rng.random_range(0..hunks.len());
2554            let hunk = &mut hunks[i];
2555            let hunk_to_change = hunk.clone();
2556            let stage = match hunk.secondary_status {
2557                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2558                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2559                    true
2560                }
2561                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2562                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2563                    false
2564                }
2565                _ => unreachable!(),
2566            };
2567
2568            index_text = diff.update(cx, |diff, cx| {
2569                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2570                    .unwrap()
2571            });
2572
2573            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2574            let found_hunks = diff.update(cx, |diff, cx| {
2575                diff.snapshot(cx)
2576                    .hunks_intersecting_range(
2577                        Anchor::min_max_range_for_buffer(diff.buffer_id),
2578                        &working_copy,
2579                    )
2580                    .collect::<Vec<_>>()
2581            });
2582            assert_eq!(hunks.len(), found_hunks.len());
2583
2584            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2585                assert_eq!(
2586                    expected_hunk.buffer_range.to_point(&working_copy),
2587                    found_hunk.buffer_range.to_point(&working_copy)
2588                );
2589                assert_eq!(
2590                    expected_hunk.diff_base_byte_range,
2591                    found_hunk.diff_base_byte_range
2592                );
2593                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2594            }
2595            hunks = found_hunks;
2596        }
2597    }
2598
2599    #[gpui::test]
2600    async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
2601        let base_text = "
2602            zero
2603            one
2604            two
2605            three
2606            four
2607            five
2608            six
2609            seven
2610            eight
2611        "
2612        .unindent();
2613        let buffer_text = "
2614            zero
2615            ONE
2616            two
2617            NINE
2618            five
2619            seven
2620        "
2621        .unindent();
2622
2623        //   zero
2624        // - one
2625        // + ONE
2626        //   two
2627        // - three
2628        // - four
2629        // + NINE
2630        //   five
2631        // - six
2632        //   seven
2633        // + eight
2634
2635        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2636        let buffer_snapshot = buffer.snapshot();
2637        let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
2638        let expected_results = [
2639            // main buffer row, base text row (right bias), base text row (left bias)
2640            (0, 0, 0),
2641            (1, 2, 1),
2642            (2, 2, 2),
2643            (3, 5, 3),
2644            (4, 5, 5),
2645            (5, 7, 7),
2646            (6, 9, 9),
2647        ];
2648        for (buffer_row, expected_right, expected_left) in expected_results {
2649            assert_eq!(
2650                diff.row_to_base_text_row(buffer_row, Bias::Right, &buffer_snapshot),
2651                expected_right,
2652                "{buffer_row}"
2653            );
2654            assert_eq!(
2655                diff.row_to_base_text_row(buffer_row, Bias::Left, &buffer_snapshot),
2656                expected_left,
2657                "{buffer_row}"
2658            );
2659        }
2660    }
2661
2662    #[gpui::test]
2663    async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2664        let base_text = "
2665            one
2666            two
2667            three
2668            four
2669            five
2670            six
2671        "
2672        .unindent();
2673        let buffer_text = "
2674            one
2675            TWO
2676            three
2677            four
2678            FIVE
2679            six
2680        "
2681        .unindent();
2682        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
2683        let diff = cx.new(|cx| {
2684            BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
2685        });
2686        cx.run_until_parked();
2687        let (tx, rx) = mpsc::channel();
2688        let subscription =
2689            cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
2690
2691        let snapshot = buffer.update(cx, |buffer, cx| {
2692            buffer.set_text(
2693                "
2694                ONE
2695                TWO
2696                THREE
2697                FOUR
2698                FIVE
2699                SIX
2700            "
2701                .unindent(),
2702                cx,
2703            );
2704            buffer.text_snapshot()
2705        });
2706        let update = diff
2707            .update(cx, |diff, cx| {
2708                diff.update_diff(
2709                    snapshot.clone(),
2710                    Some(base_text.as_str().into()),
2711                    false,
2712                    None,
2713                    cx,
2714                )
2715            })
2716            .await;
2717        diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
2718            .await;
2719        cx.run_until_parked();
2720        drop(subscription);
2721        let events = rx.into_iter().collect::<Vec<_>>();
2722        match events.as_slice() {
2723            [
2724                BufferDiffEvent::DiffChanged {
2725                    changed_range: _,
2726                    base_text_changed_range,
2727                },
2728            ] => {
2729                // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
2730                // assert_eq!(
2731                //     *changed_range,
2732                //     Some(Anchor::min_max_range_for_buffer(
2733                //         buffer.read_with(cx, |buffer, _| buffer.remote_id())
2734                //     ))
2735                // );
2736                assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
2737            }
2738            _ => panic!("unexpected events: {:?}", events),
2739        }
2740    }
2741}