buffer_diff.rs

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