buffer_diff.rs

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