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