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    pub async fn update_diff(
 902        this: Entity<BufferDiff>,
 903        buffer: text::BufferSnapshot,
 904        base_text: Option<Arc<String>>,
 905        base_text_changed: bool,
 906        language_changed: bool,
 907        language: Option<Arc<Language>>,
 908        language_registry: Option<Arc<LanguageRegistry>>,
 909        cx: &mut AsyncApp,
 910    ) -> anyhow::Result<BufferDiffSnapshot> {
 911        let inner = if base_text_changed || language_changed {
 912            cx.update(|cx| {
 913                Self::build(
 914                    buffer.clone(),
 915                    base_text,
 916                    language.clone(),
 917                    language_registry.clone(),
 918                    cx,
 919                )
 920            })?
 921            .await
 922        } else {
 923            this.read_with(cx, |this, cx| {
 924                Self::build_with_base_buffer(
 925                    buffer.clone(),
 926                    base_text,
 927                    this.base_text().clone(),
 928                    cx,
 929                )
 930            })?
 931            .await
 932        };
 933        Ok(BufferDiffSnapshot {
 934            inner,
 935            secondary_diff: None,
 936        })
 937    }
 938
 939    pub fn set_snapshot(
 940        &mut self,
 941        buffer: &text::BufferSnapshot,
 942        new_snapshot: BufferDiffSnapshot,
 943        language_changed: bool,
 944        secondary_changed_range: Option<Range<Anchor>>,
 945        cx: &mut Context<Self>,
 946    ) -> Option<Range<Anchor>> {
 947        let changed_range = self.set_state(new_snapshot.inner, buffer);
 948        if language_changed {
 949            cx.emit(BufferDiffEvent::LanguageChanged);
 950        }
 951
 952        let changed_range = match (secondary_changed_range, changed_range) {
 953            (None, None) => None,
 954            (Some(unstaged_range), None) => self.range_to_hunk_range(unstaged_range, &buffer, cx),
 955            (None, Some(uncommitted_range)) => Some(uncommitted_range),
 956            (Some(unstaged_range), Some(uncommitted_range)) => {
 957                let mut start = uncommitted_range.start;
 958                let mut end = uncommitted_range.end;
 959                if let Some(unstaged_range) = self.range_to_hunk_range(unstaged_range, &buffer, cx)
 960                {
 961                    start = unstaged_range.start.min(&uncommitted_range.start, &buffer);
 962                    end = unstaged_range.end.max(&uncommitted_range.end, &buffer);
 963                }
 964                Some(start..end)
 965            }
 966        };
 967
 968        cx.emit(BufferDiffEvent::DiffChanged {
 969            changed_range: changed_range.clone(),
 970        });
 971        changed_range
 972    }
 973
 974    fn set_state(
 975        &mut self,
 976        new_state: BufferDiffInner,
 977        buffer: &text::BufferSnapshot,
 978    ) -> Option<Range<Anchor>> {
 979        let (base_text_changed, changed_range) =
 980            match (self.inner.base_text_exists, new_state.base_text_exists) {
 981                (false, false) => (true, None),
 982                (true, true)
 983                    if self.inner.base_text.remote_id() == new_state.base_text.remote_id() =>
 984                {
 985                    (false, new_state.compare(&self.inner, buffer))
 986                }
 987                _ => (true, Some(text::Anchor::MIN..text::Anchor::MAX)),
 988            };
 989
 990        let pending_hunks = mem::replace(&mut self.inner.pending_hunks, SumTree::new(buffer));
 991
 992        self.inner = new_state;
 993        if !base_text_changed {
 994            self.inner.pending_hunks = pending_hunks;
 995        }
 996        changed_range
 997    }
 998
 999    pub fn base_text(&self) -> &language::BufferSnapshot {
1000        &self.inner.base_text
1001    }
1002
1003    pub fn base_text_exists(&self) -> bool {
1004        self.inner.base_text_exists
1005    }
1006
1007    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1008        BufferDiffSnapshot {
1009            inner: self.inner.clone(),
1010            secondary_diff: self
1011                .secondary_diff
1012                .as_ref()
1013                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1014        }
1015    }
1016
1017    pub fn hunks<'a>(
1018        &'a self,
1019        buffer_snapshot: &'a text::BufferSnapshot,
1020        cx: &'a App,
1021    ) -> impl 'a + Iterator<Item = DiffHunk> {
1022        self.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer_snapshot, cx)
1023    }
1024
1025    pub fn hunks_intersecting_range<'a>(
1026        &'a self,
1027        range: Range<text::Anchor>,
1028        buffer_snapshot: &'a text::BufferSnapshot,
1029        cx: &'a App,
1030    ) -> impl 'a + Iterator<Item = DiffHunk> {
1031        let unstaged_counterpart = self
1032            .secondary_diff
1033            .as_ref()
1034            .map(|diff| &diff.read(cx).inner);
1035        self.inner
1036            .hunks_intersecting_range(range, buffer_snapshot, unstaged_counterpart)
1037    }
1038
1039    pub fn hunks_intersecting_range_rev<'a>(
1040        &'a self,
1041        range: Range<text::Anchor>,
1042        buffer_snapshot: &'a text::BufferSnapshot,
1043    ) -> impl 'a + Iterator<Item = DiffHunk> {
1044        self.inner
1045            .hunks_intersecting_range_rev(range, buffer_snapshot)
1046    }
1047
1048    pub fn hunks_in_row_range<'a>(
1049        &'a self,
1050        range: Range<u32>,
1051        buffer: &'a text::BufferSnapshot,
1052        cx: &'a App,
1053    ) -> impl 'a + Iterator<Item = DiffHunk> {
1054        let start = buffer.anchor_before(Point::new(range.start, 0));
1055        let end = buffer.anchor_after(Point::new(range.end, 0));
1056        self.hunks_intersecting_range(start..end, buffer, cx)
1057    }
1058
1059    /// Used in cases where the change set isn't derived from git.
1060    pub fn set_base_text(
1061        &mut self,
1062        base_buffer: Entity<language::Buffer>,
1063        buffer: text::BufferSnapshot,
1064        cx: &mut Context<Self>,
1065    ) -> oneshot::Receiver<()> {
1066        let (tx, rx) = oneshot::channel();
1067        let this = cx.weak_entity();
1068        let base_buffer = base_buffer.read(cx);
1069        let language_registry = base_buffer.language_registry();
1070        let base_buffer = base_buffer.snapshot();
1071        let base_text = Arc::new(base_buffer.text());
1072
1073        let snapshot = BufferDiff::build(
1074            buffer.clone(),
1075            Some(base_text),
1076            base_buffer.language().cloned(),
1077            language_registry,
1078            cx,
1079        );
1080        let complete_on_drop = util::defer(|| {
1081            tx.send(()).ok();
1082        });
1083        cx.spawn(async move |_, cx| {
1084            let snapshot = snapshot.await;
1085            let Some(this) = this.upgrade() else {
1086                return;
1087            };
1088            this.update(cx, |this, _| {
1089                this.set_state(snapshot, &buffer);
1090            })
1091            .log_err();
1092            drop(complete_on_drop)
1093        })
1094        .detach();
1095        rx
1096    }
1097
1098    pub fn base_text_string(&self) -> Option<String> {
1099        self.inner
1100            .base_text_exists
1101            .then(|| self.inner.base_text.text())
1102    }
1103
1104    pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1105        BufferDiff {
1106            buffer_id: buffer.remote_id(),
1107            inner: BufferDiff::build_empty(buffer, cx),
1108            secondary_diff: None,
1109        }
1110    }
1111
1112    #[cfg(any(test, feature = "test-support"))]
1113    pub fn new_with_base_text(
1114        base_text: &str,
1115        buffer: &Entity<language::Buffer>,
1116        cx: &mut App,
1117    ) -> Self {
1118        let mut base_text = base_text.to_owned();
1119        text::LineEnding::normalize(&mut base_text);
1120        let snapshot = BufferDiff::build(
1121            buffer.read(cx).text_snapshot(),
1122            Some(base_text.into()),
1123            None,
1124            None,
1125            cx,
1126        );
1127        let snapshot = cx.background_executor().block(snapshot);
1128        BufferDiff {
1129            buffer_id: buffer.read(cx).remote_id(),
1130            inner: snapshot,
1131            secondary_diff: None,
1132        }
1133    }
1134
1135    #[cfg(any(test, feature = "test-support"))]
1136    pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
1137        let base_text = self.base_text_string().map(Arc::new);
1138        let snapshot = BufferDiff::build_with_base_buffer(
1139            buffer.clone(),
1140            base_text,
1141            self.inner.base_text.clone(),
1142            cx,
1143        );
1144        let snapshot = cx.background_executor().block(snapshot);
1145        let changed_range = self.set_state(snapshot, &buffer);
1146        cx.emit(BufferDiffEvent::DiffChanged { changed_range });
1147    }
1148}
1149
1150impl DiffHunk {
1151    pub fn is_created_file(&self) -> bool {
1152        self.diff_base_byte_range == (0..0) && self.buffer_range == (Anchor::MIN..Anchor::MAX)
1153    }
1154
1155    pub fn status(&self) -> DiffHunkStatus {
1156        let kind = if self.buffer_range.start == self.buffer_range.end {
1157            DiffHunkStatusKind::Deleted
1158        } else if self.diff_base_byte_range.is_empty() {
1159            DiffHunkStatusKind::Added
1160        } else {
1161            DiffHunkStatusKind::Modified
1162        };
1163        DiffHunkStatus {
1164            kind,
1165            secondary: self.secondary_status,
1166        }
1167    }
1168}
1169
1170impl DiffHunkStatus {
1171    pub fn has_secondary_hunk(&self) -> bool {
1172        matches!(
1173            self.secondary,
1174            DiffHunkSecondaryStatus::HasSecondaryHunk
1175                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1176                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1177        )
1178    }
1179
1180    pub fn is_pending(&self) -> bool {
1181        matches!(
1182            self.secondary,
1183            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1184                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1185        )
1186    }
1187
1188    pub fn is_deleted(&self) -> bool {
1189        self.kind == DiffHunkStatusKind::Deleted
1190    }
1191
1192    pub fn is_added(&self) -> bool {
1193        self.kind == DiffHunkStatusKind::Added
1194    }
1195
1196    pub fn is_modified(&self) -> bool {
1197        self.kind == DiffHunkStatusKind::Modified
1198    }
1199
1200    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1201        Self {
1202            kind: DiffHunkStatusKind::Added,
1203            secondary,
1204        }
1205    }
1206
1207    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1208        Self {
1209            kind: DiffHunkStatusKind::Modified,
1210            secondary,
1211        }
1212    }
1213
1214    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1215        Self {
1216            kind: DiffHunkStatusKind::Deleted,
1217            secondary,
1218        }
1219    }
1220
1221    pub fn deleted_none() -> Self {
1222        Self {
1223            kind: DiffHunkStatusKind::Deleted,
1224            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1225        }
1226    }
1227
1228    pub fn added_none() -> Self {
1229        Self {
1230            kind: DiffHunkStatusKind::Added,
1231            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1232        }
1233    }
1234
1235    pub fn modified_none() -> Self {
1236        Self {
1237            kind: DiffHunkStatusKind::Modified,
1238            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1239        }
1240    }
1241}
1242
1243/// Range (crossing new lines), old, new
1244#[cfg(any(test, feature = "test-support"))]
1245#[track_caller]
1246pub fn assert_hunks<ExpectedText, HunkIter>(
1247    diff_hunks: HunkIter,
1248    buffer: &text::BufferSnapshot,
1249    diff_base: &str,
1250    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1251) where
1252    HunkIter: Iterator<Item = DiffHunk>,
1253    ExpectedText: AsRef<str>,
1254{
1255    let actual_hunks = diff_hunks
1256        .map(|hunk| {
1257            (
1258                hunk.range.clone(),
1259                &diff_base[hunk.diff_base_byte_range.clone()],
1260                buffer
1261                    .text_for_range(hunk.range.clone())
1262                    .collect::<String>(),
1263                hunk.status(),
1264            )
1265        })
1266        .collect::<Vec<_>>();
1267
1268    let expected_hunks: Vec<_> = expected_hunks
1269        .iter()
1270        .map(|(r, old_text, new_text, status)| {
1271            (
1272                Point::new(r.start, 0)..Point::new(r.end, 0),
1273                old_text.as_ref(),
1274                new_text.as_ref().to_string(),
1275                *status,
1276            )
1277        })
1278        .collect();
1279
1280    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1281}
1282
1283#[cfg(test)]
1284mod tests {
1285    use std::fmt::Write as _;
1286
1287    use super::*;
1288    use gpui::TestAppContext;
1289    use rand::{rngs::StdRng, Rng as _};
1290    use text::{Buffer, BufferId, Rope};
1291    use unindent::Unindent as _;
1292    use util::test::marked_text_ranges;
1293
1294    #[ctor::ctor]
1295    fn init_logger() {
1296        if std::env::var("RUST_LOG").is_ok() {
1297            env_logger::init();
1298        }
1299    }
1300
1301    #[gpui::test]
1302    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1303        let diff_base = "
1304            one
1305            two
1306            three
1307        "
1308        .unindent();
1309
1310        let buffer_text = "
1311            one
1312            HELLO
1313            three
1314        "
1315        .unindent();
1316
1317        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1318        let mut diff = BufferDiff::build_sync(buffer.clone(), diff_base.clone(), cx);
1319        assert_hunks(
1320            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
1321            &buffer,
1322            &diff_base,
1323            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1324        );
1325
1326        buffer.edit([(0..0, "point five\n")]);
1327        diff = BufferDiff::build_sync(buffer.clone(), diff_base.clone(), cx);
1328        assert_hunks(
1329            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
1330            &buffer,
1331            &diff_base,
1332            &[
1333                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1334                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1335            ],
1336        );
1337
1338        diff = cx.update(|cx| BufferDiff::build_empty(&buffer, cx));
1339        assert_hunks::<&str, _>(
1340            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None),
1341            &buffer,
1342            &diff_base,
1343            &[],
1344        );
1345    }
1346
1347    #[gpui::test]
1348    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1349        let head_text = "
1350            zero
1351            one
1352            two
1353            three
1354            four
1355            five
1356            six
1357            seven
1358            eight
1359            nine
1360        "
1361        .unindent();
1362
1363        let index_text = "
1364            zero
1365            one
1366            TWO
1367            three
1368            FOUR
1369            five
1370            six
1371            seven
1372            eight
1373            NINE
1374        "
1375        .unindent();
1376
1377        let buffer_text = "
1378            zero
1379            one
1380            TWO
1381            three
1382            FOUR
1383            FIVE
1384            six
1385            SEVEN
1386            eight
1387            nine
1388        "
1389        .unindent();
1390
1391        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1392        let unstaged_diff = BufferDiff::build_sync(buffer.clone(), index_text.clone(), cx);
1393
1394        let uncommitted_diff = BufferDiff::build_sync(buffer.clone(), head_text.clone(), cx);
1395
1396        let expected_hunks = vec![
1397            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1398            (
1399                4..6,
1400                "four\nfive\n",
1401                "FOUR\nFIVE\n",
1402                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1403            ),
1404            (
1405                7..8,
1406                "seven\n",
1407                "SEVEN\n",
1408                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1409            ),
1410        ];
1411
1412        assert_hunks(
1413            uncommitted_diff.hunks_intersecting_range(
1414                Anchor::MIN..Anchor::MAX,
1415                &buffer,
1416                Some(&unstaged_diff),
1417            ),
1418            &buffer,
1419            &head_text,
1420            &expected_hunks,
1421        );
1422    }
1423
1424    #[gpui::test]
1425    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1426        let diff_base = Arc::new(
1427            "
1428            one
1429            two
1430            three
1431            four
1432            five
1433            six
1434            seven
1435            eight
1436            nine
1437            ten
1438        "
1439            .unindent(),
1440        );
1441
1442        let buffer_text = "
1443            A
1444            one
1445            B
1446            two
1447            C
1448            three
1449            HELLO
1450            four
1451            five
1452            SIXTEEN
1453            seven
1454            eight
1455            WORLD
1456            nine
1457
1458            ten
1459
1460        "
1461        .unindent();
1462
1463        let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1464        let diff = cx
1465            .update(|cx| {
1466                BufferDiff::build(buffer.snapshot(), Some(diff_base.clone()), None, None, cx)
1467            })
1468            .await;
1469        assert_eq!(
1470            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, None)
1471                .count(),
1472            8
1473        );
1474
1475        assert_hunks(
1476            diff.hunks_intersecting_range(
1477                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1478                &buffer,
1479                None,
1480            ),
1481            &buffer,
1482            &diff_base,
1483            &[
1484                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1485                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1486                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1487            ],
1488        );
1489    }
1490
1491    #[gpui::test]
1492    async fn test_stage_hunk(cx: &mut TestAppContext) {
1493        struct Example {
1494            name: &'static str,
1495            head_text: String,
1496            index_text: String,
1497            buffer_marked_text: String,
1498            final_index_text: String,
1499        }
1500
1501        let table = [
1502            Example {
1503                name: "uncommitted hunk straddles end of unstaged hunk",
1504                head_text: "
1505                    one
1506                    two
1507                    three
1508                    four
1509                    five
1510                "
1511                .unindent(),
1512                index_text: "
1513                    one
1514                    TWO_HUNDRED
1515                    three
1516                    FOUR_HUNDRED
1517                    five
1518                "
1519                .unindent(),
1520                buffer_marked_text: "
1521                    ZERO
1522                    one
1523                    two
1524                    «THREE_HUNDRED
1525                    FOUR_HUNDRED»
1526                    five
1527                    SIX
1528                "
1529                .unindent(),
1530                final_index_text: "
1531                    one
1532                    two
1533                    THREE_HUNDRED
1534                    FOUR_HUNDRED
1535                    five
1536                "
1537                .unindent(),
1538            },
1539            Example {
1540                name: "uncommitted hunk straddles start of unstaged hunk",
1541                head_text: "
1542                    one
1543                    two
1544                    three
1545                    four
1546                    five
1547                "
1548                .unindent(),
1549                index_text: "
1550                    one
1551                    TWO_HUNDRED
1552                    three
1553                    FOUR_HUNDRED
1554                    five
1555                "
1556                .unindent(),
1557                buffer_marked_text: "
1558                    ZERO
1559                    one
1560                    «TWO_HUNDRED
1561                    THREE_HUNDRED»
1562                    four
1563                    five
1564                    SIX
1565                "
1566                .unindent(),
1567                final_index_text: "
1568                    one
1569                    TWO_HUNDRED
1570                    THREE_HUNDRED
1571                    four
1572                    five
1573                "
1574                .unindent(),
1575            },
1576            Example {
1577                name: "uncommitted hunk strictly contains unstaged hunks",
1578                head_text: "
1579                    one
1580                    two
1581                    three
1582                    four
1583                    five
1584                    six
1585                    seven
1586                "
1587                .unindent(),
1588                index_text: "
1589                    one
1590                    TWO
1591                    THREE
1592                    FOUR
1593                    FIVE
1594                    SIX
1595                    seven
1596                "
1597                .unindent(),
1598                buffer_marked_text: "
1599                    one
1600                    TWO
1601                    «THREE_HUNDRED
1602                    FOUR
1603                    FIVE_HUNDRED»
1604                    SIX
1605                    seven
1606                "
1607                .unindent(),
1608                final_index_text: "
1609                    one
1610                    TWO
1611                    THREE_HUNDRED
1612                    FOUR
1613                    FIVE_HUNDRED
1614                    SIX
1615                    seven
1616                "
1617                .unindent(),
1618            },
1619            Example {
1620                name: "uncommitted deletion hunk",
1621                head_text: "
1622                    one
1623                    two
1624                    three
1625                    four
1626                    five
1627                "
1628                .unindent(),
1629                index_text: "
1630                    one
1631                    two
1632                    three
1633                    four
1634                    five
1635                "
1636                .unindent(),
1637                buffer_marked_text: "
1638                    one
1639                    ˇfive
1640                "
1641                .unindent(),
1642                final_index_text: "
1643                    one
1644                    five
1645                "
1646                .unindent(),
1647            },
1648        ];
1649
1650        for example in table {
1651            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
1652            let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text);
1653            let hunk_range =
1654                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
1655
1656            let unstaged = BufferDiff::build_sync(buffer.clone(), example.index_text.clone(), cx);
1657            let uncommitted = BufferDiff::build_sync(buffer.clone(), example.head_text.clone(), cx);
1658
1659            let unstaged_diff = cx.new(|cx| {
1660                let mut diff = BufferDiff::new(&buffer, cx);
1661                diff.set_state(unstaged, &buffer);
1662                diff
1663            });
1664
1665            let uncommitted_diff = cx.new(|cx| {
1666                let mut diff = BufferDiff::new(&buffer, cx);
1667                diff.set_state(uncommitted, &buffer);
1668                diff.set_secondary_diff(unstaged_diff);
1669                diff
1670            });
1671
1672            uncommitted_diff.update(cx, |diff, cx| {
1673                let hunks = diff
1674                    .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
1675                    .collect::<Vec<_>>();
1676                for hunk in &hunks {
1677                    assert_ne!(
1678                        hunk.secondary_status,
1679                        DiffHunkSecondaryStatus::NoSecondaryHunk
1680                    )
1681                }
1682
1683                let new_index_text = diff
1684                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
1685                    .unwrap()
1686                    .to_string();
1687
1688                let hunks = diff
1689                    .hunks_intersecting_range(hunk_range.clone(), &buffer, &cx)
1690                    .collect::<Vec<_>>();
1691                for hunk in &hunks {
1692                    assert_eq!(
1693                        hunk.secondary_status,
1694                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1695                    )
1696                }
1697
1698                pretty_assertions::assert_eq!(
1699                    new_index_text,
1700                    example.final_index_text,
1701                    "example: {}",
1702                    example.name
1703                );
1704            });
1705        }
1706    }
1707
1708    #[gpui::test]
1709    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
1710        let base_text = "
1711            zero
1712            one
1713            two
1714            three
1715            four
1716            five
1717            six
1718            seven
1719            eight
1720            nine
1721        "
1722        .unindent();
1723
1724        let buffer_text_1 = "
1725            one
1726            three
1727            four
1728            five
1729            SIX
1730            seven
1731            eight
1732            NINE
1733        "
1734        .unindent();
1735
1736        let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1);
1737
1738        let empty_diff = cx.update(|cx| BufferDiff::build_empty(&buffer, cx));
1739        let diff_1 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1740        let range = diff_1.compare(&empty_diff, &buffer).unwrap();
1741        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
1742
1743        // Edit does not affect the diff.
1744        buffer.edit_via_marked_text(
1745            &"
1746                one
1747                three
1748                four
1749                five
1750                «SIX.5»
1751                seven
1752                eight
1753                NINE
1754            "
1755            .unindent(),
1756        );
1757        let diff_2 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1758        assert_eq!(None, diff_2.compare(&diff_1, &buffer));
1759
1760        // Edit turns a deletion hunk into a modification.
1761        buffer.edit_via_marked_text(
1762            &"
1763                one
1764                «THREE»
1765                four
1766                five
1767                SIX.5
1768                seven
1769                eight
1770                NINE
1771            "
1772            .unindent(),
1773        );
1774        let diff_3 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1775        let range = diff_3.compare(&diff_2, &buffer).unwrap();
1776        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
1777
1778        // Edit turns a modification hunk into a deletion.
1779        buffer.edit_via_marked_text(
1780            &"
1781                one
1782                THREE
1783                four
1784                five«»
1785                seven
1786                eight
1787                NINE
1788            "
1789            .unindent(),
1790        );
1791        let diff_4 = BufferDiff::build_sync(buffer.clone(), base_text.clone(), cx);
1792        let range = diff_4.compare(&diff_3, &buffer).unwrap();
1793        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
1794
1795        // Edit introduces a new insertion hunk.
1796        buffer.edit_via_marked_text(
1797            &"
1798                one
1799                THREE
1800                four«
1801                FOUR.5
1802                »five
1803                seven
1804                eight
1805                NINE
1806            "
1807            .unindent(),
1808        );
1809        let diff_5 = BufferDiff::build_sync(buffer.snapshot(), base_text.clone(), cx);
1810        let range = diff_5.compare(&diff_4, &buffer).unwrap();
1811        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
1812
1813        // Edit removes a hunk.
1814        buffer.edit_via_marked_text(
1815            &"
1816                one
1817                THREE
1818                four
1819                FOUR.5
1820                five
1821                seven
1822                eight
1823                «nine»
1824            "
1825            .unindent(),
1826        );
1827        let diff_6 = BufferDiff::build_sync(buffer.snapshot(), base_text, cx);
1828        let range = diff_6.compare(&diff_5, &buffer).unwrap();
1829        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
1830    }
1831
1832    #[gpui::test(iterations = 100)]
1833    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
1834        fn gen_line(rng: &mut StdRng) -> String {
1835            if rng.gen_bool(0.2) {
1836                "\n".to_owned()
1837            } else {
1838                let c = rng.gen_range('A'..='Z');
1839                format!("{c}{c}{c}\n")
1840            }
1841        }
1842
1843        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
1844            let mut old_lines = {
1845                let mut old_lines = Vec::new();
1846                let mut old_lines_iter = head.lines();
1847                while let Some(line) = old_lines_iter.next() {
1848                    assert!(!line.ends_with("\n"));
1849                    old_lines.push(line.to_owned());
1850                }
1851                if old_lines.last().is_some_and(|line| line.is_empty()) {
1852                    old_lines.pop();
1853                }
1854                old_lines.into_iter()
1855            };
1856            let mut result = String::new();
1857            let unchanged_count = rng.gen_range(0..=old_lines.len());
1858            result +=
1859                &old_lines
1860                    .by_ref()
1861                    .take(unchanged_count)
1862                    .fold(String::new(), |mut s, line| {
1863                        writeln!(&mut s, "{line}").unwrap();
1864                        s
1865                    });
1866            while old_lines.len() > 0 {
1867                let deleted_count = rng.gen_range(0..=old_lines.len());
1868                let _advance = old_lines
1869                    .by_ref()
1870                    .take(deleted_count)
1871                    .map(|line| line.len() + 1)
1872                    .sum::<usize>();
1873                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
1874                let added_count = rng.gen_range(minimum_added..=5);
1875                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
1876                result += &addition;
1877
1878                if old_lines.len() > 0 {
1879                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
1880                    if blank_lines == old_lines.len() {
1881                        break;
1882                    };
1883                    let unchanged_count = rng.gen_range((blank_lines + 1).max(1)..=old_lines.len());
1884                    result += &old_lines.by_ref().take(unchanged_count).fold(
1885                        String::new(),
1886                        |mut s, line| {
1887                            writeln!(&mut s, "{line}").unwrap();
1888                            s
1889                        },
1890                    );
1891                }
1892            }
1893            result
1894        }
1895
1896        fn uncommitted_diff(
1897            working_copy: &language::BufferSnapshot,
1898            index_text: &Rope,
1899            head_text: String,
1900            cx: &mut TestAppContext,
1901        ) -> Entity<BufferDiff> {
1902            let inner = BufferDiff::build_sync(working_copy.text.clone(), head_text, cx);
1903            let secondary = BufferDiff {
1904                buffer_id: working_copy.remote_id(),
1905                inner: BufferDiff::build_sync(
1906                    working_copy.text.clone(),
1907                    index_text.to_string(),
1908                    cx,
1909                ),
1910                secondary_diff: None,
1911            };
1912            let secondary = cx.new(|_| secondary);
1913            cx.new(|_| BufferDiff {
1914                buffer_id: working_copy.remote_id(),
1915                inner,
1916                secondary_diff: Some(secondary),
1917            })
1918        }
1919
1920        let operations = std::env::var("OPERATIONS")
1921            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1922            .unwrap_or(10);
1923
1924        let rng = &mut rng;
1925        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
1926            writeln!(&mut s, "{c}{c}{c}").unwrap();
1927            s
1928        });
1929        let working_copy = gen_working_copy(rng, &head_text);
1930        let working_copy = cx.new(|cx| {
1931            language::Buffer::local_normalized(
1932                Rope::from(working_copy.as_str()),
1933                text::LineEnding::default(),
1934                cx,
1935            )
1936        });
1937        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
1938        let mut index_text = if rng.gen() {
1939            Rope::from(head_text.as_str())
1940        } else {
1941            working_copy.as_rope().clone()
1942        };
1943
1944        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
1945        let mut hunks = diff.update(cx, |diff, cx| {
1946            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
1947                .collect::<Vec<_>>()
1948        });
1949        if hunks.len() == 0 {
1950            return;
1951        }
1952
1953        for _ in 0..operations {
1954            let i = rng.gen_range(0..hunks.len());
1955            let hunk = &mut hunks[i];
1956            let hunk_to_change = hunk.clone();
1957            let stage = match hunk.secondary_status {
1958                DiffHunkSecondaryStatus::HasSecondaryHunk => {
1959                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
1960                    true
1961                }
1962                DiffHunkSecondaryStatus::NoSecondaryHunk => {
1963                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
1964                    false
1965                }
1966                _ => unreachable!(),
1967            };
1968
1969            index_text = diff.update(cx, |diff, cx| {
1970                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
1971                    .unwrap()
1972            });
1973
1974            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
1975            let found_hunks = diff.update(cx, |diff, cx| {
1976                diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &working_copy, cx)
1977                    .collect::<Vec<_>>()
1978            });
1979            assert_eq!(hunks.len(), found_hunks.len());
1980
1981            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
1982                assert_eq!(
1983                    expected_hunk.buffer_range.to_point(&working_copy),
1984                    found_hunk.buffer_range.to_point(&working_copy)
1985                );
1986                assert_eq!(
1987                    expected_hunk.diff_base_byte_range,
1988                    found_hunk.diff_base_byte_range
1989                );
1990                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
1991            }
1992            hunks = found_hunks;
1993        }
1994    }
1995}