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