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