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