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