buffer_diff.rs

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