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