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