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 // FIXME this is wrong
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 let base_text = cx.new(|cx| {
1055 let mut buffer = language::Buffer::local("", cx);
1056 buffer.set_capability(Capability::ReadOnly, cx);
1057 buffer
1058 });
1059
1060 BufferDiff {
1061 buffer_id: buffer.remote_id(),
1062 inner: BufferDiffInner {
1063 base_text,
1064 hunks: SumTree::new(buffer),
1065 pending_hunks: SumTree::new(buffer),
1066 base_text_exists: false,
1067 },
1068 secondary_diff: None,
1069 }
1070 }
1071
1072 pub fn new_unchanged(buffer: &text::BufferSnapshot, cx: &mut Context<Self>) -> Self {
1073 let base_text = buffer.text();
1074 let base_text = cx.new(|cx| {
1075 let mut buffer = language::Buffer::local(base_text, cx);
1076 buffer.set_capability(Capability::ReadOnly, cx);
1077 buffer
1078 });
1079
1080 BufferDiff {
1081 buffer_id: buffer.remote_id(),
1082 inner: BufferDiffInner {
1083 base_text,
1084 hunks: SumTree::new(buffer),
1085 pending_hunks: SumTree::new(buffer),
1086 base_text_exists: false,
1087 },
1088 secondary_diff: None,
1089 }
1090 }
1091
1092 #[cfg(any(test, feature = "test-support"))]
1093 pub fn new_with_base_text(
1094 base_text: &str,
1095 buffer: &text::BufferSnapshot,
1096 cx: &mut Context<Self>,
1097 ) -> Self {
1098 let mut this = BufferDiff::new(&buffer, cx);
1099 let executor = cx.background_executor().clone();
1100 let mut base_text = base_text.to_owned();
1101 text::LineEnding::normalize(&mut base_text);
1102 let inner = executor.block(this.update_diff(
1103 buffer.clone(),
1104 Some(Arc::from(base_text)),
1105 true,
1106 None,
1107 cx,
1108 ));
1109 // FIXME
1110 this.set_snapshot(inner, &buffer, true, cx);
1111 this
1112 }
1113
1114 pub fn set_secondary_diff(&mut self, diff: Entity<BufferDiff>) {
1115 self.secondary_diff = Some(diff);
1116 }
1117
1118 pub fn secondary_diff(&self) -> Option<Entity<BufferDiff>> {
1119 self.secondary_diff.clone()
1120 }
1121
1122 pub fn clear_pending_hunks(&mut self, cx: &mut Context<Self>) {
1123 if self.secondary_diff.is_some() {
1124 self.inner.pending_hunks = SumTree::from_summary(DiffHunkSummary {
1125 buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
1126 diff_base_byte_range: 0..0,
1127 });
1128 cx.emit(BufferDiffEvent::DiffChanged {
1129 changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
1130 base_text_changed_range: Some(0..self.base_text(cx).len()),
1131 });
1132 }
1133 }
1134
1135 pub fn stage_or_unstage_hunks(
1136 &mut self,
1137 stage: bool,
1138 hunks: &[DiffHunk],
1139 buffer: &text::BufferSnapshot,
1140 file_exists: bool,
1141 cx: &mut Context<Self>,
1142 ) -> Option<Rope> {
1143 let new_index_text = self
1144 .secondary_diff
1145 .as_ref()?
1146 .update(cx, |secondary_diff, cx| {
1147 self.inner.stage_or_unstage_hunks_impl(
1148 &secondary_diff.inner,
1149 stage,
1150 hunks,
1151 buffer,
1152 file_exists,
1153 cx,
1154 )
1155 });
1156
1157 cx.emit(BufferDiffEvent::HunksStagedOrUnstaged(
1158 new_index_text.clone(),
1159 ));
1160 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1161 let changed_range = first.buffer_range.start..last.buffer_range.end;
1162 let base_text_changed_range =
1163 first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1164 cx.emit(BufferDiffEvent::DiffChanged {
1165 changed_range: Some(changed_range),
1166 base_text_changed_range: Some(base_text_changed_range),
1167 });
1168 }
1169 new_index_text
1170 }
1171
1172 pub fn update_diff(
1173 &self,
1174 buffer: text::BufferSnapshot,
1175 base_text: Option<Arc<str>>,
1176 base_text_changed: bool,
1177 language: Option<Arc<Language>>,
1178 cx: &App,
1179 ) -> Task<BufferDiffInner<Arc<str>>> {
1180 let prev_base_text = self.base_text(cx).as_rope().clone();
1181 let diff_options = build_diff_options(
1182 None,
1183 language.as_ref().map(|l| l.name()),
1184 language.as_ref().map(|l| l.default_scope()),
1185 cx,
1186 );
1187
1188 cx.background_executor()
1189 .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
1190 let base_text_rope = if let Some(base_text) = &base_text {
1191 if base_text_changed {
1192 Rope::from(base_text.as_ref())
1193 } else {
1194 prev_base_text
1195 }
1196 } else {
1197 Rope::new()
1198 };
1199 let base_text_exists = base_text.is_some();
1200 let hunks = compute_hunks(
1201 base_text
1202 .clone()
1203 .map(|base_text| (base_text, base_text_rope.clone())),
1204 buffer.clone(),
1205 diff_options,
1206 );
1207 let base_text = base_text.unwrap_or_default();
1208 BufferDiffInner {
1209 base_text,
1210 hunks,
1211 base_text_exists,
1212 pending_hunks: SumTree::new(&buffer),
1213 }
1214 })
1215 }
1216
1217 pub fn language_changed(
1218 &mut self,
1219 language: Option<Arc<Language>>,
1220 language_registry: Option<Arc<LanguageRegistry>>,
1221 cx: &mut Context<Self>,
1222 ) {
1223 self.inner.base_text.update(cx, |base_text, cx| {
1224 base_text.set_language(language, cx);
1225 if let Some(language_registry) = language_registry {
1226 base_text.set_language_registry(language_registry);
1227 }
1228 });
1229 cx.emit(BufferDiffEvent::LanguageChanged);
1230 }
1231
1232 // FIXME name
1233 pub fn set_snapshot(
1234 &mut self,
1235 new_state: BufferDiffInner<Arc<str>>,
1236 buffer: &text::BufferSnapshot,
1237 base_text_changed: bool,
1238 cx: &mut Context<Self>,
1239 ) -> Option<Range<Anchor>> {
1240 self.set_snapshot_with_secondary(new_state, buffer, None, base_text_changed, false, cx)
1241 }
1242
1243 // FIXME name
1244 pub fn set_snapshot_with_secondary(
1245 &mut self,
1246 new_state: BufferDiffInner<Arc<str>>,
1247 buffer: &text::BufferSnapshot,
1248 secondary_diff_change: Option<Range<Anchor>>,
1249 base_text_changed: bool,
1250 clear_pending_hunks: bool,
1251 cx: &mut Context<Self>,
1252 ) -> Option<Range<Anchor>> {
1253 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1254
1255 dbg!(base_text_changed);
1256 let old_snapshot = self.snapshot(cx);
1257 let state = &mut self.inner;
1258 let (mut changed_range, mut base_text_changed_range) =
1259 match (state.base_text_exists, new_state.base_text_exists) {
1260 (false, false) => (None, None),
1261 (true, true) if !base_text_changed => {
1262 compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
1263 }
1264 _ => (
1265 Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
1266 Some(0..new_state.base_text.len()),
1267 ),
1268 };
1269
1270 if let Some(secondary_changed_range) = secondary_diff_change
1271 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1272 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1273 {
1274 if let Some(range) = &mut changed_range {
1275 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1276 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1277 } else {
1278 changed_range = Some(secondary_hunk_range);
1279 }
1280
1281 if let Some(base_text_range) = &mut base_text_changed_range {
1282 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1283 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1284 } else {
1285 base_text_changed_range = Some(secondary_base_range);
1286 }
1287 }
1288
1289 let state = &mut self.inner;
1290 state.base_text_exists = new_state.base_text_exists;
1291 if base_text_changed {
1292 state.base_text.update(cx, |base_text, cx| {
1293 base_text.set_capability(Capability::ReadWrite, cx);
1294 base_text.set_text(new_state.base_text.clone(), cx);
1295 base_text.set_capability(Capability::ReadOnly, cx);
1296 })
1297 }
1298 state.hunks = new_state.hunks;
1299 dbg!(self.buffer_id, state.hunks.iter().collect::<Vec<_>>());
1300 if base_text_changed || clear_pending_hunks {
1301 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1302 {
1303 if let Some(range) = &mut changed_range {
1304 range.start = *range.start.min(&first.buffer_range.start, buffer);
1305 range.end = *range.end.max(&last.buffer_range.end, buffer);
1306 } else {
1307 changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1308 }
1309
1310 if let Some(base_text_range) = &mut base_text_changed_range {
1311 base_text_range.start =
1312 base_text_range.start.min(first.diff_base_byte_range.start);
1313 base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1314 } else {
1315 base_text_changed_range =
1316 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1317 }
1318 }
1319 state.pending_hunks = SumTree::new(buffer);
1320 }
1321
1322 dbg!("EMIT");
1323 cx.emit(BufferDiffEvent::DiffChanged {
1324 changed_range: changed_range.clone(),
1325 base_text_changed_range,
1326 });
1327 changed_range
1328 }
1329
1330 pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1331 self.inner.base_text.read(cx).snapshot()
1332 }
1333
1334 pub fn base_text_exists(&self) -> bool {
1335 self.inner.base_text_exists
1336 }
1337
1338 pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1339 BufferDiffSnapshot {
1340 inner: BufferDiffInner {
1341 hunks: self.inner.hunks.clone(),
1342 pending_hunks: self.inner.pending_hunks.clone(),
1343 base_text: self.inner.base_text.read(cx).snapshot(),
1344 base_text_exists: self.inner.base_text_exists,
1345 },
1346 secondary_diff: self
1347 .secondary_diff
1348 .as_ref()
1349 .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1350 }
1351 }
1352
1353 /// Used in cases where the change set isn't derived from git.
1354 pub fn set_base_text(
1355 &mut self,
1356 base_text: Option<Arc<str>>,
1357 language: Option<Arc<Language>>,
1358 buffer: text::BufferSnapshot,
1359 cx: &mut Context<Self>,
1360 ) -> oneshot::Receiver<()> {
1361 let (tx, rx) = oneshot::channel();
1362 let complete_on_drop = util::defer(|| {
1363 tx.send(()).ok();
1364 });
1365 cx.spawn(async move |this, cx| {
1366 let Some(state) = this
1367 .update(cx, |this, cx| {
1368 this.update_diff(buffer.clone(), base_text, true, language, cx)
1369 })
1370 .log_err()
1371 else {
1372 return;
1373 };
1374 let state = state.await;
1375 this.update(cx, |this, cx| {
1376 this.set_snapshot(state, &buffer, true, cx);
1377 })
1378 .log_err();
1379 drop(complete_on_drop)
1380 })
1381 .detach();
1382 rx
1383 }
1384
1385 pub fn base_text_string(&self, cx: &App) -> Option<String> {
1386 self.inner
1387 .base_text_exists
1388 .then(|| self.inner.base_text.read(cx).text())
1389 }
1390
1391 #[cfg(any(test, feature = "test-support"))]
1392 pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context<Self>) {
1393 let language = self.base_text(cx).language().cloned();
1394 let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1395 let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
1396 let snapshot = cx.background_executor().block(fut);
1397 self.set_snapshot(snapshot, &buffer, false, cx);
1398 }
1399
1400 pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1401 self.inner.base_text.clone()
1402 }
1403}
1404
1405impl DiffHunk {
1406 pub fn is_created_file(&self) -> bool {
1407 self.diff_base_byte_range == (0..0)
1408 && self.buffer_range.start.is_min()
1409 && self.buffer_range.end.is_max()
1410 }
1411
1412 pub fn status(&self) -> DiffHunkStatus {
1413 let kind = if self.buffer_range.start == self.buffer_range.end {
1414 DiffHunkStatusKind::Deleted
1415 } else if self.diff_base_byte_range.is_empty() {
1416 DiffHunkStatusKind::Added
1417 } else {
1418 DiffHunkStatusKind::Modified
1419 };
1420 DiffHunkStatus {
1421 kind,
1422 secondary: self.secondary_status,
1423 }
1424 }
1425}
1426
1427impl DiffHunkStatus {
1428 pub fn has_secondary_hunk(&self) -> bool {
1429 matches!(
1430 self.secondary,
1431 DiffHunkSecondaryStatus::HasSecondaryHunk
1432 | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1433 | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1434 )
1435 }
1436
1437 pub fn is_pending(&self) -> bool {
1438 matches!(
1439 self.secondary,
1440 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1441 | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1442 )
1443 }
1444
1445 pub fn is_deleted(&self) -> bool {
1446 self.kind == DiffHunkStatusKind::Deleted
1447 }
1448
1449 pub fn is_added(&self) -> bool {
1450 self.kind == DiffHunkStatusKind::Added
1451 }
1452
1453 pub fn is_modified(&self) -> bool {
1454 self.kind == DiffHunkStatusKind::Modified
1455 }
1456
1457 pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1458 Self {
1459 kind: DiffHunkStatusKind::Added,
1460 secondary,
1461 }
1462 }
1463
1464 pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1465 Self {
1466 kind: DiffHunkStatusKind::Modified,
1467 secondary,
1468 }
1469 }
1470
1471 pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1472 Self {
1473 kind: DiffHunkStatusKind::Deleted,
1474 secondary,
1475 }
1476 }
1477
1478 pub fn deleted_none() -> Self {
1479 Self {
1480 kind: DiffHunkStatusKind::Deleted,
1481 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1482 }
1483 }
1484
1485 pub fn added_none() -> Self {
1486 Self {
1487 kind: DiffHunkStatusKind::Added,
1488 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1489 }
1490 }
1491
1492 pub fn modified_none() -> Self {
1493 Self {
1494 kind: DiffHunkStatusKind::Modified,
1495 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1496 }
1497 }
1498}
1499
1500#[cfg(any(test, feature = "test-support"))]
1501#[track_caller]
1502pub fn assert_hunks<ExpectedText, HunkIter>(
1503 diff_hunks: HunkIter,
1504 buffer: &text::BufferSnapshot,
1505 diff_base: &str,
1506 // Line range, deleted, added, status
1507 expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1508) where
1509 HunkIter: Iterator<Item = DiffHunk>,
1510 ExpectedText: AsRef<str>,
1511{
1512 let actual_hunks = diff_hunks
1513 .map(|hunk| {
1514 (
1515 hunk.range.clone(),
1516 &diff_base[hunk.diff_base_byte_range.clone()],
1517 buffer
1518 .text_for_range(hunk.range.clone())
1519 .collect::<String>(),
1520 hunk.status(),
1521 )
1522 })
1523 .collect::<Vec<_>>();
1524
1525 let expected_hunks: Vec<_> = expected_hunks
1526 .iter()
1527 .map(|(line_range, deleted_text, added_text, status)| {
1528 (
1529 Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1530 deleted_text.as_ref(),
1531 added_text.as_ref().to_string(),
1532 *status,
1533 )
1534 })
1535 .collect();
1536
1537 pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1538}
1539
1540#[cfg(test)]
1541mod tests {
1542 use std::fmt::Write as _;
1543
1544 use super::*;
1545 use gpui::TestAppContext;
1546 use pretty_assertions::{assert_eq, assert_ne};
1547 use rand::{Rng as _, rngs::StdRng};
1548 use text::{Buffer, BufferId, ReplicaId, Rope};
1549 use unindent::Unindent as _;
1550 use util::test::marked_text_ranges;
1551
1552 #[ctor::ctor]
1553 fn init_logger() {
1554 zlog::init_test();
1555 }
1556
1557 #[gpui::test]
1558 async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1559 let diff_base = "
1560 one
1561 two
1562 three
1563 "
1564 .unindent();
1565
1566 let buffer_text = "
1567 one
1568 HELLO
1569 three
1570 "
1571 .unindent();
1572
1573 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1574 let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1575 assert_hunks(
1576 diff.hunks_intersecting_range(
1577 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1578 &buffer,
1579 ),
1580 &buffer,
1581 &diff_base,
1582 &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1583 );
1584
1585 buffer.edit([(0..0, "point five\n")]);
1586 diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1587 assert_hunks(
1588 diff.hunks_intersecting_range(
1589 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1590 &buffer,
1591 ),
1592 &buffer,
1593 &diff_base,
1594 &[
1595 (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1596 (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1597 ],
1598 );
1599
1600 diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
1601 assert_hunks::<&str, _>(
1602 diff.hunks_intersecting_range(
1603 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1604 &buffer,
1605 ),
1606 &buffer,
1607 &diff_base,
1608 &[],
1609 );
1610 }
1611
1612 #[gpui::test]
1613 async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1614 let head_text = "
1615 zero
1616 one
1617 two
1618 three
1619 four
1620 five
1621 six
1622 seven
1623 eight
1624 nine
1625 "
1626 .unindent();
1627
1628 let index_text = "
1629 zero
1630 one
1631 TWO
1632 three
1633 FOUR
1634 five
1635 six
1636 seven
1637 eight
1638 NINE
1639 "
1640 .unindent();
1641
1642 let buffer_text = "
1643 zero
1644 one
1645 TWO
1646 three
1647 FOUR
1648 FIVE
1649 six
1650 SEVEN
1651 eight
1652 nine
1653 "
1654 .unindent();
1655
1656 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1657 let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1658 let mut uncommitted_diff =
1659 BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1660 uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1661
1662 let expected_hunks = vec![
1663 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1664 (
1665 4..6,
1666 "four\nfive\n",
1667 "FOUR\nFIVE\n",
1668 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1669 ),
1670 (
1671 7..8,
1672 "seven\n",
1673 "SEVEN\n",
1674 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1675 ),
1676 ];
1677
1678 assert_hunks(
1679 uncommitted_diff.hunks_intersecting_range(
1680 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1681 &buffer,
1682 ),
1683 &buffer,
1684 &head_text,
1685 &expected_hunks,
1686 );
1687 }
1688
1689 #[gpui::test]
1690 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1691 let diff_base = "
1692 one
1693 two
1694 three
1695 four
1696 five
1697 six
1698 seven
1699 eight
1700 nine
1701 ten
1702 "
1703 .unindent();
1704
1705 let buffer_text = "
1706 A
1707 one
1708 B
1709 two
1710 C
1711 three
1712 HELLO
1713 four
1714 five
1715 SIXTEEN
1716 seven
1717 eight
1718 WORLD
1719 nine
1720
1721 ten
1722
1723 "
1724 .unindent();
1725
1726 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1727 let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
1728 assert_eq!(
1729 diff.hunks_intersecting_range(
1730 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1731 &buffer
1732 )
1733 .count(),
1734 8
1735 );
1736
1737 assert_hunks(
1738 diff.hunks_intersecting_range(
1739 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1740 &buffer,
1741 ),
1742 &buffer,
1743 &diff_base,
1744 &[
1745 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1746 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1747 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1748 ],
1749 );
1750 }
1751
1752 #[gpui::test]
1753 async fn test_stage_hunk(cx: &mut TestAppContext) {
1754 struct Example {
1755 name: &'static str,
1756 head_text: String,
1757 index_text: String,
1758 buffer_marked_text: String,
1759 final_index_text: String,
1760 }
1761
1762 let table = [
1763 Example {
1764 name: "uncommitted hunk straddles end of unstaged hunk",
1765 head_text: "
1766 one
1767 two
1768 three
1769 four
1770 five
1771 "
1772 .unindent(),
1773 index_text: "
1774 one
1775 TWO_HUNDRED
1776 three
1777 FOUR_HUNDRED
1778 five
1779 "
1780 .unindent(),
1781 buffer_marked_text: "
1782 ZERO
1783 one
1784 two
1785 «THREE_HUNDRED
1786 FOUR_HUNDRED»
1787 five
1788 SIX
1789 "
1790 .unindent(),
1791 final_index_text: "
1792 one
1793 two
1794 THREE_HUNDRED
1795 FOUR_HUNDRED
1796 five
1797 "
1798 .unindent(),
1799 },
1800 Example {
1801 name: "uncommitted hunk straddles start of unstaged hunk",
1802 head_text: "
1803 one
1804 two
1805 three
1806 four
1807 five
1808 "
1809 .unindent(),
1810 index_text: "
1811 one
1812 TWO_HUNDRED
1813 three
1814 FOUR_HUNDRED
1815 five
1816 "
1817 .unindent(),
1818 buffer_marked_text: "
1819 ZERO
1820 one
1821 «TWO_HUNDRED
1822 THREE_HUNDRED»
1823 four
1824 five
1825 SIX
1826 "
1827 .unindent(),
1828 final_index_text: "
1829 one
1830 TWO_HUNDRED
1831 THREE_HUNDRED
1832 four
1833 five
1834 "
1835 .unindent(),
1836 },
1837 Example {
1838 name: "uncommitted hunk strictly contains unstaged hunks",
1839 head_text: "
1840 one
1841 two
1842 three
1843 four
1844 five
1845 six
1846 seven
1847 "
1848 .unindent(),
1849 index_text: "
1850 one
1851 TWO
1852 THREE
1853 FOUR
1854 FIVE
1855 SIX
1856 seven
1857 "
1858 .unindent(),
1859 buffer_marked_text: "
1860 one
1861 TWO
1862 «THREE_HUNDRED
1863 FOUR
1864 FIVE_HUNDRED»
1865 SIX
1866 seven
1867 "
1868 .unindent(),
1869 final_index_text: "
1870 one
1871 TWO
1872 THREE_HUNDRED
1873 FOUR
1874 FIVE_HUNDRED
1875 SIX
1876 seven
1877 "
1878 .unindent(),
1879 },
1880 Example {
1881 name: "uncommitted deletion hunk",
1882 head_text: "
1883 one
1884 two
1885 three
1886 four
1887 five
1888 "
1889 .unindent(),
1890 index_text: "
1891 one
1892 two
1893 three
1894 four
1895 five
1896 "
1897 .unindent(),
1898 buffer_marked_text: "
1899 one
1900 ˇfive
1901 "
1902 .unindent(),
1903 final_index_text: "
1904 one
1905 five
1906 "
1907 .unindent(),
1908 },
1909 Example {
1910 name: "one unstaged hunk that contains two uncommitted hunks",
1911 head_text: "
1912 one
1913 two
1914
1915 three
1916 four
1917 "
1918 .unindent(),
1919 index_text: "
1920 one
1921 two
1922 three
1923 four
1924 "
1925 .unindent(),
1926 buffer_marked_text: "
1927 «one
1928
1929 three // modified
1930 four»
1931 "
1932 .unindent(),
1933 final_index_text: "
1934 one
1935
1936 three // modified
1937 four
1938 "
1939 .unindent(),
1940 },
1941 Example {
1942 name: "one uncommitted hunk that contains two unstaged hunks",
1943 head_text: "
1944 one
1945 two
1946 three
1947 four
1948 five
1949 "
1950 .unindent(),
1951 index_text: "
1952 ZERO
1953 one
1954 TWO
1955 THREE
1956 FOUR
1957 five
1958 "
1959 .unindent(),
1960 buffer_marked_text: "
1961 «one
1962 TWO_HUNDRED
1963 THREE
1964 FOUR_HUNDRED
1965 five»
1966 "
1967 .unindent(),
1968 final_index_text: "
1969 ZERO
1970 one
1971 TWO_HUNDRED
1972 THREE
1973 FOUR_HUNDRED
1974 five
1975 "
1976 .unindent(),
1977 },
1978 ];
1979
1980 for example in table {
1981 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
1982 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1983 let hunk_range =
1984 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
1985
1986 let unstaged_diff =
1987 cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
1988
1989 let uncommitted_diff = cx.new(|cx| {
1990 let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
1991 diff.set_secondary_diff(unstaged_diff);
1992 diff
1993 });
1994
1995 uncommitted_diff.update(cx, |diff, cx| {
1996 let hunks = diff
1997 .snapshot(cx)
1998 .hunks_intersecting_range(hunk_range.clone(), &buffer)
1999 .collect::<Vec<_>>();
2000 for hunk in &hunks {
2001 assert_ne!(
2002 hunk.secondary_status,
2003 DiffHunkSecondaryStatus::NoSecondaryHunk
2004 )
2005 }
2006
2007 let new_index_text = diff
2008 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2009 .unwrap()
2010 .to_string();
2011
2012 let hunks = diff
2013 .snapshot(cx)
2014 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2015 .collect::<Vec<_>>();
2016 for hunk in &hunks {
2017 assert_eq!(
2018 hunk.secondary_status,
2019 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2020 )
2021 }
2022
2023 pretty_assertions::assert_eq!(
2024 new_index_text,
2025 example.final_index_text,
2026 "example: {}",
2027 example.name
2028 );
2029 });
2030 }
2031 }
2032
2033 #[gpui::test]
2034 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2035 let head_text = "
2036 one
2037 two
2038 three
2039 "
2040 .unindent();
2041 let index_text = head_text.clone();
2042 let buffer_text = "
2043 one
2044 three
2045 "
2046 .unindent();
2047
2048 let buffer = Buffer::new(
2049 ReplicaId::LOCAL,
2050 BufferId::new(1).unwrap(),
2051 buffer_text.clone(),
2052 );
2053 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2054 let uncommitted_diff = cx.new(|cx| {
2055 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2056 diff.set_secondary_diff(unstaged_diff.clone());
2057 diff
2058 });
2059
2060 uncommitted_diff.update(cx, |diff, cx| {
2061 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2062
2063 let new_index_text = diff
2064 .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2065 .unwrap()
2066 .to_string();
2067 assert_eq!(new_index_text, buffer_text);
2068
2069 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2070 assert_eq!(
2071 hunk.secondary_status,
2072 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2073 );
2074
2075 let index_text = diff
2076 .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2077 .unwrap()
2078 .to_string();
2079 assert_eq!(index_text, head_text);
2080
2081 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2082 // optimistically unstaged (fine, could also be HasSecondaryHunk)
2083 assert_eq!(
2084 hunk.secondary_status,
2085 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2086 );
2087 });
2088 }
2089
2090 #[gpui::test]
2091 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2092 let base_text = "
2093 zero
2094 one
2095 two
2096 three
2097 four
2098 five
2099 six
2100 seven
2101 eight
2102 nine
2103 "
2104 .unindent();
2105
2106 let buffer_text_1 = "
2107 one
2108 three
2109 four
2110 five
2111 SIX
2112 seven
2113 eight
2114 NINE
2115 "
2116 .unindent();
2117
2118 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2119
2120 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2121 let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2122 // FIXME assert the other half of the range
2123 let (range, base_text_range) =
2124 compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
2125 let range = range.unwrap();
2126 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2127
2128 // Edit does not affect the diff.
2129 buffer.edit_via_marked_text(
2130 &"
2131 one
2132 three
2133 four
2134 five
2135 «SIX.5»
2136 seven
2137 eight
2138 NINE
2139 "
2140 .unindent(),
2141 );
2142 let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2143 assert_eq!(
2144 (None, None),
2145 compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer)
2146 );
2147
2148 // Edit turns a deletion hunk into a modification.
2149 buffer.edit_via_marked_text(
2150 &"
2151 one
2152 «THREE»
2153 four
2154 five
2155 SIX.5
2156 seven
2157 eight
2158 NINE
2159 "
2160 .unindent(),
2161 );
2162 let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2163 // FIXME assert the other range
2164 let (range, base_text_range) =
2165 compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
2166 let range = range.unwrap();
2167 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2168
2169 // Edit turns a modification hunk into a deletion.
2170 buffer.edit_via_marked_text(
2171 &"
2172 one
2173 THREE
2174 four
2175 five«»
2176 seven
2177 eight
2178 NINE
2179 "
2180 .unindent(),
2181 );
2182 let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2183 let (range, base_text_range) =
2184 compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
2185 let range = range.unwrap();
2186 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2187
2188 // Edit introduces a new insertion hunk.
2189 buffer.edit_via_marked_text(
2190 &"
2191 one
2192 THREE
2193 four«
2194 FOUR.5
2195 »five
2196 seven
2197 eight
2198 NINE
2199 "
2200 .unindent(),
2201 );
2202 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2203 let (range, base_text_range) =
2204 compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
2205 let range = range.unwrap();
2206 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2207
2208 // Edit removes a hunk.
2209 buffer.edit_via_marked_text(
2210 &"
2211 one
2212 THREE
2213 four
2214 FOUR.5
2215 five
2216 seven
2217 eight
2218 «nine»
2219 "
2220 .unindent(),
2221 );
2222 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2223 let (range, base_text_range) =
2224 compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
2225 let range = range.unwrap();
2226 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2227 }
2228
2229 #[gpui::test(iterations = 100)]
2230 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2231 fn gen_line(rng: &mut StdRng) -> String {
2232 if rng.random_bool(0.2) {
2233 "\n".to_owned()
2234 } else {
2235 let c = rng.random_range('A'..='Z');
2236 format!("{c}{c}{c}\n")
2237 }
2238 }
2239
2240 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2241 let mut old_lines = {
2242 let mut old_lines = Vec::new();
2243 let old_lines_iter = head.lines();
2244 for line in old_lines_iter {
2245 assert!(!line.ends_with("\n"));
2246 old_lines.push(line.to_owned());
2247 }
2248 if old_lines.last().is_some_and(|line| line.is_empty()) {
2249 old_lines.pop();
2250 }
2251 old_lines.into_iter()
2252 };
2253 let mut result = String::new();
2254 let unchanged_count = rng.random_range(0..=old_lines.len());
2255 result +=
2256 &old_lines
2257 .by_ref()
2258 .take(unchanged_count)
2259 .fold(String::new(), |mut s, line| {
2260 writeln!(&mut s, "{line}").unwrap();
2261 s
2262 });
2263 while old_lines.len() > 0 {
2264 let deleted_count = rng.random_range(0..=old_lines.len());
2265 let _advance = old_lines
2266 .by_ref()
2267 .take(deleted_count)
2268 .map(|line| line.len() + 1)
2269 .sum::<usize>();
2270 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2271 let added_count = rng.random_range(minimum_added..=5);
2272 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2273 result += &addition;
2274
2275 if old_lines.len() > 0 {
2276 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2277 if blank_lines == old_lines.len() {
2278 break;
2279 };
2280 let unchanged_count =
2281 rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2282 result += &old_lines.by_ref().take(unchanged_count).fold(
2283 String::new(),
2284 |mut s, line| {
2285 writeln!(&mut s, "{line}").unwrap();
2286 s
2287 },
2288 );
2289 }
2290 }
2291 result
2292 }
2293
2294 fn uncommitted_diff(
2295 working_copy: &language::BufferSnapshot,
2296 index_text: &Rope,
2297 head_text: String,
2298 cx: &mut TestAppContext,
2299 ) -> Entity<BufferDiff> {
2300 let secondary = cx.new(|cx| {
2301 BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2302 });
2303 cx.new(|cx| {
2304 let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2305 diff.secondary_diff = Some(secondary);
2306 diff
2307 })
2308 }
2309
2310 let operations = std::env::var("OPERATIONS")
2311 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2312 .unwrap_or(10);
2313
2314 let rng = &mut rng;
2315 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2316 writeln!(&mut s, "{c}{c}{c}").unwrap();
2317 s
2318 });
2319 let working_copy = gen_working_copy(rng, &head_text);
2320 let working_copy = cx.new(|cx| {
2321 language::Buffer::local_normalized(
2322 Rope::from(working_copy.as_str()),
2323 text::LineEnding::default(),
2324 cx,
2325 )
2326 });
2327 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2328 let mut index_text = if rng.random() {
2329 Rope::from(head_text.as_str())
2330 } else {
2331 working_copy.as_rope().clone()
2332 };
2333
2334 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2335 let mut hunks = diff.update(cx, |diff, cx| {
2336 diff.snapshot(cx)
2337 .hunks_intersecting_range(
2338 Anchor::min_max_range_for_buffer(diff.buffer_id),
2339 &working_copy,
2340 )
2341 .collect::<Vec<_>>()
2342 });
2343 if hunks.is_empty() {
2344 return;
2345 }
2346
2347 for _ in 0..operations {
2348 let i = rng.random_range(0..hunks.len());
2349 let hunk = &mut hunks[i];
2350 let hunk_to_change = hunk.clone();
2351 let stage = match hunk.secondary_status {
2352 DiffHunkSecondaryStatus::HasSecondaryHunk => {
2353 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2354 true
2355 }
2356 DiffHunkSecondaryStatus::NoSecondaryHunk => {
2357 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2358 false
2359 }
2360 _ => unreachable!(),
2361 };
2362
2363 index_text = diff.update(cx, |diff, cx| {
2364 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2365 .unwrap()
2366 });
2367
2368 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2369 let found_hunks = diff.update(cx, |diff, cx| {
2370 diff.snapshot(cx)
2371 .hunks_intersecting_range(
2372 Anchor::min_max_range_for_buffer(diff.buffer_id),
2373 &working_copy,
2374 )
2375 .collect::<Vec<_>>()
2376 });
2377 assert_eq!(hunks.len(), found_hunks.len());
2378
2379 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2380 assert_eq!(
2381 expected_hunk.buffer_range.to_point(&working_copy),
2382 found_hunk.buffer_range.to_point(&working_copy)
2383 );
2384 assert_eq!(
2385 expected_hunk.diff_base_byte_range,
2386 found_hunk.diff_base_byte_range
2387 );
2388 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2389 }
2390 hunks = found_hunks;
2391 }
2392 }
2393
2394 #[gpui::test]
2395 async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
2396 let base_text = "
2397 zero
2398 one
2399 two
2400 three
2401 four
2402 five
2403 six
2404 seven
2405 eight
2406 "
2407 .unindent();
2408 let buffer_text = "
2409 zero
2410 ONE
2411 two
2412 NINE
2413 five
2414 seven
2415 "
2416 .unindent();
2417
2418 // zero
2419 // - one
2420 // + ONE
2421 // two
2422 // - three
2423 // - four
2424 // + NINE
2425 // five
2426 // - six
2427 // seven
2428 // + eight
2429
2430 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2431 let buffer_snapshot = buffer.snapshot();
2432 let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
2433 let expected_results = [
2434 // don't format me
2435 (0, 0),
2436 (1, 2),
2437 (2, 2),
2438 (3, 5),
2439 (4, 5),
2440 (5, 7),
2441 (6, 9),
2442 ];
2443 for (buffer_row, expected) in expected_results {
2444 assert_eq!(
2445 diff.row_to_base_text_row(buffer_row, &buffer_snapshot),
2446 expected,
2447 "{buffer_row}"
2448 );
2449 }
2450 }
2451}