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