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