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