buffer_diff.rs

   1use futures::channel::oneshot;
   2use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
   3use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
   4use language::{
   5    Capability, Diff, DiffOptions, Language, LanguageName, LanguageRegistry,
   6    language_settings::LanguageSettings, word_diff_ranges,
   7};
   8use rope::Rope;
   9use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc};
  10use sum_tree::SumTree;
  11use text::{
  12    Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,
  13};
  14use util::ResultExt;
  15
  16fn translate_point_through_patch(
  17    patch: &Patch<Point>,
  18    point: Point,
  19) -> (Range<Point>, Range<Point>) {
  20    let edits = patch.edits();
  21
  22    let ix = match edits.binary_search_by(|probe| probe.old.start.cmp(&point)) {
  23        Ok(ix) => ix,
  24        Err(ix) => {
  25            if ix == 0 {
  26                return (point..point, point..point);
  27            } else {
  28                ix - 1
  29            }
  30        }
  31    };
  32
  33    if let Some(edit) = edits.get(ix) {
  34        if point > edit.old.end {
  35            let translated = edit.new.end + (point - edit.old.end);
  36            (translated..translated, point..point)
  37        } else {
  38            (edit.new.start..edit.new.end, edit.old.start..edit.old.end)
  39        }
  40    } else {
  41        (point..point, point..point)
  42    }
  43}
  44
  45pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
  46
  47pub struct BufferDiff {
  48    pub buffer_id: BufferId,
  49    inner: BufferDiffInner<Entity<language::Buffer>>,
  50    secondary_diff: Option<Entity<BufferDiff>>,
  51}
  52
  53#[derive(Clone)]
  54pub struct BufferDiffSnapshot {
  55    inner: BufferDiffInner<language::BufferSnapshot>,
  56    secondary_diff: Option<Box<BufferDiffSnapshot>>,
  57}
  58
  59impl std::fmt::Debug for BufferDiffSnapshot {
  60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  61        f.debug_struct("BufferDiffSnapshot")
  62            .field("inner", &self.inner)
  63            .field("secondary_diff", &self.secondary_diff)
  64            .finish()
  65    }
  66}
  67
  68#[derive(Clone)]
  69pub struct BufferDiffUpdate {
  70    inner: BufferDiffInner<Arc<str>>,
  71    buffer_snapshot: text::BufferSnapshot,
  72    base_text_edits: Option<Diff>,
  73    base_text_changed: bool,
  74}
  75
  76#[derive(Clone)]
  77struct BufferDiffInner<BaseText> {
  78    hunks: SumTree<InternalDiffHunk>,
  79    pending_hunks: SumTree<PendingHunk>,
  80    base_text: BaseText,
  81    base_text_exists: bool,
  82    buffer_snapshot: text::BufferSnapshot,
  83}
  84
  85impl<BaseText> BufferDiffInner<BaseText> {
  86    fn buffer_version(&self) -> &clock::Global {
  87        self.buffer_snapshot.version()
  88    }
  89}
  90
  91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  92pub struct DiffHunkStatus {
  93    pub kind: DiffHunkStatusKind,
  94    pub secondary: DiffHunkSecondaryStatus,
  95}
  96
  97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
  98pub enum DiffHunkStatusKind {
  99    Added,
 100    Modified,
 101    Deleted,
 102}
 103
 104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 105/// Diff of Working Copy vs Index
 106/// aka 'is this hunk staged or not'
 107pub enum DiffHunkSecondaryStatus {
 108    /// Unstaged
 109    HasSecondaryHunk,
 110    /// Partially staged
 111    OverlapsWithSecondaryHunk,
 112    /// Staged
 113    NoSecondaryHunk,
 114    /// We are unstaging
 115    SecondaryHunkAdditionPending,
 116    /// We are stagind
 117    SecondaryHunkRemovalPending,
 118}
 119
 120/// A diff hunk resolved to rows in the buffer.
 121#[derive(Debug, Clone, PartialEq, Eq)]
 122pub struct DiffHunk {
 123    /// The buffer range as points.
 124    pub range: Range<Point>,
 125    /// The range in the buffer to which this hunk corresponds.
 126    pub buffer_range: Range<Anchor>,
 127    /// The range in the buffer's diff base text to which this hunk corresponds.
 128    pub diff_base_byte_range: Range<usize>,
 129    pub secondary_status: DiffHunkSecondaryStatus,
 130    // Anchors representing the word diff locations in the active buffer
 131    pub buffer_word_diffs: Vec<Range<Anchor>>,
 132    // Offsets relative to the start of the deleted diff that represent word diff locations
 133    pub base_word_diffs: Vec<Range<usize>>,
 134}
 135
 136/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
 137#[derive(Debug, Clone, PartialEq, Eq)]
 138struct InternalDiffHunk {
 139    buffer_range: Range<Anchor>,
 140    diff_base_byte_range: Range<usize>,
 141    base_word_diffs: Vec<Range<usize>>,
 142    buffer_word_diffs: Vec<Range<Anchor>>,
 143}
 144
 145#[derive(Debug, Clone, PartialEq, Eq)]
 146struct PendingHunk {
 147    buffer_range: Range<Anchor>,
 148    diff_base_byte_range: Range<usize>,
 149    buffer_version: clock::Global,
 150    new_status: DiffHunkSecondaryStatus,
 151}
 152
 153#[derive(Debug, Clone)]
 154pub struct DiffHunkSummary {
 155    buffer_range: Range<Anchor>,
 156    diff_base_byte_range: Range<usize>,
 157}
 158
 159impl sum_tree::Item for InternalDiffHunk {
 160    type Summary = DiffHunkSummary;
 161
 162    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 163        DiffHunkSummary {
 164            buffer_range: self.buffer_range.clone(),
 165            diff_base_byte_range: self.diff_base_byte_range.clone(),
 166        }
 167    }
 168}
 169
 170impl sum_tree::Item for PendingHunk {
 171    type Summary = DiffHunkSummary;
 172
 173    fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
 174        DiffHunkSummary {
 175            buffer_range: self.buffer_range.clone(),
 176            diff_base_byte_range: self.diff_base_byte_range.clone(),
 177        }
 178    }
 179}
 180
 181impl sum_tree::Summary for DiffHunkSummary {
 182    type Context<'a> = &'a text::BufferSnapshot;
 183
 184    fn zero(_cx: Self::Context<'_>) -> Self {
 185        DiffHunkSummary {
 186            buffer_range: Anchor::MIN..Anchor::MIN,
 187            diff_base_byte_range: 0..0,
 188        }
 189    }
 190
 191    fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
 192        self.buffer_range.start = *self
 193            .buffer_range
 194            .start
 195            .min(&other.buffer_range.start, buffer);
 196        self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
 197
 198        self.diff_base_byte_range.start = self
 199            .diff_base_byte_range
 200            .start
 201            .min(other.diff_base_byte_range.start);
 202        self.diff_base_byte_range.end = self
 203            .diff_base_byte_range
 204            .end
 205            .max(other.diff_base_byte_range.end);
 206    }
 207}
 208
 209impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
 210    fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
 211        if self
 212            .cmp(&cursor_location.buffer_range.start, buffer)
 213            .is_lt()
 214        {
 215            Ordering::Less
 216        } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
 217            Ordering::Greater
 218        } else {
 219            Ordering::Equal
 220        }
 221    }
 222}
 223
 224impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize {
 225    fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering {
 226        if *self < cursor_location.diff_base_byte_range.start {
 227            Ordering::Less
 228        } else if *self > cursor_location.diff_base_byte_range.end {
 229            Ordering::Greater
 230        } else {
 231            Ordering::Equal
 232        }
 233    }
 234}
 235
 236impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
 237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 238        f.debug_struct("BufferDiffSnapshot")
 239            .field("hunks", &self.hunks)
 240            .field("remote_id", &self.base_text.remote_id())
 241            .finish()
 242    }
 243}
 244
 245impl BufferDiffSnapshot {
 246    #[cfg(test)]
 247    fn new_sync(
 248        buffer: text::BufferSnapshot,
 249        diff_base: String,
 250        cx: &mut gpui::TestAppContext,
 251    ) -> BufferDiffSnapshot {
 252        let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
 253        buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
 254    }
 255
 256    pub fn is_empty(&self) -> bool {
 257        self.inner.hunks.is_empty()
 258    }
 259
 260    pub fn base_text_string(&self) -> Option<String> {
 261        self.inner
 262            .base_text_exists
 263            .then(|| self.inner.base_text.text())
 264    }
 265
 266    pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
 267        self.secondary_diff.as_deref()
 268    }
 269
 270    pub fn buffer_version(&self) -> &clock::Global {
 271        self.inner.buffer_version()
 272    }
 273
 274    pub fn original_buffer_snapshot(&self) -> &text::BufferSnapshot {
 275        &self.inner.buffer_snapshot
 276    }
 277
 278    #[ztracing::instrument(skip_all)]
 279    pub fn hunks_intersecting_range<'a>(
 280        &'a self,
 281        range: Range<Anchor>,
 282        buffer: &'a text::BufferSnapshot,
 283    ) -> impl 'a + Iterator<Item = DiffHunk> {
 284        let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
 285        self.inner
 286            .hunks_intersecting_range(range, buffer, unstaged_counterpart)
 287    }
 288
 289    pub fn hunks_intersecting_range_rev<'a>(
 290        &'a self,
 291        range: Range<Anchor>,
 292        buffer: &'a text::BufferSnapshot,
 293    ) -> impl 'a + Iterator<Item = DiffHunk> {
 294        let filter = move |summary: &DiffHunkSummary| {
 295            let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
 296            let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
 297            !before_start && !after_end
 298        };
 299        self.inner.hunks_intersecting_range_rev_impl(filter, buffer)
 300    }
 301
 302    pub fn hunks_intersecting_base_text_range<'a>(
 303        &'a self,
 304        range: Range<usize>,
 305        main_buffer: &'a text::BufferSnapshot,
 306    ) -> impl 'a + Iterator<Item = DiffHunk> {
 307        let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
 308        let filter = move |summary: &DiffHunkSummary| {
 309            let before_start = summary.diff_base_byte_range.end < range.start;
 310            let after_end = summary.diff_base_byte_range.start > range.end;
 311            !before_start && !after_end
 312        };
 313        self.inner
 314            .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart)
 315    }
 316
 317    pub fn hunks_intersecting_base_text_range_rev<'a>(
 318        &'a self,
 319        range: Range<usize>,
 320        main_buffer: &'a text::BufferSnapshot,
 321    ) -> impl 'a + Iterator<Item = DiffHunk> {
 322        let filter = move |summary: &DiffHunkSummary| {
 323            let before_start = summary.diff_base_byte_range.end.cmp(&range.start).is_lt();
 324            let after_end = summary.diff_base_byte_range.start.cmp(&range.end).is_gt();
 325            !before_start && !after_end
 326        };
 327        self.inner
 328            .hunks_intersecting_range_rev_impl(filter, main_buffer)
 329    }
 330
 331    pub fn hunks<'a>(
 332        &'a self,
 333        buffer_snapshot: &'a text::BufferSnapshot,
 334    ) -> impl 'a + Iterator<Item = DiffHunk> {
 335        self.hunks_intersecting_range(
 336            Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()),
 337            buffer_snapshot,
 338        )
 339    }
 340
 341    pub fn hunks_in_row_range<'a>(
 342        &'a self,
 343        range: Range<u32>,
 344        buffer: &'a text::BufferSnapshot,
 345    ) -> impl 'a + Iterator<Item = DiffHunk> {
 346        let start = buffer.anchor_before(Point::new(range.start, 0));
 347        let end = buffer.anchor_after(Point::new(range.end, 0));
 348        self.hunks_intersecting_range(start..end, buffer)
 349    }
 350
 351    pub fn range_to_hunk_range(
 352        &self,
 353        range: Range<Anchor>,
 354        buffer: &text::BufferSnapshot,
 355    ) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
 356        let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next();
 357        let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next();
 358        let range = first_hunk
 359            .as_ref()
 360            .zip(last_hunk.as_ref())
 361            .map(|(first, last)| first.buffer_range.start..last.buffer_range.end);
 362        let base_text_range = first_hunk
 363            .zip(last_hunk)
 364            .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end);
 365        (range, base_text_range)
 366    }
 367
 368    pub fn base_text(&self) -> &language::BufferSnapshot {
 369        &self.inner.base_text
 370    }
 371
 372    /// If this function returns `true`, the base texts are equal. If this
 373    /// function returns `false`, they might be equal, but might not. This
 374    /// result is used to avoid recalculating diffs in situations where we know
 375    /// nothing has changed.
 376    pub fn base_texts_definitely_eq(&self, other: &Self) -> bool {
 377        if self.inner.base_text_exists != other.inner.base_text_exists {
 378            return false;
 379        }
 380        let left = &self.inner.base_text;
 381        let right = &other.inner.base_text;
 382        let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty());
 383        let (new_id, new_version, new_empty) =
 384            (right.remote_id(), right.version(), right.is_empty());
 385        (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
 386    }
 387
 388    #[allow(unused)]
 389    fn hunk_before_base_text_offset<'a>(
 390        &self,
 391        target: usize,
 392        cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
 393    ) -> Option<&'a InternalDiffHunk> {
 394        cursor.seek_forward(&target, Bias::Left);
 395        if cursor
 396            .item()
 397            .is_none_or(|hunk| target < hunk.diff_base_byte_range.start)
 398        {
 399            cursor.prev();
 400        }
 401        let result = cursor
 402            .item()
 403            .filter(|hunk| target >= hunk.diff_base_byte_range.start);
 404        if cursor.item().is_none() {
 405            cursor.reset();
 406        }
 407        result
 408    }
 409
 410    #[allow(unused)]
 411    fn hunk_before_buffer_anchor<'a>(
 412        &self,
 413        target: Anchor,
 414        cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
 415        buffer: &text::BufferSnapshot,
 416    ) -> Option<&'a InternalDiffHunk> {
 417        cursor.seek_forward(&target, Bias::Left);
 418        if cursor
 419            .item()
 420            .is_none_or(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_lt())
 421        {
 422            cursor.prev();
 423        }
 424        let result = cursor
 425            .item()
 426            .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge());
 427        if cursor.item().is_none() {
 428            cursor.reset();
 429        }
 430        result
 431    }
 432
 433    pub fn points_to_base_text_points<'a>(
 434        &'a self,
 435        points: impl IntoIterator<Item = Point> + 'a,
 436        buffer: &'a text::BufferSnapshot,
 437    ) -> (
 438        impl 'a + Iterator<Item = Range<Point>>,
 439        Option<Range<Point>>,
 440        Option<(Point, Range<Point>)>,
 441    ) {
 442        let original_snapshot = self.original_buffer_snapshot();
 443
 444        let edits_since: Vec<Edit<Point>> = buffer
 445            .edits_since::<Point>(original_snapshot.version())
 446            .collect();
 447        let mut inverted_edits_since = Patch::new(edits_since);
 448        inverted_edits_since.invert();
 449
 450        let composed = inverted_edits_since.compose(
 451            self.inner
 452                .hunks
 453                .iter()
 454                .map(|hunk| {
 455                    let old_start = hunk.buffer_range.start.to_point(original_snapshot);
 456                    let old_end = hunk.buffer_range.end.to_point(original_snapshot);
 457                    let new_start = self
 458                        .base_text()
 459                        .offset_to_point(hunk.diff_base_byte_range.start);
 460                    let new_end = self
 461                        .base_text()
 462                        .offset_to_point(hunk.diff_base_byte_range.end);
 463                    Edit {
 464                        old: old_start..old_end,
 465                        new: new_start..new_end,
 466                    }
 467                })
 468                .chain(
 469                    if !self.inner.base_text_exists && self.inner.hunks.is_empty() {
 470                        Some(Edit {
 471                            old: Point::zero()..original_snapshot.max_point(),
 472                            new: Point::zero()..Point::zero(),
 473                        })
 474                    } else {
 475                        None
 476                    },
 477                ),
 478        );
 479
 480        let mut points = points.into_iter().peekable();
 481
 482        let first_group = points.peek().map(|point| {
 483            let (_, old_range) = translate_point_through_patch(&composed, *point);
 484            old_range
 485        });
 486
 487        let prev_boundary = points.peek().and_then(|first_point| {
 488            if first_point.row > 0 {
 489                let prev_point = Point::new(first_point.row - 1, 0);
 490                let (range, _) = translate_point_through_patch(&composed, prev_point);
 491                Some((prev_point, range))
 492            } else {
 493                None
 494            }
 495        });
 496
 497        let iter = points.map(move |point| {
 498            let (range, _) = translate_point_through_patch(&composed, point);
 499            range
 500        });
 501
 502        (iter, first_group, prev_boundary)
 503    }
 504
 505    pub fn base_text_points_to_points<'a>(
 506        &'a self,
 507        points: impl IntoIterator<Item = Point> + 'a,
 508        buffer: &'a text::BufferSnapshot,
 509    ) -> (
 510        impl 'a + Iterator<Item = Range<Point>>,
 511        Option<Range<Point>>,
 512        Option<(Point, Range<Point>)>,
 513    ) {
 514        let original_snapshot = self.original_buffer_snapshot();
 515
 516        let mut hunk_edits: Vec<Edit<Point>> = Vec::new();
 517        for hunk in self.inner.hunks.iter() {
 518            let old_start = self
 519                .base_text()
 520                .offset_to_point(hunk.diff_base_byte_range.start);
 521            let old_end = self
 522                .base_text()
 523                .offset_to_point(hunk.diff_base_byte_range.end);
 524            let new_start = hunk.buffer_range.start.to_point(original_snapshot);
 525            let new_end = hunk.buffer_range.end.to_point(original_snapshot);
 526            hunk_edits.push(Edit {
 527                old: old_start..old_end,
 528                new: new_start..new_end,
 529            });
 530        }
 531        if !self.inner.base_text_exists && hunk_edits.is_empty() {
 532            hunk_edits.push(Edit {
 533                old: Point::zero()..Point::zero(),
 534                new: Point::zero()..original_snapshot.max_point(),
 535            })
 536        }
 537        let hunk_patch = Patch::new(hunk_edits);
 538
 539        let composed = hunk_patch.compose(buffer.edits_since::<Point>(original_snapshot.version()));
 540
 541        let mut points = points.into_iter().peekable();
 542
 543        let first_group = points.peek().map(|point| {
 544            let (_, result) = translate_point_through_patch(&composed, *point);
 545            result
 546        });
 547
 548        let prev_boundary = points.peek().and_then(|first_point| {
 549            if first_point.row > 0 {
 550                let prev_point = Point::new(first_point.row - 1, 0);
 551                let (range, _) = translate_point_through_patch(&composed, prev_point);
 552                Some((prev_point, range))
 553            } else {
 554                None
 555            }
 556        });
 557
 558        let iter = points.map(move |point| {
 559            let (range, _) = translate_point_through_patch(&composed, point);
 560            range
 561        });
 562
 563        (iter, first_group, prev_boundary)
 564    }
 565}
 566
 567impl BufferDiffInner<Entity<language::Buffer>> {
 568    /// Returns the new index text and new pending hunks.
 569    fn stage_or_unstage_hunks_impl(
 570        &mut self,
 571        unstaged_diff: &Self,
 572        stage: bool,
 573        hunks: &[DiffHunk],
 574        buffer: &text::BufferSnapshot,
 575        file_exists: bool,
 576        cx: &mut Context<BufferDiff>,
 577    ) -> Option<Rope> {
 578        let head_text = self
 579            .base_text_exists
 580            .then(|| self.base_text.read(cx).as_rope().clone());
 581        let index_text = unstaged_diff
 582            .base_text_exists
 583            .then(|| unstaged_diff.base_text.read(cx).as_rope().clone());
 584
 585        // If the file doesn't exist in either HEAD or the index, then the
 586        // entire file must be either created or deleted in the index.
 587        let (index_text, head_text) = match (index_text, head_text) {
 588            (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
 589            (index_text, head_text) => {
 590                let (new_index_text, new_status) = if stage {
 591                    log::debug!("stage all");
 592                    (
 593                        file_exists.then(|| buffer.as_rope().clone()),
 594                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
 595                    )
 596                } else {
 597                    log::debug!("unstage all");
 598                    (
 599                        head_text,
 600                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
 601                    )
 602                };
 603
 604                let hunk = PendingHunk {
 605                    buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
 606                    diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
 607                    buffer_version: buffer.version().clone(),
 608                    new_status,
 609                };
 610                self.pending_hunks = SumTree::from_item(hunk, buffer);
 611                return new_index_text;
 612            }
 613        };
 614
 615        let mut pending_hunks = SumTree::new(buffer);
 616        let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 617
 618        // first, merge new hunks into pending_hunks
 619        for DiffHunk {
 620            buffer_range,
 621            diff_base_byte_range,
 622            secondary_status,
 623            ..
 624        } in hunks.iter().cloned()
 625        {
 626            let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
 627            pending_hunks.append(preceding_pending_hunks, buffer);
 628
 629            // Skip all overlapping or adjacent old pending hunks
 630            while old_pending_hunks.item().is_some_and(|old_hunk| {
 631                old_hunk
 632                    .buffer_range
 633                    .start
 634                    .cmp(&buffer_range.end, buffer)
 635                    .is_le()
 636            }) {
 637                old_pending_hunks.next();
 638            }
 639
 640            if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
 641                || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
 642            {
 643                continue;
 644            }
 645
 646            pending_hunks.push(
 647                PendingHunk {
 648                    buffer_range,
 649                    diff_base_byte_range,
 650                    buffer_version: buffer.version().clone(),
 651                    new_status: if stage {
 652                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
 653                    } else {
 654                        DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
 655                    },
 656                },
 657                buffer,
 658            );
 659        }
 660        // append the remainder
 661        pending_hunks.append(old_pending_hunks.suffix(), buffer);
 662
 663        let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
 664        unstaged_hunk_cursor.next();
 665
 666        // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
 667        let mut prev_unstaged_hunk_buffer_end = 0;
 668        let mut prev_unstaged_hunk_base_text_end = 0;
 669        let mut edits = Vec::<(Range<usize>, String)>::new();
 670        let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
 671        while let Some(PendingHunk {
 672            buffer_range,
 673            diff_base_byte_range,
 674            new_status,
 675            ..
 676        }) = pending_hunks_iter.next()
 677        {
 678            // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
 679            let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
 680
 681            if let Some(unstaged_hunk) = skipped_unstaged.last() {
 682                prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 683                prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
 684            }
 685
 686            // Find where this hunk is in the index if it doesn't overlap
 687            let mut buffer_offset_range = buffer_range.to_offset(buffer);
 688            let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
 689            let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
 690
 691            loop {
 692                // Merge this hunk with any overlapping unstaged hunks.
 693                if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
 694                    let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
 695                    if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
 696                        prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
 697                        prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
 698
 699                        index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
 700                        buffer_offset_range.start = buffer_offset_range
 701                            .start
 702                            .min(unstaged_hunk_offset_range.start);
 703                        buffer_offset_range.end =
 704                            buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
 705
 706                        unstaged_hunk_cursor.next();
 707                        continue;
 708                    }
 709                }
 710
 711                // If any unstaged hunks were merged, then subsequent pending hunks may
 712                // now overlap this hunk. Merge them.
 713                if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
 714                    let next_pending_hunk_offset_range =
 715                        next_pending_hunk.buffer_range.to_offset(buffer);
 716                    if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
 717                        buffer_offset_range.end = buffer_offset_range
 718                            .end
 719                            .max(next_pending_hunk_offset_range.end);
 720                        pending_hunks_iter.next();
 721                        continue;
 722                    }
 723                }
 724
 725                break;
 726            }
 727
 728            let end_overshoot = buffer_offset_range
 729                .end
 730                .saturating_sub(prev_unstaged_hunk_buffer_end);
 731            let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
 732            let index_byte_range = index_start..index_end;
 733
 734            let replacement_text = match new_status {
 735                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
 736                    log::debug!("staging hunk {:?}", buffer_offset_range);
 737                    buffer
 738                        .text_for_range(buffer_offset_range)
 739                        .collect::<String>()
 740                }
 741                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
 742                    log::debug!("unstaging hunk {:?}", buffer_offset_range);
 743                    head_text
 744                        .chunks_in_range(diff_base_byte_range.clone())
 745                        .collect::<String>()
 746                }
 747                _ => {
 748                    debug_assert!(false);
 749                    continue;
 750                }
 751            };
 752
 753            edits.push((index_byte_range, replacement_text));
 754        }
 755        drop(pending_hunks_iter);
 756        drop(old_pending_hunks);
 757        self.pending_hunks = pending_hunks;
 758
 759        #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
 760        {
 761            for window in edits.windows(2) {
 762                let (range_a, range_b) = (&window[0].0, &window[1].0);
 763                debug_assert!(range_a.end < range_b.start);
 764            }
 765        }
 766
 767        let mut new_index_text = Rope::new();
 768        let mut index_cursor = index_text.cursor(0);
 769
 770        for (old_range, replacement_text) in edits {
 771            new_index_text.append(index_cursor.slice(old_range.start));
 772            index_cursor.seek_forward(old_range.end);
 773            new_index_text.push(&replacement_text);
 774        }
 775        new_index_text.append(index_cursor.suffix());
 776        Some(new_index_text)
 777    }
 778}
 779
 780impl BufferDiffInner<language::BufferSnapshot> {
 781    fn hunks_intersecting_range<'a>(
 782        &'a self,
 783        range: Range<Anchor>,
 784        buffer: &'a text::BufferSnapshot,
 785        secondary: Option<&'a Self>,
 786    ) -> impl 'a + Iterator<Item = DiffHunk> {
 787        let range = range.to_offset(buffer);
 788        let filter = move |summary: &DiffHunkSummary| {
 789            let summary_range = summary.buffer_range.to_offset(buffer);
 790            let before_start = summary_range.end < range.start;
 791            let after_end = summary_range.start > range.end;
 792            !before_start && !after_end
 793        };
 794        self.hunks_intersecting_range_impl(filter, buffer, secondary)
 795    }
 796
 797    fn hunks_intersecting_range_impl<'a>(
 798        &'a self,
 799        filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
 800        buffer: &'a text::BufferSnapshot,
 801        secondary: Option<&'a Self>,
 802    ) -> impl 'a + Iterator<Item = DiffHunk> {
 803        let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
 804
 805        let anchor_iter = iter::from_fn(move || {
 806            cursor.next();
 807            cursor.item()
 808        })
 809        .flat_map(move |hunk| {
 810            [
 811                (
 812                    &hunk.buffer_range.start,
 813                    (
 814                        hunk.buffer_range.start,
 815                        hunk.diff_base_byte_range.start,
 816                        hunk,
 817                    ),
 818                ),
 819                (
 820                    &hunk.buffer_range.end,
 821                    (hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
 822                ),
 823            ]
 824        });
 825
 826        let mut pending_hunks_cursor = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
 827        pending_hunks_cursor.next();
 828
 829        let mut secondary_cursor = None;
 830        if let Some(secondary) = secondary.as_ref() {
 831            let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
 832            cursor.next();
 833            secondary_cursor = Some(cursor);
 834        }
 835
 836        let max_point = buffer.max_point();
 837        let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
 838        iter::from_fn(move || {
 839            loop {
 840                let (start_point, (start_anchor, start_base, hunk)) = summaries.next()?;
 841                let (mut end_point, (mut end_anchor, end_base, _)) = summaries.next()?;
 842
 843                let base_word_diffs = hunk.base_word_diffs.clone();
 844                let buffer_word_diffs = hunk.buffer_word_diffs.clone();
 845
 846                if !start_anchor.is_valid(buffer) {
 847                    continue;
 848                }
 849
 850                if end_point.column > 0 && end_point < max_point {
 851                    end_point.row += 1;
 852                    end_point.column = 0;
 853                    end_anchor = buffer.anchor_before(end_point);
 854                }
 855
 856                let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
 857
 858                let mut has_pending = false;
 859                if start_anchor
 860                    .cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
 861                    .is_gt()
 862                {
 863                    pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
 864                }
 865
 866                if let Some(pending_hunk) = pending_hunks_cursor.item() {
 867                    let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
 868                    if pending_range.end.column > 0 {
 869                        pending_range.end.row += 1;
 870                        pending_range.end.column = 0;
 871                    }
 872
 873                    if pending_range == (start_point..end_point)
 874                        && !buffer.has_edits_since_in_range(
 875                            &pending_hunk.buffer_version,
 876                            start_anchor..end_anchor,
 877                        )
 878                    {
 879                        has_pending = true;
 880                        secondary_status = pending_hunk.new_status;
 881                    }
 882                }
 883
 884                if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
 885                    if start_anchor
 886                        .cmp(&secondary_cursor.start().buffer_range.start, buffer)
 887                        .is_gt()
 888                    {
 889                        secondary_cursor.seek_forward(&start_anchor, Bias::Left);
 890                    }
 891
 892                    if let Some(secondary_hunk) = secondary_cursor.item() {
 893                        let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
 894                        if secondary_range.end.column > 0 {
 895                            secondary_range.end.row += 1;
 896                            secondary_range.end.column = 0;
 897                        }
 898                        if secondary_range.is_empty()
 899                            && secondary_hunk.diff_base_byte_range.is_empty()
 900                        {
 901                            // ignore
 902                        } else if secondary_range == (start_point..end_point) {
 903                            secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
 904                        } else if secondary_range.start <= end_point {
 905                            secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
 906                        }
 907                    }
 908                }
 909
 910                return Some(DiffHunk {
 911                    range: start_point..end_point,
 912                    diff_base_byte_range: start_base..end_base,
 913                    buffer_range: start_anchor..end_anchor,
 914                    base_word_diffs,
 915                    buffer_word_diffs,
 916                    secondary_status,
 917                });
 918            }
 919        })
 920    }
 921
 922    fn hunks_intersecting_range_rev_impl<'a>(
 923        &'a self,
 924        filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
 925        buffer: &'a text::BufferSnapshot,
 926    ) -> impl 'a + Iterator<Item = DiffHunk> {
 927        let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
 928
 929        iter::from_fn(move || {
 930            cursor.prev();
 931
 932            let hunk = cursor.item()?;
 933            let range = hunk.buffer_range.to_point(buffer);
 934
 935            Some(DiffHunk {
 936                range,
 937                diff_base_byte_range: hunk.diff_base_byte_range.clone(),
 938                buffer_range: hunk.buffer_range.clone(),
 939                // The secondary status is not used by callers of this method.
 940                secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
 941                base_word_diffs: hunk.base_word_diffs.clone(),
 942                buffer_word_diffs: hunk.buffer_word_diffs.clone(),
 943            })
 944        })
 945    }
 946}
 947
 948fn build_diff_options(
 949    language: Option<LanguageName>,
 950    language_scope: Option<language::LanguageScope>,
 951    cx: &App,
 952) -> Option<DiffOptions> {
 953    #[cfg(any(test, feature = "test-support"))]
 954    {
 955        if !cx.has_global::<settings::SettingsStore>() {
 956            return Some(DiffOptions {
 957                language_scope,
 958                max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
 959                ..Default::default()
 960            });
 961        }
 962    }
 963
 964    LanguageSettings::resolve(None, language.as_ref(), cx)
 965        .word_diff_enabled
 966        .then_some(DiffOptions {
 967            language_scope,
 968            max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
 969            ..Default::default()
 970        })
 971}
 972
 973fn compute_hunks(
 974    diff_base: Option<(Arc<str>, Rope)>,
 975    buffer: text::BufferSnapshot,
 976    diff_options: Option<DiffOptions>,
 977) -> SumTree<InternalDiffHunk> {
 978    let mut tree = SumTree::new(&buffer);
 979
 980    if let Some((diff_base, diff_base_rope)) = diff_base {
 981        let buffer_text = buffer.as_rope().to_string();
 982
 983        let mut options = GitOptions::default();
 984        options.context_lines(0);
 985        let patch = GitPatch::from_buffers(
 986            diff_base.as_bytes(),
 987            None,
 988            buffer_text.as_bytes(),
 989            None,
 990            Some(&mut options),
 991        )
 992        .log_err();
 993
 994        // A common case in Zed is that the empty buffer is represented as just a newline,
 995        // but if we just compute a naive diff you get a "preserved" line in the middle,
 996        // which is a bit odd.
 997        if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
 998            tree.push(
 999                InternalDiffHunk {
1000                    buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
1001                    diff_base_byte_range: 0..diff_base.len() - 1,
1002                    base_word_diffs: Vec::default(),
1003                    buffer_word_diffs: Vec::default(),
1004                },
1005                &buffer,
1006            );
1007            return tree;
1008        }
1009
1010        if let Some(patch) = patch {
1011            let mut divergence = 0;
1012            for hunk_index in 0..patch.num_hunks() {
1013                let hunk = process_patch_hunk(
1014                    &patch,
1015                    hunk_index,
1016                    &diff_base_rope,
1017                    &buffer,
1018                    &mut divergence,
1019                    diff_options.as_ref(),
1020                );
1021                tree.push(hunk, &buffer);
1022            }
1023        }
1024    } else {
1025        tree.push(
1026            InternalDiffHunk {
1027                buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
1028                diff_base_byte_range: 0..0,
1029                base_word_diffs: Vec::default(),
1030                buffer_word_diffs: Vec::default(),
1031            },
1032            &buffer,
1033        );
1034    }
1035
1036    tree
1037}
1038
1039fn compare_hunks(
1040    new_hunks: &SumTree<InternalDiffHunk>,
1041    old_hunks: &SumTree<InternalDiffHunk>,
1042    old_snapshot: &text::BufferSnapshot,
1043    new_snapshot: &text::BufferSnapshot,
1044) -> DiffChanged {
1045    let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
1046    let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
1047    old_cursor.next();
1048    new_cursor.next();
1049    let mut start = None;
1050    let mut end = None;
1051    let mut base_text_start = None;
1052    let mut base_text_end = None;
1053
1054    let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1055    let mut has_changes = false;
1056    let mut extended_end_candidate: Option<text::Anchor> = None;
1057
1058    loop {
1059        match (new_cursor.item(), old_cursor.item()) {
1060            (Some(new_hunk), Some(old_hunk)) => {
1061                match new_hunk
1062                    .buffer_range
1063                    .start
1064                    .cmp(&old_hunk.buffer_range.start, new_snapshot)
1065                {
1066                    Ordering::Less => {
1067                        has_changes = true;
1068                        extended_end_candidate = None;
1069                        start.get_or_insert(new_hunk.buffer_range.start);
1070                        base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
1071                        end.replace(new_hunk.buffer_range.end);
1072                        base_text_end.replace(new_hunk.diff_base_byte_range.end);
1073                        new_cursor.next();
1074                    }
1075                    Ordering::Equal => {
1076                        if new_hunk != old_hunk {
1077                            has_changes = true;
1078                            extended_end_candidate = None;
1079                            start.get_or_insert(new_hunk.buffer_range.start);
1080                            base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
1081                            if old_hunk
1082                                .buffer_range
1083                                .end
1084                                .cmp(&new_hunk.buffer_range.end, new_snapshot)
1085                                .is_ge()
1086                            {
1087                                end.replace(old_hunk.buffer_range.end);
1088                            } else {
1089                                end.replace(new_hunk.buffer_range.end);
1090                            }
1091
1092                            base_text_end.replace(
1093                                old_hunk
1094                                    .diff_base_byte_range
1095                                    .end
1096                                    .max(new_hunk.diff_base_byte_range.end),
1097                            );
1098                        } else {
1099                            if !has_changes {
1100                                last_unchanged_new_hunk_end = Some(new_hunk.buffer_range.end);
1101                            } else if extended_end_candidate.is_none() {
1102                                extended_end_candidate = Some(new_hunk.buffer_range.start);
1103                            }
1104                        }
1105
1106                        new_cursor.next();
1107                        old_cursor.next();
1108                    }
1109                    Ordering::Greater => {
1110                        has_changes = true;
1111                        extended_end_candidate = None;
1112                        start.get_or_insert(old_hunk.buffer_range.start);
1113                        base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
1114                        end.replace(old_hunk.buffer_range.end);
1115                        base_text_end.replace(old_hunk.diff_base_byte_range.end);
1116                        old_cursor.next();
1117                    }
1118                }
1119            }
1120            (Some(new_hunk), None) => {
1121                has_changes = true;
1122                extended_end_candidate = None;
1123                start.get_or_insert(new_hunk.buffer_range.start);
1124                base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
1125                if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1126                {
1127                    end.replace(new_hunk.buffer_range.end);
1128                }
1129                base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));
1130                new_cursor.next();
1131            }
1132            (None, Some(old_hunk)) => {
1133                has_changes = true;
1134                extended_end_candidate = None;
1135                start.get_or_insert(old_hunk.buffer_range.start);
1136                base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
1137                if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1138                {
1139                    end.replace(old_hunk.buffer_range.end);
1140                }
1141                base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));
1142                old_cursor.next();
1143            }
1144            (None, None) => break,
1145        }
1146    }
1147
1148    let changed_range = start.zip(end).map(|(start, end)| start..end);
1149    let base_text_changed_range = base_text_start
1150        .zip(base_text_end)
1151        .map(|(start, end)| start..end);
1152
1153    let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1154        let extended_start = *last_unchanged_new_hunk_end
1155            .unwrap_or(text::Anchor::min_for_buffer(new_snapshot.remote_id()))
1156            .min(&changed_range.start, new_snapshot);
1157        let extended_start = new_snapshot
1158            .anchored_edits_since_in_range::<usize>(
1159                &old_snapshot.version(),
1160                extended_start..changed_range.start,
1161            )
1162            .map(|(_, anchors)| anchors.start)
1163            .min_by(|a, b| a.cmp(b, new_snapshot))
1164            .unwrap_or(changed_range.start);
1165
1166        let extended_end = *extended_end_candidate
1167            .unwrap_or(text::Anchor::max_for_buffer(new_snapshot.remote_id()))
1168            .max(&changed_range.end, new_snapshot);
1169        let extended_end = new_snapshot
1170            .anchored_edits_since_in_range::<usize>(
1171                &old_snapshot.version(),
1172                changed_range.end..extended_end,
1173            )
1174            .map(|(_, anchors)| anchors.end)
1175            .max_by(|a, b| a.cmp(b, new_snapshot))
1176            .unwrap_or(changed_range.end);
1177
1178        Some(extended_start..extended_end)
1179    } else {
1180        None
1181    };
1182
1183    DiffChanged {
1184        changed_range,
1185        base_text_changed_range,
1186        extended_range,
1187    }
1188}
1189
1190fn process_patch_hunk(
1191    patch: &GitPatch<'_>,
1192    hunk_index: usize,
1193    diff_base: &Rope,
1194    buffer: &text::BufferSnapshot,
1195    buffer_row_divergence: &mut i64,
1196    diff_options: Option<&DiffOptions>,
1197) -> InternalDiffHunk {
1198    let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
1199    assert!(line_item_count > 0);
1200
1201    let mut first_deletion_buffer_row: Option<u32> = None;
1202    let mut buffer_row_range: Option<Range<u32>> = None;
1203    let mut diff_base_byte_range: Option<Range<usize>> = None;
1204    let mut first_addition_old_row: Option<u32> = None;
1205
1206    for line_index in 0..line_item_count {
1207        let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
1208        let kind = line.origin_value();
1209        let content_offset = line.content_offset() as isize;
1210        let content_len = line.content().len() as isize;
1211        match kind {
1212            GitDiffLineType::Addition => {
1213                if first_addition_old_row.is_none() {
1214                    first_addition_old_row = Some(
1215                        (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
1216                    );
1217                }
1218                *buffer_row_divergence += 1;
1219                let row = line.new_lineno().unwrap().saturating_sub(1);
1220
1221                match &mut buffer_row_range {
1222                    Some(Range { end, .. }) => *end = row + 1,
1223                    None => buffer_row_range = Some(row..row + 1),
1224                }
1225            }
1226            GitDiffLineType::Deletion => {
1227                let end = content_offset + content_len;
1228
1229                match &mut diff_base_byte_range {
1230                    Some(head_byte_range) => head_byte_range.end = end as usize,
1231                    None => diff_base_byte_range = Some(content_offset as usize..end as usize),
1232                }
1233
1234                if first_deletion_buffer_row.is_none() {
1235                    let old_row = line.old_lineno().unwrap().saturating_sub(1);
1236                    let row = old_row as i64 + *buffer_row_divergence;
1237                    first_deletion_buffer_row = Some(row as u32);
1238                }
1239
1240                *buffer_row_divergence -= 1;
1241            }
1242            _ => {}
1243        }
1244    }
1245
1246    let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
1247        // Pure deletion hunk without addition.
1248        let row = first_deletion_buffer_row.unwrap();
1249        row..row
1250    });
1251    let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
1252        // Pure addition hunk without deletion.
1253        let row = first_addition_old_row.unwrap();
1254        let offset = diff_base.point_to_offset(Point::new(row, 0));
1255        offset..offset
1256    });
1257
1258    let start = Point::new(buffer_row_range.start, 0);
1259    let end = Point::new(buffer_row_range.end, 0);
1260    let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
1261
1262    let base_line_count = line_item_count.saturating_sub(buffer_row_range.len());
1263
1264    let (base_word_diffs, buffer_word_diffs) = if let Some(diff_options) = diff_options
1265        && !buffer_row_range.is_empty()
1266        && base_line_count == buffer_row_range.len()
1267        && diff_options.max_word_diff_line_count >= base_line_count
1268    {
1269        let base_text: String = diff_base
1270            .chunks_in_range(diff_base_byte_range.clone())
1271            .collect();
1272
1273        let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
1274
1275        let (base_word_diffs, buffer_word_diffs_relative) = word_diff_ranges(
1276            &base_text,
1277            &buffer_text,
1278            DiffOptions {
1279                language_scope: diff_options.language_scope.clone(),
1280                ..*diff_options
1281            },
1282        );
1283
1284        let buffer_start_offset = buffer_range.start.to_offset(buffer);
1285        let buffer_word_diffs = buffer_word_diffs_relative
1286            .into_iter()
1287            .map(|range| {
1288                let start = buffer.anchor_after(buffer_start_offset + range.start);
1289                let end = buffer.anchor_after(buffer_start_offset + range.end);
1290                start..end
1291            })
1292            .collect();
1293
1294        (base_word_diffs, buffer_word_diffs)
1295    } else {
1296        (Vec::default(), Vec::default())
1297    };
1298
1299    InternalDiffHunk {
1300        buffer_range,
1301        diff_base_byte_range,
1302        base_word_diffs,
1303        buffer_word_diffs,
1304    }
1305}
1306
1307impl std::fmt::Debug for BufferDiff {
1308    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1309        f.debug_struct("BufferChangeSet")
1310            .field("buffer_id", &self.buffer_id)
1311            .finish()
1312    }
1313}
1314
1315#[derive(Clone, Debug, Default)]
1316pub struct DiffChanged {
1317    pub changed_range: Option<Range<text::Anchor>>,
1318    pub base_text_changed_range: Option<Range<usize>>,
1319    pub extended_range: Option<Range<text::Anchor>>,
1320}
1321
1322#[derive(Clone, Debug)]
1323pub enum BufferDiffEvent {
1324    DiffChanged(DiffChanged),
1325    LanguageChanged,
1326    HunksStagedOrUnstaged(Option<Rope>),
1327}
1328
1329impl EventEmitter<BufferDiffEvent> for BufferDiff {}
1330
1331impl BufferDiff {
1332    pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1333        let base_text = cx.new(|cx| {
1334            let mut buffer = language::Buffer::local("", cx);
1335            buffer.set_capability(Capability::ReadOnly, cx);
1336            buffer
1337        });
1338
1339        BufferDiff {
1340            buffer_id: buffer.remote_id(),
1341            inner: BufferDiffInner {
1342                base_text,
1343                hunks: SumTree::new(buffer),
1344                pending_hunks: SumTree::new(buffer),
1345                base_text_exists: false,
1346                buffer_snapshot: buffer.clone(),
1347            },
1348            secondary_diff: None,
1349        }
1350    }
1351
1352    pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1353        let base_text = buffer.text();
1354        let base_text = cx.new(|cx| {
1355            let mut buffer = language::Buffer::local(base_text, cx);
1356            buffer.set_capability(Capability::ReadOnly, cx);
1357            buffer
1358        });
1359
1360        BufferDiff {
1361            buffer_id: buffer.remote_id(),
1362            inner: BufferDiffInner {
1363                base_text,
1364                hunks: SumTree::new(buffer),
1365                pending_hunks: SumTree::new(buffer),
1366                base_text_exists: true,
1367                buffer_snapshot: buffer.clone(),
1368            },
1369            secondary_diff: None,
1370        }
1371    }
1372
1373    #[cfg(any(test, feature = "test-support"))]
1374    pub fn new_with_base_text(
1375        base_text: &str,
1376        buffer: &text::BufferSnapshot,
1377        cx: &mut Context<Self>,
1378    ) -> Self {
1379        let mut this = BufferDiff::new(&buffer, cx);
1380        let mut base_text = base_text.to_owned();
1381        text::LineEnding::normalize(&mut base_text);
1382        let inner = cx.foreground_executor().block_on(this.update_diff(
1383            buffer.clone(),
1384            Some(Arc::from(base_text)),
1385            Some(false),
1386            None,
1387            cx,
1388        ));
1389        this.set_snapshot(inner, &buffer, cx).detach();
1390        this
1391    }
1392
1393    pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1394        self.secondary_diff = Some(diff);
1395    }
1396
1397    pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1398        self.secondary_diff.clone()
1399    }
1400
1401    pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1402        if self.secondary_diff.is_some() {
1403            self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1404                buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1405                diff_base_byte_range: 0..0,
1406            });
1407            let changed_range = Some(Anchor::min_max_range_for_buffer(self.buffer_id));
1408            let base_text_range = Some(0..self.base_text(cx).len());
1409            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1410                changed_range: changed_range.clone(),
1411                base_text_changed_range: base_text_range,
1412                extended_range: changed_range,
1413            }));
1414        }
1415    }
1416
1417    pub fn stage_or_unstage_hunks(
1418        &mut self,
1419        stage: bool,
1420        hunks: &[DiffHunk],
1421        buffer: &text::BufferSnapshot,
1422        file_exists: bool,
1423        cx: &mut Context<Self>,
1424    ) -> Option<Rope> {
1425        let new_index_text = self
1426            .secondary_diff
1427            .as_ref()?
1428            .update(cx, |secondary_diff, cx| {
1429                self.inner.stage_or_unstage_hunks_impl(
1430                    &secondary_diff.inner,
1431                    stage,
1432                    hunks,
1433                    buffer,
1434                    file_exists,
1435                    cx,
1436                )
1437            });
1438
1439        cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1440            new_index_text.clone(),
1441        ));
1442        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1443            let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1444            let base_text_changed_range =
1445                Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1446            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1447                changed_range: changed_range.clone(),
1448                base_text_changed_range,
1449                extended_range: changed_range,
1450            }));
1451        }
1452        new_index_text
1453    }
1454
1455    pub fn stage_or_unstage_all_hunks(
1456        &mut self,
1457        stage: bool,
1458        buffer: &text::BufferSnapshot,
1459        file_exists: bool,
1460        cx: &mut Context<Self>,
1461    ) {
1462        let hunks = self
1463            .snapshot(cx)
1464            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1465            .collect::<Vec<_>>();
1466        let Some(secondary) = self.secondary_diff.clone() else {
1467            return;
1468        };
1469        let secondary = secondary.read(cx).inner.clone();
1470        self.inner
1471            .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1472        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1473            let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1474            let base_text_changed_range =
1475                Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1476            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1477                changed_range: changed_range.clone(),
1478                base_text_changed_range,
1479                extended_range: changed_range,
1480            }));
1481        }
1482    }
1483
1484    pub fn update_diff(
1485        &self,
1486        buffer: text::BufferSnapshot,
1487        base_text: Option<Arc<str>>,
1488        base_text_change: Option<bool>,
1489        language: Option<Arc<Language>>,
1490        cx: &App,
1491    ) -> Task<BufferDiffUpdate> {
1492        let prev_base_text = self.base_text(cx).as_rope().clone();
1493        let base_text_changed = base_text_change.is_some();
1494        let compute_base_text_edits = base_text_change == Some(true);
1495        let diff_options = build_diff_options(
1496            language.as_ref().map(|l| l.name()),
1497            language.as_ref().map(|l| l.default_scope()),
1498            cx,
1499        );
1500        let buffer_snapshot = buffer.clone();
1501
1502        let base_text_diff_task = if base_text_changed && compute_base_text_edits {
1503            base_text
1504                .as_ref()
1505                .map(|new_text| self.inner.base_text.read(cx).diff(new_text.clone(), cx))
1506        } else {
1507            None
1508        };
1509
1510        let hunk_task = cx.background_executor().spawn({
1511            let buffer_snapshot = buffer_snapshot.clone();
1512            async move {
1513                let base_text_rope = if let Some(base_text) = &base_text {
1514                    if base_text_changed {
1515                        Rope::from(base_text.as_ref())
1516                    } else {
1517                        prev_base_text
1518                    }
1519                } else {
1520                    Rope::new()
1521                };
1522                let base_text_exists = base_text.is_some();
1523                let hunks = compute_hunks(
1524                    base_text
1525                        .clone()
1526                        .map(|base_text| (base_text, base_text_rope.clone())),
1527                    buffer.clone(),
1528                    diff_options,
1529                );
1530                let base_text = base_text.unwrap_or_default();
1531                BufferDiffInner {
1532                    base_text,
1533                    hunks,
1534                    base_text_exists,
1535                    pending_hunks: SumTree::new(&buffer),
1536                    buffer_snapshot,
1537                }
1538            }
1539        });
1540
1541        cx.background_executor().spawn(async move {
1542            let (inner, base_text_edits) = match base_text_diff_task {
1543                Some(diff_task) => {
1544                    let (inner, diff) = futures::join!(hunk_task, diff_task);
1545                    (inner, Some(diff))
1546                }
1547                None => (hunk_task.await, None),
1548            };
1549
1550            BufferDiffUpdate {
1551                inner,
1552                buffer_snapshot,
1553                base_text_edits,
1554                base_text_changed,
1555            }
1556        })
1557    }
1558
1559    #[ztracing::instrument(skip_all)]
1560    pub fn language_changed(
1561        &mut self,
1562        language: Option<Arc<Language>>,
1563        language_registry: Option<Arc<LanguageRegistry>>,
1564        cx: &mut Context<Self>,
1565    ) {
1566        let fut = self.inner.base_text.update(cx, |base_text, cx| {
1567            if let Some(language_registry) = language_registry {
1568                base_text.set_language_registry(language_registry);
1569            }
1570            base_text.set_language(language, cx);
1571            base_text.parsing_idle()
1572        });
1573        cx.spawn(async move |this, cx| {
1574            fut.await;
1575            this.update(cx, |_, cx| {
1576                cx.emit(BufferDiffEvent::LanguageChanged);
1577            })
1578            .ok();
1579        })
1580        .detach();
1581    }
1582
1583    fn set_snapshot_with_secondary_inner(
1584        &mut self,
1585        update: BufferDiffUpdate,
1586        buffer: &text::BufferSnapshot,
1587        secondary_diff_change: Option<Range<Anchor>>,
1588        clear_pending_hunks: bool,
1589        cx: &mut Context<Self>,
1590    ) -> impl Future<Output = DiffChanged> + use<> {
1591        log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1592
1593        let old_snapshot = self.snapshot(cx);
1594        let state = &mut self.inner;
1595        let new_state = update.inner;
1596        let base_text_changed = update.base_text_changed;
1597
1598        let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1599        let DiffChanged {
1600            mut changed_range,
1601            mut base_text_changed_range,
1602            mut extended_range,
1603        } = match (state.base_text_exists, new_state.base_text_exists) {
1604            (false, false) => DiffChanged::default(),
1605            (true, true) if !base_text_changed => compare_hunks(
1606                &new_state.hunks,
1607                &old_snapshot.inner.hunks,
1608                old_buffer_snapshot,
1609                buffer,
1610            ),
1611            _ => {
1612                let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
1613                let full_base_range = 0..new_state.base_text.len();
1614                DiffChanged {
1615                    changed_range: Some(full_range.clone()),
1616                    base_text_changed_range: Some(full_base_range),
1617                    extended_range: Some(full_range),
1618                }
1619            }
1620        };
1621
1622        if let Some(secondary_changed_range) = secondary_diff_change
1623            && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1624                old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1625        {
1626            if let Some(range) = &mut changed_range {
1627                range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1628                range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1629            } else {
1630                changed_range = Some(secondary_hunk_range.clone());
1631            }
1632
1633            if let Some(base_text_range) = base_text_changed_range.as_mut() {
1634                base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1635                base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1636            } else {
1637                base_text_changed_range = Some(secondary_base_range);
1638            }
1639
1640            if let Some(ext) = &mut extended_range {
1641                ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1642                ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1643            } else {
1644                extended_range = Some(secondary_hunk_range);
1645            }
1646        }
1647
1648        let state = &mut self.inner;
1649        state.base_text_exists = new_state.base_text_exists;
1650        let parsing_idle = if let Some(diff) = update.base_text_edits {
1651            state.base_text.update(cx, |base_text, cx| {
1652                base_text.set_capability(Capability::ReadWrite, cx);
1653                base_text.apply_diff(diff, cx);
1654                base_text.set_capability(Capability::ReadOnly, cx);
1655                Some(base_text.parsing_idle())
1656            })
1657        } else if update.base_text_changed {
1658            state.base_text.update(cx, |base_text, cx| {
1659                base_text.set_capability(Capability::ReadWrite, cx);
1660                base_text.set_text(new_state.base_text.clone(), cx);
1661                base_text.set_capability(Capability::ReadOnly, cx);
1662                Some(base_text.parsing_idle())
1663            })
1664        } else {
1665            None
1666        };
1667        state.hunks = new_state.hunks;
1668        state.buffer_snapshot = update.buffer_snapshot;
1669        if base_text_changed || clear_pending_hunks {
1670            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1671            {
1672                let pending_range = first.buffer_range.start..last.buffer_range.end;
1673                if let Some(range) = &mut changed_range {
1674                    range.start = *range.start.min(&pending_range.start, buffer);
1675                    range.end = *range.end.max(&pending_range.end, buffer);
1676                } else {
1677                    changed_range = Some(pending_range.clone());
1678                }
1679
1680                if let Some(base_text_range) = base_text_changed_range.as_mut() {
1681                    base_text_range.start =
1682                        base_text_range.start.min(first.diff_base_byte_range.start);
1683                    base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1684                } else {
1685                    base_text_changed_range =
1686                        Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1687                }
1688
1689                if let Some(ext) = &mut extended_range {
1690                    ext.start = *ext.start.min(&pending_range.start, buffer);
1691                    ext.end = *ext.end.max(&pending_range.end, buffer);
1692                } else {
1693                    extended_range = Some(pending_range);
1694                }
1695            }
1696            state.pending_hunks = SumTree::new(buffer);
1697        }
1698
1699        async move {
1700            if let Some(parsing_idle) = parsing_idle {
1701                parsing_idle.await;
1702            }
1703            DiffChanged {
1704                changed_range,
1705                base_text_changed_range,
1706                extended_range,
1707            }
1708        }
1709    }
1710
1711    pub fn set_snapshot(
1712        &mut self,
1713        new_state: BufferDiffUpdate,
1714        buffer: &text::BufferSnapshot,
1715        cx: &mut Context<Self>,
1716    ) -> Task<Option<Range<Anchor>>> {
1717        self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1718    }
1719
1720    pub fn set_snapshot_with_secondary(
1721        &mut self,
1722        update: BufferDiffUpdate,
1723        buffer: &text::BufferSnapshot,
1724        secondary_diff_change: Option<Range<Anchor>>,
1725        clear_pending_hunks: bool,
1726        cx: &mut Context<Self>,
1727    ) -> Task<Option<Range<Anchor>>> {
1728        let fut = self.set_snapshot_with_secondary_inner(
1729            update,
1730            buffer,
1731            secondary_diff_change,
1732            clear_pending_hunks,
1733            cx,
1734        );
1735
1736        cx.spawn(async move |this, cx| {
1737            let change = fut.await;
1738            this.update(cx, |_, cx| {
1739                cx.emit(BufferDiffEvent::DiffChanged(change.clone()));
1740            })
1741            .ok();
1742            change.changed_range
1743        })
1744    }
1745
1746    pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1747        self.inner.base_text.read(cx).snapshot()
1748    }
1749
1750    pub fn base_text_exists(&self) -> bool {
1751        self.inner.base_text_exists
1752    }
1753
1754    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1755        BufferDiffSnapshot {
1756            inner: BufferDiffInner {
1757                hunks: self.inner.hunks.clone(),
1758                pending_hunks: self.inner.pending_hunks.clone(),
1759                base_text: self.inner.base_text.read(cx).snapshot(),
1760                base_text_exists: self.inner.base_text_exists,
1761                buffer_snapshot: self.inner.buffer_snapshot.clone(),
1762            },
1763            secondary_diff: self
1764                .secondary_diff
1765                .as_ref()
1766                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1767        }
1768    }
1769
1770    /// Used in cases where the change set isn't derived from git.
1771    pub fn set_base_text(
1772        &mut self,
1773        base_text: Option<Arc<str>>,
1774        language: Option<Arc<Language>>,
1775        buffer: text::BufferSnapshot,
1776        cx: &mut Context<Self>,
1777    ) -> oneshot::Receiver<()> {
1778        let (tx, rx) = oneshot::channel();
1779        let complete_on_drop = util::defer(|| {
1780            tx.send(()).ok();
1781        });
1782        cx.spawn(async move |this, cx| {
1783            let Some(state) = this
1784                .update(cx, |this, cx| {
1785                    this.update_diff(buffer.clone(), base_text, Some(false), language, cx)
1786                })
1787                .log_err()
1788            else {
1789                return;
1790            };
1791            let state = state.await;
1792            if let Some(task) = this
1793                .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
1794                .log_err()
1795            {
1796                task.await;
1797            }
1798            drop(complete_on_drop)
1799        })
1800        .detach();
1801        rx
1802    }
1803
1804    pub fn base_text_string(&self, cx: &App) -> Option<String> {
1805        self.inner
1806            .base_text_exists
1807            .then(|| self.inner.base_text.read(cx).text())
1808    }
1809
1810    #[cfg(any(test, feature = "test-support"))]
1811    pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1812        let language = self.base_text(cx).language().cloned();
1813        let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1814        let fut = self.update_diff(buffer.clone(), base_text, None, language, cx);
1815        let fg_executor = cx.foreground_executor().clone();
1816        let snapshot = fg_executor.block_on(fut);
1817        let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
1818        let change = fg_executor.block_on(fut);
1819        cx.emit(BufferDiffEvent::DiffChanged(change));
1820    }
1821
1822    pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1823        self.inner.base_text.clone()
1824    }
1825}
1826
1827impl DiffHunk {
1828    pub fn is_created_file(&self) -> bool {
1829        self.diff_base_byte_range == (0..0)
1830            && self.buffer_range.start.is_min()
1831            && self.buffer_range.end.is_max()
1832    }
1833
1834    pub fn status(&self) -> DiffHunkStatus {
1835        let kind = if self.buffer_range.start == self.buffer_range.end {
1836            DiffHunkStatusKind::Deleted
1837        } else if self.diff_base_byte_range.is_empty() {
1838            DiffHunkStatusKind::Added
1839        } else {
1840            DiffHunkStatusKind::Modified
1841        };
1842        DiffHunkStatus {
1843            kind,
1844            secondary: self.secondary_status,
1845        }
1846    }
1847}
1848
1849impl DiffHunkStatus {
1850    pub fn has_secondary_hunk(&self) -> bool {
1851        matches!(
1852            self.secondary,
1853            DiffHunkSecondaryStatus::HasSecondaryHunk
1854                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1855                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1856        )
1857    }
1858
1859    pub fn is_pending(&self) -> bool {
1860        matches!(
1861            self.secondary,
1862            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1863                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1864        )
1865    }
1866
1867    pub fn is_deleted(&self) -> bool {
1868        self.kind == DiffHunkStatusKind::Deleted
1869    }
1870
1871    pub fn is_added(&self) -> bool {
1872        self.kind == DiffHunkStatusKind::Added
1873    }
1874
1875    pub fn is_modified(&self) -> bool {
1876        self.kind == DiffHunkStatusKind::Modified
1877    }
1878
1879    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1880        Self {
1881            kind: DiffHunkStatusKind::Added,
1882            secondary,
1883        }
1884    }
1885
1886    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1887        Self {
1888            kind: DiffHunkStatusKind::Modified,
1889            secondary,
1890        }
1891    }
1892
1893    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1894        Self {
1895            kind: DiffHunkStatusKind::Deleted,
1896            secondary,
1897        }
1898    }
1899
1900    pub fn deleted_none() -> Self {
1901        Self {
1902            kind: DiffHunkStatusKind::Deleted,
1903            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1904        }
1905    }
1906
1907    pub fn added_none() -> Self {
1908        Self {
1909            kind: DiffHunkStatusKind::Added,
1910            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1911        }
1912    }
1913
1914    pub fn modified_none() -> Self {
1915        Self {
1916            kind: DiffHunkStatusKind::Modified,
1917            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1918        }
1919    }
1920}
1921
1922#[cfg(any(test, feature = "test-support"))]
1923#[track_caller]
1924pub fn assert_hunks<ExpectedText, HunkIter>(
1925    diff_hunks: HunkIter,
1926    buffer: &text::BufferSnapshot,
1927    diff_base: &str,
1928    // Line range, deleted, added, status
1929    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1930) where
1931    HunkIter: Iterator<Item = DiffHunk>,
1932    ExpectedText: AsRef<str>,
1933{
1934    let actual_hunks = diff_hunks
1935        .map(|hunk| {
1936            (
1937                hunk.range.clone(),
1938                &diff_base[hunk.diff_base_byte_range.clone()],
1939                buffer
1940                    .text_for_range(hunk.range.clone())
1941                    .collect::<String>(),
1942                hunk.status(),
1943            )
1944        })
1945        .collect::<Vec<_>>();
1946
1947    let expected_hunks: Vec<_> = expected_hunks
1948        .iter()
1949        .map(|(line_range, deleted_text, added_text, status)| {
1950            (
1951                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1952                deleted_text.as_ref(),
1953                added_text.as_ref().to_string(),
1954                *status,
1955            )
1956        })
1957        .collect();
1958
1959    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1960}
1961
1962#[cfg(test)]
1963mod tests {
1964    use std::{fmt::Write as _, sync::mpsc};
1965
1966    use super::*;
1967    use gpui::TestAppContext;
1968    use pretty_assertions::{assert_eq, assert_ne};
1969    use rand::{Rng as _, rngs::StdRng};
1970    use text::{Buffer, BufferId, ReplicaId, Rope};
1971    use unindent::Unindent as _;
1972    use util::test::marked_text_ranges;
1973
1974    #[ctor::ctor]
1975    fn init_logger() {
1976        zlog::init_test();
1977    }
1978
1979    #[gpui::test]
1980    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1981        let diff_base = "
1982            one
1983            two
1984            three
1985        "
1986        .unindent();
1987
1988        let buffer_text = "
1989            one
1990            HELLO
1991            three
1992        "
1993        .unindent();
1994
1995        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1996        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1997        assert_hunks(
1998            diff.hunks_intersecting_range(
1999                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2000                &buffer,
2001            ),
2002            &buffer,
2003            &diff_base,
2004            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
2005        );
2006
2007        buffer.edit([(0..0, "point five\n")]);
2008        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
2009        assert_hunks(
2010            diff.hunks_intersecting_range(
2011                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2012                &buffer,
2013            ),
2014            &buffer,
2015            &diff_base,
2016            &[
2017                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
2018                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
2019            ],
2020        );
2021
2022        diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2023        assert_hunks::<&str, _>(
2024            diff.hunks_intersecting_range(
2025                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2026                &buffer,
2027            ),
2028            &buffer,
2029            &diff_base,
2030            &[],
2031        );
2032    }
2033
2034    #[gpui::test]
2035    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
2036        let head_text = "
2037            zero
2038            one
2039            two
2040            three
2041            four
2042            five
2043            six
2044            seven
2045            eight
2046            nine
2047        "
2048        .unindent();
2049
2050        let index_text = "
2051            zero
2052            one
2053            TWO
2054            three
2055            FOUR
2056            five
2057            six
2058            seven
2059            eight
2060            NINE
2061        "
2062        .unindent();
2063
2064        let buffer_text = "
2065            zero
2066            one
2067            TWO
2068            three
2069            FOUR
2070            FIVE
2071            six
2072            SEVEN
2073            eight
2074            nine
2075        "
2076        .unindent();
2077
2078        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2079        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
2080        let mut uncommitted_diff =
2081            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
2082        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
2083
2084        let expected_hunks = vec![
2085            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
2086            (
2087                4..6,
2088                "four\nfive\n",
2089                "FOUR\nFIVE\n",
2090                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2091            ),
2092            (
2093                7..8,
2094                "seven\n",
2095                "SEVEN\n",
2096                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2097            ),
2098        ];
2099
2100        assert_hunks(
2101            uncommitted_diff.hunks_intersecting_range(
2102                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2103                &buffer,
2104            ),
2105            &buffer,
2106            &head_text,
2107            &expected_hunks,
2108        );
2109    }
2110
2111    #[gpui::test]
2112    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
2113        let diff_base = "
2114            one
2115            two
2116            three
2117            four
2118            five
2119            six
2120            seven
2121            eight
2122            nine
2123            ten
2124        "
2125        .unindent();
2126
2127        let buffer_text = "
2128            A
2129            one
2130            B
2131            two
2132            C
2133            three
2134            HELLO
2135            four
2136            five
2137            SIXTEEN
2138            seven
2139            eight
2140            WORLD
2141            nine
2142
2143            ten
2144
2145        "
2146        .unindent();
2147
2148        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2149        let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
2150        assert_eq!(
2151            diff.hunks_intersecting_range(
2152                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2153                &buffer
2154            )
2155            .count(),
2156            8
2157        );
2158
2159        assert_hunks(
2160            diff.hunks_intersecting_range(
2161                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
2162                &buffer,
2163            ),
2164            &buffer,
2165            &diff_base,
2166            &[
2167                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
2168                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
2169                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
2170            ],
2171        );
2172    }
2173
2174    #[gpui::test]
2175    async fn test_stage_hunk(cx: &mut TestAppContext) {
2176        struct Example {
2177            name: &'static str,
2178            head_text: String,
2179            index_text: String,
2180            buffer_marked_text: String,
2181            final_index_text: String,
2182        }
2183
2184        let table = [
2185            Example {
2186                name: "uncommitted hunk straddles end of unstaged hunk",
2187                head_text: "
2188                    one
2189                    two
2190                    three
2191                    four
2192                    five
2193                "
2194                .unindent(),
2195                index_text: "
2196                    one
2197                    TWO_HUNDRED
2198                    three
2199                    FOUR_HUNDRED
2200                    five
2201                "
2202                .unindent(),
2203                buffer_marked_text: "
2204                    ZERO
2205                    one
2206                    two
2207                    «THREE_HUNDRED
2208                    FOUR_HUNDRED»
2209                    five
2210                    SIX
2211                "
2212                .unindent(),
2213                final_index_text: "
2214                    one
2215                    two
2216                    THREE_HUNDRED
2217                    FOUR_HUNDRED
2218                    five
2219                "
2220                .unindent(),
2221            },
2222            Example {
2223                name: "uncommitted hunk straddles start of unstaged hunk",
2224                head_text: "
2225                    one
2226                    two
2227                    three
2228                    four
2229                    five
2230                "
2231                .unindent(),
2232                index_text: "
2233                    one
2234                    TWO_HUNDRED
2235                    three
2236                    FOUR_HUNDRED
2237                    five
2238                "
2239                .unindent(),
2240                buffer_marked_text: "
2241                    ZERO
2242                    one
2243                    «TWO_HUNDRED
2244                    THREE_HUNDRED»
2245                    four
2246                    five
2247                    SIX
2248                "
2249                .unindent(),
2250                final_index_text: "
2251                    one
2252                    TWO_HUNDRED
2253                    THREE_HUNDRED
2254                    four
2255                    five
2256                "
2257                .unindent(),
2258            },
2259            Example {
2260                name: "uncommitted hunk strictly contains unstaged hunks",
2261                head_text: "
2262                    one
2263                    two
2264                    three
2265                    four
2266                    five
2267                    six
2268                    seven
2269                "
2270                .unindent(),
2271                index_text: "
2272                    one
2273                    TWO
2274                    THREE
2275                    FOUR
2276                    FIVE
2277                    SIX
2278                    seven
2279                "
2280                .unindent(),
2281                buffer_marked_text: "
2282                    one
2283                    TWO
2284                    «THREE_HUNDRED
2285                    FOUR
2286                    FIVE_HUNDRED»
2287                    SIX
2288                    seven
2289                "
2290                .unindent(),
2291                final_index_text: "
2292                    one
2293                    TWO
2294                    THREE_HUNDRED
2295                    FOUR
2296                    FIVE_HUNDRED
2297                    SIX
2298                    seven
2299                "
2300                .unindent(),
2301            },
2302            Example {
2303                name: "uncommitted deletion hunk",
2304                head_text: "
2305                    one
2306                    two
2307                    three
2308                    four
2309                    five
2310                "
2311                .unindent(),
2312                index_text: "
2313                    one
2314                    two
2315                    three
2316                    four
2317                    five
2318                "
2319                .unindent(),
2320                buffer_marked_text: "
2321                    one
2322                    ˇfive
2323                "
2324                .unindent(),
2325                final_index_text: "
2326                    one
2327                    five
2328                "
2329                .unindent(),
2330            },
2331            Example {
2332                name: "one unstaged hunk that contains two uncommitted hunks",
2333                head_text: "
2334                    one
2335                    two
2336
2337                    three
2338                    four
2339                "
2340                .unindent(),
2341                index_text: "
2342                    one
2343                    two
2344                    three
2345                    four
2346                "
2347                .unindent(),
2348                buffer_marked_text: "
2349                    «one
2350
2351                    three // modified
2352                    four»
2353                "
2354                .unindent(),
2355                final_index_text: "
2356                    one
2357
2358                    three // modified
2359                    four
2360                "
2361                .unindent(),
2362            },
2363            Example {
2364                name: "one uncommitted hunk that contains two unstaged hunks",
2365                head_text: "
2366                    one
2367                    two
2368                    three
2369                    four
2370                    five
2371                "
2372                .unindent(),
2373                index_text: "
2374                    ZERO
2375                    one
2376                    TWO
2377                    THREE
2378                    FOUR
2379                    five
2380                "
2381                .unindent(),
2382                buffer_marked_text: "
2383                    «one
2384                    TWO_HUNDRED
2385                    THREE
2386                    FOUR_HUNDRED
2387                    five»
2388                "
2389                .unindent(),
2390                final_index_text: "
2391                    ZERO
2392                    one
2393                    TWO_HUNDRED
2394                    THREE
2395                    FOUR_HUNDRED
2396                    five
2397                "
2398                .unindent(),
2399            },
2400        ];
2401
2402        for example in table {
2403            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2404            let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2405            let hunk_range =
2406                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2407
2408            let unstaged_diff =
2409                cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2410
2411            let uncommitted_diff = cx.new(|cx| {
2412                let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2413                diff.set_secondary_diff(unstaged_diff);
2414                diff
2415            });
2416
2417            uncommitted_diff.update(cx, |diff, cx| {
2418                let hunks = diff
2419                    .snapshot(cx)
2420                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2421                    .collect::<Vec<_>>();
2422                for hunk in &hunks {
2423                    assert_ne!(
2424                        hunk.secondary_status,
2425                        DiffHunkSecondaryStatus::NoSecondaryHunk
2426                    )
2427                }
2428
2429                let new_index_text = diff
2430                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2431                    .unwrap()
2432                    .to_string();
2433
2434                let hunks = diff
2435                    .snapshot(cx)
2436                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2437                    .collect::<Vec<_>>();
2438                for hunk in &hunks {
2439                    assert_eq!(
2440                        hunk.secondary_status,
2441                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2442                    )
2443                }
2444
2445                pretty_assertions::assert_eq!(
2446                    new_index_text,
2447                    example.final_index_text,
2448                    "example: {}",
2449                    example.name
2450                );
2451            });
2452        }
2453    }
2454
2455    #[gpui::test]
2456    async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2457        // This test reproduces a crash where staging all hunks would cause an underflow
2458        // when there's one large unstaged hunk containing multiple uncommitted hunks.
2459        let head_text = "
2460            aaa
2461            bbb
2462            ccc
2463            ddd
2464            eee
2465            fff
2466            ggg
2467            hhh
2468            iii
2469            jjj
2470            kkk
2471            lll
2472        "
2473        .unindent();
2474
2475        let index_text = "
2476            aaa
2477            bbb
2478            CCC-index
2479            DDD-index
2480            EEE-index
2481            FFF-index
2482            GGG-index
2483            HHH-index
2484            III-index
2485            JJJ-index
2486            kkk
2487            lll
2488        "
2489        .unindent();
2490
2491        let buffer_text = "
2492            aaa
2493            bbb
2494            ccc-modified
2495            ddd
2496            eee-modified
2497            fff
2498            ggg
2499            hhh-modified
2500            iii
2501            jjj
2502            kkk
2503            lll
2504        "
2505        .unindent();
2506
2507        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2508
2509        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2510        let uncommitted_diff = cx.new(|cx| {
2511            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2512            diff.set_secondary_diff(unstaged_diff);
2513            diff
2514        });
2515
2516        uncommitted_diff.update(cx, |diff, cx| {
2517            diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2518        });
2519    }
2520
2521    #[gpui::test]
2522    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2523        let head_text = "
2524            one
2525            two
2526            three
2527        "
2528        .unindent();
2529        let index_text = head_text.clone();
2530        let buffer_text = "
2531            one
2532            three
2533        "
2534        .unindent();
2535
2536        let buffer = Buffer::new(
2537            ReplicaId::LOCAL,
2538            BufferId::new(1).unwrap(),
2539            buffer_text.clone(),
2540        );
2541        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2542        let uncommitted_diff = cx.new(|cx| {
2543            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2544            diff.set_secondary_diff(unstaged_diff.clone());
2545            diff
2546        });
2547
2548        uncommitted_diff.update(cx, |diff, cx| {
2549            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2550
2551            let new_index_text = diff
2552                .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2553                .unwrap()
2554                .to_string();
2555            assert_eq!(new_index_text, buffer_text);
2556
2557            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2558            assert_eq!(
2559                hunk.secondary_status,
2560                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2561            );
2562
2563            let index_text = diff
2564                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2565                .unwrap()
2566                .to_string();
2567            assert_eq!(index_text, head_text);
2568
2569            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2570            // optimistically unstaged (fine, could also be HasSecondaryHunk)
2571            assert_eq!(
2572                hunk.secondary_status,
2573                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2574            );
2575        });
2576    }
2577
2578    #[gpui::test]
2579    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2580        let base_text = "
2581            zero
2582            one
2583            two
2584            three
2585            four
2586            five
2587            six
2588            seven
2589            eight
2590            nine
2591        "
2592        .unindent();
2593
2594        let buffer_text_1 = "
2595            one
2596            three
2597            four
2598            five
2599            SIX
2600            seven
2601            eight
2602            NINE
2603        "
2604        .unindent();
2605
2606        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2607
2608        let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2609        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2610        let DiffChanged {
2611            changed_range,
2612            base_text_changed_range,
2613            extended_range: _,
2614        } = compare_hunks(
2615            &diff_1.inner.hunks,
2616            &empty_diff.inner.hunks,
2617            &buffer,
2618            &buffer,
2619        );
2620        let range = changed_range.unwrap();
2621        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2622        let base_text_range = base_text_changed_range.unwrap();
2623        assert_eq!(
2624            base_text_range.to_point(diff_1.base_text()),
2625            Point::new(0, 0)..Point::new(10, 0)
2626        );
2627
2628        // Edit does affects the diff because it recalculates word diffs.
2629        buffer.edit_via_marked_text(
2630            &"
2631                one
2632                three
2633                four
2634                five
2635                «SIX.5»
2636                seven
2637                eight
2638                NINE
2639            "
2640            .unindent(),
2641        );
2642        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2643        let DiffChanged {
2644            changed_range,
2645            base_text_changed_range,
2646            extended_range: _,
2647        } = compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer, &buffer);
2648        assert_eq!(
2649            changed_range.unwrap().to_point(&buffer),
2650            Point::new(4, 0)..Point::new(5, 0),
2651        );
2652        assert_eq!(
2653            base_text_changed_range
2654                .unwrap()
2655                .to_point(diff_2.base_text()),
2656            Point::new(6, 0)..Point::new(7, 0),
2657        );
2658
2659        // Edit turns a deletion hunk into a modification.
2660        buffer.edit_via_marked_text(
2661            &"
2662                one
2663                «THREE»
2664                four
2665                five
2666                SIX.5
2667                seven
2668                eight
2669                NINE
2670            "
2671            .unindent(),
2672        );
2673        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2674        let DiffChanged {
2675            changed_range,
2676            base_text_changed_range,
2677            extended_range: _,
2678        } = compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer, &buffer);
2679        let range = changed_range.unwrap();
2680        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2681        let base_text_range = base_text_changed_range.unwrap();
2682        assert_eq!(
2683            base_text_range.to_point(diff_3.base_text()),
2684            Point::new(2, 0)..Point::new(4, 0)
2685        );
2686
2687        // Edit turns a modification hunk into a deletion.
2688        buffer.edit_via_marked_text(
2689            &"
2690                one
2691                THREE
2692                four
2693                five«»
2694                seven
2695                eight
2696                NINE
2697            "
2698            .unindent(),
2699        );
2700        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2701        let DiffChanged {
2702            changed_range,
2703            base_text_changed_range,
2704            extended_range: _,
2705        } = compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer, &buffer);
2706        let range = changed_range.unwrap();
2707        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2708        let base_text_range = base_text_changed_range.unwrap();
2709        assert_eq!(
2710            base_text_range.to_point(diff_4.base_text()),
2711            Point::new(6, 0)..Point::new(7, 0)
2712        );
2713
2714        // Edit introduces a new insertion hunk.
2715        buffer.edit_via_marked_text(
2716            &"
2717                one
2718                THREE
2719                four«
2720                FOUR.5
2721                »five
2722                seven
2723                eight
2724                NINE
2725            "
2726            .unindent(),
2727        );
2728        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2729        let DiffChanged {
2730            changed_range,
2731            base_text_changed_range,
2732            extended_range: _,
2733        } = compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer, &buffer);
2734        let range = changed_range.unwrap();
2735        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2736        let base_text_range = base_text_changed_range.unwrap();
2737        assert_eq!(
2738            base_text_range.to_point(diff_5.base_text()),
2739            Point::new(5, 0)..Point::new(5, 0)
2740        );
2741
2742        // Edit removes a hunk.
2743        buffer.edit_via_marked_text(
2744            &"
2745                one
2746                THREE
2747                four
2748                FOUR.5
2749                five
2750                seven
2751                eight
2752                «nine»
2753            "
2754            .unindent(),
2755        );
2756        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2757        let DiffChanged {
2758            changed_range,
2759            base_text_changed_range,
2760            extended_range: _,
2761        } = compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer, &buffer);
2762        let range = changed_range.unwrap();
2763        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2764        let base_text_range = base_text_changed_range.unwrap();
2765        assert_eq!(
2766            base_text_range.to_point(diff_6.base_text()),
2767            Point::new(9, 0)..Point::new(10, 0)
2768        );
2769    }
2770
2771    #[gpui::test(iterations = 100)]
2772    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2773        fn gen_line(rng: &mut StdRng) -> String {
2774            if rng.random_bool(0.2) {
2775                "\n".to_owned()
2776            } else {
2777                let c = rng.random_range('A'..='Z');
2778                format!("{c}{c}{c}\n")
2779            }
2780        }
2781
2782        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2783            let mut old_lines = {
2784                let mut old_lines = Vec::new();
2785                let old_lines_iter = head.lines();
2786                for line in old_lines_iter {
2787                    assert!(!line.ends_with("\n"));
2788                    old_lines.push(line.to_owned());
2789                }
2790                if old_lines.last().is_some_and(|line| line.is_empty()) {
2791                    old_lines.pop();
2792                }
2793                old_lines.into_iter()
2794            };
2795            let mut result = String::new();
2796            let unchanged_count = rng.random_range(0..=old_lines.len());
2797            result +=
2798                &old_lines
2799                    .by_ref()
2800                    .take(unchanged_count)
2801                    .fold(String::new(), |mut s, line| {
2802                        writeln!(&mut s, "{line}").unwrap();
2803                        s
2804                    });
2805            while old_lines.len() > 0 {
2806                let deleted_count = rng.random_range(0..=old_lines.len());
2807                let _advance = old_lines
2808                    .by_ref()
2809                    .take(deleted_count)
2810                    .map(|line| line.len() + 1)
2811                    .sum::<usize>();
2812                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2813                let added_count = rng.random_range(minimum_added..=5);
2814                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2815                result += &addition;
2816
2817                if old_lines.len() > 0 {
2818                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2819                    if blank_lines == old_lines.len() {
2820                        break;
2821                    };
2822                    let unchanged_count =
2823                        rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2824                    result += &old_lines.by_ref().take(unchanged_count).fold(
2825                        String::new(),
2826                        |mut s, line| {
2827                            writeln!(&mut s, "{line}").unwrap();
2828                            s
2829                        },
2830                    );
2831                }
2832            }
2833            result
2834        }
2835
2836        fn uncommitted_diff(
2837            working_copy: &language::BufferSnapshot,
2838            index_text: &Rope,
2839            head_text: String,
2840            cx: &mut TestAppContext,
2841        ) -> Entity<BufferDiff> {
2842            let secondary = cx.new(|cx| {
2843                BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2844            });
2845            cx.new(|cx| {
2846                let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2847                diff.secondary_diff = Some(secondary);
2848                diff
2849            })
2850        }
2851
2852        let operations = std::env::var("OPERATIONS")
2853            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2854            .unwrap_or(10);
2855
2856        let rng = &mut rng;
2857        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2858            writeln!(&mut s, "{c}{c}{c}").unwrap();
2859            s
2860        });
2861        let working_copy = gen_working_copy(rng, &head_text);
2862        let working_copy = cx.new(|cx| {
2863            language::Buffer::local_normalized(
2864                Rope::from(working_copy.as_str()),
2865                text::LineEnding::default(),
2866                cx,
2867            )
2868        });
2869        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2870        let mut index_text = if rng.random() {
2871            Rope::from(head_text.as_str())
2872        } else {
2873            working_copy.as_rope().clone()
2874        };
2875
2876        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2877        let mut hunks = diff.update(cx, |diff, cx| {
2878            diff.snapshot(cx)
2879                .hunks_intersecting_range(
2880                    Anchor::min_max_range_for_buffer(diff.buffer_id),
2881                    &working_copy,
2882                )
2883                .collect::<Vec<_>>()
2884        });
2885        if hunks.is_empty() {
2886            return;
2887        }
2888
2889        for _ in 0..operations {
2890            let i = rng.random_range(0..hunks.len());
2891            let hunk = &mut hunks[i];
2892            let hunk_to_change = hunk.clone();
2893            let stage = match hunk.secondary_status {
2894                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2895                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2896                    true
2897                }
2898                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2899                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2900                    false
2901                }
2902                _ => unreachable!(),
2903            };
2904
2905            index_text = diff.update(cx, |diff, cx| {
2906                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2907                    .unwrap()
2908            });
2909
2910            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2911            let found_hunks = diff.update(cx, |diff, cx| {
2912                diff.snapshot(cx)
2913                    .hunks_intersecting_range(
2914                        Anchor::min_max_range_for_buffer(diff.buffer_id),
2915                        &working_copy,
2916                    )
2917                    .collect::<Vec<_>>()
2918            });
2919            assert_eq!(hunks.len(), found_hunks.len());
2920
2921            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2922                assert_eq!(
2923                    expected_hunk.buffer_range.to_point(&working_copy),
2924                    found_hunk.buffer_range.to_point(&working_copy)
2925                );
2926                assert_eq!(
2927                    expected_hunk.diff_base_byte_range,
2928                    found_hunk.diff_base_byte_range
2929                );
2930                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2931            }
2932            hunks = found_hunks;
2933        }
2934    }
2935
2936    #[gpui::test]
2937    async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2938        let base_text = "
2939            one
2940            two
2941            three
2942            four
2943            five
2944            six
2945        "
2946        .unindent();
2947        let buffer_text = "
2948            one
2949            TWO
2950            three
2951            four
2952            FIVE
2953            six
2954        "
2955        .unindent();
2956        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
2957        let diff = cx.new(|cx| {
2958            BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
2959        });
2960        cx.run_until_parked();
2961        let (tx, rx) = mpsc::channel();
2962        let subscription =
2963            cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
2964
2965        let snapshot = buffer.update(cx, |buffer, cx| {
2966            buffer.set_text(
2967                "
2968                ONE
2969                TWO
2970                THREE
2971                FOUR
2972                FIVE
2973                SIX
2974            "
2975                .unindent(),
2976                cx,
2977            );
2978            buffer.text_snapshot()
2979        });
2980        let update = diff
2981            .update(cx, |diff, cx| {
2982                diff.update_diff(
2983                    snapshot.clone(),
2984                    Some(base_text.as_str().into()),
2985                    None,
2986                    None,
2987                    cx,
2988                )
2989            })
2990            .await;
2991        diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
2992            .await;
2993        cx.run_until_parked();
2994        drop(subscription);
2995        let events = rx.into_iter().collect::<Vec<_>>();
2996        match events.as_slice() {
2997            [
2998                BufferDiffEvent::DiffChanged(DiffChanged {
2999                    changed_range: _,
3000                    base_text_changed_range,
3001                    extended_range: _,
3002                }),
3003            ] => {
3004                // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3005                // assert_eq!(
3006                //     *changed_range,
3007                //     Some(Anchor::min_max_range_for_buffer(
3008                //         buffer.read_with(cx, |buffer, _| buffer.remote_id())
3009                //     ))
3010                // );
3011                assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3012            }
3013            _ => panic!("unexpected events: {:?}", events),
3014        }
3015    }
3016
3017    #[gpui::test]
3018    async fn test_extended_range(cx: &mut TestAppContext) {
3019        let base_text = "
3020            aaa
3021            bbb
3022
3023
3024
3025
3026
3027            ccc
3028            ddd
3029        "
3030        .unindent();
3031
3032        let buffer_text = "
3033            aaa
3034            bbb
3035
3036
3037
3038
3039
3040            CCC
3041            ddd
3042        "
3043        .unindent();
3044
3045        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3046        let old_buffer = buffer.snapshot();
3047        let diff_a = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
3048
3049        buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3050        let diff_b = BufferDiffSnapshot::new_sync(buffer.clone(), base_text, cx);
3051
3052        let DiffChanged {
3053            changed_range,
3054            base_text_changed_range: _,
3055            extended_range,
3056        } = compare_hunks(
3057            &diff_b.inner.hunks,
3058            &diff_a.inner.hunks,
3059            &old_buffer,
3060            &buffer,
3061        );
3062
3063        let changed_range = changed_range.unwrap();
3064        assert_eq!(
3065            changed_range.to_point(&buffer),
3066            Point::new(7, 0)..Point::new(9, 0),
3067            "changed_range should span from old hunk position to new hunk end"
3068        );
3069
3070        let extended_range = extended_range.unwrap();
3071        assert_eq!(
3072            extended_range.start.to_point(&buffer),
3073            Point::new(1, 3),
3074            "extended_range.start should extend to include the edit outside changed_range"
3075        );
3076        assert_eq!(
3077            extended_range.end.to_point(&buffer),
3078            Point::new(9, 0),
3079            "extended_range.end should collapse to changed_range.end when no edits in end margin"
3080        );
3081
3082        let base_text_2 = "
3083            one
3084            two
3085            three
3086            four
3087            five
3088            six
3089            seven
3090            eight
3091        "
3092        .unindent();
3093
3094        let buffer_text_2 = "
3095            ONE
3096            two
3097            THREE
3098            four
3099            FIVE
3100            six
3101            SEVEN
3102            eight
3103        "
3104        .unindent();
3105
3106        let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3107        let old_buffer_2 = buffer_2.snapshot();
3108        let diff_2a = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2.clone(), cx);
3109
3110        buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3111        let diff_2b = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2, cx);
3112
3113        let DiffChanged {
3114            changed_range,
3115            base_text_changed_range: _,
3116            extended_range,
3117        } = compare_hunks(
3118            &diff_2b.inner.hunks,
3119            &diff_2a.inner.hunks,
3120            &old_buffer_2,
3121            &buffer_2,
3122        );
3123
3124        let changed_range = changed_range.unwrap();
3125        assert_eq!(
3126            changed_range.to_point(&buffer_2),
3127            Point::new(4, 0)..Point::new(5, 0),
3128            "changed_range should be just the hunk that changed (FIVE)"
3129        );
3130
3131        let extended_range = extended_range.unwrap();
3132        assert_eq!(
3133            extended_range.to_point(&buffer_2),
3134            Point::new(4, 0)..Point::new(5, 0),
3135            "extended_range should equal changed_range when edit is within the hunk"
3136        );
3137    }
3138
3139    fn assert_rows_to_base_text_rows_visual(
3140        buffer: &Entity<language::Buffer>,
3141        diff: &Entity<BufferDiff>,
3142        source_text: &str,
3143        annotated_target: &str,
3144        cx: &mut gpui::TestAppContext,
3145    ) {
3146        let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3147
3148        let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3149        let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3150
3151        assert_eq!(
3152            buffer.text(),
3153            source_text,
3154            "buffer text does not match source text"
3155        );
3156
3157        assert_eq!(
3158            diff.base_text_string().unwrap_or_default(),
3159            target_text,
3160            "base text does not match stripped annotated target"
3161        );
3162
3163        let num_rows = source_text.lines().count() as u32;
3164        let max_point = buffer.max_point();
3165        let points = (0..=num_rows).map(move |row| {
3166            if row == num_rows && max_point.column > 0 {
3167                max_point
3168            } else {
3169                Point::new(row, 0)
3170            }
3171        });
3172        let actual_ranges: Vec<_> = diff.points_to_base_text_points(points, &buffer).0.collect();
3173
3174        assert_eq!(
3175            actual_ranges, expected_ranges,
3176            "\nsource (buffer):\n{}\ntarget (base):\n{}\nexpected: {:?}\nactual: {:?}",
3177            source_text, target_text, expected_ranges, actual_ranges
3178        );
3179    }
3180
3181    fn assert_base_text_rows_to_rows_visual(
3182        buffer: &Entity<language::Buffer>,
3183        diff: &Entity<BufferDiff>,
3184        source_text: &str,
3185        annotated_target: &str,
3186        cx: &mut gpui::TestAppContext,
3187    ) {
3188        let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3189
3190        let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3191        let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3192
3193        assert_eq!(
3194            diff.base_text_string().unwrap_or_default(),
3195            source_text,
3196            "base text does not match source text"
3197        );
3198
3199        assert_eq!(
3200            buffer.text(),
3201            target_text,
3202            "buffer text does not match stripped annotated target"
3203        );
3204
3205        let num_rows = source_text.lines().count() as u32;
3206        let base_max_point = diff.base_text().max_point();
3207        let points = (0..=num_rows).map(move |row| {
3208            if row == num_rows && base_max_point.column > 0 {
3209                base_max_point
3210            } else {
3211                Point::new(row, 0)
3212            }
3213        });
3214        let actual_ranges: Vec<_> = diff.base_text_points_to_points(points, &buffer).0.collect();
3215
3216        assert_eq!(
3217            actual_ranges, expected_ranges,
3218            "\nsource (base):\n{}\ntarget (buffer):\n{}\nexpected: {:?}\nactual: {:?}",
3219            source_text, target_text, expected_ranges, actual_ranges
3220        );
3221    }
3222
3223    fn parse_row_annotations(annotated_text: &str) -> (String, Vec<Range<Point>>) {
3224        let mut starts: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3225        let mut ends: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3226
3227        let mut clean_text = String::new();
3228        let mut current_point = Point::new(0, 0);
3229        let mut chars = annotated_text.chars().peekable();
3230
3231        while let Some(c) = chars.next() {
3232            if c == '<' {
3233                let mut num_str = String::new();
3234                while let Some(&next) = chars.peek() {
3235                    if next.is_ascii_digit() {
3236                        num_str.push(chars.next().unwrap());
3237                    } else {
3238                        break;
3239                    }
3240                }
3241                if !num_str.is_empty() {
3242                    let row_num: u32 = num_str.parse().unwrap();
3243                    starts.insert(row_num, current_point);
3244
3245                    if chars.peek() == Some(&'>') {
3246                        chars.next();
3247                        ends.insert(row_num, current_point);
3248                    }
3249                } else {
3250                    clean_text.push(c);
3251                    current_point.column += 1;
3252                }
3253            } else if c.is_ascii_digit() {
3254                let mut num_str = String::from(c);
3255                while let Some(&next) = chars.peek() {
3256                    if next.is_ascii_digit() {
3257                        num_str.push(chars.next().unwrap());
3258                    } else {
3259                        break;
3260                    }
3261                }
3262                if chars.peek() == Some(&'>') {
3263                    chars.next();
3264                    let row_num: u32 = num_str.parse().unwrap();
3265                    ends.insert(row_num, current_point);
3266                } else {
3267                    for ch in num_str.chars() {
3268                        clean_text.push(ch);
3269                        current_point.column += 1;
3270                    }
3271                }
3272            } else if c == '\n' {
3273                clean_text.push(c);
3274                current_point.row += 1;
3275                current_point.column = 0;
3276            } else {
3277                clean_text.push(c);
3278                current_point.column += 1;
3279            }
3280        }
3281
3282        let max_row = starts.keys().chain(ends.keys()).max().copied().unwrap_or(0);
3283        let mut ranges: Vec<Range<Point>> = Vec::new();
3284        for row in 0..=max_row {
3285            let start = starts.get(&row).copied().unwrap_or(Point::new(0, 0));
3286            let end = ends.get(&row).copied().unwrap_or(start);
3287            ranges.push(start..end);
3288        }
3289
3290        (clean_text, ranges)
3291    }
3292
3293    fn make_diff(
3294        base_text: &str,
3295        buffer_text: &str,
3296        cx: &mut gpui::TestAppContext,
3297    ) -> (Entity<language::Buffer>, Entity<BufferDiff>) {
3298        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3299        let diff = cx.new(|cx| {
3300            BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
3301        });
3302        (buffer, diff)
3303    }
3304
3305    #[gpui::test]
3306    async fn test_row_translation_visual(cx: &mut gpui::TestAppContext) {
3307        use unindent::Unindent;
3308
3309        {
3310            let buffer_text = "
3311                aaa
3312                bbb
3313                ccc
3314            "
3315            .unindent();
3316            let annotated_base = "
3317                <0>aaa
3318                <1>bbb
3319                <2>ccc
3320                <3>"
3321            .unindent();
3322            let (base_text, _) = parse_row_annotations(&annotated_base);
3323            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3324            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3325        }
3326
3327        {
3328            let base_text = "
3329                aaa
3330                bbb
3331                ccc
3332            "
3333            .unindent();
3334            let annotated_buffer = "
3335                <0>aaa
3336                <1>bbb
3337                <2>ccc
3338                <3>"
3339            .unindent();
3340            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3341            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3342            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3343        }
3344
3345        {
3346            let buffer_text = "
3347                XXX
3348                bbb
3349                ccc
3350            "
3351            .unindent();
3352            let annotated_base = "
3353                <0<1aaa
3354                0>1>bbb
3355                <2>ccc
3356                <3>"
3357            .unindent();
3358            let (base_text, _) = parse_row_annotations(&annotated_base);
3359            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3360            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3361        }
3362
3363        {
3364            let buffer_text = "
3365                aaa
3366                NEW
3367                ccc
3368            "
3369            .unindent();
3370            let annotated_base = "
3371                <0>aaa
3372                <1><2>ccc
3373                <3>"
3374            .unindent();
3375            let (base_text, _) = parse_row_annotations(&annotated_base);
3376            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3377            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3378        }
3379
3380        {
3381            let base_text = "
3382                aaa
3383                ccc
3384            "
3385            .unindent();
3386            let annotated_buffer = "
3387                <0>aaa
3388                <1NEW
3389                1>ccc
3390                <2>"
3391            .unindent();
3392            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3393            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3394            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3395        }
3396
3397        {
3398            let buffer_text = "aaa\nbbb";
3399            let annotated_base = "<0>aaa\n<1>bbb<2>";
3400            let (base_text, _) = parse_row_annotations(annotated_base);
3401            let (buffer, diff) = make_diff(&base_text, buffer_text, cx);
3402            assert_rows_to_base_text_rows_visual(&buffer, &diff, buffer_text, annotated_base, cx);
3403            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, annotated_base, cx);
3404        }
3405
3406        {
3407            let base_text = "
3408                aaa
3409                bbb
3410                ccc
3411            "
3412            .unindent();
3413            let annotated_buffer = "
3414                <0<1XXX
3415                0>1>bbb
3416                <2>ccc
3417                <3>"
3418            .unindent();
3419            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3420            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3421            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3422        }
3423
3424        {
3425            let buffer_text = "
3426                aaa
3427                bbb
3428                XXX
3429            "
3430            .unindent();
3431            let annotated_base = "
3432                <0>aaa
3433                <1>bbb
3434                <2<3ccc
3435                2>3>"
3436                .unindent();
3437            let (base_text, _) = parse_row_annotations(&annotated_base);
3438            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3439            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3440        }
3441
3442        {
3443            let base_text = "
3444                aaa
3445                bbb
3446                ccc
3447            "
3448            .unindent();
3449            let annotated_buffer = "
3450                <0>aaa
3451                <1>bbb
3452                <2<3XXX
3453                2>3>"
3454                .unindent();
3455            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3456            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3457            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3458        }
3459
3460        {
3461            let buffer_text = "
3462                aaa
3463                ccc
3464            "
3465            .unindent();
3466            let annotated_base = "
3467                <0>aaa
3468                <1DELETED
3469                1>ccc
3470                <2>"
3471            .unindent();
3472            let (base_text, _) = parse_row_annotations(&annotated_base);
3473            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3474            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3475        }
3476
3477        {
3478            let base_text = "
3479                aaa
3480                DELETED
3481                ccc
3482            "
3483            .unindent();
3484            let annotated_buffer = "
3485                <0>aaa
3486                <1><2>ccc
3487                <3>"
3488            .unindent();
3489            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3490            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3491            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3492        }
3493    }
3494
3495    #[gpui::test]
3496    async fn test_row_translation_with_edits_since_diff(cx: &mut gpui::TestAppContext) {
3497        use unindent::Unindent;
3498
3499        {
3500            let base_text = "
3501                aaa
3502                bbb
3503                ccc
3504            "
3505            .unindent();
3506            let buffer_text = base_text.clone();
3507            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3508
3509            buffer.update(cx, |buffer, cx| {
3510                buffer.edit([(4..7, "XXX")], None, cx);
3511            });
3512
3513            let new_buffer_text = "
3514                aaa
3515                XXX
3516                ccc
3517            "
3518            .unindent();
3519            let annotated_base = "
3520                <0>aaa
3521                <1bbb1>
3522                <2>ccc
3523                <3>"
3524            .unindent();
3525            assert_rows_to_base_text_rows_visual(
3526                &buffer,
3527                &diff,
3528                &new_buffer_text,
3529                &annotated_base,
3530                cx,
3531            );
3532        }
3533
3534        {
3535            let base_text = "
3536                aaa
3537                bbb
3538                ccc
3539            "
3540            .unindent();
3541            let buffer_text = base_text.clone();
3542            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3543
3544            buffer.update(cx, |buffer, cx| {
3545                buffer.edit([(4..7, "XXX")], None, cx);
3546            });
3547
3548            let annotated_buffer = "
3549                <0>aaa
3550                <1XXX1>
3551                <2>ccc
3552                <3>"
3553            .unindent();
3554            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3555        }
3556
3557        {
3558            let base_text = "
3559                aaa
3560                bbb
3561                ccc
3562            "
3563            .unindent();
3564            let buffer_text = base_text.clone();
3565            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3566
3567            buffer.update(cx, |buffer, cx| {
3568                buffer.edit([(4..4, "NEW\n")], None, cx);
3569            });
3570
3571            let new_buffer_text = "
3572                aaa
3573                NEW
3574                bbb
3575                ccc
3576            "
3577            .unindent();
3578            let annotated_base = "
3579                <0>aaa
3580                <1><2>bbb
3581                <3>ccc
3582                <4>"
3583            .unindent();
3584            assert_rows_to_base_text_rows_visual(
3585                &buffer,
3586                &diff,
3587                &new_buffer_text,
3588                &annotated_base,
3589                cx,
3590            );
3591        }
3592
3593        {
3594            let base_text = "
3595                aaa
3596                bbb
3597                ccc
3598            "
3599            .unindent();
3600            let buffer_text = base_text.clone();
3601            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3602
3603            buffer.update(cx, |buffer, cx| {
3604                buffer.edit([(4..4, "NEW\n")], None, cx);
3605            });
3606
3607            let annotated_buffer = "
3608                <0>aaa
3609                <1NEW
3610                1>bbb
3611                <2>ccc
3612                <3>"
3613            .unindent();
3614            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3615        }
3616
3617        {
3618            let base_text = "
3619                aaa
3620                bbb
3621                ccc
3622            "
3623            .unindent();
3624            let buffer_text = base_text.clone();
3625            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3626
3627            buffer.update(cx, |buffer, cx| {
3628                buffer.edit([(4..8, "")], None, cx);
3629            });
3630
3631            let new_buffer_text = "
3632                aaa
3633                ccc
3634            "
3635            .unindent();
3636            let annotated_base = "
3637                <0>aaa
3638                <1bbb
3639                1>ccc
3640                <2>"
3641            .unindent();
3642            assert_rows_to_base_text_rows_visual(
3643                &buffer,
3644                &diff,
3645                &new_buffer_text,
3646                &annotated_base,
3647                cx,
3648            );
3649        }
3650
3651        {
3652            let base_text = "
3653                aaa
3654                bbb
3655                ccc
3656            "
3657            .unindent();
3658            let buffer_text = base_text.clone();
3659            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3660
3661            buffer.update(cx, |buffer, cx| {
3662                buffer.edit([(4..8, "")], None, cx);
3663            });
3664
3665            let annotated_buffer = "
3666                <0>aaa
3667                <1><2>ccc
3668                <3>"
3669            .unindent();
3670            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3671        }
3672
3673        {
3674            let base_text = "
3675                aaa
3676                bbb
3677                ccc
3678                ddd
3679                eee
3680            "
3681            .unindent();
3682            let buffer_text = "
3683                aaa
3684                XXX
3685                ccc
3686                ddd
3687                eee
3688            "
3689            .unindent();
3690            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3691
3692            buffer.update(cx, |buffer, cx| {
3693                buffer.edit([(12..15, "YYY")], None, cx);
3694            });
3695
3696            let new_buffer_text = "
3697                aaa
3698                XXX
3699                ccc
3700                YYY
3701                eee
3702            "
3703            .unindent();
3704            let annotated_base = "
3705                <0>aaa
3706                <1<2bbb
3707                1>2>ccc
3708                <3ddd3>
3709                <4>eee
3710                <5>"
3711            .unindent();
3712            assert_rows_to_base_text_rows_visual(
3713                &buffer,
3714                &diff,
3715                &new_buffer_text,
3716                &annotated_base,
3717                cx,
3718            );
3719        }
3720
3721        {
3722            let base_text = "
3723                aaa
3724                bbb
3725                ccc
3726                ddd
3727                eee
3728            "
3729            .unindent();
3730            let buffer_text = "
3731                aaa
3732                XXX
3733                ccc
3734                ddd
3735                eee
3736            "
3737            .unindent();
3738            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3739
3740            buffer.update(cx, |buffer, cx| {
3741                buffer.edit([(12..15, "YYY")], None, cx);
3742            });
3743
3744            let annotated_buffer = "
3745                <0>aaa
3746                <1<2XXX
3747                1>2>ccc
3748                <3YYY3>
3749                <4>eee
3750                <5>"
3751            .unindent();
3752            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3753        }
3754
3755        {
3756            let base_text = "
3757                aaa
3758                bbb
3759                ccc
3760            "
3761            .unindent();
3762            let buffer_text = base_text.clone();
3763            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3764
3765            buffer.update(cx, |buffer, cx| {
3766                buffer.edit([(0..0, "NEW\n")], None, cx);
3767            });
3768
3769            let new_buffer_text = "
3770                NEW
3771                aaa
3772                bbb
3773                ccc
3774            "
3775            .unindent();
3776            let annotated_base = "
3777                <0><1>aaa
3778                <2>bbb
3779                <3>ccc
3780                <4>"
3781            .unindent();
3782            assert_rows_to_base_text_rows_visual(
3783                &buffer,
3784                &diff,
3785                &new_buffer_text,
3786                &annotated_base,
3787                cx,
3788            );
3789        }
3790
3791        {
3792            let base_text = "
3793                aaa
3794                bbb
3795                ccc
3796            "
3797            .unindent();
3798            let buffer_text = base_text.clone();
3799            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3800
3801            buffer.update(cx, |buffer, cx| {
3802                buffer.edit([(0..0, "NEW\n")], None, cx);
3803            });
3804
3805            let annotated_buffer = "
3806                <0NEW
3807                0>aaa
3808                <1>bbb
3809                <2>ccc
3810                <3>"
3811            .unindent();
3812            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3813        }
3814
3815        {
3816            let base_text = "
3817                aaa
3818                bbb
3819                ccc
3820            "
3821            .unindent();
3822            let buffer_text = base_text.clone();
3823            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3824
3825            buffer.update(cx, |buffer, cx| {
3826                buffer.edit([(12..12, "NEW\n")], None, cx);
3827            });
3828
3829            let new_buffer_text = "
3830                aaa
3831                bbb
3832                ccc
3833                NEW
3834            "
3835            .unindent();
3836            let annotated_base = "
3837                <0>aaa
3838                <1>bbb
3839                <2>ccc
3840                <3><4>"
3841                .unindent();
3842            assert_rows_to_base_text_rows_visual(
3843                &buffer,
3844                &diff,
3845                &new_buffer_text,
3846                &annotated_base,
3847                cx,
3848            );
3849        }
3850
3851        {
3852            let base_text = "
3853                aaa
3854                bbb
3855                ccc
3856            "
3857            .unindent();
3858            let buffer_text = base_text.clone();
3859            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3860
3861            buffer.update(cx, |buffer, cx| {
3862                buffer.edit([(12..12, "NEW\n")], None, cx);
3863            });
3864
3865            let annotated_buffer = "
3866                <0>aaa
3867                <1>bbb
3868                <2>ccc
3869                <3NEW
3870                3>"
3871            .unindent();
3872            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3873        }
3874
3875        {
3876            let base_text = "";
3877            let buffer_text = "aaa\n";
3878            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3879
3880            buffer.update(cx, |buffer, cx| {
3881                buffer.edit([(4..4, "bbb\n")], None, cx);
3882            });
3883
3884            let new_buffer_text = "
3885                aaa
3886                bbb
3887            "
3888            .unindent();
3889            let annotated_base = "<0><1><2>";
3890            assert_rows_to_base_text_rows_visual(
3891                &buffer,
3892                &diff,
3893                &new_buffer_text,
3894                &annotated_base,
3895                cx,
3896            );
3897        }
3898
3899        {
3900            let base_text = "aaa\n";
3901            let buffer_text = "";
3902            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3903
3904            buffer.update(cx, |buffer, cx| {
3905                buffer.edit([(0..0, "bbb\n")], None, cx);
3906            });
3907
3908            let new_buffer_text = "bbb\n";
3909            let annotated_base = "
3910                <0<1aaa
3911                0>1>"
3912                .unindent();
3913            assert_rows_to_base_text_rows_visual(
3914                &buffer,
3915                &diff,
3916                &new_buffer_text,
3917                &annotated_base,
3918                cx,
3919            );
3920        }
3921
3922        {
3923            let base_text = "";
3924            let buffer_text = "";
3925            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3926
3927            buffer.update(cx, |buffer, cx| {
3928                buffer.edit([(0..0, "aaa\n")], None, cx);
3929            });
3930
3931            let new_buffer_text = "aaa\n";
3932            let annotated_base = "<0><1>";
3933            assert_rows_to_base_text_rows_visual(
3934                &buffer,
3935                &diff,
3936                &new_buffer_text,
3937                &annotated_base,
3938                cx,
3939            );
3940        }
3941
3942        {
3943            let base_text = "
3944                aaa
3945                bbb
3946                ccc
3947            "
3948            .unindent();
3949            let buffer_text = "
3950                aaa
3951                XXX
3952                ccc
3953            "
3954            .unindent();
3955            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3956
3957            buffer.update(cx, |buffer, cx| {
3958                buffer.edit([(4..7, "YYY")], None, cx);
3959            });
3960
3961            let new_buffer_text = "
3962                aaa
3963                YYY
3964                ccc
3965            "
3966            .unindent();
3967            let annotated_base = "
3968                <0>aaa
3969                <1<2bbb
3970                1>2>ccc
3971                <3>"
3972            .unindent();
3973            assert_rows_to_base_text_rows_visual(
3974                &buffer,
3975                &diff,
3976                &new_buffer_text,
3977                &annotated_base,
3978                cx,
3979            );
3980        }
3981
3982        {
3983            let base_text = "
3984                aaa
3985                bbb
3986                ccc
3987            "
3988            .unindent();
3989            let buffer_text = "
3990                aaa
3991                XXX
3992                ccc
3993            "
3994            .unindent();
3995            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3996
3997            buffer.update(cx, |buffer, cx| {
3998                buffer.edit([(4..7, "YYY")], None, cx);
3999            });
4000
4001            let annotated_buffer = "
4002                <0>aaa
4003                <1<2YYY
4004                1>2>ccc
4005                <3>"
4006            .unindent();
4007            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4008        }
4009
4010        {
4011            let base_text = "
4012                aaa
4013                bbb
4014                ccc
4015            "
4016            .unindent();
4017            let buffer_text = "
4018                aaa
4019                XXXX
4020                ccc
4021            "
4022            .unindent();
4023            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4024
4025            buffer.update(cx, |buffer, cx| {
4026                buffer.edit([(4..6, "YY")], None, cx);
4027            });
4028
4029            let new_buffer_text = "
4030                aaa
4031                YYXX
4032                ccc
4033            "
4034            .unindent();
4035            let annotated_base = "
4036                <0>aaa
4037                <1<2bbb
4038                1>2>ccc
4039                <3>"
4040            .unindent();
4041            assert_rows_to_base_text_rows_visual(
4042                &buffer,
4043                &diff,
4044                &new_buffer_text,
4045                &annotated_base,
4046                cx,
4047            );
4048        }
4049
4050        {
4051            let base_text = "
4052                aaa
4053                bbb
4054                ccc
4055            "
4056            .unindent();
4057            let buffer_text = "
4058                aaa
4059                XXXX
4060                ccc
4061            "
4062            .unindent();
4063            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4064
4065            buffer.update(cx, |buffer, cx| {
4066                buffer.edit([(6..8, "YY")], None, cx);
4067            });
4068
4069            let new_buffer_text = "
4070                aaa
4071                XXYY
4072                ccc
4073            "
4074            .unindent();
4075            let annotated_base = "
4076                <0>aaa
4077                <1<2bbb
4078                1>2>ccc
4079                <3>"
4080            .unindent();
4081            assert_rows_to_base_text_rows_visual(
4082                &buffer,
4083                &diff,
4084                &new_buffer_text,
4085                &annotated_base,
4086                cx,
4087            );
4088        }
4089
4090        {
4091            let base_text = "
4092                aaa
4093                bbb
4094                ccc
4095            "
4096            .unindent();
4097            let buffer_text = "
4098                aaa
4099                XXX
4100                ccc
4101            "
4102            .unindent();
4103            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4104
4105            buffer.update(cx, |buffer, cx| {
4106                buffer.edit([(4..4, "NEW")], None, cx);
4107            });
4108
4109            let new_buffer_text = "
4110                aaa
4111                NEWXXX
4112                ccc
4113            "
4114            .unindent();
4115            let annotated_base = "
4116                <0>aaa
4117                <1<2bbb
4118                1>2>ccc
4119                <3>"
4120            .unindent();
4121            assert_rows_to_base_text_rows_visual(
4122                &buffer,
4123                &diff,
4124                &new_buffer_text,
4125                &annotated_base,
4126                cx,
4127            );
4128        }
4129
4130        {
4131            let base_text = "
4132                aaa
4133                bbb
4134                ccc
4135            "
4136            .unindent();
4137            let buffer_text = "
4138                aaa
4139                XXX
4140                ccc
4141            "
4142            .unindent();
4143            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4144
4145            buffer.update(cx, |buffer, cx| {
4146                buffer.edit([(7..7, "NEW")], None, cx);
4147            });
4148
4149            let new_buffer_text = "
4150                aaa
4151                XXXNEW
4152                ccc
4153            "
4154            .unindent();
4155            let annotated_base = "
4156                <0>aaa
4157                <1<2bbb
4158                1>2>ccc
4159                <3>"
4160            .unindent();
4161            assert_rows_to_base_text_rows_visual(
4162                &buffer,
4163                &diff,
4164                &new_buffer_text,
4165                &annotated_base,
4166                cx,
4167            );
4168        }
4169
4170        {
4171            let base_text = "
4172                aaa
4173                bbb
4174                ccc
4175            "
4176            .unindent();
4177            let buffer_text = "
4178                aaa
4179                ccc
4180            "
4181            .unindent();
4182            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4183
4184            buffer.update(cx, |buffer, cx| {
4185                buffer.edit([(4..4, "NEW\n")], None, cx);
4186            });
4187
4188            let new_buffer_text = "
4189                aaa
4190                NEW
4191                ccc
4192            "
4193            .unindent();
4194            let annotated_base = "
4195                <0>aaa
4196                <1<2bbb
4197                1>2>ccc
4198                <3>"
4199            .unindent();
4200            assert_rows_to_base_text_rows_visual(
4201                &buffer,
4202                &diff,
4203                &new_buffer_text,
4204                &annotated_base,
4205                cx,
4206            );
4207        }
4208
4209        {
4210            let base_text = "
4211                aaa
4212                bbb
4213                ccc
4214            "
4215            .unindent();
4216            let buffer_text = "
4217                aaa
4218                ccc
4219            "
4220            .unindent();
4221            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4222
4223            buffer.update(cx, |buffer, cx| {
4224                buffer.edit([(4..4, "NEW\n")], None, cx);
4225            });
4226
4227            let annotated_buffer = "
4228                <0>aaa
4229                <1<2NEW
4230                1>2>ccc
4231                <3>"
4232            .unindent();
4233            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4234        }
4235
4236        {
4237            let base_text = "
4238                aaa
4239                bbb
4240                ccc
4241                ddd
4242            "
4243            .unindent();
4244            let buffer_text = "
4245                aaa
4246                ddd
4247            "
4248            .unindent();
4249            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4250
4251            buffer.update(cx, |buffer, cx| {
4252                buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4253            });
4254
4255            let new_buffer_text = "
4256                aaa
4257                XXX
4258                YYY
4259                ddd
4260            "
4261            .unindent();
4262            let annotated_base = "
4263                <0>aaa
4264                <1<2<3bbb
4265                ccc
4266                1>2>3>ddd
4267                <4>"
4268            .unindent();
4269            assert_rows_to_base_text_rows_visual(
4270                &buffer,
4271                &diff,
4272                &new_buffer_text,
4273                &annotated_base,
4274                cx,
4275            );
4276        }
4277
4278        {
4279            let base_text = "
4280                aaa
4281                bbb
4282                ccc
4283                ddd
4284            "
4285            .unindent();
4286            let buffer_text = "
4287                aaa
4288                ddd
4289            "
4290            .unindent();
4291            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4292
4293            buffer.update(cx, |buffer, cx| {
4294                buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4295            });
4296
4297            let annotated_buffer = "
4298                <0>aaa
4299                <1<2<3XXX
4300                YYY
4301                1>2>3>ddd
4302                <4>"
4303            .unindent();
4304            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4305        }
4306
4307        {
4308            let base_text = "
4309                aaa
4310                bbb
4311                ccc
4312            "
4313            .unindent();
4314            let buffer_text = "
4315                aaa
4316                XXXX
4317                ccc
4318            "
4319            .unindent();
4320            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4321
4322            buffer.update(cx, |buffer, cx| {
4323                buffer.edit([(2..10, "YY\nZZ")], None, cx);
4324            });
4325
4326            let new_buffer_text = "
4327                aaYY
4328                ZZcc
4329            "
4330            .unindent();
4331            let annotated_base = "
4332                <0>aa<1a
4333                bbb
4334                c1>cc
4335                <2>"
4336            .unindent();
4337            assert_rows_to_base_text_rows_visual(
4338                &buffer,
4339                &diff,
4340                &new_buffer_text,
4341                &annotated_base,
4342                cx,
4343            );
4344        }
4345
4346        {
4347            let base_text = "
4348                aaa
4349                bbb
4350                ccc
4351            "
4352            .unindent();
4353            let buffer_text = "
4354                aaa
4355                XXXX
4356                ccc
4357            "
4358            .unindent();
4359            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4360
4361            buffer.update(cx, |buffer, cx| {
4362                buffer.edit([(0..9, "ZZ\n")], None, cx);
4363            });
4364
4365            let new_buffer_text = "
4366                ZZ
4367                ccc
4368            "
4369            .unindent();
4370            let annotated_base = "
4371                <0<1aaa
4372                bbb
4373                0>1>ccc
4374                <2>"
4375            .unindent();
4376            assert_rows_to_base_text_rows_visual(
4377                &buffer,
4378                &diff,
4379                &new_buffer_text,
4380                &annotated_base,
4381                cx,
4382            );
4383        }
4384    }
4385
4386    #[gpui::test]
4387    async fn test_row_translation_no_base_text(cx: &mut gpui::TestAppContext) {
4388        let buffer_text = "aaa\nbbb\nccc\n";
4389        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
4390        let diff = cx.new(|cx| BufferDiff::new(&buffer.read(cx).text_snapshot(), cx));
4391
4392        let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4393        let diff_snapshot = diff.update(cx, |diff, cx| diff.snapshot(cx));
4394
4395        let points = vec![
4396            Point::new(0, 0),
4397            Point::new(1, 0),
4398            Point::new(2, 0),
4399            Point::new(3, 0),
4400        ];
4401        let base_rows: Vec<_> = diff_snapshot
4402            .points_to_base_text_points(points, &buffer_snapshot)
4403            .0
4404            .collect();
4405
4406        let zero = Point::new(0, 0);
4407        assert_eq!(
4408            base_rows,
4409            vec![zero..zero, zero..zero, zero..zero, zero..zero],
4410            "all buffer rows should map to point 0,0 in empty base text"
4411        );
4412
4413        let base_points = vec![Point::new(0, 0)];
4414        let (rows_iter, _, _) =
4415            diff_snapshot.base_text_points_to_points(base_points, &buffer_snapshot);
4416        let buffer_rows: Vec<_> = rows_iter.collect();
4417
4418        let max_point = buffer_snapshot.max_point();
4419        assert_eq!(
4420            buffer_rows,
4421            vec![zero..max_point],
4422            "base text row 0 should map to entire buffer range"
4423        );
4424    }
4425}