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 BufferRow, Capability, DiffOptions, File, Language, LanguageName, LanguageRegistry,
6 language_settings::language_settings, word_diff_ranges,
7};
8use rope::Rope;
9use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc};
10use sum_tree::SumTree;
11use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _, ToPoint as _};
12use util::ResultExt;
13
14pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
15
16pub struct BufferDiff {
17 pub buffer_id: BufferId,
18 inner: BufferDiffInner<Entity<language::Buffer>>,
19 // diff of the index vs head
20 secondary_diff: Option<Entity<BufferDiff>>,
21}
22
23#[derive(Clone)]
24pub struct BufferDiffSnapshot {
25 inner: BufferDiffInner<language::BufferSnapshot>,
26 secondary_diff: Option<Box<BufferDiffSnapshot>>,
27}
28
29impl std::fmt::Debug for BufferDiffSnapshot {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 f.debug_struct("BufferDiffSnapshot")
32 .field("inner", &self.inner)
33 .field("secondary_diff", &self.secondary_diff)
34 .finish()
35 }
36}
37
38#[derive(Clone)]
39pub struct BufferDiffUpdate {
40 base_text_changed: bool,
41 inner: BufferDiffInner<Arc<str>>,
42}
43
44#[derive(Clone)]
45struct BufferDiffInner<BaseText> {
46 hunks: SumTree<InternalDiffHunk>,
47 pending_hunks: SumTree<PendingHunk>,
48 base_text: BaseText,
49 base_text_exists: bool,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub struct DiffHunkStatus {
54 pub kind: DiffHunkStatusKind,
55 pub secondary: DiffHunkSecondaryStatus,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
59pub enum DiffHunkStatusKind {
60 Added,
61 Modified,
62 Deleted,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66/// Diff of Working Copy vs Index
67/// aka 'is this hunk staged or not'
68pub enum DiffHunkSecondaryStatus {
69 /// Unstaged
70 HasSecondaryHunk,
71 /// Partially staged
72 OverlapsWithSecondaryHunk,
73 /// Staged
74 NoSecondaryHunk,
75 /// We are unstaging
76 SecondaryHunkAdditionPending,
77 /// We are stagind
78 SecondaryHunkRemovalPending,
79}
80
81/// A diff hunk resolved to rows in the buffer.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct DiffHunk {
84 /// The buffer range as points.
85 pub range: Range<Point>,
86 /// The range in the buffer to which this hunk corresponds.
87 pub buffer_range: Range<Anchor>,
88 /// The range in the buffer's diff base text to which this hunk corresponds.
89 pub diff_base_byte_range: Range<usize>,
90 pub secondary_status: DiffHunkSecondaryStatus,
91 // Anchors representing the word diff locations in the active buffer
92 pub buffer_word_diffs: Vec<Range<Anchor>>,
93 // Offsets relative to the start of the deleted diff that represent word diff locations
94 pub base_word_diffs: Vec<Range<usize>>,
95}
96
97/// We store [`InternalDiffHunk`]s internally so we don't need to store the additional row range.
98#[derive(Debug, Clone, PartialEq, Eq)]
99struct InternalDiffHunk {
100 buffer_range: Range<Anchor>,
101 diff_base_byte_range: Range<usize>,
102 base_word_diffs: Vec<Range<usize>>,
103 buffer_word_diffs: Vec<Range<Anchor>>,
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
107struct PendingHunk {
108 buffer_range: Range<Anchor>,
109 diff_base_byte_range: Range<usize>,
110 buffer_version: clock::Global,
111 new_status: DiffHunkSecondaryStatus,
112}
113
114#[derive(Debug, Clone)]
115pub struct DiffHunkSummary {
116 buffer_range: Range<Anchor>,
117 diff_base_byte_range: Range<usize>,
118}
119
120impl sum_tree::Item for InternalDiffHunk {
121 type Summary = DiffHunkSummary;
122
123 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
124 DiffHunkSummary {
125 buffer_range: self.buffer_range.clone(),
126 diff_base_byte_range: self.diff_base_byte_range.clone(),
127 }
128 }
129}
130
131impl sum_tree::Item for PendingHunk {
132 type Summary = DiffHunkSummary;
133
134 fn summary(&self, _cx: &text::BufferSnapshot) -> Self::Summary {
135 DiffHunkSummary {
136 buffer_range: self.buffer_range.clone(),
137 diff_base_byte_range: self.diff_base_byte_range.clone(),
138 }
139 }
140}
141
142impl sum_tree::Summary for DiffHunkSummary {
143 type Context<'a> = &'a text::BufferSnapshot;
144
145 fn zero(_cx: Self::Context<'_>) -> Self {
146 DiffHunkSummary {
147 buffer_range: Anchor::MIN..Anchor::MIN,
148 diff_base_byte_range: 0..0,
149 }
150 }
151
152 fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) {
153 self.buffer_range.start = *self
154 .buffer_range
155 .start
156 .min(&other.buffer_range.start, buffer);
157 self.buffer_range.end = *self.buffer_range.end.max(&other.buffer_range.end, buffer);
158
159 self.diff_base_byte_range.start = self
160 .diff_base_byte_range
161 .start
162 .min(other.diff_base_byte_range.start);
163 self.diff_base_byte_range.end = self
164 .diff_base_byte_range
165 .end
166 .max(other.diff_base_byte_range.end);
167 }
168}
169
170impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
171 fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
172 if self
173 .cmp(&cursor_location.buffer_range.start, buffer)
174 .is_lt()
175 {
176 Ordering::Less
177 } else if self.cmp(&cursor_location.buffer_range.end, buffer).is_gt() {
178 Ordering::Greater
179 } else {
180 Ordering::Equal
181 }
182 }
183}
184
185impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
186 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 f.debug_struct("BufferDiffSnapshot")
188 .field("hunks", &self.hunks)
189 .field("remote_id", &self.base_text.remote_id())
190 .finish()
191 }
192}
193
194impl BufferDiffSnapshot {
195 #[cfg(test)]
196 fn new_sync(
197 buffer: text::BufferSnapshot,
198 diff_base: String,
199 cx: &mut gpui::TestAppContext,
200 ) -> BufferDiffSnapshot {
201 let buffer_diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
202 buffer_diff.update(cx, |buffer_diff, cx| buffer_diff.snapshot(cx))
203 }
204
205 pub fn is_empty(&self) -> bool {
206 self.inner.hunks.is_empty()
207 }
208
209 pub fn base_text_string(&self) -> Option<String> {
210 self.inner
211 .base_text_exists
212 .then(|| self.inner.base_text.text())
213 }
214
215 pub fn secondary_diff(&self) -> Option<&BufferDiffSnapshot> {
216 self.secondary_diff.as_deref()
217 }
218
219 pub fn hunks_intersecting_range<'a>(
220 &'a self,
221 range: Range<Anchor>,
222 buffer: &'a text::BufferSnapshot,
223 ) -> impl 'a + Iterator<Item = DiffHunk> {
224 let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
225 self.inner
226 .hunks_intersecting_range(range, buffer, unstaged_counterpart)
227 }
228
229 pub fn hunks_intersecting_range_rev<'a>(
230 &'a self,
231 range: Range<Anchor>,
232 buffer: &'a text::BufferSnapshot,
233 ) -> impl 'a + Iterator<Item = DiffHunk> {
234 let filter = move |summary: &DiffHunkSummary| {
235 let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt();
236 let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt();
237 !before_start && !after_end
238 };
239 self.inner.hunks_intersecting_range_rev_impl(filter, buffer)
240 }
241
242 pub fn hunks_intersecting_base_text_range<'a>(
243 &'a self,
244 range: Range<usize>,
245 main_buffer: &'a text::BufferSnapshot,
246 ) -> impl 'a + Iterator<Item = DiffHunk> {
247 let unstaged_counterpart = self.secondary_diff.as_ref().map(|diff| &diff.inner);
248 let filter = move |summary: &DiffHunkSummary| {
249 let before_start = summary.diff_base_byte_range.end < range.start;
250 let after_end = summary.diff_base_byte_range.start > range.end;
251 !before_start && !after_end
252 };
253 self.inner
254 .hunks_intersecting_range_impl(filter, main_buffer, unstaged_counterpart)
255 }
256
257 pub fn hunks_intersecting_base_text_range_rev<'a>(
258 &'a self,
259 range: Range<usize>,
260 main_buffer: &'a text::BufferSnapshot,
261 ) -> impl 'a + Iterator<Item = DiffHunk> {
262 let filter = move |summary: &DiffHunkSummary| {
263 let before_start = summary.diff_base_byte_range.end.cmp(&range.start).is_lt();
264 let after_end = summary.diff_base_byte_range.start.cmp(&range.end).is_gt();
265 !before_start && !after_end
266 };
267 self.inner
268 .hunks_intersecting_range_rev_impl(filter, main_buffer)
269 }
270
271 pub fn hunks<'a>(
272 &'a self,
273 buffer_snapshot: &'a text::BufferSnapshot,
274 ) -> impl 'a + Iterator<Item = DiffHunk> {
275 self.hunks_intersecting_range(
276 Anchor::min_max_range_for_buffer(buffer_snapshot.remote_id()),
277 buffer_snapshot,
278 )
279 }
280
281 pub fn hunks_in_row_range<'a>(
282 &'a self,
283 range: Range<u32>,
284 buffer: &'a text::BufferSnapshot,
285 ) -> impl 'a + Iterator<Item = DiffHunk> {
286 let start = buffer.anchor_before(Point::new(range.start, 0));
287 let end = buffer.anchor_after(Point::new(range.end, 0));
288 self.hunks_intersecting_range(start..end, buffer)
289 }
290
291 pub fn range_to_hunk_range(
292 &self,
293 range: Range<Anchor>,
294 buffer: &text::BufferSnapshot,
295 ) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
296 let first_hunk = self.hunks_intersecting_range(range.clone(), buffer).next();
297 let last_hunk = self.hunks_intersecting_range_rev(range, buffer).next();
298 let range = first_hunk
299 .as_ref()
300 .zip(last_hunk.as_ref())
301 .map(|(first, last)| first.buffer_range.start..last.buffer_range.end);
302 let base_text_range = first_hunk
303 .zip(last_hunk)
304 .map(|(first, last)| first.diff_base_byte_range.start..last.diff_base_byte_range.end);
305 (range, base_text_range)
306 }
307
308 pub fn base_text(&self) -> &language::BufferSnapshot {
309 &self.inner.base_text
310 }
311
312 /// If this function returns `true`, the base texts are equal. If this
313 /// function returns `false`, they might be equal, but might not. This
314 /// result is used to avoid recalculating diffs in situations where we know
315 /// nothing has changed.
316 pub fn base_texts_definitely_eq(&self, other: &Self) -> bool {
317 if self.inner.base_text_exists != other.inner.base_text_exists {
318 return false;
319 }
320 let left = &self.inner.base_text;
321 let right = &other.inner.base_text;
322 let (old_id, old_version, old_empty) = (left.remote_id(), left.version(), left.is_empty());
323 let (new_id, new_version, new_empty) =
324 (right.remote_id(), right.version(), right.is_empty());
325 (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
326 }
327
328 pub fn row_to_base_text_row(
329 &self,
330 row: BufferRow,
331 bias: Bias,
332 buffer: &text::BufferSnapshot,
333 ) -> u32 {
334 // TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start
335 let target = buffer.anchor_before(Point::new(row, 0));
336 // Find the last hunk that starts before the target.
337 let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);
338 cursor.seek(&target, Bias::Left);
339 if cursor
340 .item()
341 .is_none_or(|hunk| hunk.buffer_range.start.cmp(&target, buffer).is_gt())
342 {
343 cursor.prev();
344 }
345
346 let unclipped_point = if let Some(hunk) = cursor.item()
347 && hunk.buffer_range.start.cmp(&target, buffer).is_le()
348 {
349 // Found a hunk that starts before the target.
350 let hunk_base_text_end = cursor.end().diff_base_byte_range.end;
351 let unclipped_point = if target.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {
352 // Target falls strictly between two hunks.
353 let mut unclipped_point = hunk_base_text_end.to_point(self.base_text());
354 unclipped_point +=
355 Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);
356 unclipped_point
357 } else if bias == Bias::Right {
358 hunk_base_text_end.to_point(self.base_text())
359 } else {
360 hunk.diff_base_byte_range.start.to_point(self.base_text())
361 };
362 // Move the cursor so that at the next step we can clip with the start of the next hunk.
363 cursor.next();
364 unclipped_point
365 } else {
366 // Target is before the added region for the first hunk.
367 debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {
368 target.cmp(&first_hunk.buffer_range.start, buffer).is_le()
369 }));
370 Point::new(row, 0)
371 };
372
373 // If the target falls in the region between two hunks, we added an overshoot above.
374 // There may be changes in the main buffer that are not reflected in the hunks,
375 // so we need to ensure this overshoot keeps us in the corresponding base text region.
376 let max_point = if let Some(next_hunk) = cursor.item() {
377 next_hunk
378 .diff_base_byte_range
379 .start
380 .to_point(self.base_text())
381 } else {
382 self.base_text().max_point()
383 };
384 unclipped_point.min(max_point).row
385 }
386}
387
388impl BufferDiffInner<Entity<language::Buffer>> {
389 /// Returns the new index text and new pending hunks.
390 fn stage_or_unstage_hunks_impl(
391 &mut self,
392 unstaged_diff: &Self,
393 stage: bool,
394 hunks: &[DiffHunk],
395 buffer: &text::BufferSnapshot,
396 file_exists: bool,
397 cx: &mut Context<BufferDiff>,
398 ) -> Option<Rope> {
399 let head_text = self
400 .base_text_exists
401 .then(|| self.base_text.read(cx).as_rope().clone());
402 let index_text = unstaged_diff
403 .base_text_exists
404 .then(|| unstaged_diff.base_text.read(cx).as_rope().clone());
405
406 // If the file doesn't exist in either HEAD or the index, then the
407 // entire file must be either created or deleted in the index.
408 let (index_text, head_text) = match (index_text, head_text) {
409 (Some(index_text), Some(head_text)) if file_exists || !stage => (index_text, head_text),
410 (index_text, head_text) => {
411 let (new_index_text, new_status) = if stage {
412 log::debug!("stage all");
413 (
414 file_exists.then(|| buffer.as_rope().clone()),
415 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending,
416 )
417 } else {
418 log::debug!("unstage all");
419 (
420 head_text,
421 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending,
422 )
423 };
424
425 let hunk = PendingHunk {
426 buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
427 diff_base_byte_range: 0..index_text.map_or(0, |rope| rope.len()),
428 buffer_version: buffer.version().clone(),
429 new_status,
430 };
431 self.pending_hunks = SumTree::from_item(hunk, buffer);
432 return new_index_text;
433 }
434 };
435
436 let mut pending_hunks = SumTree::new(buffer);
437 let mut old_pending_hunks = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
438
439 // first, merge new hunks into pending_hunks
440 for DiffHunk {
441 buffer_range,
442 diff_base_byte_range,
443 secondary_status,
444 ..
445 } in hunks.iter().cloned()
446 {
447 let preceding_pending_hunks = old_pending_hunks.slice(&buffer_range.start, Bias::Left);
448 pending_hunks.append(preceding_pending_hunks, buffer);
449
450 // Skip all overlapping or adjacent old pending hunks
451 while old_pending_hunks.item().is_some_and(|old_hunk| {
452 old_hunk
453 .buffer_range
454 .start
455 .cmp(&buffer_range.end, buffer)
456 .is_le()
457 }) {
458 old_pending_hunks.next();
459 }
460
461 if (stage && secondary_status == DiffHunkSecondaryStatus::NoSecondaryHunk)
462 || (!stage && secondary_status == DiffHunkSecondaryStatus::HasSecondaryHunk)
463 {
464 continue;
465 }
466
467 pending_hunks.push(
468 PendingHunk {
469 buffer_range,
470 diff_base_byte_range,
471 buffer_version: buffer.version().clone(),
472 new_status: if stage {
473 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
474 } else {
475 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
476 },
477 },
478 buffer,
479 );
480 }
481 // append the remainder
482 pending_hunks.append(old_pending_hunks.suffix(), buffer);
483
484 let mut unstaged_hunk_cursor = unstaged_diff.hunks.cursor::<DiffHunkSummary>(buffer);
485 unstaged_hunk_cursor.next();
486
487 // then, iterate over all pending hunks (both new ones and the existing ones) and compute the edits
488 let mut prev_unstaged_hunk_buffer_end = 0;
489 let mut prev_unstaged_hunk_base_text_end = 0;
490 let mut edits = Vec::<(Range<usize>, String)>::new();
491 let mut pending_hunks_iter = pending_hunks.iter().cloned().peekable();
492 while let Some(PendingHunk {
493 buffer_range,
494 diff_base_byte_range,
495 new_status,
496 ..
497 }) = pending_hunks_iter.next()
498 {
499 // Advance unstaged_hunk_cursor to skip unstaged hunks before current hunk
500 let skipped_unstaged = unstaged_hunk_cursor.slice(&buffer_range.start, Bias::Left);
501
502 if let Some(unstaged_hunk) = skipped_unstaged.last() {
503 prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
504 prev_unstaged_hunk_buffer_end = unstaged_hunk.buffer_range.end.to_offset(buffer);
505 }
506
507 // Find where this hunk is in the index if it doesn't overlap
508 let mut buffer_offset_range = buffer_range.to_offset(buffer);
509 let start_overshoot = buffer_offset_range.start - prev_unstaged_hunk_buffer_end;
510 let mut index_start = prev_unstaged_hunk_base_text_end + start_overshoot;
511
512 loop {
513 // Merge this hunk with any overlapping unstaged hunks.
514 if let Some(unstaged_hunk) = unstaged_hunk_cursor.item() {
515 let unstaged_hunk_offset_range = unstaged_hunk.buffer_range.to_offset(buffer);
516 if unstaged_hunk_offset_range.start <= buffer_offset_range.end {
517 prev_unstaged_hunk_base_text_end = unstaged_hunk.diff_base_byte_range.end;
518 prev_unstaged_hunk_buffer_end = unstaged_hunk_offset_range.end;
519
520 index_start = index_start.min(unstaged_hunk.diff_base_byte_range.start);
521 buffer_offset_range.start = buffer_offset_range
522 .start
523 .min(unstaged_hunk_offset_range.start);
524 buffer_offset_range.end =
525 buffer_offset_range.end.max(unstaged_hunk_offset_range.end);
526
527 unstaged_hunk_cursor.next();
528 continue;
529 }
530 }
531
532 // If any unstaged hunks were merged, then subsequent pending hunks may
533 // now overlap this hunk. Merge them.
534 if let Some(next_pending_hunk) = pending_hunks_iter.peek() {
535 let next_pending_hunk_offset_range =
536 next_pending_hunk.buffer_range.to_offset(buffer);
537 if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
538 buffer_offset_range.end = buffer_offset_range
539 .end
540 .max(next_pending_hunk_offset_range.end);
541 pending_hunks_iter.next();
542 continue;
543 }
544 }
545
546 break;
547 }
548
549 let end_overshoot = buffer_offset_range
550 .end
551 .saturating_sub(prev_unstaged_hunk_buffer_end);
552 let index_end = prev_unstaged_hunk_base_text_end + end_overshoot;
553 let index_byte_range = index_start..index_end;
554
555 let replacement_text = match new_status {
556 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending => {
557 log::debug!("staging hunk {:?}", buffer_offset_range);
558 buffer
559 .text_for_range(buffer_offset_range)
560 .collect::<String>()
561 }
562 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
563 log::debug!("unstaging hunk {:?}", buffer_offset_range);
564 head_text
565 .chunks_in_range(diff_base_byte_range.clone())
566 .collect::<String>()
567 }
568 _ => {
569 debug_assert!(false);
570 continue;
571 }
572 };
573
574 edits.push((index_byte_range, replacement_text));
575 }
576 drop(pending_hunks_iter);
577 drop(old_pending_hunks);
578 self.pending_hunks = pending_hunks;
579
580 #[cfg(debug_assertions)] // invariants: non-overlapping and sorted
581 {
582 for window in edits.windows(2) {
583 let (range_a, range_b) = (&window[0].0, &window[1].0);
584 debug_assert!(range_a.end < range_b.start);
585 }
586 }
587
588 let mut new_index_text = Rope::new();
589 let mut index_cursor = index_text.cursor(0);
590
591 for (old_range, replacement_text) in edits {
592 new_index_text.append(index_cursor.slice(old_range.start));
593 index_cursor.seek_forward(old_range.end);
594 new_index_text.push(&replacement_text);
595 }
596 new_index_text.append(index_cursor.suffix());
597 Some(new_index_text)
598 }
599}
600
601impl BufferDiffInner<language::BufferSnapshot> {
602 fn hunks_intersecting_range<'a>(
603 &'a self,
604 range: Range<Anchor>,
605 buffer: &'a text::BufferSnapshot,
606 secondary: Option<&'a Self>,
607 ) -> impl 'a + Iterator<Item = DiffHunk> {
608 let range = range.to_offset(buffer);
609 let filter = move |summary: &DiffHunkSummary| {
610 let summary_range = summary.buffer_range.to_offset(buffer);
611 let before_start = summary_range.end < range.start;
612 let after_end = summary_range.start > range.end;
613 !before_start && !after_end
614 };
615 self.hunks_intersecting_range_impl(filter, buffer, secondary)
616 }
617
618 fn hunks_intersecting_range_impl<'a>(
619 &'a self,
620 filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
621 buffer: &'a text::BufferSnapshot,
622 secondary: Option<&'a Self>,
623 ) -> impl 'a + Iterator<Item = DiffHunk> {
624 let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
625
626 let anchor_iter = iter::from_fn(move || {
627 cursor.next();
628 cursor.item()
629 })
630 .flat_map(move |hunk| {
631 [
632 (
633 &hunk.buffer_range.start,
634 (
635 hunk.buffer_range.start,
636 hunk.diff_base_byte_range.start,
637 hunk,
638 ),
639 ),
640 (
641 &hunk.buffer_range.end,
642 (hunk.buffer_range.end, hunk.diff_base_byte_range.end, hunk),
643 ),
644 ]
645 });
646
647 let mut pending_hunks_cursor = self.pending_hunks.cursor::<DiffHunkSummary>(buffer);
648 pending_hunks_cursor.next();
649
650 let mut secondary_cursor = None;
651 if let Some(secondary) = secondary.as_ref() {
652 let mut cursor = secondary.hunks.cursor::<DiffHunkSummary>(buffer);
653 cursor.next();
654 secondary_cursor = Some(cursor);
655 }
656
657 let max_point = buffer.max_point();
658 let mut summaries = buffer.summaries_for_anchors_with_payload::<Point, _, _>(anchor_iter);
659 iter::from_fn(move || {
660 loop {
661 let (start_point, (start_anchor, start_base, hunk)) = summaries.next()?;
662 let (mut end_point, (mut end_anchor, end_base, _)) = summaries.next()?;
663
664 let base_word_diffs = hunk.base_word_diffs.clone();
665 let buffer_word_diffs = hunk.buffer_word_diffs.clone();
666
667 if !start_anchor.is_valid(buffer) {
668 continue;
669 }
670
671 if end_point.column > 0 && end_point < max_point {
672 end_point.row += 1;
673 end_point.column = 0;
674 end_anchor = buffer.anchor_before(end_point);
675 }
676
677 let mut secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
678
679 let mut has_pending = false;
680 if start_anchor
681 .cmp(&pending_hunks_cursor.start().buffer_range.start, buffer)
682 .is_gt()
683 {
684 pending_hunks_cursor.seek_forward(&start_anchor, Bias::Left);
685 }
686
687 if let Some(pending_hunk) = pending_hunks_cursor.item() {
688 let mut pending_range = pending_hunk.buffer_range.to_point(buffer);
689 if pending_range.end.column > 0 {
690 pending_range.end.row += 1;
691 pending_range.end.column = 0;
692 }
693
694 if pending_range == (start_point..end_point)
695 && !buffer.has_edits_since_in_range(
696 &pending_hunk.buffer_version,
697 start_anchor..end_anchor,
698 )
699 {
700 has_pending = true;
701 secondary_status = pending_hunk.new_status;
702 }
703 }
704
705 if let (Some(secondary_cursor), false) = (secondary_cursor.as_mut(), has_pending) {
706 if start_anchor
707 .cmp(&secondary_cursor.start().buffer_range.start, buffer)
708 .is_gt()
709 {
710 secondary_cursor.seek_forward(&start_anchor, Bias::Left);
711 }
712
713 if let Some(secondary_hunk) = secondary_cursor.item() {
714 let mut secondary_range = secondary_hunk.buffer_range.to_point(buffer);
715 if secondary_range.end.column > 0 {
716 secondary_range.end.row += 1;
717 secondary_range.end.column = 0;
718 }
719 if secondary_range.is_empty()
720 && secondary_hunk.diff_base_byte_range.is_empty()
721 {
722 // ignore
723 } else if secondary_range == (start_point..end_point) {
724 secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
725 } else if secondary_range.start <= end_point {
726 secondary_status = DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk;
727 }
728 }
729 }
730
731 return Some(DiffHunk {
732 range: start_point..end_point,
733 diff_base_byte_range: start_base..end_base,
734 buffer_range: start_anchor..end_anchor,
735 base_word_diffs,
736 buffer_word_diffs,
737 secondary_status,
738 });
739 }
740 })
741 }
742
743 fn hunks_intersecting_range_rev_impl<'a>(
744 &'a self,
745 filter: impl 'a + Fn(&DiffHunkSummary) -> bool,
746 buffer: &'a text::BufferSnapshot,
747 ) -> impl 'a + Iterator<Item = DiffHunk> {
748 let mut cursor = self.hunks.filter::<_, DiffHunkSummary>(buffer, filter);
749
750 iter::from_fn(move || {
751 cursor.prev();
752
753 let hunk = cursor.item()?;
754 let range = hunk.buffer_range.to_point(buffer);
755
756 Some(DiffHunk {
757 range,
758 diff_base_byte_range: hunk.diff_base_byte_range.clone(),
759 buffer_range: hunk.buffer_range.clone(),
760 // The secondary status is not used by callers of this method.
761 secondary_status: DiffHunkSecondaryStatus::NoSecondaryHunk,
762 base_word_diffs: hunk.base_word_diffs.clone(),
763 buffer_word_diffs: hunk.buffer_word_diffs.clone(),
764 })
765 })
766 }
767}
768
769fn build_diff_options(
770 file: Option<&Arc<dyn File>>,
771 language: Option<LanguageName>,
772 language_scope: Option<language::LanguageScope>,
773 cx: &App,
774) -> Option<DiffOptions> {
775 #[cfg(any(test, feature = "test-support"))]
776 {
777 if !cx.has_global::<settings::SettingsStore>() {
778 return Some(DiffOptions {
779 language_scope,
780 max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
781 ..Default::default()
782 });
783 }
784 }
785
786 language_settings(language, file, cx)
787 .word_diff_enabled
788 .then_some(DiffOptions {
789 language_scope,
790 max_word_diff_line_count: MAX_WORD_DIFF_LINE_COUNT,
791 ..Default::default()
792 })
793}
794
795fn compute_hunks(
796 diff_base: Option<(Arc<str>, Rope)>,
797 buffer: text::BufferSnapshot,
798 diff_options: Option<DiffOptions>,
799) -> SumTree<InternalDiffHunk> {
800 let mut tree = SumTree::new(&buffer);
801
802 if let Some((diff_base, diff_base_rope)) = diff_base {
803 let buffer_text = buffer.as_rope().to_string();
804
805 let mut options = GitOptions::default();
806 options.context_lines(0);
807 let patch = GitPatch::from_buffers(
808 diff_base.as_bytes(),
809 None,
810 buffer_text.as_bytes(),
811 None,
812 Some(&mut options),
813 )
814 .log_err();
815
816 // A common case in Zed is that the empty buffer is represented as just a newline,
817 // but if we just compute a naive diff you get a "preserved" line in the middle,
818 // which is a bit odd.
819 if buffer_text == "\n" && diff_base.ends_with("\n") && diff_base.len() > 1 {
820 tree.push(
821 InternalDiffHunk {
822 buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0),
823 diff_base_byte_range: 0..diff_base.len() - 1,
824 base_word_diffs: Vec::default(),
825 buffer_word_diffs: Vec::default(),
826 },
827 &buffer,
828 );
829 return tree;
830 }
831
832 if let Some(patch) = patch {
833 let mut divergence = 0;
834 for hunk_index in 0..patch.num_hunks() {
835 let hunk = process_patch_hunk(
836 &patch,
837 hunk_index,
838 &diff_base_rope,
839 &buffer,
840 &mut divergence,
841 diff_options.as_ref(),
842 );
843 tree.push(hunk, &buffer);
844 }
845 }
846 } else {
847 tree.push(
848 InternalDiffHunk {
849 buffer_range: Anchor::min_max_range_for_buffer(buffer.remote_id()),
850 diff_base_byte_range: 0..0,
851 base_word_diffs: Vec::default(),
852 buffer_word_diffs: Vec::default(),
853 },
854 &buffer,
855 );
856 }
857
858 tree
859}
860
861fn compare_hunks(
862 new_hunks: &SumTree<InternalDiffHunk>,
863 old_hunks: &SumTree<InternalDiffHunk>,
864 new_snapshot: &text::BufferSnapshot,
865) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
866 let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
867 let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
868 old_cursor.next();
869 new_cursor.next();
870 let mut start = None;
871 let mut end = None;
872 let mut base_text_start = None;
873 let mut base_text_end = None;
874
875 loop {
876 match (new_cursor.item(), old_cursor.item()) {
877 (Some(new_hunk), Some(old_hunk)) => {
878 match new_hunk
879 .buffer_range
880 .start
881 .cmp(&old_hunk.buffer_range.start, new_snapshot)
882 {
883 Ordering::Less => {
884 start.get_or_insert(new_hunk.buffer_range.start);
885 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
886 end.replace(new_hunk.buffer_range.end);
887 base_text_end.replace(new_hunk.diff_base_byte_range.end);
888 new_cursor.next();
889 }
890 Ordering::Equal => {
891 if new_hunk != old_hunk {
892 start.get_or_insert(new_hunk.buffer_range.start);
893 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
894 if old_hunk
895 .buffer_range
896 .end
897 .cmp(&new_hunk.buffer_range.end, new_snapshot)
898 .is_ge()
899 {
900 end.replace(old_hunk.buffer_range.end);
901 } else {
902 end.replace(new_hunk.buffer_range.end);
903 }
904
905 base_text_end.replace(
906 old_hunk
907 .diff_base_byte_range
908 .end
909 .max(new_hunk.diff_base_byte_range.end),
910 );
911 }
912
913 new_cursor.next();
914 old_cursor.next();
915 }
916 Ordering::Greater => {
917 start.get_or_insert(old_hunk.buffer_range.start);
918 base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
919 end.replace(old_hunk.buffer_range.end);
920 base_text_end.replace(old_hunk.diff_base_byte_range.end);
921 old_cursor.next();
922 }
923 }
924 }
925 (Some(new_hunk), None) => {
926 start.get_or_insert(new_hunk.buffer_range.start);
927 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
928 // TODO(cole) it seems like this could move end backward?
929 end.replace(new_hunk.buffer_range.end);
930 base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));
931 new_cursor.next();
932 }
933 (None, Some(old_hunk)) => {
934 start.get_or_insert(old_hunk.buffer_range.start);
935 base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
936 // TODO(cole) it seems like this could move end backward?
937 end.replace(old_hunk.buffer_range.end);
938 base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));
939 old_cursor.next();
940 }
941 (None, None) => break,
942 }
943 }
944
945 (
946 start.zip(end).map(|(start, end)| start..end),
947 base_text_start
948 .zip(base_text_end)
949 .map(|(start, end)| start..end),
950 )
951}
952
953fn process_patch_hunk(
954 patch: &GitPatch<'_>,
955 hunk_index: usize,
956 diff_base: &Rope,
957 buffer: &text::BufferSnapshot,
958 buffer_row_divergence: &mut i64,
959 diff_options: Option<&DiffOptions>,
960) -> InternalDiffHunk {
961 let line_item_count = patch.num_lines_in_hunk(hunk_index).unwrap();
962 assert!(line_item_count > 0);
963
964 let mut first_deletion_buffer_row: Option<u32> = None;
965 let mut buffer_row_range: Option<Range<u32>> = None;
966 let mut diff_base_byte_range: Option<Range<usize>> = None;
967 let mut first_addition_old_row: Option<u32> = None;
968
969 for line_index in 0..line_item_count {
970 let line = patch.line_in_hunk(hunk_index, line_index).unwrap();
971 let kind = line.origin_value();
972 let content_offset = line.content_offset() as isize;
973 let content_len = line.content().len() as isize;
974 match kind {
975 GitDiffLineType::Addition => {
976 if first_addition_old_row.is_none() {
977 first_addition_old_row = Some(
978 (line.new_lineno().unwrap() as i64 - *buffer_row_divergence - 1) as u32,
979 );
980 }
981 *buffer_row_divergence += 1;
982 let row = line.new_lineno().unwrap().saturating_sub(1);
983
984 match &mut buffer_row_range {
985 Some(Range { end, .. }) => *end = row + 1,
986 None => buffer_row_range = Some(row..row + 1),
987 }
988 }
989 GitDiffLineType::Deletion => {
990 let end = content_offset + content_len;
991
992 match &mut diff_base_byte_range {
993 Some(head_byte_range) => head_byte_range.end = end as usize,
994 None => diff_base_byte_range = Some(content_offset as usize..end as usize),
995 }
996
997 if first_deletion_buffer_row.is_none() {
998 let old_row = line.old_lineno().unwrap().saturating_sub(1);
999 let row = old_row as i64 + *buffer_row_divergence;
1000 first_deletion_buffer_row = Some(row as u32);
1001 }
1002
1003 *buffer_row_divergence -= 1;
1004 }
1005 _ => {}
1006 }
1007 }
1008
1009 let buffer_row_range = buffer_row_range.unwrap_or_else(|| {
1010 // Pure deletion hunk without addition.
1011 let row = first_deletion_buffer_row.unwrap();
1012 row..row
1013 });
1014 let diff_base_byte_range = diff_base_byte_range.unwrap_or_else(|| {
1015 // Pure addition hunk without deletion.
1016 let row = first_addition_old_row.unwrap();
1017 let offset = diff_base.point_to_offset(Point::new(row, 0));
1018 offset..offset
1019 });
1020
1021 let start = Point::new(buffer_row_range.start, 0);
1022 let end = Point::new(buffer_row_range.end, 0);
1023 let buffer_range = buffer.anchor_before(start)..buffer.anchor_before(end);
1024
1025 let base_line_count = line_item_count.saturating_sub(buffer_row_range.len());
1026
1027 let (base_word_diffs, buffer_word_diffs) = if let Some(diff_options) = diff_options
1028 && !buffer_row_range.is_empty()
1029 && base_line_count == buffer_row_range.len()
1030 && diff_options.max_word_diff_line_count >= base_line_count
1031 {
1032 let base_text: String = diff_base
1033 .chunks_in_range(diff_base_byte_range.clone())
1034 .collect();
1035
1036 let buffer_text: String = buffer.text_for_range(buffer_range.clone()).collect();
1037
1038 let (base_word_diffs, buffer_word_diffs_relative) = word_diff_ranges(
1039 &base_text,
1040 &buffer_text,
1041 DiffOptions {
1042 language_scope: diff_options.language_scope.clone(),
1043 ..*diff_options
1044 },
1045 );
1046
1047 let buffer_start_offset = buffer_range.start.to_offset(buffer);
1048 let buffer_word_diffs = buffer_word_diffs_relative
1049 .into_iter()
1050 .map(|range| {
1051 let start = buffer.anchor_after(buffer_start_offset + range.start);
1052 let end = buffer.anchor_after(buffer_start_offset + range.end);
1053 start..end
1054 })
1055 .collect();
1056
1057 (base_word_diffs, buffer_word_diffs)
1058 } else {
1059 (Vec::default(), Vec::default())
1060 };
1061
1062 InternalDiffHunk {
1063 buffer_range,
1064 diff_base_byte_range,
1065 base_word_diffs,
1066 buffer_word_diffs,
1067 }
1068}
1069
1070impl std::fmt::Debug for BufferDiff {
1071 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1072 f.debug_struct("BufferChangeSet")
1073 .field("buffer_id", &self.buffer_id)
1074 .finish()
1075 }
1076}
1077
1078#[derive(Clone, Debug)]
1079pub enum BufferDiffEvent {
1080 DiffChanged {
1081 changed_range: Option<Range<text::Anchor>>,
1082 base_text_changed_range: Option<Range<usize>>,
1083 },
1084 LanguageChanged,
1085 HunksStagedOrUnstaged(Option<Rope>),
1086}
1087
1088impl EventEmitter<BufferDiffEvent> for BufferDiff {}
1089
1090impl BufferDiff {
1091 pub fn new(buffer: &text::BufferSnapshot, cx: &mut App) -> Self {
1092 let base_text = cx.new(|cx| {
1093 let mut buffer = language::Buffer::local("", cx);
1094 buffer.set_capability(Capability::ReadOnly, cx);
1095 buffer
1096 });
1097
1098 BufferDiff {
1099 buffer_id: buffer.remote_id(),
1100 inner: BufferDiffInner {
1101 base_text,
1102 hunks: SumTree::new(buffer),
1103 pending_hunks: SumTree::new(buffer),
1104 base_text_exists: false,
1105 },
1106 secondary_diff: None,
1107 }
1108 }
1109
1110 pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1111 let base_text = buffer.text();
1112 let base_text = cx.new(|cx| {
1113 let mut buffer = language::Buffer::local(base_text, cx);
1114 buffer.set_capability(Capability::ReadOnly, cx);
1115 buffer
1116 });
1117
1118 BufferDiff {
1119 buffer_id: buffer.remote_id(),
1120 inner: BufferDiffInner {
1121 base_text,
1122 hunks: SumTree::new(buffer),
1123 pending_hunks: SumTree::new(buffer),
1124 base_text_exists: true,
1125 },
1126 secondary_diff: None,
1127 }
1128 }
1129
1130 #[cfg(any(test, feature = "test-support"))]
1131 pub fn new_with_base_text(
1132 base_text: &str,
1133 buffer: &text::BufferSnapshot,
1134 cx: &mut Context<Self>,
1135 ) -> Self {
1136 let mut this = BufferDiff::new(&buffer, cx);
1137 let mut base_text = base_text.to_owned();
1138 text::LineEnding::normalize(&mut base_text);
1139 let inner = cx.foreground_executor().block_on(this.update_diff(
1140 buffer.clone(),
1141 Some(Arc::from(base_text)),
1142 true,
1143 None,
1144 cx,
1145 ));
1146 this.set_snapshot(inner, &buffer, cx).detach();
1147 this
1148 }
1149
1150 pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1151 self.secondary_diff = Some(diff);
1152 }
1153
1154 pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1155 self.secondary_diff.clone()
1156 }
1157
1158 pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1159 if self.secondary_diff.is_some() {
1160 self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1161 buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1162 diff_base_byte_range: 0..0,
1163 });
1164 cx.emit(BufferDiffEvent::DiffChanged {
1165 changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
1166 base_text_changed_range: Some(0..self.base_text(cx).len()),
1167 });
1168 }
1169 }
1170
1171 pub fn stage_or_unstage_hunks(
1172 &mut self,
1173 stage: bool,
1174 hunks: &[DiffHunk],
1175 buffer: &text::BufferSnapshot,
1176 file_exists: bool,
1177 cx: &mut Context<Self>,
1178 ) -> Option<Rope> {
1179 let new_index_text = self
1180 .secondary_diff
1181 .as_ref()?
1182 .update(cx, |secondary_diff, cx| {
1183 self.inner.stage_or_unstage_hunks_impl(
1184 &secondary_diff.inner,
1185 stage,
1186 hunks,
1187 buffer,
1188 file_exists,
1189 cx,
1190 )
1191 });
1192
1193 cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1194 new_index_text.clone(),
1195 ));
1196 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1197 let changed_range = first.buffer_range.start..last.buffer_range.end;
1198 let base_text_changed_range =
1199 first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1200 cx.emit(BufferDiffEvent::DiffChanged {
1201 changed_range: Some(changed_range),
1202 base_text_changed_range: Some(base_text_changed_range),
1203 });
1204 }
1205 new_index_text
1206 }
1207
1208 pub fn stage_or_unstage_all_hunks(
1209 &mut self,
1210 stage: bool,
1211 buffer: &text::BufferSnapshot,
1212 file_exists: bool,
1213 cx: &mut Context<Self>,
1214 ) {
1215 let hunks = self
1216 .snapshot(cx)
1217 .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer)
1218 .collect::<Vec<_>>();
1219 let Some(secondary) = self.secondary_diff.clone() else {
1220 return;
1221 };
1222 let secondary = secondary.read(cx).inner.clone();
1223 self.inner
1224 .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
1225 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1226 let changed_range = first.buffer_range.start..last.buffer_range.end;
1227 let base_text_changed_range =
1228 first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1229 cx.emit(BufferDiffEvent::DiffChanged {
1230 changed_range: Some(changed_range),
1231 base_text_changed_range: Some(base_text_changed_range),
1232 });
1233 }
1234 }
1235
1236 pub fn update_diff(
1237 &self,
1238 buffer: text::BufferSnapshot,
1239 base_text: Option<Arc<str>>,
1240 base_text_changed: bool,
1241 language: Option<Arc<Language>>,
1242 cx: &App,
1243 ) -> Task<BufferDiffUpdate> {
1244 let prev_base_text = self.base_text(cx).as_rope().clone();
1245 let diff_options = build_diff_options(
1246 None,
1247 language.as_ref().map(|l| l.name()),
1248 language.as_ref().map(|l| l.default_scope()),
1249 cx,
1250 );
1251
1252 cx.background_executor().spawn(async move {
1253 let base_text_rope = if let Some(base_text) = &base_text {
1254 if base_text_changed {
1255 Rope::from(base_text.as_ref())
1256 } else {
1257 prev_base_text
1258 }
1259 } else {
1260 Rope::new()
1261 };
1262 let base_text_exists = base_text.is_some();
1263 let hunks = compute_hunks(
1264 base_text
1265 .clone()
1266 .map(|base_text| (base_text, base_text_rope.clone())),
1267 buffer.clone(),
1268 diff_options,
1269 );
1270 let base_text = base_text.unwrap_or_default();
1271 let inner = BufferDiffInner {
1272 base_text,
1273 hunks,
1274 base_text_exists,
1275 pending_hunks: SumTree::new(&buffer),
1276 };
1277 BufferDiffUpdate {
1278 inner,
1279 base_text_changed,
1280 }
1281 })
1282 }
1283
1284 pub fn language_changed(
1285 &mut self,
1286 language: Option<Arc<Language>>,
1287 language_registry: Option<Arc<LanguageRegistry>>,
1288 cx: &mut Context<Self>,
1289 ) {
1290 let fut = self.inner.base_text.update(cx, |base_text, cx| {
1291 if let Some(language_registry) = language_registry {
1292 base_text.set_language_registry(language_registry);
1293 }
1294 base_text.set_language(language, cx);
1295 base_text.parsing_idle()
1296 });
1297 cx.spawn(async move |this, cx| {
1298 fut.await;
1299 this.update(cx, |_, cx| {
1300 cx.emit(BufferDiffEvent::LanguageChanged);
1301 })
1302 .ok();
1303 })
1304 .detach();
1305 }
1306
1307 fn set_snapshot_with_secondary_inner(
1308 &mut self,
1309 update: BufferDiffUpdate,
1310 buffer: &text::BufferSnapshot,
1311 secondary_diff_change: Option<Range<Anchor>>,
1312 clear_pending_hunks: bool,
1313 cx: &mut Context<Self>,
1314 ) -> impl Future<Output = (Option<Range<Anchor>>, Option<Range<usize>>)> + use<> {
1315 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1316
1317 let old_snapshot = self.snapshot(cx);
1318 let state = &mut self.inner;
1319 let new_state = update.inner;
1320 let (mut changed_range, mut base_text_changed_range) =
1321 match (state.base_text_exists, new_state.base_text_exists) {
1322 (false, false) => (None, None),
1323 (true, true) if !update.base_text_changed => {
1324 compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
1325 }
1326 _ => (
1327 Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
1328 Some(0..new_state.base_text.len()),
1329 ),
1330 };
1331
1332 if let Some(secondary_changed_range) = secondary_diff_change
1333 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1334 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1335 {
1336 if let Some(range) = &mut changed_range {
1337 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1338 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1339 } else {
1340 changed_range = Some(secondary_hunk_range);
1341 }
1342
1343 if let Some(base_text_range) = &mut base_text_changed_range {
1344 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1345 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1346 } else {
1347 base_text_changed_range = Some(secondary_base_range);
1348 }
1349 }
1350
1351 let state = &mut self.inner;
1352 state.base_text_exists = new_state.base_text_exists;
1353 let parsing_idle = if update.base_text_changed {
1354 state.base_text.update(cx, |base_text, cx| {
1355 base_text.set_capability(Capability::ReadWrite, cx);
1356 base_text.set_text(new_state.base_text.clone(), cx);
1357 base_text.set_capability(Capability::ReadOnly, cx);
1358 Some(base_text.parsing_idle())
1359 })
1360 } else {
1361 None
1362 };
1363 state.hunks = new_state.hunks;
1364 if update.base_text_changed || clear_pending_hunks {
1365 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1366 {
1367 if let Some(range) = &mut changed_range {
1368 range.start = *range.start.min(&first.buffer_range.start, buffer);
1369 range.end = *range.end.max(&last.buffer_range.end, buffer);
1370 } else {
1371 changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1372 }
1373
1374 if let Some(base_text_range) = &mut base_text_changed_range {
1375 base_text_range.start =
1376 base_text_range.start.min(first.diff_base_byte_range.start);
1377 base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1378 } else {
1379 base_text_changed_range =
1380 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1381 }
1382 }
1383 state.pending_hunks = SumTree::new(buffer);
1384 }
1385
1386 async move {
1387 if let Some(parsing_idle) = parsing_idle {
1388 parsing_idle.await;
1389 }
1390 (changed_range, base_text_changed_range)
1391 }
1392 }
1393
1394 pub fn set_snapshot(
1395 &mut self,
1396 new_state: BufferDiffUpdate,
1397 buffer: &text::BufferSnapshot,
1398 cx: &mut Context<Self>,
1399 ) -> Task<Option<Range<Anchor>>> {
1400 self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1401 }
1402
1403 pub fn set_snapshot_with_secondary(
1404 &mut self,
1405 update: BufferDiffUpdate,
1406 buffer: &text::BufferSnapshot,
1407 secondary_diff_change: Option<Range<Anchor>>,
1408 clear_pending_hunks: bool,
1409 cx: &mut Context<Self>,
1410 ) -> Task<Option<Range<Anchor>>> {
1411 let fut = self.set_snapshot_with_secondary_inner(
1412 update,
1413 buffer,
1414 secondary_diff_change,
1415 clear_pending_hunks,
1416 cx,
1417 );
1418
1419 cx.spawn(async move |this, cx| {
1420 let (changed_range, base_text_changed_range) = fut.await;
1421 this.update(cx, |_, cx| {
1422 cx.emit(BufferDiffEvent::DiffChanged {
1423 changed_range: changed_range.clone(),
1424 base_text_changed_range,
1425 });
1426 })
1427 .ok();
1428 changed_range
1429 })
1430 }
1431
1432 pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1433 self.inner.base_text.read(cx).snapshot()
1434 }
1435
1436 pub fn base_text_exists(&self) -> bool {
1437 self.inner.base_text_exists
1438 }
1439
1440 pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1441 BufferDiffSnapshot {
1442 inner: BufferDiffInner {
1443 hunks: self.inner.hunks.clone(),
1444 pending_hunks: self.inner.pending_hunks.clone(),
1445 base_text: self.inner.base_text.read(cx).snapshot(),
1446 base_text_exists: self.inner.base_text_exists,
1447 },
1448 secondary_diff: self
1449 .secondary_diff
1450 .as_ref()
1451 .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1452 }
1453 }
1454
1455 /// Used in cases where the change set isn't derived from git.
1456 pub fn set_base_text(
1457 &mut self,
1458 base_text: Option<Arc<str>>,
1459 language: Option<Arc<Language>>,
1460 buffer: text::BufferSnapshot,
1461 cx: &mut Context<Self>,
1462 ) -> oneshot::Receiver<()> {
1463 let (tx, rx) = oneshot::channel();
1464 let complete_on_drop = util::defer(|| {
1465 tx.send(()).ok();
1466 });
1467 cx.spawn(async move |this, cx| {
1468 let Some(state) = this
1469 .update(cx, |this, cx| {
1470 this.update_diff(buffer.clone(), base_text, true, language, cx)
1471 })
1472 .log_err()
1473 else {
1474 return;
1475 };
1476 let state = state.await;
1477 if let Some(task) = this
1478 .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
1479 .log_err()
1480 {
1481 task.await;
1482 }
1483 drop(complete_on_drop)
1484 })
1485 .detach();
1486 rx
1487 }
1488
1489 pub fn base_text_string(&self, cx: &App) -> Option<String> {
1490 self.inner
1491 .base_text_exists
1492 .then(|| self.inner.base_text.read(cx).text())
1493 }
1494
1495 #[cfg(any(test, feature = "test-support"))]
1496 pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1497 let language = self.base_text(cx).language().cloned();
1498 let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1499 let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
1500 let fg_executor = cx.foreground_executor().clone();
1501 let snapshot = fg_executor.block_on(fut);
1502 let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
1503 let (changed_range, base_text_changed_range) = fg_executor.block_on(fut);
1504 cx.emit(BufferDiffEvent::DiffChanged {
1505 changed_range,
1506 base_text_changed_range,
1507 })
1508 }
1509
1510 pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1511 self.inner.base_text.clone()
1512 }
1513}
1514
1515impl DiffHunk {
1516 pub fn is_created_file(&self) -> bool {
1517 self.diff_base_byte_range == (0..0)
1518 && self.buffer_range.start.is_min()
1519 && self.buffer_range.end.is_max()
1520 }
1521
1522 pub fn status(&self) -> DiffHunkStatus {
1523 let kind = if self.buffer_range.start == self.buffer_range.end {
1524 DiffHunkStatusKind::Deleted
1525 } else if self.diff_base_byte_range.is_empty() {
1526 DiffHunkStatusKind::Added
1527 } else {
1528 DiffHunkStatusKind::Modified
1529 };
1530 DiffHunkStatus {
1531 kind,
1532 secondary: self.secondary_status,
1533 }
1534 }
1535}
1536
1537impl DiffHunkStatus {
1538 pub fn has_secondary_hunk(&self) -> bool {
1539 matches!(
1540 self.secondary,
1541 DiffHunkSecondaryStatus::HasSecondaryHunk
1542 | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1543 | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1544 )
1545 }
1546
1547 pub fn is_pending(&self) -> bool {
1548 matches!(
1549 self.secondary,
1550 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1551 | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1552 )
1553 }
1554
1555 pub fn is_deleted(&self) -> bool {
1556 self.kind == DiffHunkStatusKind::Deleted
1557 }
1558
1559 pub fn is_added(&self) -> bool {
1560 self.kind == DiffHunkStatusKind::Added
1561 }
1562
1563 pub fn is_modified(&self) -> bool {
1564 self.kind == DiffHunkStatusKind::Modified
1565 }
1566
1567 pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1568 Self {
1569 kind: DiffHunkStatusKind::Added,
1570 secondary,
1571 }
1572 }
1573
1574 pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1575 Self {
1576 kind: DiffHunkStatusKind::Modified,
1577 secondary,
1578 }
1579 }
1580
1581 pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1582 Self {
1583 kind: DiffHunkStatusKind::Deleted,
1584 secondary,
1585 }
1586 }
1587
1588 pub fn deleted_none() -> Self {
1589 Self {
1590 kind: DiffHunkStatusKind::Deleted,
1591 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1592 }
1593 }
1594
1595 pub fn added_none() -> Self {
1596 Self {
1597 kind: DiffHunkStatusKind::Added,
1598 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1599 }
1600 }
1601
1602 pub fn modified_none() -> Self {
1603 Self {
1604 kind: DiffHunkStatusKind::Modified,
1605 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1606 }
1607 }
1608}
1609
1610#[cfg(any(test, feature = "test-support"))]
1611#[track_caller]
1612pub fn assert_hunks<ExpectedText, HunkIter>(
1613 diff_hunks: HunkIter,
1614 buffer: &text::BufferSnapshot,
1615 diff_base: &str,
1616 // Line range, deleted, added, status
1617 expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1618) where
1619 HunkIter: Iterator<Item = DiffHunk>,
1620 ExpectedText: AsRef<str>,
1621{
1622 let actual_hunks = diff_hunks
1623 .map(|hunk| {
1624 (
1625 hunk.range.clone(),
1626 &diff_base[hunk.diff_base_byte_range.clone()],
1627 buffer
1628 .text_for_range(hunk.range.clone())
1629 .collect::<String>(),
1630 hunk.status(),
1631 )
1632 })
1633 .collect::<Vec<_>>();
1634
1635 let expected_hunks: Vec<_> = expected_hunks
1636 .iter()
1637 .map(|(line_range, deleted_text, added_text, status)| {
1638 (
1639 Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1640 deleted_text.as_ref(),
1641 added_text.as_ref().to_string(),
1642 *status,
1643 )
1644 })
1645 .collect();
1646
1647 pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1648}
1649
1650#[cfg(test)]
1651mod tests {
1652 use std::{fmt::Write as _, sync::mpsc};
1653
1654 use super::*;
1655 use gpui::TestAppContext;
1656 use pretty_assertions::{assert_eq, assert_ne};
1657 use rand::{Rng as _, rngs::StdRng};
1658 use text::{Buffer, BufferId, ReplicaId, Rope};
1659 use unindent::Unindent as _;
1660 use util::test::marked_text_ranges;
1661
1662 #[ctor::ctor]
1663 fn init_logger() {
1664 zlog::init_test();
1665 }
1666
1667 #[gpui::test]
1668 async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1669 let diff_base = "
1670 one
1671 two
1672 three
1673 "
1674 .unindent();
1675
1676 let buffer_text = "
1677 one
1678 HELLO
1679 three
1680 "
1681 .unindent();
1682
1683 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1684 let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1685 assert_hunks(
1686 diff.hunks_intersecting_range(
1687 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1688 &buffer,
1689 ),
1690 &buffer,
1691 &diff_base,
1692 &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1693 );
1694
1695 buffer.edit([(0..0, "point five\n")]);
1696 diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1697 assert_hunks(
1698 diff.hunks_intersecting_range(
1699 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1700 &buffer,
1701 ),
1702 &buffer,
1703 &diff_base,
1704 &[
1705 (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1706 (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1707 ],
1708 );
1709
1710 diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
1711 assert_hunks::<&str, _>(
1712 diff.hunks_intersecting_range(
1713 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1714 &buffer,
1715 ),
1716 &buffer,
1717 &diff_base,
1718 &[],
1719 );
1720 }
1721
1722 #[gpui::test]
1723 async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1724 let head_text = "
1725 zero
1726 one
1727 two
1728 three
1729 four
1730 five
1731 six
1732 seven
1733 eight
1734 nine
1735 "
1736 .unindent();
1737
1738 let index_text = "
1739 zero
1740 one
1741 TWO
1742 three
1743 FOUR
1744 five
1745 six
1746 seven
1747 eight
1748 NINE
1749 "
1750 .unindent();
1751
1752 let buffer_text = "
1753 zero
1754 one
1755 TWO
1756 three
1757 FOUR
1758 FIVE
1759 six
1760 SEVEN
1761 eight
1762 nine
1763 "
1764 .unindent();
1765
1766 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1767 let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1768 let mut uncommitted_diff =
1769 BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1770 uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1771
1772 let expected_hunks = vec![
1773 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1774 (
1775 4..6,
1776 "four\nfive\n",
1777 "FOUR\nFIVE\n",
1778 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1779 ),
1780 (
1781 7..8,
1782 "seven\n",
1783 "SEVEN\n",
1784 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1785 ),
1786 ];
1787
1788 assert_hunks(
1789 uncommitted_diff.hunks_intersecting_range(
1790 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1791 &buffer,
1792 ),
1793 &buffer,
1794 &head_text,
1795 &expected_hunks,
1796 );
1797 }
1798
1799 #[gpui::test]
1800 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1801 let diff_base = "
1802 one
1803 two
1804 three
1805 four
1806 five
1807 six
1808 seven
1809 eight
1810 nine
1811 ten
1812 "
1813 .unindent();
1814
1815 let buffer_text = "
1816 A
1817 one
1818 B
1819 two
1820 C
1821 three
1822 HELLO
1823 four
1824 five
1825 SIXTEEN
1826 seven
1827 eight
1828 WORLD
1829 nine
1830
1831 ten
1832
1833 "
1834 .unindent();
1835
1836 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1837 let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
1838 assert_eq!(
1839 diff.hunks_intersecting_range(
1840 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1841 &buffer
1842 )
1843 .count(),
1844 8
1845 );
1846
1847 assert_hunks(
1848 diff.hunks_intersecting_range(
1849 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1850 &buffer,
1851 ),
1852 &buffer,
1853 &diff_base,
1854 &[
1855 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1856 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1857 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1858 ],
1859 );
1860 }
1861
1862 #[gpui::test]
1863 async fn test_stage_hunk(cx: &mut TestAppContext) {
1864 struct Example {
1865 name: &'static str,
1866 head_text: String,
1867 index_text: String,
1868 buffer_marked_text: String,
1869 final_index_text: String,
1870 }
1871
1872 let table = [
1873 Example {
1874 name: "uncommitted hunk straddles end of unstaged hunk",
1875 head_text: "
1876 one
1877 two
1878 three
1879 four
1880 five
1881 "
1882 .unindent(),
1883 index_text: "
1884 one
1885 TWO_HUNDRED
1886 three
1887 FOUR_HUNDRED
1888 five
1889 "
1890 .unindent(),
1891 buffer_marked_text: "
1892 ZERO
1893 one
1894 two
1895 «THREE_HUNDRED
1896 FOUR_HUNDRED»
1897 five
1898 SIX
1899 "
1900 .unindent(),
1901 final_index_text: "
1902 one
1903 two
1904 THREE_HUNDRED
1905 FOUR_HUNDRED
1906 five
1907 "
1908 .unindent(),
1909 },
1910 Example {
1911 name: "uncommitted hunk straddles start of unstaged hunk",
1912 head_text: "
1913 one
1914 two
1915 three
1916 four
1917 five
1918 "
1919 .unindent(),
1920 index_text: "
1921 one
1922 TWO_HUNDRED
1923 three
1924 FOUR_HUNDRED
1925 five
1926 "
1927 .unindent(),
1928 buffer_marked_text: "
1929 ZERO
1930 one
1931 «TWO_HUNDRED
1932 THREE_HUNDRED»
1933 four
1934 five
1935 SIX
1936 "
1937 .unindent(),
1938 final_index_text: "
1939 one
1940 TWO_HUNDRED
1941 THREE_HUNDRED
1942 four
1943 five
1944 "
1945 .unindent(),
1946 },
1947 Example {
1948 name: "uncommitted hunk strictly contains unstaged hunks",
1949 head_text: "
1950 one
1951 two
1952 three
1953 four
1954 five
1955 six
1956 seven
1957 "
1958 .unindent(),
1959 index_text: "
1960 one
1961 TWO
1962 THREE
1963 FOUR
1964 FIVE
1965 SIX
1966 seven
1967 "
1968 .unindent(),
1969 buffer_marked_text: "
1970 one
1971 TWO
1972 «THREE_HUNDRED
1973 FOUR
1974 FIVE_HUNDRED»
1975 SIX
1976 seven
1977 "
1978 .unindent(),
1979 final_index_text: "
1980 one
1981 TWO
1982 THREE_HUNDRED
1983 FOUR
1984 FIVE_HUNDRED
1985 SIX
1986 seven
1987 "
1988 .unindent(),
1989 },
1990 Example {
1991 name: "uncommitted deletion hunk",
1992 head_text: "
1993 one
1994 two
1995 three
1996 four
1997 five
1998 "
1999 .unindent(),
2000 index_text: "
2001 one
2002 two
2003 three
2004 four
2005 five
2006 "
2007 .unindent(),
2008 buffer_marked_text: "
2009 one
2010 ˇfive
2011 "
2012 .unindent(),
2013 final_index_text: "
2014 one
2015 five
2016 "
2017 .unindent(),
2018 },
2019 Example {
2020 name: "one unstaged hunk that contains two uncommitted hunks",
2021 head_text: "
2022 one
2023 two
2024
2025 three
2026 four
2027 "
2028 .unindent(),
2029 index_text: "
2030 one
2031 two
2032 three
2033 four
2034 "
2035 .unindent(),
2036 buffer_marked_text: "
2037 «one
2038
2039 three // modified
2040 four»
2041 "
2042 .unindent(),
2043 final_index_text: "
2044 one
2045
2046 three // modified
2047 four
2048 "
2049 .unindent(),
2050 },
2051 Example {
2052 name: "one uncommitted hunk that contains two unstaged hunks",
2053 head_text: "
2054 one
2055 two
2056 three
2057 four
2058 five
2059 "
2060 .unindent(),
2061 index_text: "
2062 ZERO
2063 one
2064 TWO
2065 THREE
2066 FOUR
2067 five
2068 "
2069 .unindent(),
2070 buffer_marked_text: "
2071 «one
2072 TWO_HUNDRED
2073 THREE
2074 FOUR_HUNDRED
2075 five»
2076 "
2077 .unindent(),
2078 final_index_text: "
2079 ZERO
2080 one
2081 TWO_HUNDRED
2082 THREE
2083 FOUR_HUNDRED
2084 five
2085 "
2086 .unindent(),
2087 },
2088 ];
2089
2090 for example in table {
2091 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2092 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2093 let hunk_range =
2094 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2095
2096 let unstaged_diff =
2097 cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2098
2099 let uncommitted_diff = cx.new(|cx| {
2100 let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2101 diff.set_secondary_diff(unstaged_diff);
2102 diff
2103 });
2104
2105 uncommitted_diff.update(cx, |diff, cx| {
2106 let hunks = diff
2107 .snapshot(cx)
2108 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2109 .collect::<Vec<_>>();
2110 for hunk in &hunks {
2111 assert_ne!(
2112 hunk.secondary_status,
2113 DiffHunkSecondaryStatus::NoSecondaryHunk
2114 )
2115 }
2116
2117 let new_index_text = diff
2118 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2119 .unwrap()
2120 .to_string();
2121
2122 let hunks = diff
2123 .snapshot(cx)
2124 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2125 .collect::<Vec<_>>();
2126 for hunk in &hunks {
2127 assert_eq!(
2128 hunk.secondary_status,
2129 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2130 )
2131 }
2132
2133 pretty_assertions::assert_eq!(
2134 new_index_text,
2135 example.final_index_text,
2136 "example: {}",
2137 example.name
2138 );
2139 });
2140 }
2141 }
2142
2143 #[gpui::test]
2144 async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2145 // This test reproduces a crash where staging all hunks would cause an underflow
2146 // when there's one large unstaged hunk containing multiple uncommitted hunks.
2147 let head_text = "
2148 aaa
2149 bbb
2150 ccc
2151 ddd
2152 eee
2153 fff
2154 ggg
2155 hhh
2156 iii
2157 jjj
2158 kkk
2159 lll
2160 "
2161 .unindent();
2162
2163 let index_text = "
2164 aaa
2165 bbb
2166 CCC-index
2167 DDD-index
2168 EEE-index
2169 FFF-index
2170 GGG-index
2171 HHH-index
2172 III-index
2173 JJJ-index
2174 kkk
2175 lll
2176 "
2177 .unindent();
2178
2179 let buffer_text = "
2180 aaa
2181 bbb
2182 ccc-modified
2183 ddd
2184 eee-modified
2185 fff
2186 ggg
2187 hhh-modified
2188 iii
2189 jjj
2190 kkk
2191 lll
2192 "
2193 .unindent();
2194
2195 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2196
2197 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2198 let uncommitted_diff = cx.new(|cx| {
2199 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2200 diff.set_secondary_diff(unstaged_diff);
2201 diff
2202 });
2203
2204 uncommitted_diff.update(cx, |diff, cx| {
2205 diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2206 });
2207 }
2208
2209 #[gpui::test]
2210 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2211 let head_text = "
2212 one
2213 two
2214 three
2215 "
2216 .unindent();
2217 let index_text = head_text.clone();
2218 let buffer_text = "
2219 one
2220 three
2221 "
2222 .unindent();
2223
2224 let buffer = Buffer::new(
2225 ReplicaId::LOCAL,
2226 BufferId::new(1).unwrap(),
2227 buffer_text.clone(),
2228 );
2229 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2230 let uncommitted_diff = cx.new(|cx| {
2231 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2232 diff.set_secondary_diff(unstaged_diff.clone());
2233 diff
2234 });
2235
2236 uncommitted_diff.update(cx, |diff, cx| {
2237 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2238
2239 let new_index_text = diff
2240 .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2241 .unwrap()
2242 .to_string();
2243 assert_eq!(new_index_text, buffer_text);
2244
2245 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2246 assert_eq!(
2247 hunk.secondary_status,
2248 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2249 );
2250
2251 let index_text = diff
2252 .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2253 .unwrap()
2254 .to_string();
2255 assert_eq!(index_text, head_text);
2256
2257 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2258 // optimistically unstaged (fine, could also be HasSecondaryHunk)
2259 assert_eq!(
2260 hunk.secondary_status,
2261 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2262 );
2263 });
2264 }
2265
2266 #[gpui::test]
2267 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2268 let base_text = "
2269 zero
2270 one
2271 two
2272 three
2273 four
2274 five
2275 six
2276 seven
2277 eight
2278 nine
2279 "
2280 .unindent();
2281
2282 let buffer_text_1 = "
2283 one
2284 three
2285 four
2286 five
2287 SIX
2288 seven
2289 eight
2290 NINE
2291 "
2292 .unindent();
2293
2294 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2295
2296 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2297 let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2298 let (range, base_text_range) =
2299 compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
2300 let range = range.unwrap();
2301 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2302 let base_text_range = base_text_range.unwrap();
2303 assert_eq!(
2304 base_text_range.to_point(diff_1.base_text()),
2305 Point::new(0, 0)..Point::new(10, 0)
2306 );
2307
2308 // Edit does affects the diff because it recalculates word diffs.
2309 buffer.edit_via_marked_text(
2310 &"
2311 one
2312 three
2313 four
2314 five
2315 «SIX.5»
2316 seven
2317 eight
2318 NINE
2319 "
2320 .unindent(),
2321 );
2322 let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2323 let (range, base_text_range) =
2324 compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer);
2325 assert_eq!(
2326 range.unwrap().to_point(&buffer),
2327 Point::new(4, 0)..Point::new(5, 0),
2328 );
2329 assert_eq!(
2330 base_text_range.unwrap().to_point(diff_2.base_text()),
2331 Point::new(6, 0)..Point::new(7, 0),
2332 );
2333
2334 // Edit turns a deletion hunk into a modification.
2335 buffer.edit_via_marked_text(
2336 &"
2337 one
2338 «THREE»
2339 four
2340 five
2341 SIX.5
2342 seven
2343 eight
2344 NINE
2345 "
2346 .unindent(),
2347 );
2348 let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2349 let (range, base_text_range) =
2350 compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
2351 let range = range.unwrap();
2352 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2353 let base_text_range = base_text_range.unwrap();
2354 assert_eq!(
2355 base_text_range.to_point(diff_3.base_text()),
2356 Point::new(2, 0)..Point::new(4, 0)
2357 );
2358
2359 // Edit turns a modification hunk into a deletion.
2360 buffer.edit_via_marked_text(
2361 &"
2362 one
2363 THREE
2364 four
2365 five«»
2366 seven
2367 eight
2368 NINE
2369 "
2370 .unindent(),
2371 );
2372 let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2373 let (range, base_text_range) =
2374 compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
2375 let range = range.unwrap();
2376 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2377 let base_text_range = base_text_range.unwrap();
2378 assert_eq!(
2379 base_text_range.to_point(diff_4.base_text()),
2380 Point::new(6, 0)..Point::new(7, 0)
2381 );
2382
2383 // Edit introduces a new insertion hunk.
2384 buffer.edit_via_marked_text(
2385 &"
2386 one
2387 THREE
2388 four«
2389 FOUR.5
2390 »five
2391 seven
2392 eight
2393 NINE
2394 "
2395 .unindent(),
2396 );
2397 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2398 let (range, base_text_range) =
2399 compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
2400 let range = range.unwrap();
2401 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2402 let base_text_range = base_text_range.unwrap();
2403 assert_eq!(
2404 base_text_range.to_point(diff_5.base_text()),
2405 Point::new(5, 0)..Point::new(5, 0)
2406 );
2407
2408 // Edit removes a hunk.
2409 buffer.edit_via_marked_text(
2410 &"
2411 one
2412 THREE
2413 four
2414 FOUR.5
2415 five
2416 seven
2417 eight
2418 «nine»
2419 "
2420 .unindent(),
2421 );
2422 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2423 let (range, base_text_range) =
2424 compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
2425 let range = range.unwrap();
2426 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2427 let base_text_range = base_text_range.unwrap();
2428 assert_eq!(
2429 base_text_range.to_point(diff_6.base_text()),
2430 Point::new(9, 0)..Point::new(10, 0)
2431 );
2432 }
2433
2434 #[gpui::test(iterations = 100)]
2435 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2436 fn gen_line(rng: &mut StdRng) -> String {
2437 if rng.random_bool(0.2) {
2438 "\n".to_owned()
2439 } else {
2440 let c = rng.random_range('A'..='Z');
2441 format!("{c}{c}{c}\n")
2442 }
2443 }
2444
2445 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2446 let mut old_lines = {
2447 let mut old_lines = Vec::new();
2448 let old_lines_iter = head.lines();
2449 for line in old_lines_iter {
2450 assert!(!line.ends_with("\n"));
2451 old_lines.push(line.to_owned());
2452 }
2453 if old_lines.last().is_some_and(|line| line.is_empty()) {
2454 old_lines.pop();
2455 }
2456 old_lines.into_iter()
2457 };
2458 let mut result = String::new();
2459 let unchanged_count = rng.random_range(0..=old_lines.len());
2460 result +=
2461 &old_lines
2462 .by_ref()
2463 .take(unchanged_count)
2464 .fold(String::new(), |mut s, line| {
2465 writeln!(&mut s, "{line}").unwrap();
2466 s
2467 });
2468 while old_lines.len() > 0 {
2469 let deleted_count = rng.random_range(0..=old_lines.len());
2470 let _advance = old_lines
2471 .by_ref()
2472 .take(deleted_count)
2473 .map(|line| line.len() + 1)
2474 .sum::<usize>();
2475 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2476 let added_count = rng.random_range(minimum_added..=5);
2477 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2478 result += &addition;
2479
2480 if old_lines.len() > 0 {
2481 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2482 if blank_lines == old_lines.len() {
2483 break;
2484 };
2485 let unchanged_count =
2486 rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2487 result += &old_lines.by_ref().take(unchanged_count).fold(
2488 String::new(),
2489 |mut s, line| {
2490 writeln!(&mut s, "{line}").unwrap();
2491 s
2492 },
2493 );
2494 }
2495 }
2496 result
2497 }
2498
2499 fn uncommitted_diff(
2500 working_copy: &language::BufferSnapshot,
2501 index_text: &Rope,
2502 head_text: String,
2503 cx: &mut TestAppContext,
2504 ) -> Entity<BufferDiff> {
2505 let secondary = cx.new(|cx| {
2506 BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2507 });
2508 cx.new(|cx| {
2509 let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2510 diff.secondary_diff = Some(secondary);
2511 diff
2512 })
2513 }
2514
2515 let operations = std::env::var("OPERATIONS")
2516 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2517 .unwrap_or(10);
2518
2519 let rng = &mut rng;
2520 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2521 writeln!(&mut s, "{c}{c}{c}").unwrap();
2522 s
2523 });
2524 let working_copy = gen_working_copy(rng, &head_text);
2525 let working_copy = cx.new(|cx| {
2526 language::Buffer::local_normalized(
2527 Rope::from(working_copy.as_str()),
2528 text::LineEnding::default(),
2529 cx,
2530 )
2531 });
2532 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2533 let mut index_text = if rng.random() {
2534 Rope::from(head_text.as_str())
2535 } else {
2536 working_copy.as_rope().clone()
2537 };
2538
2539 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2540 let mut hunks = diff.update(cx, |diff, cx| {
2541 diff.snapshot(cx)
2542 .hunks_intersecting_range(
2543 Anchor::min_max_range_for_buffer(diff.buffer_id),
2544 &working_copy,
2545 )
2546 .collect::<Vec<_>>()
2547 });
2548 if hunks.is_empty() {
2549 return;
2550 }
2551
2552 for _ in 0..operations {
2553 let i = rng.random_range(0..hunks.len());
2554 let hunk = &mut hunks[i];
2555 let hunk_to_change = hunk.clone();
2556 let stage = match hunk.secondary_status {
2557 DiffHunkSecondaryStatus::HasSecondaryHunk => {
2558 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2559 true
2560 }
2561 DiffHunkSecondaryStatus::NoSecondaryHunk => {
2562 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2563 false
2564 }
2565 _ => unreachable!(),
2566 };
2567
2568 index_text = diff.update(cx, |diff, cx| {
2569 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2570 .unwrap()
2571 });
2572
2573 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2574 let found_hunks = diff.update(cx, |diff, cx| {
2575 diff.snapshot(cx)
2576 .hunks_intersecting_range(
2577 Anchor::min_max_range_for_buffer(diff.buffer_id),
2578 &working_copy,
2579 )
2580 .collect::<Vec<_>>()
2581 });
2582 assert_eq!(hunks.len(), found_hunks.len());
2583
2584 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2585 assert_eq!(
2586 expected_hunk.buffer_range.to_point(&working_copy),
2587 found_hunk.buffer_range.to_point(&working_copy)
2588 );
2589 assert_eq!(
2590 expected_hunk.diff_base_byte_range,
2591 found_hunk.diff_base_byte_range
2592 );
2593 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2594 }
2595 hunks = found_hunks;
2596 }
2597 }
2598
2599 #[gpui::test]
2600 async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
2601 let base_text = "
2602 zero
2603 one
2604 two
2605 three
2606 four
2607 five
2608 six
2609 seven
2610 eight
2611 "
2612 .unindent();
2613 let buffer_text = "
2614 zero
2615 ONE
2616 two
2617 NINE
2618 five
2619 seven
2620 "
2621 .unindent();
2622
2623 // zero
2624 // - one
2625 // + ONE
2626 // two
2627 // - three
2628 // - four
2629 // + NINE
2630 // five
2631 // - six
2632 // seven
2633 // + eight
2634
2635 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2636 let buffer_snapshot = buffer.snapshot();
2637 let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
2638 let expected_results = [
2639 // main buffer row, base text row (right bias), base text row (left bias)
2640 (0, 0, 0),
2641 (1, 2, 1),
2642 (2, 2, 2),
2643 (3, 5, 3),
2644 (4, 5, 5),
2645 (5, 7, 7),
2646 (6, 9, 9),
2647 ];
2648 for (buffer_row, expected_right, expected_left) in expected_results {
2649 assert_eq!(
2650 diff.row_to_base_text_row(buffer_row, Bias::Right, &buffer_snapshot),
2651 expected_right,
2652 "{buffer_row}"
2653 );
2654 assert_eq!(
2655 diff.row_to_base_text_row(buffer_row, Bias::Left, &buffer_snapshot),
2656 expected_left,
2657 "{buffer_row}"
2658 );
2659 }
2660 }
2661
2662 #[gpui::test]
2663 async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2664 let base_text = "
2665 one
2666 two
2667 three
2668 four
2669 five
2670 six
2671 "
2672 .unindent();
2673 let buffer_text = "
2674 one
2675 TWO
2676 three
2677 four
2678 FIVE
2679 six
2680 "
2681 .unindent();
2682 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
2683 let diff = cx.new(|cx| {
2684 BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
2685 });
2686 cx.run_until_parked();
2687 let (tx, rx) = mpsc::channel();
2688 let subscription =
2689 cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
2690
2691 let snapshot = buffer.update(cx, |buffer, cx| {
2692 buffer.set_text(
2693 "
2694 ONE
2695 TWO
2696 THREE
2697 FOUR
2698 FIVE
2699 SIX
2700 "
2701 .unindent(),
2702 cx,
2703 );
2704 buffer.text_snapshot()
2705 });
2706 let update = diff
2707 .update(cx, |diff, cx| {
2708 diff.update_diff(
2709 snapshot.clone(),
2710 Some(base_text.as_str().into()),
2711 false,
2712 None,
2713 cx,
2714 )
2715 })
2716 .await;
2717 diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
2718 .await;
2719 cx.run_until_parked();
2720 drop(subscription);
2721 let events = rx.into_iter().collect::<Vec<_>>();
2722 match events.as_slice() {
2723 [
2724 BufferDiffEvent::DiffChanged {
2725 changed_range: _,
2726 base_text_changed_range,
2727 },
2728 ] => {
2729 // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
2730 // assert_eq!(
2731 // *changed_range,
2732 // Some(Anchor::min_max_range_for_buffer(
2733 // buffer.read_with(cx, |buffer, _| buffer.remote_id())
2734 // ))
2735 // );
2736 assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
2737 }
2738 _ => panic!("unexpected events: {:?}", events),
2739 }
2740 }
2741}