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