Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:24:44.257Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

buffer_diff.rs

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