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