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 = base_text_end.max(Some(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 = base_text_end.max(Some(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.clone(), 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        buffer.edit_via_marked_text(
2773            &"
2774                one
2775                THREE
2776                four«»
2777                five
2778                seven
2779                eight
2780                «NINE»
2781            "
2782            .unindent(),
2783        );
2784
2785        let diff_7 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2786        let DiffChanged {
2787            changed_range,
2788            base_text_changed_range,
2789            extended_range: _,
2790        } = compare_hunks(&diff_7.inner.hunks, &diff_6.inner.hunks, &buffer, &buffer);
2791        let range = changed_range.unwrap();
2792        assert_eq!(range.to_point(&buffer), Point::new(2, 4)..Point::new(7, 0));
2793        let base_text_range = base_text_changed_range.unwrap();
2794        assert_eq!(
2795            base_text_range.to_point(diff_7.base_text()),
2796            Point::new(5, 0)..Point::new(10, 0)
2797        );
2798
2799        buffer.edit_via_marked_text(
2800            &"
2801                one
2802                THREE
2803                four
2804                five«»seven
2805                eight
2806                NINE
2807            "
2808            .unindent(),
2809        );
2810
2811        let diff_8 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2812        let DiffChanged {
2813            changed_range,
2814            base_text_changed_range,
2815            extended_range: _,
2816        } = compare_hunks(&diff_8.inner.hunks, &diff_7.inner.hunks, &buffer, &buffer);
2817        let range = changed_range.unwrap();
2818        assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(3, 4));
2819        let base_text_range = base_text_changed_range.unwrap();
2820        assert_eq!(
2821            base_text_range.to_point(diff_8.base_text()),
2822            Point::new(5, 0)..Point::new(8, 0)
2823        );
2824    }
2825
2826    #[gpui::test(iterations = 100)]
2827    async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2828        fn gen_line(rng: &mut StdRng) -> String {
2829            if rng.random_bool(0.2) {
2830                "\n".to_owned()
2831            } else {
2832                let c = rng.random_range('A'..='Z');
2833                format!("{c}{c}{c}\n")
2834            }
2835        }
2836
2837        fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2838            let mut old_lines = {
2839                let mut old_lines = Vec::new();
2840                let old_lines_iter = head.lines();
2841                for line in old_lines_iter {
2842                    assert!(!line.ends_with("\n"));
2843                    old_lines.push(line.to_owned());
2844                }
2845                if old_lines.last().is_some_and(|line| line.is_empty()) {
2846                    old_lines.pop();
2847                }
2848                old_lines.into_iter()
2849            };
2850            let mut result = String::new();
2851            let unchanged_count = rng.random_range(0..=old_lines.len());
2852            result +=
2853                &old_lines
2854                    .by_ref()
2855                    .take(unchanged_count)
2856                    .fold(String::new(), |mut s, line| {
2857                        writeln!(&mut s, "{line}").unwrap();
2858                        s
2859                    });
2860            while old_lines.len() > 0 {
2861                let deleted_count = rng.random_range(0..=old_lines.len());
2862                let _advance = old_lines
2863                    .by_ref()
2864                    .take(deleted_count)
2865                    .map(|line| line.len() + 1)
2866                    .sum::<usize>();
2867                let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2868                let added_count = rng.random_range(minimum_added..=5);
2869                let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2870                result += &addition;
2871
2872                if old_lines.len() > 0 {
2873                    let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2874                    if blank_lines == old_lines.len() {
2875                        break;
2876                    };
2877                    let unchanged_count =
2878                        rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2879                    result += &old_lines.by_ref().take(unchanged_count).fold(
2880                        String::new(),
2881                        |mut s, line| {
2882                            writeln!(&mut s, "{line}").unwrap();
2883                            s
2884                        },
2885                    );
2886                }
2887            }
2888            result
2889        }
2890
2891        fn uncommitted_diff(
2892            working_copy: &language::BufferSnapshot,
2893            index_text: &Rope,
2894            head_text: String,
2895            cx: &mut TestAppContext,
2896        ) -> Entity<BufferDiff> {
2897            let secondary = cx.new(|cx| {
2898                BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2899            });
2900            cx.new(|cx| {
2901                let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2902                diff.secondary_diff = Some(secondary);
2903                diff
2904            })
2905        }
2906
2907        let operations = std::env::var("OPERATIONS")
2908            .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2909            .unwrap_or(10);
2910
2911        let rng = &mut rng;
2912        let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2913            writeln!(&mut s, "{c}{c}{c}").unwrap();
2914            s
2915        });
2916        let working_copy = gen_working_copy(rng, &head_text);
2917        let working_copy = cx.new(|cx| {
2918            language::Buffer::local_normalized(
2919                Rope::from(working_copy.as_str()),
2920                text::LineEnding::default(),
2921                cx,
2922            )
2923        });
2924        let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2925        let mut index_text = if rng.random() {
2926            Rope::from(head_text.as_str())
2927        } else {
2928            working_copy.as_rope().clone()
2929        };
2930
2931        let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2932        let mut hunks = diff.update(cx, |diff, cx| {
2933            diff.snapshot(cx)
2934                .hunks_intersecting_range(
2935                    Anchor::min_max_range_for_buffer(diff.buffer_id),
2936                    &working_copy,
2937                )
2938                .collect::<Vec<_>>()
2939        });
2940        if hunks.is_empty() {
2941            return;
2942        }
2943
2944        for _ in 0..operations {
2945            let i = rng.random_range(0..hunks.len());
2946            let hunk = &mut hunks[i];
2947            let hunk_to_change = hunk.clone();
2948            let stage = match hunk.secondary_status {
2949                DiffHunkSecondaryStatus::HasSecondaryHunk => {
2950                    hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2951                    true
2952                }
2953                DiffHunkSecondaryStatus::NoSecondaryHunk => {
2954                    hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2955                    false
2956                }
2957                _ => unreachable!(),
2958            };
2959
2960            index_text = diff.update(cx, |diff, cx| {
2961                diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2962                    .unwrap()
2963            });
2964
2965            diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2966            let found_hunks = diff.update(cx, |diff, cx| {
2967                diff.snapshot(cx)
2968                    .hunks_intersecting_range(
2969                        Anchor::min_max_range_for_buffer(diff.buffer_id),
2970                        &working_copy,
2971                    )
2972                    .collect::<Vec<_>>()
2973            });
2974            assert_eq!(hunks.len(), found_hunks.len());
2975
2976            for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2977                assert_eq!(
2978                    expected_hunk.buffer_range.to_point(&working_copy),
2979                    found_hunk.buffer_range.to_point(&working_copy)
2980                );
2981                assert_eq!(
2982                    expected_hunk.diff_base_byte_range,
2983                    found_hunk.diff_base_byte_range
2984                );
2985                assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2986            }
2987            hunks = found_hunks;
2988        }
2989    }
2990
2991    #[gpui::test]
2992    async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2993        let base_text = "
2994            one
2995            two
2996            three
2997            four
2998            five
2999            six
3000        "
3001        .unindent();
3002        let buffer_text = "
3003            one
3004            TWO
3005            three
3006            four
3007            FIVE
3008            six
3009        "
3010        .unindent();
3011        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3012        let diff = cx.new(|cx| {
3013            BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
3014        });
3015        cx.run_until_parked();
3016        let (tx, rx) = mpsc::channel();
3017        let subscription =
3018            cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
3019
3020        let snapshot = buffer.update(cx, |buffer, cx| {
3021            buffer.set_text(
3022                "
3023                ONE
3024                TWO
3025                THREE
3026                FOUR
3027                FIVE
3028                SIX
3029            "
3030                .unindent(),
3031                cx,
3032            );
3033            buffer.text_snapshot()
3034        });
3035        let update = diff
3036            .update(cx, |diff, cx| {
3037                diff.update_diff(
3038                    snapshot.clone(),
3039                    Some(base_text.as_str().into()),
3040                    None,
3041                    None,
3042                    cx,
3043                )
3044            })
3045            .await;
3046        diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
3047            .await;
3048        cx.run_until_parked();
3049        drop(subscription);
3050        let events = rx.into_iter().collect::<Vec<_>>();
3051        match events.as_slice() {
3052            [
3053                BufferDiffEvent::DiffChanged(DiffChanged {
3054                    changed_range: _,
3055                    base_text_changed_range,
3056                    extended_range: _,
3057                }),
3058            ] => {
3059                // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
3060                // assert_eq!(
3061                //     *changed_range,
3062                //     Some(Anchor::min_max_range_for_buffer(
3063                //         buffer.read_with(cx, |buffer, _| buffer.remote_id())
3064                //     ))
3065                // );
3066                assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
3067            }
3068            _ => panic!("unexpected events: {:?}", events),
3069        }
3070    }
3071
3072    #[gpui::test]
3073    async fn test_extended_range(cx: &mut TestAppContext) {
3074        let base_text = "
3075            aaa
3076            bbb
3077
3078
3079
3080
3081
3082            ccc
3083            ddd
3084        "
3085        .unindent();
3086
3087        let buffer_text = "
3088            aaa
3089            bbb
3090
3091
3092
3093
3094
3095            CCC
3096            ddd
3097        "
3098        .unindent();
3099
3100        let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3101        let old_buffer = buffer.snapshot();
3102        let diff_a = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
3103
3104        buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3105        let diff_b = BufferDiffSnapshot::new_sync(buffer.clone(), base_text, cx);
3106
3107        let DiffChanged {
3108            changed_range,
3109            base_text_changed_range: _,
3110            extended_range,
3111        } = compare_hunks(
3112            &diff_b.inner.hunks,
3113            &diff_a.inner.hunks,
3114            &old_buffer,
3115            &buffer,
3116        );
3117
3118        let changed_range = changed_range.unwrap();
3119        assert_eq!(
3120            changed_range.to_point(&buffer),
3121            Point::new(7, 0)..Point::new(9, 0),
3122            "changed_range should span from old hunk position to new hunk end"
3123        );
3124
3125        let extended_range = extended_range.unwrap();
3126        assert_eq!(
3127            extended_range.start.to_point(&buffer),
3128            Point::new(1, 3),
3129            "extended_range.start should extend to include the edit outside changed_range"
3130        );
3131        assert_eq!(
3132            extended_range.end.to_point(&buffer),
3133            Point::new(9, 0),
3134            "extended_range.end should collapse to changed_range.end when no edits in end margin"
3135        );
3136
3137        let base_text_2 = "
3138            one
3139            two
3140            three
3141            four
3142            five
3143            six
3144            seven
3145            eight
3146        "
3147        .unindent();
3148
3149        let buffer_text_2 = "
3150            ONE
3151            two
3152            THREE
3153            four
3154            FIVE
3155            six
3156            SEVEN
3157            eight
3158        "
3159        .unindent();
3160
3161        let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3162        let old_buffer_2 = buffer_2.snapshot();
3163        let diff_2a = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2.clone(), cx);
3164
3165        buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3166        let diff_2b = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2, cx);
3167
3168        let DiffChanged {
3169            changed_range,
3170            base_text_changed_range: _,
3171            extended_range,
3172        } = compare_hunks(
3173            &diff_2b.inner.hunks,
3174            &diff_2a.inner.hunks,
3175            &old_buffer_2,
3176            &buffer_2,
3177        );
3178
3179        let changed_range = changed_range.unwrap();
3180        assert_eq!(
3181            changed_range.to_point(&buffer_2),
3182            Point::new(4, 0)..Point::new(5, 0),
3183            "changed_range should be just the hunk that changed (FIVE)"
3184        );
3185
3186        let extended_range = extended_range.unwrap();
3187        assert_eq!(
3188            extended_range.to_point(&buffer_2),
3189            Point::new(4, 0)..Point::new(5, 0),
3190            "extended_range should equal changed_range when edit is within the hunk"
3191        );
3192    }
3193
3194    fn assert_rows_to_base_text_rows_visual(
3195        buffer: &Entity<language::Buffer>,
3196        diff: &Entity<BufferDiff>,
3197        source_text: &str,
3198        annotated_target: &str,
3199        cx: &mut gpui::TestAppContext,
3200    ) {
3201        let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3202
3203        let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3204        let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3205
3206        assert_eq!(
3207            buffer.text(),
3208            source_text,
3209            "buffer text does not match source text"
3210        );
3211
3212        assert_eq!(
3213            diff.base_text_string().unwrap_or_default(),
3214            target_text,
3215            "base text does not match stripped annotated target"
3216        );
3217
3218        let num_rows = source_text.lines().count() as u32;
3219        let max_point = buffer.max_point();
3220        let points = (0..=num_rows).map(move |row| {
3221            if row == num_rows && max_point.column > 0 {
3222                max_point
3223            } else {
3224                Point::new(row, 0)
3225            }
3226        });
3227        let actual_ranges: Vec<_> = diff.points_to_base_text_points(points, &buffer).0.collect();
3228
3229        assert_eq!(
3230            actual_ranges, expected_ranges,
3231            "\nsource (buffer):\n{}\ntarget (base):\n{}\nexpected: {:?}\nactual: {:?}",
3232            source_text, target_text, expected_ranges, actual_ranges
3233        );
3234    }
3235
3236    fn assert_base_text_rows_to_rows_visual(
3237        buffer: &Entity<language::Buffer>,
3238        diff: &Entity<BufferDiff>,
3239        source_text: &str,
3240        annotated_target: &str,
3241        cx: &mut gpui::TestAppContext,
3242    ) {
3243        let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3244
3245        let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3246        let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3247
3248        assert_eq!(
3249            diff.base_text_string().unwrap_or_default(),
3250            source_text,
3251            "base text does not match source text"
3252        );
3253
3254        assert_eq!(
3255            buffer.text(),
3256            target_text,
3257            "buffer text does not match stripped annotated target"
3258        );
3259
3260        let num_rows = source_text.lines().count() as u32;
3261        let base_max_point = diff.base_text().max_point();
3262        let points = (0..=num_rows).map(move |row| {
3263            if row == num_rows && base_max_point.column > 0 {
3264                base_max_point
3265            } else {
3266                Point::new(row, 0)
3267            }
3268        });
3269        let actual_ranges: Vec<_> = diff.base_text_points_to_points(points, &buffer).0.collect();
3270
3271        assert_eq!(
3272            actual_ranges, expected_ranges,
3273            "\nsource (base):\n{}\ntarget (buffer):\n{}\nexpected: {:?}\nactual: {:?}",
3274            source_text, target_text, expected_ranges, actual_ranges
3275        );
3276    }
3277
3278    fn parse_row_annotations(annotated_text: &str) -> (String, Vec<Range<Point>>) {
3279        let mut starts: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3280        let mut ends: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3281
3282        let mut clean_text = String::new();
3283        let mut current_point = Point::new(0, 0);
3284        let mut chars = annotated_text.chars().peekable();
3285
3286        while let Some(c) = chars.next() {
3287            if c == '<' {
3288                let mut num_str = String::new();
3289                while let Some(&next) = chars.peek() {
3290                    if next.is_ascii_digit() {
3291                        num_str.push(chars.next().unwrap());
3292                    } else {
3293                        break;
3294                    }
3295                }
3296                if !num_str.is_empty() {
3297                    let row_num: u32 = num_str.parse().unwrap();
3298                    starts.insert(row_num, current_point);
3299
3300                    if chars.peek() == Some(&'>') {
3301                        chars.next();
3302                        ends.insert(row_num, current_point);
3303                    }
3304                } else {
3305                    clean_text.push(c);
3306                    current_point.column += 1;
3307                }
3308            } else if c.is_ascii_digit() {
3309                let mut num_str = String::from(c);
3310                while let Some(&next) = chars.peek() {
3311                    if next.is_ascii_digit() {
3312                        num_str.push(chars.next().unwrap());
3313                    } else {
3314                        break;
3315                    }
3316                }
3317                if chars.peek() == Some(&'>') {
3318                    chars.next();
3319                    let row_num: u32 = num_str.parse().unwrap();
3320                    ends.insert(row_num, current_point);
3321                } else {
3322                    for ch in num_str.chars() {
3323                        clean_text.push(ch);
3324                        current_point.column += 1;
3325                    }
3326                }
3327            } else if c == '\n' {
3328                clean_text.push(c);
3329                current_point.row += 1;
3330                current_point.column = 0;
3331            } else {
3332                clean_text.push(c);
3333                current_point.column += 1;
3334            }
3335        }
3336
3337        let max_row = starts.keys().chain(ends.keys()).max().copied().unwrap_or(0);
3338        let mut ranges: Vec<Range<Point>> = Vec::new();
3339        for row in 0..=max_row {
3340            let start = starts.get(&row).copied().unwrap_or(Point::new(0, 0));
3341            let end = ends.get(&row).copied().unwrap_or(start);
3342            ranges.push(start..end);
3343        }
3344
3345        (clean_text, ranges)
3346    }
3347
3348    fn make_diff(
3349        base_text: &str,
3350        buffer_text: &str,
3351        cx: &mut gpui::TestAppContext,
3352    ) -> (Entity<language::Buffer>, Entity<BufferDiff>) {
3353        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3354        let diff = cx.new(|cx| {
3355            BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
3356        });
3357        (buffer, diff)
3358    }
3359
3360    #[gpui::test]
3361    async fn test_row_translation_visual(cx: &mut gpui::TestAppContext) {
3362        use unindent::Unindent;
3363
3364        {
3365            let buffer_text = "
3366                aaa
3367                bbb
3368                ccc
3369            "
3370            .unindent();
3371            let annotated_base = "
3372                <0>aaa
3373                <1>bbb
3374                <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                bbb
3386                ccc
3387            "
3388            .unindent();
3389            let annotated_buffer = "
3390                <0>aaa
3391                <1>bbb
3392                <2>ccc
3393                <3>"
3394            .unindent();
3395            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3396            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3397            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3398        }
3399
3400        {
3401            let buffer_text = "
3402                XXX
3403                bbb
3404                ccc
3405            "
3406            .unindent();
3407            let annotated_base = "
3408                <0<1aaa
3409                0>1>bbb
3410                <2>ccc
3411                <3>"
3412            .unindent();
3413            let (base_text, _) = parse_row_annotations(&annotated_base);
3414            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3415            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3416        }
3417
3418        {
3419            let buffer_text = "
3420                aaa
3421                NEW
3422                ccc
3423            "
3424            .unindent();
3425            let annotated_base = "
3426                <0>aaa
3427                <1><2>ccc
3428                <3>"
3429            .unindent();
3430            let (base_text, _) = parse_row_annotations(&annotated_base);
3431            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3432            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3433        }
3434
3435        {
3436            let base_text = "
3437                aaa
3438                ccc
3439            "
3440            .unindent();
3441            let annotated_buffer = "
3442                <0>aaa
3443                <1NEW
3444                1>ccc
3445                <2>"
3446            .unindent();
3447            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3448            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3449            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3450        }
3451
3452        {
3453            let buffer_text = "aaa\nbbb";
3454            let annotated_base = "<0>aaa\n<1>bbb<2>";
3455            let (base_text, _) = parse_row_annotations(annotated_base);
3456            let (buffer, diff) = make_diff(&base_text, buffer_text, cx);
3457            assert_rows_to_base_text_rows_visual(&buffer, &diff, buffer_text, annotated_base, cx);
3458            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, annotated_base, cx);
3459        }
3460
3461        {
3462            let base_text = "
3463                aaa
3464                bbb
3465                ccc
3466            "
3467            .unindent();
3468            let annotated_buffer = "
3469                <0<1XXX
3470                0>1>bbb
3471                <2>ccc
3472                <3>"
3473            .unindent();
3474            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3475            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3476            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3477        }
3478
3479        {
3480            let buffer_text = "
3481                aaa
3482                bbb
3483                XXX
3484            "
3485            .unindent();
3486            let annotated_base = "
3487                <0>aaa
3488                <1>bbb
3489                <2<3ccc
3490                2>3>"
3491                .unindent();
3492            let (base_text, _) = parse_row_annotations(&annotated_base);
3493            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3494            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3495        }
3496
3497        {
3498            let base_text = "
3499                aaa
3500                bbb
3501                ccc
3502            "
3503            .unindent();
3504            let annotated_buffer = "
3505                <0>aaa
3506                <1>bbb
3507                <2<3XXX
3508                2>3>"
3509                .unindent();
3510            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3511            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3512            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3513        }
3514
3515        {
3516            let buffer_text = "
3517                aaa
3518                ccc
3519            "
3520            .unindent();
3521            let annotated_base = "
3522                <0>aaa
3523                <1DELETED
3524                1>ccc
3525                <2>"
3526            .unindent();
3527            let (base_text, _) = parse_row_annotations(&annotated_base);
3528            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3529            assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3530        }
3531
3532        {
3533            let base_text = "
3534                aaa
3535                DELETED
3536                ccc
3537            "
3538            .unindent();
3539            let annotated_buffer = "
3540                <0>aaa
3541                <1><2>ccc
3542                <3>"
3543            .unindent();
3544            let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3545            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3546            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3547        }
3548    }
3549
3550    #[gpui::test]
3551    async fn test_row_translation_with_edits_since_diff(cx: &mut gpui::TestAppContext) {
3552        use unindent::Unindent;
3553
3554        {
3555            let base_text = "
3556                aaa
3557                bbb
3558                ccc
3559            "
3560            .unindent();
3561            let buffer_text = base_text.clone();
3562            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3563
3564            buffer.update(cx, |buffer, cx| {
3565                buffer.edit([(4..7, "XXX")], None, cx);
3566            });
3567
3568            let new_buffer_text = "
3569                aaa
3570                XXX
3571                ccc
3572            "
3573            .unindent();
3574            let annotated_base = "
3575                <0>aaa
3576                <1bbb1>
3577                <2>ccc
3578                <3>"
3579            .unindent();
3580            assert_rows_to_base_text_rows_visual(
3581                &buffer,
3582                &diff,
3583                &new_buffer_text,
3584                &annotated_base,
3585                cx,
3586            );
3587        }
3588
3589        {
3590            let base_text = "
3591                aaa
3592                bbb
3593                ccc
3594            "
3595            .unindent();
3596            let buffer_text = base_text.clone();
3597            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3598
3599            buffer.update(cx, |buffer, cx| {
3600                buffer.edit([(4..7, "XXX")], None, cx);
3601            });
3602
3603            let annotated_buffer = "
3604                <0>aaa
3605                <1XXX1>
3606                <2>ccc
3607                <3>"
3608            .unindent();
3609            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3610        }
3611
3612        {
3613            let base_text = "
3614                aaa
3615                bbb
3616                ccc
3617            "
3618            .unindent();
3619            let buffer_text = base_text.clone();
3620            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3621
3622            buffer.update(cx, |buffer, cx| {
3623                buffer.edit([(4..4, "NEW\n")], None, cx);
3624            });
3625
3626            let new_buffer_text = "
3627                aaa
3628                NEW
3629                bbb
3630                ccc
3631            "
3632            .unindent();
3633            let annotated_base = "
3634                <0>aaa
3635                <1><2>bbb
3636                <3>ccc
3637                <4>"
3638            .unindent();
3639            assert_rows_to_base_text_rows_visual(
3640                &buffer,
3641                &diff,
3642                &new_buffer_text,
3643                &annotated_base,
3644                cx,
3645            );
3646        }
3647
3648        {
3649            let base_text = "
3650                aaa
3651                bbb
3652                ccc
3653            "
3654            .unindent();
3655            let buffer_text = base_text.clone();
3656            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3657
3658            buffer.update(cx, |buffer, cx| {
3659                buffer.edit([(4..4, "NEW\n")], None, cx);
3660            });
3661
3662            let annotated_buffer = "
3663                <0>aaa
3664                <1NEW
3665                1>bbb
3666                <2>ccc
3667                <3>"
3668            .unindent();
3669            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3670        }
3671
3672        {
3673            let base_text = "
3674                aaa
3675                bbb
3676                ccc
3677            "
3678            .unindent();
3679            let buffer_text = base_text.clone();
3680            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3681
3682            buffer.update(cx, |buffer, cx| {
3683                buffer.edit([(4..8, "")], None, cx);
3684            });
3685
3686            let new_buffer_text = "
3687                aaa
3688                ccc
3689            "
3690            .unindent();
3691            let annotated_base = "
3692                <0>aaa
3693                <1bbb
3694                1>ccc
3695                <2>"
3696            .unindent();
3697            assert_rows_to_base_text_rows_visual(
3698                &buffer,
3699                &diff,
3700                &new_buffer_text,
3701                &annotated_base,
3702                cx,
3703            );
3704        }
3705
3706        {
3707            let base_text = "
3708                aaa
3709                bbb
3710                ccc
3711            "
3712            .unindent();
3713            let buffer_text = base_text.clone();
3714            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3715
3716            buffer.update(cx, |buffer, cx| {
3717                buffer.edit([(4..8, "")], None, cx);
3718            });
3719
3720            let annotated_buffer = "
3721                <0>aaa
3722                <1><2>ccc
3723                <3>"
3724            .unindent();
3725            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3726        }
3727
3728        {
3729            let base_text = "
3730                aaa
3731                bbb
3732                ccc
3733                ddd
3734                eee
3735            "
3736            .unindent();
3737            let buffer_text = "
3738                aaa
3739                XXX
3740                ccc
3741                ddd
3742                eee
3743            "
3744            .unindent();
3745            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3746
3747            buffer.update(cx, |buffer, cx| {
3748                buffer.edit([(12..15, "YYY")], None, cx);
3749            });
3750
3751            let new_buffer_text = "
3752                aaa
3753                XXX
3754                ccc
3755                YYY
3756                eee
3757            "
3758            .unindent();
3759            let annotated_base = "
3760                <0>aaa
3761                <1<2bbb
3762                1>2>ccc
3763                <3ddd3>
3764                <4>eee
3765                <5>"
3766            .unindent();
3767            assert_rows_to_base_text_rows_visual(
3768                &buffer,
3769                &diff,
3770                &new_buffer_text,
3771                &annotated_base,
3772                cx,
3773            );
3774        }
3775
3776        {
3777            let base_text = "
3778                aaa
3779                bbb
3780                ccc
3781                ddd
3782                eee
3783            "
3784            .unindent();
3785            let buffer_text = "
3786                aaa
3787                XXX
3788                ccc
3789                ddd
3790                eee
3791            "
3792            .unindent();
3793            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3794
3795            buffer.update(cx, |buffer, cx| {
3796                buffer.edit([(12..15, "YYY")], None, cx);
3797            });
3798
3799            let annotated_buffer = "
3800                <0>aaa
3801                <1<2XXX
3802                1>2>ccc
3803                <3YYY3>
3804                <4>eee
3805                <5>"
3806            .unindent();
3807            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3808        }
3809
3810        {
3811            let base_text = "
3812                aaa
3813                bbb
3814                ccc
3815            "
3816            .unindent();
3817            let buffer_text = base_text.clone();
3818            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3819
3820            buffer.update(cx, |buffer, cx| {
3821                buffer.edit([(0..0, "NEW\n")], None, cx);
3822            });
3823
3824            let new_buffer_text = "
3825                NEW
3826                aaa
3827                bbb
3828                ccc
3829            "
3830            .unindent();
3831            let annotated_base = "
3832                <0><1>aaa
3833                <2>bbb
3834                <3>ccc
3835                <4>"
3836            .unindent();
3837            assert_rows_to_base_text_rows_visual(
3838                &buffer,
3839                &diff,
3840                &new_buffer_text,
3841                &annotated_base,
3842                cx,
3843            );
3844        }
3845
3846        {
3847            let base_text = "
3848                aaa
3849                bbb
3850                ccc
3851            "
3852            .unindent();
3853            let buffer_text = base_text.clone();
3854            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3855
3856            buffer.update(cx, |buffer, cx| {
3857                buffer.edit([(0..0, "NEW\n")], None, cx);
3858            });
3859
3860            let annotated_buffer = "
3861                <0NEW
3862                0>aaa
3863                <1>bbb
3864                <2>ccc
3865                <3>"
3866            .unindent();
3867            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3868        }
3869
3870        {
3871            let base_text = "
3872                aaa
3873                bbb
3874                ccc
3875            "
3876            .unindent();
3877            let buffer_text = base_text.clone();
3878            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3879
3880            buffer.update(cx, |buffer, cx| {
3881                buffer.edit([(12..12, "NEW\n")], None, cx);
3882            });
3883
3884            let new_buffer_text = "
3885                aaa
3886                bbb
3887                ccc
3888                NEW
3889            "
3890            .unindent();
3891            let annotated_base = "
3892                <0>aaa
3893                <1>bbb
3894                <2>ccc
3895                <3><4>"
3896                .unindent();
3897            assert_rows_to_base_text_rows_visual(
3898                &buffer,
3899                &diff,
3900                &new_buffer_text,
3901                &annotated_base,
3902                cx,
3903            );
3904        }
3905
3906        {
3907            let base_text = "
3908                aaa
3909                bbb
3910                ccc
3911            "
3912            .unindent();
3913            let buffer_text = base_text.clone();
3914            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3915
3916            buffer.update(cx, |buffer, cx| {
3917                buffer.edit([(12..12, "NEW\n")], None, cx);
3918            });
3919
3920            let annotated_buffer = "
3921                <0>aaa
3922                <1>bbb
3923                <2>ccc
3924                <3NEW
3925                3>"
3926            .unindent();
3927            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3928        }
3929
3930        {
3931            let base_text = "";
3932            let buffer_text = "aaa\n";
3933            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3934
3935            buffer.update(cx, |buffer, cx| {
3936                buffer.edit([(4..4, "bbb\n")], None, cx);
3937            });
3938
3939            let new_buffer_text = "
3940                aaa
3941                bbb
3942            "
3943            .unindent();
3944            let annotated_base = "<0><1><2>";
3945            assert_rows_to_base_text_rows_visual(
3946                &buffer,
3947                &diff,
3948                &new_buffer_text,
3949                &annotated_base,
3950                cx,
3951            );
3952        }
3953
3954        {
3955            let base_text = "aaa\n";
3956            let buffer_text = "";
3957            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3958
3959            buffer.update(cx, |buffer, cx| {
3960                buffer.edit([(0..0, "bbb\n")], None, cx);
3961            });
3962
3963            let new_buffer_text = "bbb\n";
3964            let annotated_base = "
3965                <0<1aaa
3966                0>1>"
3967                .unindent();
3968            assert_rows_to_base_text_rows_visual(
3969                &buffer,
3970                &diff,
3971                &new_buffer_text,
3972                &annotated_base,
3973                cx,
3974            );
3975        }
3976
3977        {
3978            let base_text = "";
3979            let buffer_text = "";
3980            let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3981
3982            buffer.update(cx, |buffer, cx| {
3983                buffer.edit([(0..0, "aaa\n")], None, cx);
3984            });
3985
3986            let new_buffer_text = "aaa\n";
3987            let annotated_base = "<0><1>";
3988            assert_rows_to_base_text_rows_visual(
3989                &buffer,
3990                &diff,
3991                &new_buffer_text,
3992                &annotated_base,
3993                cx,
3994            );
3995        }
3996
3997        {
3998            let base_text = "
3999                aaa
4000                bbb
4001                ccc
4002            "
4003            .unindent();
4004            let buffer_text = "
4005                aaa
4006                XXX
4007                ccc
4008            "
4009            .unindent();
4010            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4011
4012            buffer.update(cx, |buffer, cx| {
4013                buffer.edit([(4..7, "YYY")], None, cx);
4014            });
4015
4016            let new_buffer_text = "
4017                aaa
4018                YYY
4019                ccc
4020            "
4021            .unindent();
4022            let annotated_base = "
4023                <0>aaa
4024                <1<2bbb
4025                1>2>ccc
4026                <3>"
4027            .unindent();
4028            assert_rows_to_base_text_rows_visual(
4029                &buffer,
4030                &diff,
4031                &new_buffer_text,
4032                &annotated_base,
4033                cx,
4034            );
4035        }
4036
4037        {
4038            let base_text = "
4039                aaa
4040                bbb
4041                ccc
4042            "
4043            .unindent();
4044            let buffer_text = "
4045                aaa
4046                XXX
4047                ccc
4048            "
4049            .unindent();
4050            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4051
4052            buffer.update(cx, |buffer, cx| {
4053                buffer.edit([(4..7, "YYY")], None, cx);
4054            });
4055
4056            let annotated_buffer = "
4057                <0>aaa
4058                <1<2YYY
4059                1>2>ccc
4060                <3>"
4061            .unindent();
4062            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4063        }
4064
4065        {
4066            let base_text = "
4067                aaa
4068                bbb
4069                ccc
4070            "
4071            .unindent();
4072            let buffer_text = "
4073                aaa
4074                XXXX
4075                ccc
4076            "
4077            .unindent();
4078            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4079
4080            buffer.update(cx, |buffer, cx| {
4081                buffer.edit([(4..6, "YY")], None, cx);
4082            });
4083
4084            let new_buffer_text = "
4085                aaa
4086                YYXX
4087                ccc
4088            "
4089            .unindent();
4090            let annotated_base = "
4091                <0>aaa
4092                <1<2bbb
4093                1>2>ccc
4094                <3>"
4095            .unindent();
4096            assert_rows_to_base_text_rows_visual(
4097                &buffer,
4098                &diff,
4099                &new_buffer_text,
4100                &annotated_base,
4101                cx,
4102            );
4103        }
4104
4105        {
4106            let base_text = "
4107                aaa
4108                bbb
4109                ccc
4110            "
4111            .unindent();
4112            let buffer_text = "
4113                aaa
4114                XXXX
4115                ccc
4116            "
4117            .unindent();
4118            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4119
4120            buffer.update(cx, |buffer, cx| {
4121                buffer.edit([(6..8, "YY")], None, cx);
4122            });
4123
4124            let new_buffer_text = "
4125                aaa
4126                XXYY
4127                ccc
4128            "
4129            .unindent();
4130            let annotated_base = "
4131                <0>aaa
4132                <1<2bbb
4133                1>2>ccc
4134                <3>"
4135            .unindent();
4136            assert_rows_to_base_text_rows_visual(
4137                &buffer,
4138                &diff,
4139                &new_buffer_text,
4140                &annotated_base,
4141                cx,
4142            );
4143        }
4144
4145        {
4146            let base_text = "
4147                aaa
4148                bbb
4149                ccc
4150            "
4151            .unindent();
4152            let buffer_text = "
4153                aaa
4154                XXX
4155                ccc
4156            "
4157            .unindent();
4158            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4159
4160            buffer.update(cx, |buffer, cx| {
4161                buffer.edit([(4..4, "NEW")], None, cx);
4162            });
4163
4164            let new_buffer_text = "
4165                aaa
4166                NEWXXX
4167                ccc
4168            "
4169            .unindent();
4170            let annotated_base = "
4171                <0>aaa
4172                <1<2bbb
4173                1>2>ccc
4174                <3>"
4175            .unindent();
4176            assert_rows_to_base_text_rows_visual(
4177                &buffer,
4178                &diff,
4179                &new_buffer_text,
4180                &annotated_base,
4181                cx,
4182            );
4183        }
4184
4185        {
4186            let base_text = "
4187                aaa
4188                bbb
4189                ccc
4190            "
4191            .unindent();
4192            let buffer_text = "
4193                aaa
4194                XXX
4195                ccc
4196            "
4197            .unindent();
4198            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4199
4200            buffer.update(cx, |buffer, cx| {
4201                buffer.edit([(7..7, "NEW")], None, cx);
4202            });
4203
4204            let new_buffer_text = "
4205                aaa
4206                XXXNEW
4207                ccc
4208            "
4209            .unindent();
4210            let annotated_base = "
4211                <0>aaa
4212                <1<2bbb
4213                1>2>ccc
4214                <3>"
4215            .unindent();
4216            assert_rows_to_base_text_rows_visual(
4217                &buffer,
4218                &diff,
4219                &new_buffer_text,
4220                &annotated_base,
4221                cx,
4222            );
4223        }
4224
4225        {
4226            let base_text = "
4227                aaa
4228                bbb
4229                ccc
4230            "
4231            .unindent();
4232            let buffer_text = "
4233                aaa
4234                ccc
4235            "
4236            .unindent();
4237            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4238
4239            buffer.update(cx, |buffer, cx| {
4240                buffer.edit([(4..4, "NEW\n")], None, cx);
4241            });
4242
4243            let new_buffer_text = "
4244                aaa
4245                NEW
4246                ccc
4247            "
4248            .unindent();
4249            let annotated_base = "
4250                <0>aaa
4251                <1<2bbb
4252                1>2>ccc
4253                <3>"
4254            .unindent();
4255            assert_rows_to_base_text_rows_visual(
4256                &buffer,
4257                &diff,
4258                &new_buffer_text,
4259                &annotated_base,
4260                cx,
4261            );
4262        }
4263
4264        {
4265            let base_text = "
4266                aaa
4267                bbb
4268                ccc
4269            "
4270            .unindent();
4271            let buffer_text = "
4272                aaa
4273                ccc
4274            "
4275            .unindent();
4276            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4277
4278            buffer.update(cx, |buffer, cx| {
4279                buffer.edit([(4..4, "NEW\n")], None, cx);
4280            });
4281
4282            let annotated_buffer = "
4283                <0>aaa
4284                <1<2NEW
4285                1>2>ccc
4286                <3>"
4287            .unindent();
4288            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4289        }
4290
4291        {
4292            let base_text = "
4293                aaa
4294                bbb
4295                ccc
4296                ddd
4297            "
4298            .unindent();
4299            let buffer_text = "
4300                aaa
4301                ddd
4302            "
4303            .unindent();
4304            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4305
4306            buffer.update(cx, |buffer, cx| {
4307                buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4308            });
4309
4310            let new_buffer_text = "
4311                aaa
4312                XXX
4313                YYY
4314                ddd
4315            "
4316            .unindent();
4317            let annotated_base = "
4318                <0>aaa
4319                <1<2<3bbb
4320                ccc
4321                1>2>3>ddd
4322                <4>"
4323            .unindent();
4324            assert_rows_to_base_text_rows_visual(
4325                &buffer,
4326                &diff,
4327                &new_buffer_text,
4328                &annotated_base,
4329                cx,
4330            );
4331        }
4332
4333        {
4334            let base_text = "
4335                aaa
4336                bbb
4337                ccc
4338                ddd
4339            "
4340            .unindent();
4341            let buffer_text = "
4342                aaa
4343                ddd
4344            "
4345            .unindent();
4346            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4347
4348            buffer.update(cx, |buffer, cx| {
4349                buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4350            });
4351
4352            let annotated_buffer = "
4353                <0>aaa
4354                <1<2<3XXX
4355                YYY
4356                1>2>3>ddd
4357                <4>"
4358            .unindent();
4359            assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4360        }
4361
4362        {
4363            let base_text = "
4364                aaa
4365                bbb
4366                ccc
4367            "
4368            .unindent();
4369            let buffer_text = "
4370                aaa
4371                XXXX
4372                ccc
4373            "
4374            .unindent();
4375            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4376
4377            buffer.update(cx, |buffer, cx| {
4378                buffer.edit([(2..10, "YY\nZZ")], None, cx);
4379            });
4380
4381            let new_buffer_text = "
4382                aaYY
4383                ZZcc
4384            "
4385            .unindent();
4386            let annotated_base = "
4387                <0>aa<1a
4388                bbb
4389                c1>cc
4390                <2>"
4391            .unindent();
4392            assert_rows_to_base_text_rows_visual(
4393                &buffer,
4394                &diff,
4395                &new_buffer_text,
4396                &annotated_base,
4397                cx,
4398            );
4399        }
4400
4401        {
4402            let base_text = "
4403                aaa
4404                bbb
4405                ccc
4406            "
4407            .unindent();
4408            let buffer_text = "
4409                aaa
4410                XXXX
4411                ccc
4412            "
4413            .unindent();
4414            let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4415
4416            buffer.update(cx, |buffer, cx| {
4417                buffer.edit([(0..9, "ZZ\n")], None, cx);
4418            });
4419
4420            let new_buffer_text = "
4421                ZZ
4422                ccc
4423            "
4424            .unindent();
4425            let annotated_base = "
4426                <0<1aaa
4427                bbb
4428                0>1>ccc
4429                <2>"
4430            .unindent();
4431            assert_rows_to_base_text_rows_visual(
4432                &buffer,
4433                &diff,
4434                &new_buffer_text,
4435                &annotated_base,
4436                cx,
4437            );
4438        }
4439    }
4440
4441    #[gpui::test]
4442    async fn test_row_translation_no_base_text(cx: &mut gpui::TestAppContext) {
4443        let buffer_text = "aaa\nbbb\nccc\n";
4444        let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
4445        let diff = cx.new(|cx| BufferDiff::new(&buffer.read(cx).text_snapshot(), cx));
4446
4447        let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4448        let diff_snapshot = diff.update(cx, |diff, cx| diff.snapshot(cx));
4449
4450        let points = vec![
4451            Point::new(0, 0),
4452            Point::new(1, 0),
4453            Point::new(2, 0),
4454            Point::new(3, 0),
4455        ];
4456        let base_rows: Vec<_> = diff_snapshot
4457            .points_to_base_text_points(points, &buffer_snapshot)
4458            .0
4459            .collect();
4460
4461        let zero = Point::new(0, 0);
4462        assert_eq!(
4463            base_rows,
4464            vec![zero..zero, zero..zero, zero..zero, zero..zero],
4465            "all buffer rows should map to point 0,0 in empty base text"
4466        );
4467
4468        let base_points = vec![Point::new(0, 0)];
4469        let (rows_iter, _, _) =
4470            diff_snapshot.base_text_points_to_points(base_points, &buffer_snapshot);
4471        let buffer_rows: Vec<_> = rows_iter.collect();
4472
4473        let max_point = buffer_snapshot.max_point();
4474        assert_eq!(
4475            buffer_rows,
4476            vec![zero..max_point],
4477            "base text row 0 should map to entire buffer range"
4478        );
4479    }
4480}