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