buffer_diff.rs

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