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