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