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, File, Language, LanguageName, LanguageRegistry,
   6    language_settings::language_settings, 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    file: Option<&Arc<dyn File>>,
 950    language: Option<LanguageName>,
 951    language_scope: Option<language::LanguageScope>,
 952    cx: &App,
 953) -> Option<DiffOptions> {
 954    #[cfg(any(test, feature = "test-support"))]
 955    {
 956        if !cx.has_global::<settings::SettingsStore>() {
 957            return Some(DiffOptions {
 958                language_scope,
 959                max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
 960                ..Default::default()
 961            });
 962        }
 963    }
 964
 965    language_settings(language, file, cx)
 966        .word_diff_enabled
 967        .then_some(DiffOptions {
 968            language_scope,
 969            max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
 970            ..Default::default()
 971        })
 972}
 973
 974fn compute_hunks(
 975    diff_base: Option<(Arc<str>, Rope)>,
 976    buffer: text::BufferSnapshot,
 977    diff_options: Option<DiffOptions>,
 978) -> SumTree<InternalDiffHunk> {
 979    let mut tree = SumTree::new(&buffer);
 980
 981    if let Some((diff_base, diff_base_rope)) = diff_base {
 982        let buffer_text = buffer.as_rope().to_string();
 983
 984        let mut options = GitOptions::default();
 985        options.context_lines(0);
 986        let patch = GitPatch::from_buffers(
 987            diff_base.as_bytes(),
 988            None,
 989            buffer_text.as_bytes(),
 990            None,
 991            Some(&mut options),
 992        )
 993        .log_err();
 994
 995        // A common case in Zed is that the empty buffer is represented as just a newline,
 996        // but if we just compute a naive diff you get a "preserved" line in the middle,
 997        // which is a bit odd.
 998        if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
 999            tree.push(
1000                InternalDiffHunk {
1001                    buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
1002                    diff_base_byte_range: 0..diff_base.len() - 1,
1003                    base_word_diffs: Vec::default(),
1004                    buffer_word_diffs: Vec::default(),
1005                },
1006                &buffer,
1007            );
1008            return tree;
1009        }
1010
1011        if let Some(patch) = patch {
1012            let mut divergence = 0;
1013            for hunk_index in 0..patch.num_hunks() {
1014                let hunk = process_patch_hunk(
1015                    &patch,
1016                    hunk_index,
1017                    &diff_base_rope,
1018                    &buffer,
1019                    &mut divergence,
1020                    diff_options.as_ref(),
1021                );
1022                tree.push(hunk, &buffer);
1023            }
1024        }
1025    } else {
1026        tree.push(
1027            InternalDiffHunk {
1028                buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
1029                diff_base_byte_range: 0..0,
1030                base_word_diffs: Vec::default(),
1031                buffer_word_diffs: Vec::default(),
1032            },
1033            &buffer,
1034        );
1035    }
1036
1037    tree
1038}
1039
1040fn compare_hunks(
1041    new_hunks: &SumTree<InternalDiffHunk>,
1042    old_hunks: &SumTree<InternalDiffHunk>,
1043    old_snapshot: &text::BufferSnapshot,
1044    new_snapshot: &text::BufferSnapshot,
1045) -> DiffChanged {
1046    let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
1047    let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
1048    old_cursor.next();
1049    new_cursor.next();
1050    let mut start = None;
1051    let mut end = None;
1052    let mut base_text_start = None;
1053    let mut base_text_end = None;
1054
1055    let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1056    let mut has_changes = false;
1057    let mut extended_end_candidate: Option<text::Anchor> = None;
1058
1059    loop {
1060        match (new_cursor.item(), old_cursor.item()) {
1061            (Some(new_hunk), Some(old_hunk)) => {
1062                match new_hunk
1063                    .buffer_range
1064                    .start
1065                    .cmp(&old_hunk.buffer_range.start, new_snapshot)
1066                {
1067                    Ordering::Less => {
1068                        has_changes = true;
1069                        extended_end_candidate = None;
1070                        start.get_or_insert(new_hunk.buffer_range.start);
1071                        base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
1072                        end.replace(new_hunk.buffer_range.end);
1073                        base_text_end.replace(new_hunk.diff_base_byte_range.end);
1074                        new_cursor.next();
1075                    }
1076                    Ordering::Equal => {
1077                        if new_hunk != old_hunk {
1078                            has_changes = true;
1079                            extended_end_candidate = None;
1080                            start.get_or_insert(new_hunk.buffer_range.start);
1081                            base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
1082                            if old_hunk
1083                                .buffer_range
1084                                .end
1085                                .cmp(&new_hunk.buffer_range.end, new_snapshot)
1086                                .is_ge()
1087                            {
1088                                end.replace(old_hunk.buffer_range.end);
1089                            } else {
1090                                end.replace(new_hunk.buffer_range.end);
1091                            }
1092
1093                            base_text_end.replace(
1094                                old_hunk
1095                                    .diff_base_byte_range
1096                                    .end
1097                                    .max(new_hunk.diff_base_byte_range.end),
1098                            );
1099                        } else {
1100                            if !has_changes {
1101                                last_unchanged_new_hunk_end = Some(new_hunk.buffer_range.end);
1102                            } else if extended_end_candidate.is_none() {
1103                                extended_end_candidate = Some(new_hunk.buffer_range.start);
1104                            }
1105                        }
1106
1107                        new_cursor.next();
1108                        old_cursor.next();
1109                    }
1110                    Ordering::Greater => {
1111                        has_changes = true;
1112                        extended_end_candidate = None;
1113                        start.get_or_insert(old_hunk.buffer_range.start);
1114                        base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
1115                        end.replace(old_hunk.buffer_range.end);
1116                        base_text_end.replace(old_hunk.diff_base_byte_range.end);
1117                        old_cursor.next();
1118                    }
1119                }
1120            }
1121            (Some(new_hunk), None) => {
1122                has_changes = true;
1123                extended_end_candidate = None;
1124                start.get_or_insert(new_hunk.buffer_range.start);
1125                base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
1126                if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1127                {
1128                    end.replace(new_hunk.buffer_range.end);
1129                }
1130                base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));
1131                new_cursor.next();
1132            }
1133            (None, Some(old_hunk)) => {
1134                has_changes = true;
1135                extended_end_candidate = None;
1136                start.get_or_insert(old_hunk.buffer_range.start);
1137                base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
1138                if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1139                {
1140                    end.replace(old_hunk.buffer_range.end);
1141                }
1142                base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));
1143                old_cursor.next();
1144            }
1145            (None, None) => break,
1146        }
1147    }
1148
1149    let changed_range = start.zip(end).map(|(start, end)| start..end);
1150    let base_text_changed_range = base_text_start
1151        .zip(base_text_end)
1152        .map(|(start, end)| start..end);
1153
1154    let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1155        let extended_start = *last_unchanged_new_hunk_end
1156            .unwrap_or(text::Anchor::min_for_buffer(new_snapshot.remote_id()))
1157            .min(&changed_range.start, new_snapshot);
1158        let extended_start = new_snapshot
1159            .anchored_edits_since_in_range::<usize>(
1160                &old_snapshot.version(),
1161                extended_start..changed_range.start,
1162            )
1163            .map(|(_, anchors)| anchors.start)
1164            .min_by(|a, b| a.cmp(b, new_snapshot))
1165            .unwrap_or(changed_range.start);
1166
1167        let extended_end = *extended_end_candidate
1168            .unwrap_or(text::Anchor::max_for_buffer(new_snapshot.remote_id()))
1169            .max(&changed_range.end, new_snapshot);
1170        let extended_end = new_snapshot
1171            .anchored_edits_since_in_range::<usize>(
1172                &old_snapshot.version(),
1173                changed_range.end..extended_end,
1174            )
1175            .map(|(_, anchors)| anchors.end)
1176            .max_by(|a, b| a.cmp(b, new_snapshot))
1177            .unwrap_or(changed_range.end);
1178
1179        Some(extended_start..extended_end)
1180    } else {
1181        None
1182    };
1183
1184    DiffChanged {
1185        changed_range,
1186        base_text_changed_range,
1187        extended_range,
1188    }
1189}
1190
1191fn process_patch_hunk(
1192    patch: &GitPatch<'_>,
1193    hunk_index: usize,
1194    diff_base: &Rope,
1195    buffer: &text::BufferSnapshot,
1196    buffer_row_divergence: &mut i64,
1197    diff_options: Option<&DiffOptions>,
1198) -> InternalDiffHunk {
1199    let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
1200    assert!(line_item_count > 0);
1201
1202    let mut first_deletion_buffer_row: Option<u32> = None;
1203    let mut buffer_row_range: Option<Range<u32>> = None;
1204    let mut diff_base_byte_range: Option<Range<usize>> = None;
1205    let mut first_addition_old_row: Option<u32> = None;
1206
1207    for line_index in 0..line_item_count {
1208        let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
1209        let kind = line.origin_value();
1210        let content_offset = line.content_offset() as isize;
1211        let content_len = line.content().len() as isize;
1212        match kind {
1213            GitDiffLineType::Addition => {
1214                if first_addition_old_row.is_none() {
1215                    first_addition_old_row = Some(
1216                        (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
1217                    );
1218                }
1219                *buffer_row_divergence += 1;
1220                let row = line.new_lineno().unwrap().saturating_sub(1);
1221
1222                match &mut buffer_row_range {
1223                    Some(Range { end, .. }) => *end = row + 1,
1224                    None => buffer_row_range = Some(row..row + 1),
1225                }
1226            }
1227            GitDiffLineType::Deletion => {
1228                let end = content_offset + content_len;
1229
1230                match &mut diff_base_byte_range {
1231                    Some(head_byte_range) => head_byte_range.end = end as usize,
1232                    None => diff_base_byte_range = Some(content_offset as usize..end as usize),
1233                }
1234
1235                if first_deletion_buffer_row.is_none() {
1236                    let old_row = line.old_lineno().unwrap().saturating_sub(1);
1237                    let row = old_row as i64 + *buffer_row_divergence;
1238                    first_deletion_buffer_row = Some(row as u32);
1239                }
1240
1241                *buffer_row_divergence -= 1;
1242            }
1243            _ => {}
1244        }
1245    }
1246
1247    let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
1248        // Pure deletion hunk without addition.
1249        let row = first_deletion_buffer_row.unwrap();
1250        row..row
1251    });
1252    let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
1253        // Pure addition hunk without deletion.
1254        let row = first_addition_old_row.unwrap();
1255        let offset = diff_base.point_to_offset(Point::new(row, 0));
1256        offset..offset
1257    });
1258
1259    let start = Point::new(buffer_row_range.start, 0);
1260    let end = Point::new(buffer_row_range.end, 0);
1261    let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
1262
1263    let base_line_count = line_item_count.saturating_sub(buffer_row_range.len());
1264
1265    let (base_word_diffs, buffer_word_diffs) = if let Some(diff_options) = diff_options
1266        && !buffer_row_range.is_empty()
1267        && base_line_count == buffer_row_range.len()
1268        && diff_options.max_word_diff_line_count >= base_line_count
1269    {
1270        let base_text: String = diff_base
1271            .chunks_in_range(diff_base_byte_range.clone())
1272            .collect();
1273
1274        let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
1275
1276        let (base_word_diffs, buffer_word_diffs_relative) = word_diff_ranges(
1277            &base_text,
1278            &buffer_text,
1279            DiffOptions {
1280                language_scope: diff_options.language_scope.clone(),
1281                ..*diff_options
1282            },
1283        );
1284
1285        let buffer_start_offset = buffer_range.start.to_offset(buffer);
1286        let buffer_word_diffs = buffer_word_diffs_relative
1287            .into_iter()
1288            .map(|range| {
1289                let start = buffer.anchor_after(buffer_start_offset + range.start);
1290                let end = buffer.anchor_after(buffer_start_offset + range.end);
1291                start..end
1292            })
1293            .collect();
1294
1295        (base_word_diffs, buffer_word_diffs)
1296    } else {
1297        (Vec::default(), Vec::default())
1298    };
1299
1300    InternalDiffHunk {
1301        buffer_range,
1302        diff_base_byte_range,
1303        base_word_diffs,
1304        buffer_word_diffs,
1305    }
1306}
1307
1308impl std::fmt::Debug for BufferDiff {
1309    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1310        f.debug_struct("BufferChangeSet")
1311            .field("buffer_id", &self.buffer_id)
1312            .finish()
1313    }
1314}
1315
1316#[derive(Clone, Debug, Default)]
1317pub struct DiffChanged {
1318    pub changed_range: Option<Range<text::Anchor>>,
1319    pub base_text_changed_range: Option<Range<usize>>,
1320    pub extended_range: Option<Range<text::Anchor>>,
1321}
1322
1323#[derive(Clone, Debug)]
1324pub enum BufferDiffEvent {
1325    DiffChanged(DiffChanged),
1326    LanguageChanged,
1327    HunksStagedOrUnstaged(Option<Rope>),
1328}
1329
1330impl EventEmitter<BufferDiffEvent> for BufferDiff {}
1331
1332impl BufferDiff {
1333    pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1334        let base_text = cx.new(|cx| {
1335            let mut buffer = language::Buffer::local("", cx);
1336            buffer.set_capability(Capability::ReadOnly, cx);
1337            buffer
1338        });
1339
1340        BufferDiff {
1341            buffer_id: buffer.remote_id(),
1342            inner: BufferDiffInner {
1343                base_text,
1344                hunks: SumTree::new(buffer),
1345                pending_hunks: SumTree::new(buffer),
1346                base_text_exists: false,
1347                buffer_snapshot: buffer.clone(),
1348            },
1349            secondary_diff: None,
1350        }
1351    }
1352
1353    pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1354        let base_text = buffer.text();
1355        let base_text = cx.new(|cx| {
1356            let mut buffer = language::Buffer::local(base_text, cx);
1357            buffer.set_capability(Capability::ReadOnly, cx);
1358            buffer
1359        });
1360
1361        BufferDiff {
1362            buffer_id: buffer.remote_id(),
1363            inner: BufferDiffInner {
1364                base_text,
1365                hunks: SumTree::new(buffer),
1366                pending_hunks: SumTree::new(buffer),
1367                base_text_exists: true,
1368                buffer_snapshot: buffer.clone(),
1369            },
1370            secondary_diff: None,
1371        }
1372    }
1373
1374    #[cfg(any(test, feature = "test-support"))]
1375    pub fn new_with_base_text(
1376        base_text: &str,
1377        buffer: &text::BufferSnapshot,
1378        cx: &mut Context<Self>,
1379    ) -> Self {
1380        let mut this = BufferDiff::new(&buffer, cx);
1381        let mut base_text = base_text.to_owned();
1382        text::LineEnding::normalize(&mut base_text);
1383        let inner = cx.foreground_executor().block_on(this.update_diff(
1384            buffer.clone(),
1385            Some(Arc::from(base_text)),
1386            Some(false),
1387            None,
1388            cx,
1389        ));
1390        this.set_snapshot(inner, &buffer, cx).detach();
1391        this
1392    }
1393
1394    pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1395        self.secondary_diff = Some(diff);
1396    }
1397
1398    pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1399        self.secondary_diff.clone()
1400    }
1401
1402    pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1403        if self.secondary_diff.is_some() {
1404            self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1405                buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1406                diff_base_byte_range: 0..0,
1407            });
1408            let changed_range = Some(Anchor::min_max_range_for_buffer(self.buffer_id));
1409            let base_text_range = Some(0..self.base_text(cx).len());
1410            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1411                changed_range: changed_range.clone(),
1412                base_text_changed_range: base_text_range,
1413                extended_range: changed_range,
1414            }));
1415        }
1416    }
1417
1418    pub fn stage_or_unstage_hunks(
1419        &mut self,
1420        stage: bool,
1421        hunks: &[DiffHunk],
1422        buffer: &text::BufferSnapshot,
1423        file_exists: bool,
1424        cx: &mut Context<Self>,
1425    ) -> Option<Rope> {
1426        let new_index_text = self
1427            .secondary_diff
1428            .as_ref()?
1429            .update(cx, |secondary_diff, cx| {
1430                self.inner.stage_or_unstage_hunks_impl(
1431                    &secondary_diff.inner,
1432                    stage,
1433                    hunks,
1434                    buffer,
1435                    file_exists,
1436                    cx,
1437                )
1438            });
1439
1440        cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1441            new_index_text.clone(),
1442        ));
1443        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1444            let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1445            let base_text_changed_range =
1446                Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1447            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1448                changed_range: changed_range.clone(),
1449                base_text_changed_range,
1450                extended_range: changed_range,
1451            }));
1452        }
1453        new_index_text
1454    }
1455
1456    pub fn stage_or_unstage_all_hunks(
1457        &mut self,
1458        stage: bool,
1459        buffer: &text::BufferSnapshot,
1460        file_exists: bool,
1461        cx: &mut Context<Self>,
1462    ) {
1463        let hunks = self
1464            .snapshot(cx)
1465            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1466            .collect::<Vec<_>>();
1467        let Some(secondary) = self.secondary_diff.clone() else {
1468            return;
1469        };
1470        let secondary = secondary.read(cx).inner.clone();
1471        self.inner
1472            .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1473        if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1474            let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1475            let base_text_changed_range =
1476                Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1477            cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1478                changed_range: changed_range.clone(),
1479                base_text_changed_range,
1480                extended_range: changed_range,
1481            }));
1482        }
1483    }
1484
1485    pub fn update_diff(
1486        &self,
1487        buffer: text::BufferSnapshot,
1488        base_text: Option<Arc<str>>,
1489        base_text_change: Option<bool>,
1490        language: Option<Arc<Language>>,
1491        cx: &App,
1492    ) -> Task<BufferDiffUpdate> {
1493        let prev_base_text = self.base_text(cx).as_rope().clone();
1494        let base_text_changed = base_text_change.is_some();
1495        let compute_base_text_edits = base_text_change == Some(true);
1496        let diff_options = build_diff_options(
1497            None,
1498            language.as_ref().map(|l| l.name()),
1499            language.as_ref().map(|l| l.default_scope()),
1500            cx,
1501        );
1502        let buffer_snapshot = buffer.clone();
1503
1504        let base_text_diff_task = if base_text_changed && compute_base_text_edits {
1505            base_text
1506                .as_ref()
1507                .map(|new_text| self.inner.base_text.read(cx).diff(new_text.clone(), cx))
1508        } else {
1509            None
1510        };
1511
1512        let hunk_task = cx.background_executor().spawn({
1513            let buffer_snapshot = buffer_snapshot.clone();
1514            async move {
1515                let base_text_rope = if let Some(base_text) = &base_text {
1516                    if base_text_changed {
1517                        Rope::from(base_text.as_ref())
1518                    } else {
1519                        prev_base_text
1520                    }
1521                } else {
1522                    Rope::new()
1523                };
1524                let base_text_exists = base_text.is_some();
1525                let hunks = compute_hunks(
1526                    base_text
1527                        .clone()
1528                        .map(|base_text| (base_text, base_text_rope.clone())),
1529                    buffer.clone(),
1530                    diff_options,
1531                );
1532                let base_text = base_text.unwrap_or_default();
1533                BufferDiffInner {
1534                    base_text,
1535                    hunks,
1536                    base_text_exists,
1537                    pending_hunks: SumTree::new(&buffer),
1538                    buffer_snapshot,
1539                }
1540            }
1541        });
1542
1543        cx.background_executor().spawn(async move {
1544            let (inner, base_text_edits) = match base_text_diff_task {
1545                Some(diff_task) => {
1546                    let (inner, diff) = futures::join!(hunk_task, diff_task);
1547                    (inner, Some(diff))
1548                }
1549                None => (hunk_task.await, None),
1550            };
1551
1552            BufferDiffUpdate {
1553                inner,
1554                buffer_snapshot,
1555                base_text_edits,
1556                base_text_changed,
1557            }
1558        })
1559    }
1560
1561    #[ztracing::instrument(skip_all)]
1562    pub fn language_changed(
1563        &mut self,
1564        language: Option<Arc<Language>>,
1565        language_registry: Option<Arc<LanguageRegistry>>,
1566        cx: &mut Context<Self>,
1567    ) {
1568        let fut = self.inner.base_text.update(cx, |base_text, cx| {
1569            if let Some(language_registry) = language_registry {
1570                base_text.set_language_registry(language_registry);
1571            }
1572            base_text.set_language(language, cx);
1573            base_text.parsing_idle()
1574        });
1575        cx.spawn(async move |this, cx| {
1576            fut.await;
1577            this.update(cx, |_, cx| {
1578                cx.emit(BufferDiffEvent::LanguageChanged);
1579            })
1580            .ok();
1581        })
1582        .detach();
1583    }
1584
1585    fn set_snapshot_with_secondary_inner(
1586        &mut self,
1587        update: BufferDiffUpdate,
1588        buffer: &text::BufferSnapshot,
1589        secondary_diff_change: Option<Range<Anchor>>,
1590        clear_pending_hunks: bool,
1591        cx: &mut Context<Self>,
1592    ) -> impl Future<Output = DiffChanged> + use<> {
1593        log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1594
1595        let old_snapshot = self.snapshot(cx);
1596        let state = &mut self.inner;
1597        let new_state = update.inner;
1598        let base_text_changed = update.base_text_changed;
1599
1600        let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1601        let DiffChanged {
1602            mut changed_range,
1603            mut base_text_changed_range,
1604            mut extended_range,
1605        } = match (state.base_text_exists, new_state.base_text_exists) {
1606            (false, false) => DiffChanged::default(),
1607            (true, true) if !base_text_changed => compare_hunks(
1608                &new_state.hunks,
1609                &old_snapshot.inner.hunks,
1610                old_buffer_snapshot,
1611                buffer,
1612            ),
1613            _ => {
1614                let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
1615                let full_base_range = 0..new_state.base_text.len();
1616                DiffChanged {
1617                    changed_range: Some(full_range.clone()),
1618                    base_text_changed_range: Some(full_base_range),
1619                    extended_range: Some(full_range),
1620                }
1621            }
1622        };
1623
1624        if let Some(secondary_changed_range) = secondary_diff_change
1625            && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1626                old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1627        {
1628            if let Some(range) = &mut changed_range {
1629                range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1630                range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1631            } else {
1632                changed_range = Some(secondary_hunk_range.clone());
1633            }
1634
1635            if let Some(base_text_range) = base_text_changed_range.as_mut() {
1636                base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1637                base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1638            } else {
1639                base_text_changed_range = Some(secondary_base_range);
1640            }
1641
1642            if let Some(ext) = &mut extended_range {
1643                ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1644                ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1645            } else {
1646                extended_range = Some(secondary_hunk_range);
1647            }
1648        }
1649
1650        let state = &mut self.inner;
1651        state.base_text_exists = new_state.base_text_exists;
1652        let parsing_idle = if let Some(diff) = update.base_text_edits {
1653            state.base_text.update(cx, |base_text, cx| {
1654                base_text.set_capability(Capability::ReadWrite, cx);
1655                base_text.apply_diff(diff, cx);
1656                base_text.set_capability(Capability::ReadOnly, cx);
1657                Some(base_text.parsing_idle())
1658            })
1659        } else if update.base_text_changed {
1660            state.base_text.update(cx, |base_text, cx| {
1661                base_text.set_capability(Capability::ReadWrite, cx);
1662                base_text.set_text(new_state.base_text.clone(), cx);
1663                base_text.set_capability(Capability::ReadOnly, cx);
1664                Some(base_text.parsing_idle())
1665            })
1666        } else {
1667            None
1668        };
1669        state.hunks = new_state.hunks;
1670        state.buffer_snapshot = update.buffer_snapshot;
1671        if base_text_changed || clear_pending_hunks {
1672            if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1673            {
1674                let pending_range = first.buffer_range.start..last.buffer_range.end;
1675                if let Some(range) = &mut changed_range {
1676                    range.start = *range.start.min(&pending_range.start, buffer);
1677                    range.end = *range.end.max(&pending_range.end, buffer);
1678                } else {
1679                    changed_range = Some(pending_range.clone());
1680                }
1681
1682                if let Some(base_text_range) = base_text_changed_range.as_mut() {
1683                    base_text_range.start =
1684                        base_text_range.start.min(first.diff_base_byte_range.start);
1685                    base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1686                } else {
1687                    base_text_changed_range =
1688                        Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1689                }
1690
1691                if let Some(ext) = &mut extended_range {
1692                    ext.start = *ext.start.min(&pending_range.start, buffer);
1693                    ext.end = *ext.end.max(&pending_range.end, buffer);
1694                } else {
1695                    extended_range = Some(pending_range);
1696                }
1697            }
1698            state.pending_hunks = SumTree::new(buffer);
1699        }
1700
1701        async move {
1702            if let Some(parsing_idle) = parsing_idle {
1703                parsing_idle.await;
1704            }
1705            DiffChanged {
1706                changed_range,
1707                base_text_changed_range,
1708                extended_range,
1709            }
1710        }
1711    }
1712
1713    pub fn set_snapshot(
1714        &mut self,
1715        new_state: BufferDiffUpdate,
1716        buffer: &text::BufferSnapshot,
1717        cx: &mut Context<Self>,
1718    ) -> Task<Option<Range<Anchor>>> {
1719        self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1720    }
1721
1722    pub fn set_snapshot_with_secondary(
1723        &mut self,
1724        update: BufferDiffUpdate,
1725        buffer: &text::BufferSnapshot,
1726        secondary_diff_change: Option<Range<Anchor>>,
1727        clear_pending_hunks: bool,
1728        cx: &mut Context<Self>,
1729    ) -> Task<Option<Range<Anchor>>> {
1730        let fut = self.set_snapshot_with_secondary_inner(
1731            update,
1732            buffer,
1733            secondary_diff_change,
1734            clear_pending_hunks,
1735            cx,
1736        );
1737
1738        cx.spawn(async move |this, cx| {
1739            let change = fut.await;
1740            this.update(cx, |_, cx| {
1741                cx.emit(BufferDiffEvent::DiffChanged(change.clone()));
1742            })
1743            .ok();
1744            change.changed_range
1745        })
1746    }
1747
1748    pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1749        self.inner.base_text.read(cx).snapshot()
1750    }
1751
1752    pub fn base_text_exists(&self) -> bool {
1753        self.inner.base_text_exists
1754    }
1755
1756    pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1757        BufferDiffSnapshot {
1758            inner: BufferDiffInner {
1759                hunks: self.inner.hunks.clone(),
1760                pending_hunks: self.inner.pending_hunks.clone(),
1761                base_text: self.inner.base_text.read(cx).snapshot(),
1762                base_text_exists: self.inner.base_text_exists,
1763                buffer_snapshot: self.inner.buffer_snapshot.clone(),
1764            },
1765            secondary_diff: self
1766                .secondary_diff
1767                .as_ref()
1768                .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1769        }
1770    }
1771
1772    /// Used in cases where the change set isn't derived from git.
1773    pub fn set_base_text(
1774        &mut self,
1775        base_text: Option<Arc<str>>,
1776        language: Option<Arc<Language>>,
1777        buffer: text::BufferSnapshot,
1778        cx: &mut Context<Self>,
1779    ) -> oneshot::Receiver<()> {
1780        let (tx, rx) = oneshot::channel();
1781        let complete_on_drop = util::defer(|| {
1782            tx.send(()).ok();
1783        });
1784        cx.spawn(async move |this, cx| {
1785            let Some(state) = this
1786                .update(cx, |this, cx| {
1787                    this.update_diff(buffer.clone(), base_text, Some(false), language, cx)
1788                })
1789                .log_err()
1790            else {
1791                return;
1792            };
1793            let state = state.await;
1794            if let Some(task) = this
1795                .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
1796                .log_err()
1797            {
1798                task.await;
1799            }
1800            drop(complete_on_drop)
1801        })
1802        .detach();
1803        rx
1804    }
1805
1806    pub fn base_text_string(&self, cx: &App) -> Option<String> {
1807        self.inner
1808            .base_text_exists
1809            .then(|| self.inner.base_text.read(cx).text())
1810    }
1811
1812    #[cfg(any(test, feature = "test-support"))]
1813    pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1814        let language = self.base_text(cx).language().cloned();
1815        let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1816        let fut = self.update_diff(buffer.clone(), base_text, None, language, cx);
1817        let fg_executor = cx.foreground_executor().clone();
1818        let snapshot = fg_executor.block_on(fut);
1819        let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
1820        let change = fg_executor.block_on(fut);
1821        cx.emit(BufferDiffEvent::DiffChanged(change));
1822    }
1823
1824    pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1825        self.inner.base_text.clone()
1826    }
1827}
1828
1829impl DiffHunk {
1830    pub fn is_created_file(&self) -> bool {
1831        self.diff_base_byte_range == (0..0)
1832            && self.buffer_range.start.is_min()
1833            && self.buffer_range.end.is_max()
1834    }
1835
1836    pub fn status(&self) -> DiffHunkStatus {
1837        let kind = if self.buffer_range.start == self.buffer_range.end {
1838            DiffHunkStatusKind::Deleted
1839        } else if self.diff_base_byte_range.is_empty() {
1840            DiffHunkStatusKind::Added
1841        } else {
1842            DiffHunkStatusKind::Modified
1843        };
1844        DiffHunkStatus {
1845            kind,
1846            secondary: self.secondary_status,
1847        }
1848    }
1849}
1850
1851impl DiffHunkStatus {
1852    pub fn has_secondary_hunk(&self) -> bool {
1853        matches!(
1854            self.secondary,
1855            DiffHunkSecondaryStatus::HasSecondaryHunk
1856                | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1857                | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1858        )
1859    }
1860
1861    pub fn is_pending(&self) -> bool {
1862        matches!(
1863            self.secondary,
1864            DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1865                | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1866        )
1867    }
1868
1869    pub fn is_deleted(&self) -> bool {
1870        self.kind == DiffHunkStatusKind::Deleted
1871    }
1872
1873    pub fn is_added(&self) -> bool {
1874        self.kind == DiffHunkStatusKind::Added
1875    }
1876
1877    pub fn is_modified(&self) -> bool {
1878        self.kind == DiffHunkStatusKind::Modified
1879    }
1880
1881    pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1882        Self {
1883            kind: DiffHunkStatusKind::Added,
1884            secondary,
1885        }
1886    }
1887
1888    pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1889        Self {
1890            kind: DiffHunkStatusKind::Modified,
1891            secondary,
1892        }
1893    }
1894
1895    pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1896        Self {
1897            kind: DiffHunkStatusKind::Deleted,
1898            secondary,
1899        }
1900    }
1901
1902    pub fn deleted_none() -> Self {
1903        Self {
1904            kind: DiffHunkStatusKind::Deleted,
1905            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1906        }
1907    }
1908
1909    pub fn added_none() -> Self {
1910        Self {
1911            kind: DiffHunkStatusKind::Added,
1912            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1913        }
1914    }
1915
1916    pub fn modified_none() -> Self {
1917        Self {
1918            kind: DiffHunkStatusKind::Modified,
1919            secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1920        }
1921    }
1922}
1923
1924#[cfg(any(test, feature = "test-support"))]
1925#[track_caller]
1926pub fn assert_hunks<ExpectedText, HunkIter>(
1927    diff_hunks: HunkIter,
1928    buffer: &text::BufferSnapshot,
1929    diff_base: &str,
1930    // Line range, deleted, added, status
1931    expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1932) where
1933    HunkIter: Iterator<Item = DiffHunk>,
1934    ExpectedText: AsRef<str>,
1935{
1936    let actual_hunks = diff_hunks
1937        .map(|hunk| {
1938            (
1939                hunk.range.clone(),
1940                &diff_base[hunk.diff_base_byte_range.clone()],
1941                buffer
1942                    .text_for_range(hunk.range.clone())
1943                    .collect::<String>(),
1944                hunk.status(),
1945            )
1946        })
1947        .collect::<Vec<_>>();
1948
1949    let expected_hunks: Vec<_> = expected_hunks
1950        .iter()
1951        .map(|(line_range, deleted_text, added_text, status)| {
1952            (
1953                Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1954                deleted_text.as_ref(),
1955                added_text.as_ref().to_string(),
1956                *status,
1957            )
1958        })
1959        .collect();
1960
1961    pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1962}
1963
1964#[cfg(test)]
1965mod tests {
1966    use std::{fmt::Write as _, sync::mpsc};
1967
1968    use super::*;
1969    use gpui::TestAppContext;
1970    use pretty_assertions::{assert_eq, assert_ne};
1971    use rand::{Rng as _, rngs::StdRng};
1972    use text::{Buffer, BufferId, ReplicaId, Rope};
1973    use unindent::Unindent as _;
1974    use util::test::marked_text_ranges;
1975
1976    #[ctor::ctor]
1977    fn init_logger() {
1978        zlog::init_test();
1979    }
1980
1981    #[gpui::test]
1982    async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1983        let diff_base = "
1984            one
1985            two
1986            three
1987        "
1988        .unindent();
1989
1990        let buffer_text = "
1991            one
1992            HELLO
1993            three
1994        "
1995        .unindent();
1996
1997        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1998        let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1999        assert_hunks(
2000            diff.hunks_intersecting_range(
2001                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2002                &buffer,
2003            ),
2004            &buffer,
2005            &diff_base,
2006            &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
2007        );
2008
2009        buffer.edit([(0..0, "point five\n")]);
2010        diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
2011        assert_hunks(
2012            diff.hunks_intersecting_range(
2013                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2014                &buffer,
2015            ),
2016            &buffer,
2017            &diff_base,
2018            &[
2019                (0..1, "", "point five\n", DiffHunkStatus::added_none()),
2020                (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
2021            ],
2022        );
2023
2024        diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2025        assert_hunks::<&str, _>(
2026            diff.hunks_intersecting_range(
2027                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2028                &buffer,
2029            ),
2030            &buffer,
2031            &diff_base,
2032            &[],
2033        );
2034    }
2035
2036    #[gpui::test]
2037    async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
2038        let head_text = "
2039            zero
2040            one
2041            two
2042            three
2043            four
2044            five
2045            six
2046            seven
2047            eight
2048            nine
2049        "
2050        .unindent();
2051
2052        let index_text = "
2053            zero
2054            one
2055            TWO
2056            three
2057            FOUR
2058            five
2059            six
2060            seven
2061            eight
2062            NINE
2063        "
2064        .unindent();
2065
2066        let buffer_text = "
2067            zero
2068            one
2069            TWO
2070            three
2071            FOUR
2072            FIVE
2073            six
2074            SEVEN
2075            eight
2076            nine
2077        "
2078        .unindent();
2079
2080        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2081        let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
2082        let mut uncommitted_diff =
2083            BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
2084        uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
2085
2086        let expected_hunks = vec![
2087            (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
2088            (
2089                4..6,
2090                "four\nfive\n",
2091                "FOUR\nFIVE\n",
2092                DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
2093            ),
2094            (
2095                7..8,
2096                "seven\n",
2097                "SEVEN\n",
2098                DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
2099            ),
2100        ];
2101
2102        assert_hunks(
2103            uncommitted_diff.hunks_intersecting_range(
2104                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2105                &buffer,
2106            ),
2107            &buffer,
2108            &head_text,
2109            &expected_hunks,
2110        );
2111    }
2112
2113    #[gpui::test]
2114    async fn test_buffer_diff_range(cx: &mut TestAppContext) {
2115        let diff_base = "
2116            one
2117            two
2118            three
2119            four
2120            five
2121            six
2122            seven
2123            eight
2124            nine
2125            ten
2126        "
2127        .unindent();
2128
2129        let buffer_text = "
2130            A
2131            one
2132            B
2133            two
2134            C
2135            three
2136            HELLO
2137            four
2138            five
2139            SIXTEEN
2140            seven
2141            eight
2142            WORLD
2143            nine
2144
2145            ten
2146
2147        "
2148        .unindent();
2149
2150        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2151        let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
2152        assert_eq!(
2153            diff.hunks_intersecting_range(
2154                Anchor::min_max_range_for_buffer(buffer.remote_id()),
2155                &buffer
2156            )
2157            .count(),
2158            8
2159        );
2160
2161        assert_hunks(
2162            diff.hunks_intersecting_range(
2163                buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
2164                &buffer,
2165            ),
2166            &buffer,
2167            &diff_base,
2168            &[
2169                (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
2170                (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
2171                (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
2172            ],
2173        );
2174    }
2175
2176    #[gpui::test]
2177    async fn test_stage_hunk(cx: &mut TestAppContext) {
2178        struct Example {
2179            name: &'static str,
2180            head_text: String,
2181            index_text: String,
2182            buffer_marked_text: String,
2183            final_index_text: String,
2184        }
2185
2186        let table = [
2187            Example {
2188                name: "uncommitted hunk straddles end of unstaged hunk",
2189                head_text: "
2190                    one
2191                    two
2192                    three
2193                    four
2194                    five
2195                "
2196                .unindent(),
2197                index_text: "
2198                    one
2199                    TWO_HUNDRED
2200                    three
2201                    FOUR_HUNDRED
2202                    five
2203                "
2204                .unindent(),
2205                buffer_marked_text: "
2206                    ZERO
2207                    one
2208                    two
2209                    «THREE_HUNDRED
2210                    FOUR_HUNDRED»
2211                    five
2212                    SIX
2213                "
2214                .unindent(),
2215                final_index_text: "
2216                    one
2217                    two
2218                    THREE_HUNDRED
2219                    FOUR_HUNDRED
2220                    five
2221                "
2222                .unindent(),
2223            },
2224            Example {
2225                name: "uncommitted hunk straddles start of unstaged hunk",
2226                head_text: "
2227                    one
2228                    two
2229                    three
2230                    four
2231                    five
2232                "
2233                .unindent(),
2234                index_text: "
2235                    one
2236                    TWO_HUNDRED
2237                    three
2238                    FOUR_HUNDRED
2239                    five
2240                "
2241                .unindent(),
2242                buffer_marked_text: "
2243                    ZERO
2244                    one
2245                    «TWO_HUNDRED
2246                    THREE_HUNDRED»
2247                    four
2248                    five
2249                    SIX
2250                "
2251                .unindent(),
2252                final_index_text: "
2253                    one
2254                    TWO_HUNDRED
2255                    THREE_HUNDRED
2256                    four
2257                    five
2258                "
2259                .unindent(),
2260            },
2261            Example {
2262                name: "uncommitted hunk strictly contains unstaged hunks",
2263                head_text: "
2264                    one
2265                    two
2266                    three
2267                    four
2268                    five
2269                    six
2270                    seven
2271                "
2272                .unindent(),
2273                index_text: "
2274                    one
2275                    TWO
2276                    THREE
2277                    FOUR
2278                    FIVE
2279                    SIX
2280                    seven
2281                "
2282                .unindent(),
2283                buffer_marked_text: "
2284                    one
2285                    TWO
2286                    «THREE_HUNDRED
2287                    FOUR
2288                    FIVE_HUNDRED»
2289                    SIX
2290                    seven
2291                "
2292                .unindent(),
2293                final_index_text: "
2294                    one
2295                    TWO
2296                    THREE_HUNDRED
2297                    FOUR
2298                    FIVE_HUNDRED
2299                    SIX
2300                    seven
2301                "
2302                .unindent(),
2303            },
2304            Example {
2305                name: "uncommitted deletion hunk",
2306                head_text: "
2307                    one
2308                    two
2309                    three
2310                    four
2311                    five
2312                "
2313                .unindent(),
2314                index_text: "
2315                    one
2316                    two
2317                    three
2318                    four
2319                    five
2320                "
2321                .unindent(),
2322                buffer_marked_text: "
2323                    one
2324                    ˇfive
2325                "
2326                .unindent(),
2327                final_index_text: "
2328                    one
2329                    five
2330                "
2331                .unindent(),
2332            },
2333            Example {
2334                name: "one unstaged hunk that contains two uncommitted hunks",
2335                head_text: "
2336                    one
2337                    two
2338
2339                    three
2340                    four
2341                "
2342                .unindent(),
2343                index_text: "
2344                    one
2345                    two
2346                    three
2347                    four
2348                "
2349                .unindent(),
2350                buffer_marked_text: "
2351                    «one
2352
2353                    three // modified
2354                    four»
2355                "
2356                .unindent(),
2357                final_index_text: "
2358                    one
2359
2360                    three // modified
2361                    four
2362                "
2363                .unindent(),
2364            },
2365            Example {
2366                name: "one uncommitted hunk that contains two unstaged hunks",
2367                head_text: "
2368                    one
2369                    two
2370                    three
2371                    four
2372                    five
2373                "
2374                .unindent(),
2375                index_text: "
2376                    ZERO
2377                    one
2378                    TWO
2379                    THREE
2380                    FOUR
2381                    five
2382                "
2383                .unindent(),
2384                buffer_marked_text: "
2385                    «one
2386                    TWO_HUNDRED
2387                    THREE
2388                    FOUR_HUNDRED
2389                    five»
2390                "
2391                .unindent(),
2392                final_index_text: "
2393                    ZERO
2394                    one
2395                    TWO_HUNDRED
2396                    THREE
2397                    FOUR_HUNDRED
2398                    five
2399                "
2400                .unindent(),
2401            },
2402        ];
2403
2404        for example in table {
2405            let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2406            let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2407            let hunk_range =
2408                buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2409
2410            let unstaged_diff =
2411                cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2412
2413            let uncommitted_diff = cx.new(|cx| {
2414                let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2415                diff.set_secondary_diff(unstaged_diff);
2416                diff
2417            });
2418
2419            uncommitted_diff.update(cx, |diff, cx| {
2420                let hunks = diff
2421                    .snapshot(cx)
2422                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2423                    .collect::<Vec<_>>();
2424                for hunk in &hunks {
2425                    assert_ne!(
2426                        hunk.secondary_status,
2427                        DiffHunkSecondaryStatus::NoSecondaryHunk
2428                    )
2429                }
2430
2431                let new_index_text = diff
2432                    .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2433                    .unwrap()
2434                    .to_string();
2435
2436                let hunks = diff
2437                    .snapshot(cx)
2438                    .hunks_intersecting_range(hunk_range.clone(), &buffer)
2439                    .collect::<Vec<_>>();
2440                for hunk in &hunks {
2441                    assert_eq!(
2442                        hunk.secondary_status,
2443                        DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2444                    )
2445                }
2446
2447                pretty_assertions::assert_eq!(
2448                    new_index_text,
2449                    example.final_index_text,
2450                    "example: {}",
2451                    example.name
2452                );
2453            });
2454        }
2455    }
2456
2457    #[gpui::test]
2458    async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2459        // This test reproduces a crash where staging all hunks would cause an underflow
2460        // when there's one large unstaged hunk containing multiple uncommitted hunks.
2461        let head_text = "
2462            aaa
2463            bbb
2464            ccc
2465            ddd
2466            eee
2467            fff
2468            ggg
2469            hhh
2470            iii
2471            jjj
2472            kkk
2473            lll
2474        "
2475        .unindent();
2476
2477        let index_text = "
2478            aaa
2479            bbb
2480            CCC-index
2481            DDD-index
2482            EEE-index
2483            FFF-index
2484            GGG-index
2485            HHH-index
2486            III-index
2487            JJJ-index
2488            kkk
2489            lll
2490        "
2491        .unindent();
2492
2493        let buffer_text = "
2494            aaa
2495            bbb
2496            ccc-modified
2497            ddd
2498            eee-modified
2499            fff
2500            ggg
2501            hhh-modified
2502            iii
2503            jjj
2504            kkk
2505            lll
2506        "
2507        .unindent();
2508
2509        let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2510
2511        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2512        let uncommitted_diff = cx.new(|cx| {
2513            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2514            diff.set_secondary_diff(unstaged_diff);
2515            diff
2516        });
2517
2518        uncommitted_diff.update(cx, |diff, cx| {
2519            diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2520        });
2521    }
2522
2523    #[gpui::test]
2524    async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2525        let head_text = "
2526            one
2527            two
2528            three
2529        "
2530        .unindent();
2531        let index_text = head_text.clone();
2532        let buffer_text = "
2533            one
2534            three
2535        "
2536        .unindent();
2537
2538        let buffer = Buffer::new(
2539            ReplicaId::LOCAL,
2540            BufferId::new(1).unwrap(),
2541            buffer_text.clone(),
2542        );
2543        let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2544        let uncommitted_diff = cx.new(|cx| {
2545            let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2546            diff.set_secondary_diff(unstaged_diff.clone());
2547            diff
2548        });
2549
2550        uncommitted_diff.update(cx, |diff, cx| {
2551            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2552
2553            let new_index_text = diff
2554                .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2555                .unwrap()
2556                .to_string();
2557            assert_eq!(new_index_text, buffer_text);
2558
2559            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2560            assert_eq!(
2561                hunk.secondary_status,
2562                DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2563            );
2564
2565            let index_text = diff
2566                .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2567                .unwrap()
2568                .to_string();
2569            assert_eq!(index_text, head_text);
2570
2571            let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2572            // optimistically unstaged (fine, could also be HasSecondaryHunk)
2573            assert_eq!(
2574                hunk.secondary_status,
2575                DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2576            );
2577        });
2578    }
2579
2580    #[gpui::test]
2581    async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2582        let base_text = "
2583            zero
2584            one
2585            two
2586            three
2587            four
2588            five
2589            six
2590            seven
2591            eight
2592            nine
2593        "
2594        .unindent();
2595
2596        let buffer_text_1 = "
2597            one
2598            three
2599            four
2600            five
2601            SIX
2602            seven
2603            eight
2604            NINE
2605        "
2606        .unindent();
2607
2608        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2609
2610        let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2611        let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2612        let DiffChanged {
2613            changed_range,
2614            base_text_changed_range,
2615            extended_range: _,
2616        } = compare_hunks(
2617            &diff_1.inner.hunks,
2618            &empty_diff.inner.hunks,
2619            &buffer,
2620            &buffer,
2621        );
2622        let range = changed_range.unwrap();
2623        assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2624        let base_text_range = base_text_changed_range.unwrap();
2625        assert_eq!(
2626            base_text_range.to_point(diff_1.base_text()),
2627            Point::new(0, 0)..Point::new(10, 0)
2628        );
2629
2630        // Edit does affects the diff because it recalculates word diffs.
2631        buffer.edit_via_marked_text(
2632            &"
2633                one
2634                three
2635                four
2636                five
2637                «SIX.5»
2638                seven
2639                eight
2640                NINE
2641            "
2642            .unindent(),
2643        );
2644        let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2645        let DiffChanged {
2646            changed_range,
2647            base_text_changed_range,
2648            extended_range: _,
2649        } = compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer, &buffer);
2650        assert_eq!(
2651            changed_range.unwrap().to_point(&buffer),
2652            Point::new(4, 0)..Point::new(5, 0),
2653        );
2654        assert_eq!(
2655            base_text_changed_range
2656                .unwrap()
2657                .to_point(diff_2.base_text()),
2658            Point::new(6, 0)..Point::new(7, 0),
2659        );
2660
2661        // Edit turns a deletion hunk into a modification.
2662        buffer.edit_via_marked_text(
2663            &"
2664                one
2665                «THREE»
2666                four
2667                five
2668                SIX.5
2669                seven
2670                eight
2671                NINE
2672            "
2673            .unindent(),
2674        );
2675        let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2676        let DiffChanged {
2677            changed_range,
2678            base_text_changed_range,
2679            extended_range: _,
2680        } = compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer, &buffer);
2681        let range = changed_range.unwrap();
2682        assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2683        let base_text_range = base_text_changed_range.unwrap();
2684        assert_eq!(
2685            base_text_range.to_point(diff_3.base_text()),
2686            Point::new(2, 0)..Point::new(4, 0)
2687        );
2688
2689        // Edit turns a modification hunk into a deletion.
2690        buffer.edit_via_marked_text(
2691            &"
2692                one
2693                THREE
2694                four
2695                five«»
2696                seven
2697                eight
2698                NINE
2699            "
2700            .unindent(),
2701        );
2702        let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2703        let DiffChanged {
2704            changed_range,
2705            base_text_changed_range,
2706            extended_range: _,
2707        } = compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer, &buffer);
2708        let range = changed_range.unwrap();
2709        assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2710        let base_text_range = base_text_changed_range.unwrap();
2711        assert_eq!(
2712            base_text_range.to_point(diff_4.base_text()),
2713            Point::new(6, 0)..Point::new(7, 0)
2714        );
2715
2716        // Edit introduces a new insertion hunk.
2717        buffer.edit_via_marked_text(
2718            &"
2719                one
2720                THREE
2721                four«
2722                FOUR.5
2723                »five
2724                seven
2725                eight
2726                NINE
2727            "
2728            .unindent(),
2729        );
2730        let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2731        let DiffChanged {
2732            changed_range,
2733            base_text_changed_range,
2734            extended_range: _,
2735        } = compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer, &buffer);
2736        let range = changed_range.unwrap();
2737        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2738        let base_text_range = base_text_changed_range.unwrap();
2739        assert_eq!(
2740            base_text_range.to_point(diff_5.base_text()),
2741            Point::new(5, 0)..Point::new(5, 0)
2742        );
2743
2744        // Edit removes a hunk.
2745        buffer.edit_via_marked_text(
2746            &"
2747                one
2748                THREE
2749                four
2750                FOUR.5
2751                five
2752                seven
2753                eight
2754                «nine»
2755            "
2756            .unindent(),
2757        );
2758        let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2759        let DiffChanged {
2760            changed_range,
2761            base_text_changed_range,
2762            extended_range: _,
2763        } = compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer, &buffer);
2764        let range = changed_range.unwrap();
2765        assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2766        let base_text_range = base_text_changed_range.unwrap();
2767        assert_eq!(
2768            base_text_range.to_point(diff_6.base_text()),
2769            Point::new(9, 0)..Point::new(10, 0)
2770        );
2771    }
2772
2773    #[gpui::test(iterations = 100)]
2774    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2775        fn gen_line(rng: &mut StdRng) -> String {
2776            if rng.random_bool(0.2) {
2777                "\n".to_owned()
2778            } else {
2779                let c = rng.random_range('A'..='Z');
2780                format!("{c}{c}{c}\n")
2781            }
2782        }
2783
2784        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2785            let mut old_lines = {
2786                let mut old_lines = Vec::new();
2787                let old_lines_iter = head.lines();
2788                for line in old_lines_iter {
2789                    assert!(!line.ends_with("\n"));
2790                    old_lines.push(line.to_owned());
2791                }
2792                if old_lines.last().is_some_and(|line| line.is_empty()) {
2793                    old_lines.pop();
2794                }
2795                old_lines.into_iter()
2796            };
2797            let mut result = String::new();
2798            let unchanged_count = rng.random_range(0..=old_lines.len());
2799            result +=
2800                &old_lines
2801                    .by_ref()
2802                    .take(unchanged_count)
2803                    .fold(String::new(), |mut s, line| {
2804                        writeln!(&mut s, "{line}").unwrap();
2805                        s
2806                    });
2807            while old_lines.len() > 0 {
2808                let deleted_count = rng.random_range(0..=old_lines.len());
2809                let _advance = old_lines
2810                    .by_ref()
2811                    .take(deleted_count)
2812                    .map(|line| line.len() + 1)
2813                    .sum::<usize>();
2814                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2815                let added_count = rng.random_range(minimum_added..=5);
2816                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2817                result += &addition;
2818
2819                if old_lines.len() > 0 {
2820                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2821                    if blank_lines == old_lines.len() {
2822                        break;
2823                    };
2824                    let unchanged_count =
2825                        rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2826                    result += &old_lines.by_ref().take(unchanged_count).fold(
2827                        String::new(),
2828                        |mut s, line| {
2829                            writeln!(&mut s, "{line}").unwrap();
2830                            s
2831                        },
2832                    );
2833                }
2834            }
2835            result
2836        }
2837
2838        fn uncommitted_diff(
2839            working_copy: &language::BufferSnapshot,
2840            index_text: &Rope,
2841            head_text: String,
2842            cx: &mut TestAppContext,
2843        ) -> Entity<BufferDiff> {
2844            let secondary = cx.new(|cx| {
2845                BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2846            });
2847            cx.new(|cx| {
2848                let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2849                diff.secondary_diff = Some(secondary);
2850                diff
2851            })
2852        }
2853
2854        let operations = std::env::var("OPERATIONS")
2855            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2856            .unwrap_or(10);
2857
2858        let rng = &mut rng;
2859        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2860            writeln!(&mut s, "{c}{c}{c}").unwrap();
2861            s
2862        });
2863        let working_copy = gen_working_copy(rng, &head_text);
2864        let working_copy = cx.new(|cx| {
2865            language::Buffer::local_normalized(
2866                Rope::from(working_copy.as_str()),
2867                text::LineEnding::default(),
2868                cx,
2869            )
2870        });
2871        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2872        let mut index_text = if rng.random() {
2873            Rope::from(head_text.as_str())
2874        } else {
2875            working_copy.as_rope().clone()
2876        };
2877
2878        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2879        let mut hunks = diff.update(cx, |diff, cx| {
2880            diff.snapshot(cx)
2881                .hunks_intersecting_range(
2882                    Anchor::min_max_range_for_buffer(diff.buffer_id),
2883                    &working_copy,
2884                )
2885                .collect::<Vec<_>>()
2886        });
2887        if hunks.is_empty() {
2888            return;
2889        }
2890
2891        for _ in 0..operations {
2892            let i = rng.random_range(0..hunks.len());
2893            let hunk = &mut hunks[i];
2894            let hunk_to_change = hunk.clone();
2895            let stage = match hunk.secondary_status {
2896                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2897                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2898                    true
2899                }
2900                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2901                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2902                    false
2903                }
2904                _ => unreachable!(),
2905            };
2906
2907            index_text = diff.update(cx, |diff, cx| {
2908                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2909                    .unwrap()
2910            });
2911
2912            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2913            let found_hunks = diff.update(cx, |diff, cx| {
2914                diff.snapshot(cx)
2915                    .hunks_intersecting_range(
2916                        Anchor::min_max_range_for_buffer(diff.buffer_id),
2917                        &working_copy,
2918                    )
2919                    .collect::<Vec<_>>()
2920            });
2921            assert_eq!(hunks.len(), found_hunks.len());
2922
2923            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2924                assert_eq!(
2925                    expected_hunk.buffer_range.to_point(&working_copy),
2926                    found_hunk.buffer_range.to_point(&working_copy)
2927                );
2928                assert_eq!(
2929                    expected_hunk.diff_base_byte_range,
2930                    found_hunk.diff_base_byte_range
2931                );
2932                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2933            }
2934            hunks = found_hunks;
2935        }
2936    }
2937
2938    #[gpui::test]
2939    async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2940        let base_text = "
2941            one
2942            two
2943            three
2944            four
2945            five
2946            six
2947        "
2948        .unindent();
2949        let buffer_text = "
2950            one
2951            TWO
2952            three
2953            four
2954            FIVE
2955            six
2956        "
2957        .unindent();
2958        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
2959        let diff = cx.new(|cx| {
2960            BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
2961        });
2962        cx.run_until_parked();
2963        let (tx, rx) = mpsc::channel();
2964        let subscription =
2965            cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
2966
2967        let snapshot = buffer.update(cx, |buffer, cx| {
2968            buffer.set_text(
2969                "
2970                ONE
2971                TWO
2972                THREE
2973                FOUR
2974                FIVE
2975                SIX
2976            "
2977                .unindent(),
2978                cx,
2979            );
2980            buffer.text_snapshot()
2981        });
2982        let update = diff
2983            .update(cx, |diff, cx| {
2984                diff.update_diff(
2985                    snapshot.clone(),
2986                    Some(base_text.as_str().into()),
2987                    None,
2988                    None,
2989                    cx,
2990                )
2991            })
2992            .await;
2993        diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
2994            .await;
2995        cx.run_until_parked();
2996        drop(subscription);
2997        let events = rx.into_iter().collect::<Vec<_>>();
2998        match events.as_slice() {
2999            [
3000                BufferDiffEvent::DiffChanged(DiffChanged {
3001                    changed_range: _,
3002                    base_text_changed_range,
3003                    extended_range: _,
3004                }),
3005            ] => {
3006                // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3007                // assert_eq!(
3008                //     *changed_range,
3009                //     Some(Anchor::min_max_range_for_buffer(
3010                //         buffer.read_with(cx, |buffer, _| buffer.remote_id())
3011                //     ))
3012                // );
3013                assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3014            }
3015            _ => panic!("unexpected events: {:?}", events),
3016        }
3017    }
3018
3019    #[gpui::test]
3020    async fn test_extended_range(cx: &mut TestAppContext) {
3021        let base_text = "
3022            aaa
3023            bbb
3024
3025
3026
3027
3028
3029            ccc
3030            ddd
3031        "
3032        .unindent();
3033
3034        let buffer_text = "
3035            aaa
3036            bbb
3037
3038
3039
3040
3041
3042            CCC
3043            ddd
3044        "
3045        .unindent();
3046
3047        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3048        let old_buffer = buffer.snapshot();
3049        let diff_a = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
3050
3051        buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3052        let diff_b = BufferDiffSnapshot::new_sync(buffer.clone(), base_text, cx);
3053
3054        let DiffChanged {
3055            changed_range,
3056            base_text_changed_range: _,
3057            extended_range,
3058        } = compare_hunks(
3059            &diff_b.inner.hunks,
3060            &diff_a.inner.hunks,
3061            &old_buffer,
3062            &buffer,
3063        );
3064
3065        let changed_range = changed_range.unwrap();
3066        assert_eq!(
3067            changed_range.to_point(&buffer),
3068            Point::new(7, 0)..Point::new(9, 0),
3069            "changed_range should span from old hunk position to new hunk end"
3070        );
3071
3072        let extended_range = extended_range.unwrap();
3073        assert_eq!(
3074            extended_range.start.to_point(&buffer),
3075            Point::new(1, 3),
3076            "extended_range.start should extend to include the edit outside changed_range"
3077        );
3078        assert_eq!(
3079            extended_range.end.to_point(&buffer),
3080            Point::new(9, 0),
3081            "extended_range.end should collapse to changed_range.end when no edits in end margin"
3082        );
3083
3084        let base_text_2 = "
3085            one
3086            two
3087            three
3088            four
3089            five
3090            six
3091            seven
3092            eight
3093        "
3094        .unindent();
3095
3096        let buffer_text_2 = "
3097            ONE
3098            two
3099            THREE
3100            four
3101            FIVE
3102            six
3103            SEVEN
3104            eight
3105        "
3106        .unindent();
3107
3108        let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3109        let old_buffer_2 = buffer_2.snapshot();
3110        let diff_2a = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2.clone(), cx);
3111
3112        buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3113        let diff_2b = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2, cx);
3114
3115        let DiffChanged {
3116            changed_range,
3117            base_text_changed_range: _,
3118            extended_range,
3119        } = compare_hunks(
3120            &diff_2b.inner.hunks,
3121            &diff_2a.inner.hunks,
3122            &old_buffer_2,
3123            &buffer_2,
3124        );
3125
3126        let changed_range = changed_range.unwrap();
3127        assert_eq!(
3128            changed_range.to_point(&buffer_2),
3129            Point::new(4, 0)..Point::new(5, 0),
3130            "changed_range should be just the hunk that changed (FIVE)"
3131        );
3132
3133        let extended_range = extended_range.unwrap();
3134        assert_eq!(
3135            extended_range.to_point(&buffer_2),
3136            Point::new(4, 0)..Point::new(5, 0),
3137            "extended_range should equal changed_range when edit is within the hunk"
3138        );
3139    }
3140
3141    fn assert_rows_to_base_text_rows_visual(
3142        buffer: &Entity<language::Buffer>,
3143        diff: &Entity<BufferDiff>,
3144        source_text: &str,
3145        annotated_target: &str,
3146        cx: &mut gpui::TestAppContext,
3147    ) {
3148        let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3149
3150        let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3151        let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3152
3153        assert_eq!(
3154            buffer.text(),
3155            source_text,
3156            "buffer text does not match source text"
3157        );
3158
3159        assert_eq!(
3160            diff.base_text_string().unwrap_or_default(),
3161            target_text,
3162            "base text does not match stripped annotated target"
3163        );
3164
3165        let num_rows = source_text.lines().count() as u32;
3166        let max_point = buffer.max_point();
3167        let points = (0..=num_rows).map(move |row| {
3168            if row == num_rows && max_point.column > 0 {
3169                max_point
3170            } else {
3171                Point::new(row, 0)
3172            }
3173        });
3174        let actual_ranges: Vec<_> = diff.points_to_base_text_points(points, &buffer).0.collect();
3175
3176        assert_eq!(
3177            actual_ranges, expected_ranges,
3178            "\nsource (buffer):\n{}\ntarget (base):\n{}\nexpected: {:?}\nactual: {:?}",
3179            source_text, target_text, expected_ranges, actual_ranges
3180        );
3181    }
3182
3183    fn assert_base_text_rows_to_rows_visual(
3184        buffer: &Entity<language::Buffer>,
3185        diff: &Entity<BufferDiff>,
3186        source_text: &str,
3187        annotated_target: &str,
3188        cx: &mut gpui::TestAppContext,
3189    ) {
3190        let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3191
3192        let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3193        let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3194
3195        assert_eq!(
3196            diff.base_text_string().unwrap_or_default(),
3197            source_text,
3198            "base text does not match source text"
3199        );
3200
3201        assert_eq!(
3202            buffer.text(),
3203            target_text,
3204            "buffer text does not match stripped annotated target"
3205        );
3206
3207        let num_rows = source_text.lines().count() as u32;
3208        let base_max_point = diff.base_text().max_point();
3209        let points = (0..=num_rows).map(move |row| {
3210            if row == num_rows && base_max_point.column > 0 {
3211                base_max_point
3212            } else {
3213                Point::new(row, 0)
3214            }
3215        });
3216        let actual_ranges: Vec<_> = diff.base_text_points_to_points(points, &buffer).0.collect();
3217
3218        assert_eq!(
3219            actual_ranges, expected_ranges,
3220            "\nsource (base):\n{}\ntarget (buffer):\n{}\nexpected: {:?}\nactual: {:?}",
3221            source_text, target_text, expected_ranges, actual_ranges
3222        );
3223    }
3224
3225    fn parse_row_annotations(annotated_text: &str) -> (String, Vec<Range<Point>>) {
3226        let mut starts: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3227        let mut ends: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3228
3229        let mut clean_text = String::new();
3230        let mut current_point = Point::new(0, 0);
3231        let mut chars = annotated_text.chars().peekable();
3232
3233        while let Some(c) = chars.next() {
3234            if c == '<' {
3235                let mut num_str = String::new();
3236                while let Some(&next) = chars.peek() {
3237                    if next.is_ascii_digit() {
3238                        num_str.push(chars.next().unwrap());
3239                    } else {
3240                        break;
3241                    }
3242                }
3243                if !num_str.is_empty() {
3244                    let row_num: u32 = num_str.parse().unwrap();
3245                    starts.insert(row_num, current_point);
3246
3247                    if chars.peek() == Some(&'>') {
3248                        chars.next();
3249                        ends.insert(row_num, current_point);
3250                    }
3251                } else {
3252                    clean_text.push(c);
3253                    current_point.column += 1;
3254                }
3255            } else if c.is_ascii_digit() {
3256                let mut num_str = String::from(c);
3257                while let Some(&next) = chars.peek() {
3258                    if next.is_ascii_digit() {
3259                        num_str.push(chars.next().unwrap());
3260                    } else {
3261                        break;
3262                    }
3263                }
3264                if chars.peek() == Some(&'>') {
3265                    chars.next();
3266                    let row_num: u32 = num_str.parse().unwrap();
3267                    ends.insert(row_num, current_point);
3268                } else {
3269                    for ch in num_str.chars() {
3270                        clean_text.push(ch);
3271                        current_point.column += 1;
3272                    }
3273                }
3274            } else if c == '\n' {
3275                clean_text.push(c);
3276                current_point.row += 1;
3277                current_point.column = 0;
3278            } else {
3279                clean_text.push(c);
3280                current_point.column += 1;
3281            }
3282        }
3283
3284        let max_row = starts.keys().chain(ends.keys()).max().copied().unwrap_or(0);
3285        let mut ranges: Vec<Range<Point>> = Vec::new();
3286        for row in 0..=max_row {
3287            let start = starts.get(&row).copied().unwrap_or(Point::new(0, 0));
3288            let end = ends.get(&row).copied().unwrap_or(start);
3289            ranges.push(start..end);
3290        }
3291
3292        (clean_text, ranges)
3293    }
3294
3295    fn make_diff(
3296        base_text: &str,
3297        buffer_text: &str,
3298        cx: &mut gpui::TestAppContext,
3299    ) -> (Entity<language::Buffer>, Entity<BufferDiff>) {
3300        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3301        let diff = cx.new(|cx| {
3302            BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
3303        });
3304        (buffer, diff)
3305    }
3306
3307    #[gpui::test]
3308    async fn test_row_translation_visual(cx: &mut gpui::TestAppContext) {
3309        use unindent::Unindent;
3310
3311        {
3312            let buffer_text = "
3313                aaa
3314                bbb
3315                ccc
3316            "
3317            .unindent();
3318            let annotated_base = "
3319                <0>aaa
3320                <1>bbb
3321                <2>ccc
3322                <3>"
3323            .unindent();
3324            let (base_text, _) = parse_row_annotations(&annotated_base);
3325            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3326            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3327        }
3328
3329        {
3330            let base_text = "
3331                aaa
3332                bbb
3333                ccc
3334            "
3335            .unindent();
3336            let annotated_buffer = "
3337                <0>aaa
3338                <1>bbb
3339                <2>ccc
3340                <3>"
3341            .unindent();
3342            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3343            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3344            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3345        }
3346
3347        {
3348            let buffer_text = "
3349                XXX
3350                bbb
3351                ccc
3352            "
3353            .unindent();
3354            let annotated_base = "
3355                <0<1aaa
3356                0>1>bbb
3357                <2>ccc
3358                <3>"
3359            .unindent();
3360            let (base_text, _) = parse_row_annotations(&annotated_base);
3361            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3362            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3363        }
3364
3365        {
3366            let buffer_text = "
3367                aaa
3368                NEW
3369                ccc
3370            "
3371            .unindent();
3372            let annotated_base = "
3373                <0>aaa
3374                <1><2>ccc
3375                <3>"
3376            .unindent();
3377            let (base_text, _) = parse_row_annotations(&annotated_base);
3378            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3379            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3380        }
3381
3382        {
3383            let base_text = "
3384                aaa
3385                ccc
3386            "
3387            .unindent();
3388            let annotated_buffer = "
3389                <0>aaa
3390                <1NEW
3391                1>ccc
3392                <2>"
3393            .unindent();
3394            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3395            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3396            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3397        }
3398
3399        {
3400            let buffer_text = "aaa\nbbb";
3401            let annotated_base = "<0>aaa\n<1>bbb<2>";
3402            let (base_text, _) = parse_row_annotations(annotated_base);
3403            let (buffer, diff) = make_diff(&base_text, buffer_text, cx);
3404            assert_rows_to_base_text_rows_visual(&buffer, &diff, buffer_text, annotated_base, cx);
3405            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, annotated_base, cx);
3406        }
3407
3408        {
3409            let base_text = "
3410                aaa
3411                bbb
3412                ccc
3413            "
3414            .unindent();
3415            let annotated_buffer = "
3416                <0<1XXX
3417                0>1>bbb
3418                <2>ccc
3419                <3>"
3420            .unindent();
3421            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3422            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3423            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3424        }
3425
3426        {
3427            let buffer_text = "
3428                aaa
3429                bbb
3430                XXX
3431            "
3432            .unindent();
3433            let annotated_base = "
3434                <0>aaa
3435                <1>bbb
3436                <2<3ccc
3437                2>3>"
3438                .unindent();
3439            let (base_text, _) = parse_row_annotations(&annotated_base);
3440            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3441            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3442        }
3443
3444        {
3445            let base_text = "
3446                aaa
3447                bbb
3448                ccc
3449            "
3450            .unindent();
3451            let annotated_buffer = "
3452                <0>aaa
3453                <1>bbb
3454                <2<3XXX
3455                2>3>"
3456                .unindent();
3457            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3458            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3459            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3460        }
3461
3462        {
3463            let buffer_text = "
3464                aaa
3465                ccc
3466            "
3467            .unindent();
3468            let annotated_base = "
3469                <0>aaa
3470                <1DELETED
3471                1>ccc
3472                <2>"
3473            .unindent();
3474            let (base_text, _) = parse_row_annotations(&annotated_base);
3475            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3476            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3477        }
3478
3479        {
3480            let base_text = "
3481                aaa
3482                DELETED
3483                ccc
3484            "
3485            .unindent();
3486            let annotated_buffer = "
3487                <0>aaa
3488                <1><2>ccc
3489                <3>"
3490            .unindent();
3491            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3492            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3493            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3494        }
3495    }
3496
3497    #[gpui::test]
3498    async fn test_row_translation_with_edits_since_diff(cx: &mut gpui::TestAppContext) {
3499        use unindent::Unindent;
3500
3501        {
3502            let base_text = "
3503                aaa
3504                bbb
3505                ccc
3506            "
3507            .unindent();
3508            let buffer_text = base_text.clone();
3509            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3510
3511            buffer.update(cx, |buffer, cx| {
3512                buffer.edit([(4..7, "XXX")], None, cx);
3513            });
3514
3515            let new_buffer_text = "
3516                aaa
3517                XXX
3518                ccc
3519            "
3520            .unindent();
3521            let annotated_base = "
3522                <0>aaa
3523                <1bbb1>
3524                <2>ccc
3525                <3>"
3526            .unindent();
3527            assert_rows_to_base_text_rows_visual(
3528                &buffer,
3529                &diff,
3530                &new_buffer_text,
3531                &annotated_base,
3532                cx,
3533            );
3534        }
3535
3536        {
3537            let base_text = "
3538                aaa
3539                bbb
3540                ccc
3541            "
3542            .unindent();
3543            let buffer_text = base_text.clone();
3544            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3545
3546            buffer.update(cx, |buffer, cx| {
3547                buffer.edit([(4..7, "XXX")], None, cx);
3548            });
3549
3550            let annotated_buffer = "
3551                <0>aaa
3552                <1XXX1>
3553                <2>ccc
3554                <3>"
3555            .unindent();
3556            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3557        }
3558
3559        {
3560            let base_text = "
3561                aaa
3562                bbb
3563                ccc
3564            "
3565            .unindent();
3566            let buffer_text = base_text.clone();
3567            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3568
3569            buffer.update(cx, |buffer, cx| {
3570                buffer.edit([(4..4, "NEW\n")], None, cx);
3571            });
3572
3573            let new_buffer_text = "
3574                aaa
3575                NEW
3576                bbb
3577                ccc
3578            "
3579            .unindent();
3580            let annotated_base = "
3581                <0>aaa
3582                <1><2>bbb
3583                <3>ccc
3584                <4>"
3585            .unindent();
3586            assert_rows_to_base_text_rows_visual(
3587                &buffer,
3588                &diff,
3589                &new_buffer_text,
3590                &annotated_base,
3591                cx,
3592            );
3593        }
3594
3595        {
3596            let base_text = "
3597                aaa
3598                bbb
3599                ccc
3600            "
3601            .unindent();
3602            let buffer_text = base_text.clone();
3603            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3604
3605            buffer.update(cx, |buffer, cx| {
3606                buffer.edit([(4..4, "NEW\n")], None, cx);
3607            });
3608
3609            let annotated_buffer = "
3610                <0>aaa
3611                <1NEW
3612                1>bbb
3613                <2>ccc
3614                <3>"
3615            .unindent();
3616            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3617        }
3618
3619        {
3620            let base_text = "
3621                aaa
3622                bbb
3623                ccc
3624            "
3625            .unindent();
3626            let buffer_text = base_text.clone();
3627            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3628
3629            buffer.update(cx, |buffer, cx| {
3630                buffer.edit([(4..8, "")], None, cx);
3631            });
3632
3633            let new_buffer_text = "
3634                aaa
3635                ccc
3636            "
3637            .unindent();
3638            let annotated_base = "
3639                <0>aaa
3640                <1bbb
3641                1>ccc
3642                <2>"
3643            .unindent();
3644            assert_rows_to_base_text_rows_visual(
3645                &buffer,
3646                &diff,
3647                &new_buffer_text,
3648                &annotated_base,
3649                cx,
3650            );
3651        }
3652
3653        {
3654            let base_text = "
3655                aaa
3656                bbb
3657                ccc
3658            "
3659            .unindent();
3660            let buffer_text = base_text.clone();
3661            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3662
3663            buffer.update(cx, |buffer, cx| {
3664                buffer.edit([(4..8, "")], None, cx);
3665            });
3666
3667            let annotated_buffer = "
3668                <0>aaa
3669                <1><2>ccc
3670                <3>"
3671            .unindent();
3672            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3673        }
3674
3675        {
3676            let base_text = "
3677                aaa
3678                bbb
3679                ccc
3680                ddd
3681                eee
3682            "
3683            .unindent();
3684            let buffer_text = "
3685                aaa
3686                XXX
3687                ccc
3688                ddd
3689                eee
3690            "
3691            .unindent();
3692            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3693
3694            buffer.update(cx, |buffer, cx| {
3695                buffer.edit([(12..15, "YYY")], None, cx);
3696            });
3697
3698            let new_buffer_text = "
3699                aaa
3700                XXX
3701                ccc
3702                YYY
3703                eee
3704            "
3705            .unindent();
3706            let annotated_base = "
3707                <0>aaa
3708                <1<2bbb
3709                1>2>ccc
3710                <3ddd3>
3711                <4>eee
3712                <5>"
3713            .unindent();
3714            assert_rows_to_base_text_rows_visual(
3715                &buffer,
3716                &diff,
3717                &new_buffer_text,
3718                &annotated_base,
3719                cx,
3720            );
3721        }
3722
3723        {
3724            let base_text = "
3725                aaa
3726                bbb
3727                ccc
3728                ddd
3729                eee
3730            "
3731            .unindent();
3732            let buffer_text = "
3733                aaa
3734                XXX
3735                ccc
3736                ddd
3737                eee
3738            "
3739            .unindent();
3740            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3741
3742            buffer.update(cx, |buffer, cx| {
3743                buffer.edit([(12..15, "YYY")], None, cx);
3744            });
3745
3746            let annotated_buffer = "
3747                <0>aaa
3748                <1<2XXX
3749                1>2>ccc
3750                <3YYY3>
3751                <4>eee
3752                <5>"
3753            .unindent();
3754            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3755        }
3756
3757        {
3758            let base_text = "
3759                aaa
3760                bbb
3761                ccc
3762            "
3763            .unindent();
3764            let buffer_text = base_text.clone();
3765            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3766
3767            buffer.update(cx, |buffer, cx| {
3768                buffer.edit([(0..0, "NEW\n")], None, cx);
3769            });
3770
3771            let new_buffer_text = "
3772                NEW
3773                aaa
3774                bbb
3775                ccc
3776            "
3777            .unindent();
3778            let annotated_base = "
3779                <0><1>aaa
3780                <2>bbb
3781                <3>ccc
3782                <4>"
3783            .unindent();
3784            assert_rows_to_base_text_rows_visual(
3785                &buffer,
3786                &diff,
3787                &new_buffer_text,
3788                &annotated_base,
3789                cx,
3790            );
3791        }
3792
3793        {
3794            let base_text = "
3795                aaa
3796                bbb
3797                ccc
3798            "
3799            .unindent();
3800            let buffer_text = base_text.clone();
3801            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3802
3803            buffer.update(cx, |buffer, cx| {
3804                buffer.edit([(0..0, "NEW\n")], None, cx);
3805            });
3806
3807            let annotated_buffer = "
3808                <0NEW
3809                0>aaa
3810                <1>bbb
3811                <2>ccc
3812                <3>"
3813            .unindent();
3814            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3815        }
3816
3817        {
3818            let base_text = "
3819                aaa
3820                bbb
3821                ccc
3822            "
3823            .unindent();
3824            let buffer_text = base_text.clone();
3825            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3826
3827            buffer.update(cx, |buffer, cx| {
3828                buffer.edit([(12..12, "NEW\n")], None, cx);
3829            });
3830
3831            let new_buffer_text = "
3832                aaa
3833                bbb
3834                ccc
3835                NEW
3836            "
3837            .unindent();
3838            let annotated_base = "
3839                <0>aaa
3840                <1>bbb
3841                <2>ccc
3842                <3><4>"
3843                .unindent();
3844            assert_rows_to_base_text_rows_visual(
3845                &buffer,
3846                &diff,
3847                &new_buffer_text,
3848                &annotated_base,
3849                cx,
3850            );
3851        }
3852
3853        {
3854            let base_text = "
3855                aaa
3856                bbb
3857                ccc
3858            "
3859            .unindent();
3860            let buffer_text = base_text.clone();
3861            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3862
3863            buffer.update(cx, |buffer, cx| {
3864                buffer.edit([(12..12, "NEW\n")], None, cx);
3865            });
3866
3867            let annotated_buffer = "
3868                <0>aaa
3869                <1>bbb
3870                <2>ccc
3871                <3NEW
3872                3>"
3873            .unindent();
3874            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3875        }
3876
3877        {
3878            let base_text = "";
3879            let buffer_text = "aaa\n";
3880            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3881
3882            buffer.update(cx, |buffer, cx| {
3883                buffer.edit([(4..4, "bbb\n")], None, cx);
3884            });
3885
3886            let new_buffer_text = "
3887                aaa
3888                bbb
3889            "
3890            .unindent();
3891            let annotated_base = "<0><1><2>";
3892            assert_rows_to_base_text_rows_visual(
3893                &buffer,
3894                &diff,
3895                &new_buffer_text,
3896                &annotated_base,
3897                cx,
3898            );
3899        }
3900
3901        {
3902            let base_text = "aaa\n";
3903            let buffer_text = "";
3904            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3905
3906            buffer.update(cx, |buffer, cx| {
3907                buffer.edit([(0..0, "bbb\n")], None, cx);
3908            });
3909
3910            let new_buffer_text = "bbb\n";
3911            let annotated_base = "
3912                <0<1aaa
3913                0>1>"
3914                .unindent();
3915            assert_rows_to_base_text_rows_visual(
3916                &buffer,
3917                &diff,
3918                &new_buffer_text,
3919                &annotated_base,
3920                cx,
3921            );
3922        }
3923
3924        {
3925            let base_text = "";
3926            let buffer_text = "";
3927            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3928
3929            buffer.update(cx, |buffer, cx| {
3930                buffer.edit([(0..0, "aaa\n")], None, cx);
3931            });
3932
3933            let new_buffer_text = "aaa\n";
3934            let annotated_base = "<0><1>";
3935            assert_rows_to_base_text_rows_visual(
3936                &buffer,
3937                &diff,
3938                &new_buffer_text,
3939                &annotated_base,
3940                cx,
3941            );
3942        }
3943
3944        {
3945            let base_text = "
3946                aaa
3947                bbb
3948                ccc
3949            "
3950            .unindent();
3951            let buffer_text = "
3952                aaa
3953                XXX
3954                ccc
3955            "
3956            .unindent();
3957            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3958
3959            buffer.update(cx, |buffer, cx| {
3960                buffer.edit([(4..7, "YYY")], None, cx);
3961            });
3962
3963            let new_buffer_text = "
3964                aaa
3965                YYY
3966                ccc
3967            "
3968            .unindent();
3969            let annotated_base = "
3970                <0>aaa
3971                <1<2bbb
3972                1>2>ccc
3973                <3>"
3974            .unindent();
3975            assert_rows_to_base_text_rows_visual(
3976                &buffer,
3977                &diff,
3978                &new_buffer_text,
3979                &annotated_base,
3980                cx,
3981            );
3982        }
3983
3984        {
3985            let base_text = "
3986                aaa
3987                bbb
3988                ccc
3989            "
3990            .unindent();
3991            let buffer_text = "
3992                aaa
3993                XXX
3994                ccc
3995            "
3996            .unindent();
3997            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3998
3999            buffer.update(cx, |buffer, cx| {
4000                buffer.edit([(4..7, "YYY")], None, cx);
4001            });
4002
4003            let annotated_buffer = "
4004                <0>aaa
4005                <1<2YYY
4006                1>2>ccc
4007                <3>"
4008            .unindent();
4009            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4010        }
4011
4012        {
4013            let base_text = "
4014                aaa
4015                bbb
4016                ccc
4017            "
4018            .unindent();
4019            let buffer_text = "
4020                aaa
4021                XXXX
4022                ccc
4023            "
4024            .unindent();
4025            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4026
4027            buffer.update(cx, |buffer, cx| {
4028                buffer.edit([(4..6, "YY")], None, cx);
4029            });
4030
4031            let new_buffer_text = "
4032                aaa
4033                YYXX
4034                ccc
4035            "
4036            .unindent();
4037            let annotated_base = "
4038                <0>aaa
4039                <1<2bbb
4040                1>2>ccc
4041                <3>"
4042            .unindent();
4043            assert_rows_to_base_text_rows_visual(
4044                &buffer,
4045                &diff,
4046                &new_buffer_text,
4047                &annotated_base,
4048                cx,
4049            );
4050        }
4051
4052        {
4053            let base_text = "
4054                aaa
4055                bbb
4056                ccc
4057            "
4058            .unindent();
4059            let buffer_text = "
4060                aaa
4061                XXXX
4062                ccc
4063            "
4064            .unindent();
4065            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4066
4067            buffer.update(cx, |buffer, cx| {
4068                buffer.edit([(6..8, "YY")], None, cx);
4069            });
4070
4071            let new_buffer_text = "
4072                aaa
4073                XXYY
4074                ccc
4075            "
4076            .unindent();
4077            let annotated_base = "
4078                <0>aaa
4079                <1<2bbb
4080                1>2>ccc
4081                <3>"
4082            .unindent();
4083            assert_rows_to_base_text_rows_visual(
4084                &buffer,
4085                &diff,
4086                &new_buffer_text,
4087                &annotated_base,
4088                cx,
4089            );
4090        }
4091
4092        {
4093            let base_text = "
4094                aaa
4095                bbb
4096                ccc
4097            "
4098            .unindent();
4099            let buffer_text = "
4100                aaa
4101                XXX
4102                ccc
4103            "
4104            .unindent();
4105            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4106
4107            buffer.update(cx, |buffer, cx| {
4108                buffer.edit([(4..4, "NEW")], None, cx);
4109            });
4110
4111            let new_buffer_text = "
4112                aaa
4113                NEWXXX
4114                ccc
4115            "
4116            .unindent();
4117            let annotated_base = "
4118                <0>aaa
4119                <1<2bbb
4120                1>2>ccc
4121                <3>"
4122            .unindent();
4123            assert_rows_to_base_text_rows_visual(
4124                &buffer,
4125                &diff,
4126                &new_buffer_text,
4127                &annotated_base,
4128                cx,
4129            );
4130        }
4131
4132        {
4133            let base_text = "
4134                aaa
4135                bbb
4136                ccc
4137            "
4138            .unindent();
4139            let buffer_text = "
4140                aaa
4141                XXX
4142                ccc
4143            "
4144            .unindent();
4145            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4146
4147            buffer.update(cx, |buffer, cx| {
4148                buffer.edit([(7..7, "NEW")], None, cx);
4149            });
4150
4151            let new_buffer_text = "
4152                aaa
4153                XXXNEW
4154                ccc
4155            "
4156            .unindent();
4157            let annotated_base = "
4158                <0>aaa
4159                <1<2bbb
4160                1>2>ccc
4161                <3>"
4162            .unindent();
4163            assert_rows_to_base_text_rows_visual(
4164                &buffer,
4165                &diff,
4166                &new_buffer_text,
4167                &annotated_base,
4168                cx,
4169            );
4170        }
4171
4172        {
4173            let base_text = "
4174                aaa
4175                bbb
4176                ccc
4177            "
4178            .unindent();
4179            let buffer_text = "
4180                aaa
4181                ccc
4182            "
4183            .unindent();
4184            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4185
4186            buffer.update(cx, |buffer, cx| {
4187                buffer.edit([(4..4, "NEW\n")], None, cx);
4188            });
4189
4190            let new_buffer_text = "
4191                aaa
4192                NEW
4193                ccc
4194            "
4195            .unindent();
4196            let annotated_base = "
4197                <0>aaa
4198                <1<2bbb
4199                1>2>ccc
4200                <3>"
4201            .unindent();
4202            assert_rows_to_base_text_rows_visual(
4203                &buffer,
4204                &diff,
4205                &new_buffer_text,
4206                &annotated_base,
4207                cx,
4208            );
4209        }
4210
4211        {
4212            let base_text = "
4213                aaa
4214                bbb
4215                ccc
4216            "
4217            .unindent();
4218            let buffer_text = "
4219                aaa
4220                ccc
4221            "
4222            .unindent();
4223            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4224
4225            buffer.update(cx, |buffer, cx| {
4226                buffer.edit([(4..4, "NEW\n")], None, cx);
4227            });
4228
4229            let annotated_buffer = "
4230                <0>aaa
4231                <1<2NEW
4232                1>2>ccc
4233                <3>"
4234            .unindent();
4235            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4236        }
4237
4238        {
4239            let base_text = "
4240                aaa
4241                bbb
4242                ccc
4243                ddd
4244            "
4245            .unindent();
4246            let buffer_text = "
4247                aaa
4248                ddd
4249            "
4250            .unindent();
4251            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4252
4253            buffer.update(cx, |buffer, cx| {
4254                buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4255            });
4256
4257            let new_buffer_text = "
4258                aaa
4259                XXX
4260                YYY
4261                ddd
4262            "
4263            .unindent();
4264            let annotated_base = "
4265                <0>aaa
4266                <1<2<3bbb
4267                ccc
4268                1>2>3>ddd
4269                <4>"
4270            .unindent();
4271            assert_rows_to_base_text_rows_visual(
4272                &buffer,
4273                &diff,
4274                &new_buffer_text,
4275                &annotated_base,
4276                cx,
4277            );
4278        }
4279
4280        {
4281            let base_text = "
4282                aaa
4283                bbb
4284                ccc
4285                ddd
4286            "
4287            .unindent();
4288            let buffer_text = "
4289                aaa
4290                ddd
4291            "
4292            .unindent();
4293            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4294
4295            buffer.update(cx, |buffer, cx| {
4296                buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4297            });
4298
4299            let annotated_buffer = "
4300                <0>aaa
4301                <1<2<3XXX
4302                YYY
4303                1>2>3>ddd
4304                <4>"
4305            .unindent();
4306            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4307        }
4308
4309        {
4310            let base_text = "
4311                aaa
4312                bbb
4313                ccc
4314            "
4315            .unindent();
4316            let buffer_text = "
4317                aaa
4318                XXXX
4319                ccc
4320            "
4321            .unindent();
4322            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4323
4324            buffer.update(cx, |buffer, cx| {
4325                buffer.edit([(2..10, "YY\nZZ")], None, cx);
4326            });
4327
4328            let new_buffer_text = "
4329                aaYY
4330                ZZcc
4331            "
4332            .unindent();
4333            let annotated_base = "
4334                <0>aa<1a
4335                bbb
4336                c1>cc
4337                <2>"
4338            .unindent();
4339            assert_rows_to_base_text_rows_visual(
4340                &buffer,
4341                &diff,
4342                &new_buffer_text,
4343                &annotated_base,
4344                cx,
4345            );
4346        }
4347
4348        {
4349            let base_text = "
4350                aaa
4351                bbb
4352                ccc
4353            "
4354            .unindent();
4355            let buffer_text = "
4356                aaa
4357                XXXX
4358                ccc
4359            "
4360            .unindent();
4361            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4362
4363            buffer.update(cx, |buffer, cx| {
4364                buffer.edit([(0..9, "ZZ\n")], None, cx);
4365            });
4366
4367            let new_buffer_text = "
4368                ZZ
4369                ccc
4370            "
4371            .unindent();
4372            let annotated_base = "
4373                <0<1aaa
4374                bbb
4375                0>1>ccc
4376                <2>"
4377            .unindent();
4378            assert_rows_to_base_text_rows_visual(
4379                &buffer,
4380                &diff,
4381                &new_buffer_text,
4382                &annotated_base,
4383                cx,
4384            );
4385        }
4386    }
4387
4388    #[gpui::test]
4389    async fn test_row_translation_no_base_text(cx: &mut gpui::TestAppContext) {
4390        let buffer_text = "aaa\nbbb\nccc\n";
4391        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
4392        let diff = cx.new(|cx| BufferDiff::new(&buffer.read(cx).text_snapshot(), cx));
4393
4394        let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4395        let diff_snapshot = diff.update(cx, |diff, cx| diff.snapshot(cx));
4396
4397        let points = vec![
4398            Point::new(0, 0),
4399            Point::new(1, 0),
4400            Point::new(2, 0),
4401            Point::new(3, 0),
4402        ];
4403        let base_rows: Vec<_> = diff_snapshot
4404            .points_to_base_text_points(points, &buffer_snapshot)
4405            .0
4406            .collect();
4407
4408        let zero = Point::new(0, 0);
4409        assert_eq!(
4410            base_rows,
4411            vec![zero..zero, zero..zero, zero..zero, zero..zero],
4412            "all buffer rows should map to point 0,0 in empty base text"
4413        );
4414
4415        let base_points = vec![Point::new(0, 0)];
4416        let (rows_iter, _, _) =
4417            diff_snapshot.base_text_points_to_points(base_points, &buffer_snapshot);
4418        let buffer_rows: Vec<_> = rows_iter.collect();
4419
4420        let max_point = buffer_snapshot.max_point();
4421        assert_eq!(
4422            buffer_rows,
4423            vec![zero..max_point],
4424            "base text row 0 should map to entire buffer range"
4425        );
4426    }
4427}