buffer_diff.rs

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