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 pub fn set_snapshot(
1314 &mut self,
1315 new_state: BufferDiffUpdate,
1316 buffer: &text::BufferSnapshot,
1317 cx: &mut Context<Self>,
1318 ) -> Task<Option<Range<Anchor>>> {
1319 self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
1320 }
1321
1322 pub fn set_snapshot_with_secondary(
1323 &mut self,
1324 update: BufferDiffUpdate,
1325 buffer: &text::BufferSnapshot,
1326 secondary_diff_change: Option<Range<Anchor>>,
1327 clear_pending_hunks: bool,
1328 cx: &mut Context<Self>,
1329 ) -> Task<Option<Range<Anchor>>> {
1330 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
1331
1332 let old_snapshot = self.snapshot(cx);
1333 let state = &mut self.inner;
1334 let new_state = update.inner;
1335 let (mut changed_range, mut base_text_changed_range) =
1336 match (state.base_text_exists, new_state.base_text_exists) {
1337 (false, false) => (None, None),
1338 (true, true) if !update.base_text_changed => {
1339 compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
1340 }
1341 _ => (
1342 Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
1343 Some(0..new_state.base_text.len()),
1344 ),
1345 };
1346
1347 if let Some(secondary_changed_range) = secondary_diff_change
1348 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
1349 old_snapshot.range_to_hunk_range(secondary_changed_range, buffer)
1350 {
1351 if let Some(range) = &mut changed_range {
1352 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
1353 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
1354 } else {
1355 changed_range = Some(secondary_hunk_range);
1356 }
1357
1358 if let Some(base_text_range) = &mut base_text_changed_range {
1359 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
1360 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
1361 } else {
1362 base_text_changed_range = Some(secondary_base_range);
1363 }
1364 }
1365
1366 let state = &mut self.inner;
1367 state.base_text_exists = new_state.base_text_exists;
1368 let parsing_idle = if update.base_text_changed {
1369 state.base_text.update(cx, |base_text, cx| {
1370 base_text.set_capability(Capability::ReadWrite, cx);
1371 base_text.set_text(new_state.base_text.clone(), cx);
1372 base_text.set_capability(Capability::ReadOnly, cx);
1373 Some(base_text.parsing_idle())
1374 })
1375 } else {
1376 None
1377 };
1378 state.hunks = new_state.hunks;
1379 if update.base_text_changed || clear_pending_hunks {
1380 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
1381 {
1382 if let Some(range) = &mut changed_range {
1383 range.start = *range.start.min(&first.buffer_range.start, buffer);
1384 range.end = *range.end.max(&last.buffer_range.end, buffer);
1385 } else {
1386 changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1387 }
1388
1389 if let Some(base_text_range) = &mut base_text_changed_range {
1390 base_text_range.start =
1391 base_text_range.start.min(first.diff_base_byte_range.start);
1392 base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
1393 } else {
1394 base_text_changed_range =
1395 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1396 }
1397 }
1398 state.pending_hunks = SumTree::new(buffer);
1399 }
1400
1401 cx.spawn(async move |this, cx| {
1402 if let Some(parsing_idle) = parsing_idle {
1403 parsing_idle.await;
1404 }
1405 this.update(cx, |_, cx| {
1406 cx.emit(BufferDiffEvent::DiffChanged {
1407 changed_range: changed_range.clone(),
1408 base_text_changed_range,
1409 });
1410 })
1411 .ok();
1412 changed_range
1413 })
1414 }
1415
1416 pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
1417 self.inner.base_text.read(cx).snapshot()
1418 }
1419
1420 pub fn base_text_exists(&self) -> bool {
1421 self.inner.base_text_exists
1422 }
1423
1424 pub fn snapshot(&self, cx: &App) -> BufferDiffSnapshot {
1425 BufferDiffSnapshot {
1426 inner: BufferDiffInner {
1427 hunks: self.inner.hunks.clone(),
1428 pending_hunks: self.inner.pending_hunks.clone(),
1429 base_text: self.inner.base_text.read(cx).snapshot(),
1430 base_text_exists: self.inner.base_text_exists,
1431 },
1432 secondary_diff: self
1433 .secondary_diff
1434 .as_ref()
1435 .map(|diff| Box::new(diff.read(cx).snapshot(cx))),
1436 }
1437 }
1438
1439 /// Used in cases where the change set isn't derived from git.
1440 pub fn set_base_text(
1441 &mut self,
1442 base_text: Option<Arc<str>>,
1443 language: Option<Arc<Language>>,
1444 buffer: text::BufferSnapshot,
1445 cx: &mut Context<Self>,
1446 ) -> oneshot::Receiver<()> {
1447 let (tx, rx) = oneshot::channel();
1448 let complete_on_drop = util::defer(|| {
1449 tx.send(()).ok();
1450 });
1451 cx.spawn(async move |this, cx| {
1452 let Some(state) = this
1453 .update(cx, |this, cx| {
1454 this.update_diff(buffer.clone(), base_text, true, language, cx)
1455 })
1456 .log_err()
1457 else {
1458 return;
1459 };
1460 let state = state.await;
1461 if let Some(task) = this
1462 .update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
1463 .log_err()
1464 {
1465 task.await;
1466 }
1467 drop(complete_on_drop)
1468 })
1469 .detach();
1470 rx
1471 }
1472
1473 pub fn base_text_string(&self, cx: &App) -> Option<String> {
1474 self.inner
1475 .base_text_exists
1476 .then(|| self.inner.base_text.read(cx).text())
1477 }
1478
1479 #[cfg(any(test, feature = "test-support"))]
1480 pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
1481 let language = self.base_text(cx).language().cloned();
1482 let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1483 let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
1484 let executor = cx.background_executor().clone();
1485 let snapshot = executor.block(fut);
1486 self.set_snapshot(snapshot, &buffer, cx).detach();
1487 }
1488
1489 pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
1490 self.inner.base_text.clone()
1491 }
1492}
1493
1494impl DiffHunk {
1495 pub fn is_created_file(&self) -> bool {
1496 self.diff_base_byte_range == (0..0)
1497 && self.buffer_range.start.is_min()
1498 && self.buffer_range.end.is_max()
1499 }
1500
1501 pub fn status(&self) -> DiffHunkStatus {
1502 let kind = if self.buffer_range.start == self.buffer_range.end {
1503 DiffHunkStatusKind::Deleted
1504 } else if self.diff_base_byte_range.is_empty() {
1505 DiffHunkStatusKind::Added
1506 } else {
1507 DiffHunkStatusKind::Modified
1508 };
1509 DiffHunkStatus {
1510 kind,
1511 secondary: self.secondary_status,
1512 }
1513 }
1514}
1515
1516impl DiffHunkStatus {
1517 pub fn has_secondary_hunk(&self) -> bool {
1518 matches!(
1519 self.secondary,
1520 DiffHunkSecondaryStatus::HasSecondaryHunk
1521 | DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1522 | DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
1523 )
1524 }
1525
1526 pub fn is_pending(&self) -> bool {
1527 matches!(
1528 self.secondary,
1529 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
1530 | DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
1531 )
1532 }
1533
1534 pub fn is_deleted(&self) -> bool {
1535 self.kind == DiffHunkStatusKind::Deleted
1536 }
1537
1538 pub fn is_added(&self) -> bool {
1539 self.kind == DiffHunkStatusKind::Added
1540 }
1541
1542 pub fn is_modified(&self) -> bool {
1543 self.kind == DiffHunkStatusKind::Modified
1544 }
1545
1546 pub fn added(secondary: DiffHunkSecondaryStatus) -> Self {
1547 Self {
1548 kind: DiffHunkStatusKind::Added,
1549 secondary,
1550 }
1551 }
1552
1553 pub fn modified(secondary: DiffHunkSecondaryStatus) -> Self {
1554 Self {
1555 kind: DiffHunkStatusKind::Modified,
1556 secondary,
1557 }
1558 }
1559
1560 pub fn deleted(secondary: DiffHunkSecondaryStatus) -> Self {
1561 Self {
1562 kind: DiffHunkStatusKind::Deleted,
1563 secondary,
1564 }
1565 }
1566
1567 pub fn deleted_none() -> Self {
1568 Self {
1569 kind: DiffHunkStatusKind::Deleted,
1570 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1571 }
1572 }
1573
1574 pub fn added_none() -> Self {
1575 Self {
1576 kind: DiffHunkStatusKind::Added,
1577 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1578 }
1579 }
1580
1581 pub fn modified_none() -> Self {
1582 Self {
1583 kind: DiffHunkStatusKind::Modified,
1584 secondary: DiffHunkSecondaryStatus::NoSecondaryHunk,
1585 }
1586 }
1587}
1588
1589#[cfg(any(test, feature = "test-support"))]
1590#[track_caller]
1591pub fn assert_hunks<ExpectedText, HunkIter>(
1592 diff_hunks: HunkIter,
1593 buffer: &text::BufferSnapshot,
1594 diff_base: &str,
1595 // Line range, deleted, added, status
1596 expected_hunks: &[(Range<u32>, ExpectedText, ExpectedText, DiffHunkStatus)],
1597) where
1598 HunkIter: Iterator<Item = DiffHunk>,
1599 ExpectedText: AsRef<str>,
1600{
1601 let actual_hunks = diff_hunks
1602 .map(|hunk| {
1603 (
1604 hunk.range.clone(),
1605 &diff_base[hunk.diff_base_byte_range.clone()],
1606 buffer
1607 .text_for_range(hunk.range.clone())
1608 .collect::<String>(),
1609 hunk.status(),
1610 )
1611 })
1612 .collect::<Vec<_>>();
1613
1614 let expected_hunks: Vec<_> = expected_hunks
1615 .iter()
1616 .map(|(line_range, deleted_text, added_text, status)| {
1617 (
1618 Point::new(line_range.start, 0)..Point::new(line_range.end, 0),
1619 deleted_text.as_ref(),
1620 added_text.as_ref().to_string(),
1621 *status,
1622 )
1623 })
1624 .collect();
1625
1626 pretty_assertions::assert_eq!(actual_hunks, expected_hunks);
1627}
1628
1629#[cfg(test)]
1630mod tests {
1631 use std::{fmt::Write as _, sync::mpsc};
1632
1633 use super::*;
1634 use gpui::TestAppContext;
1635 use pretty_assertions::{assert_eq, assert_ne};
1636 use rand::{Rng as _, rngs::StdRng};
1637 use text::{Buffer, BufferId, ReplicaId, Rope};
1638 use unindent::Unindent as _;
1639 use util::test::marked_text_ranges;
1640
1641 #[ctor::ctor]
1642 fn init_logger() {
1643 zlog::init_test();
1644 }
1645
1646 #[gpui::test]
1647 async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) {
1648 let diff_base = "
1649 one
1650 two
1651 three
1652 "
1653 .unindent();
1654
1655 let buffer_text = "
1656 one
1657 HELLO
1658 three
1659 "
1660 .unindent();
1661
1662 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1663 let mut diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1664 assert_hunks(
1665 diff.hunks_intersecting_range(
1666 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1667 &buffer,
1668 ),
1669 &buffer,
1670 &diff_base,
1671 &[(1..2, "two\n", "HELLO\n", DiffHunkStatus::modified_none())],
1672 );
1673
1674 buffer.edit([(0..0, "point five\n")]);
1675 diff = BufferDiffSnapshot::new_sync(buffer.clone(), diff_base.clone(), cx);
1676 assert_hunks(
1677 diff.hunks_intersecting_range(
1678 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1679 &buffer,
1680 ),
1681 &buffer,
1682 &diff_base,
1683 &[
1684 (0..1, "", "point five\n", DiffHunkStatus::added_none()),
1685 (2..3, "two\n", "HELLO\n", DiffHunkStatus::modified_none()),
1686 ],
1687 );
1688
1689 diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
1690 assert_hunks::<&str, _>(
1691 diff.hunks_intersecting_range(
1692 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1693 &buffer,
1694 ),
1695 &buffer,
1696 &diff_base,
1697 &[],
1698 );
1699 }
1700
1701 #[gpui::test]
1702 async fn test_buffer_diff_with_secondary(cx: &mut gpui::TestAppContext) {
1703 let head_text = "
1704 zero
1705 one
1706 two
1707 three
1708 four
1709 five
1710 six
1711 seven
1712 eight
1713 nine
1714 "
1715 .unindent();
1716
1717 let index_text = "
1718 zero
1719 one
1720 TWO
1721 three
1722 FOUR
1723 five
1724 six
1725 seven
1726 eight
1727 NINE
1728 "
1729 .unindent();
1730
1731 let buffer_text = "
1732 zero
1733 one
1734 TWO
1735 three
1736 FOUR
1737 FIVE
1738 six
1739 SEVEN
1740 eight
1741 nine
1742 "
1743 .unindent();
1744
1745 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1746 let unstaged_diff = BufferDiffSnapshot::new_sync(buffer.clone(), index_text, cx);
1747 let mut uncommitted_diff =
1748 BufferDiffSnapshot::new_sync(buffer.clone(), head_text.clone(), cx);
1749 uncommitted_diff.secondary_diff = Some(Box::new(unstaged_diff));
1750
1751 let expected_hunks = vec![
1752 (2..3, "two\n", "TWO\n", DiffHunkStatus::modified_none()),
1753 (
1754 4..6,
1755 "four\nfive\n",
1756 "FOUR\nFIVE\n",
1757 DiffHunkStatus::modified(DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk),
1758 ),
1759 (
1760 7..8,
1761 "seven\n",
1762 "SEVEN\n",
1763 DiffHunkStatus::modified(DiffHunkSecondaryStatus::HasSecondaryHunk),
1764 ),
1765 ];
1766
1767 assert_hunks(
1768 uncommitted_diff.hunks_intersecting_range(
1769 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1770 &buffer,
1771 ),
1772 &buffer,
1773 &head_text,
1774 &expected_hunks,
1775 );
1776 }
1777
1778 #[gpui::test]
1779 async fn test_buffer_diff_range(cx: &mut TestAppContext) {
1780 let diff_base = "
1781 one
1782 two
1783 three
1784 four
1785 five
1786 six
1787 seven
1788 eight
1789 nine
1790 ten
1791 "
1792 .unindent();
1793
1794 let buffer_text = "
1795 A
1796 one
1797 B
1798 two
1799 C
1800 three
1801 HELLO
1802 four
1803 five
1804 SIXTEEN
1805 seven
1806 eight
1807 WORLD
1808 nine
1809
1810 ten
1811
1812 "
1813 .unindent();
1814
1815 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
1816 let diff = BufferDiffSnapshot::new_sync(buffer.snapshot(), diff_base.clone(), cx);
1817 assert_eq!(
1818 diff.hunks_intersecting_range(
1819 Anchor::min_max_range_for_buffer(buffer.remote_id()),
1820 &buffer
1821 )
1822 .count(),
1823 8
1824 );
1825
1826 assert_hunks(
1827 diff.hunks_intersecting_range(
1828 buffer.anchor_before(Point::new(7, 0))..buffer.anchor_before(Point::new(12, 0)),
1829 &buffer,
1830 ),
1831 &buffer,
1832 &diff_base,
1833 &[
1834 (6..7, "", "HELLO\n", DiffHunkStatus::added_none()),
1835 (9..10, "six\n", "SIXTEEN\n", DiffHunkStatus::modified_none()),
1836 (12..13, "", "WORLD\n", DiffHunkStatus::added_none()),
1837 ],
1838 );
1839 }
1840
1841 #[gpui::test]
1842 async fn test_stage_hunk(cx: &mut TestAppContext) {
1843 struct Example {
1844 name: &'static str,
1845 head_text: String,
1846 index_text: String,
1847 buffer_marked_text: String,
1848 final_index_text: String,
1849 }
1850
1851 let table = [
1852 Example {
1853 name: "uncommitted hunk straddles end of unstaged hunk",
1854 head_text: "
1855 one
1856 two
1857 three
1858 four
1859 five
1860 "
1861 .unindent(),
1862 index_text: "
1863 one
1864 TWO_HUNDRED
1865 three
1866 FOUR_HUNDRED
1867 five
1868 "
1869 .unindent(),
1870 buffer_marked_text: "
1871 ZERO
1872 one
1873 two
1874 «THREE_HUNDRED
1875 FOUR_HUNDRED»
1876 five
1877 SIX
1878 "
1879 .unindent(),
1880 final_index_text: "
1881 one
1882 two
1883 THREE_HUNDRED
1884 FOUR_HUNDRED
1885 five
1886 "
1887 .unindent(),
1888 },
1889 Example {
1890 name: "uncommitted hunk straddles start of unstaged hunk",
1891 head_text: "
1892 one
1893 two
1894 three
1895 four
1896 five
1897 "
1898 .unindent(),
1899 index_text: "
1900 one
1901 TWO_HUNDRED
1902 three
1903 FOUR_HUNDRED
1904 five
1905 "
1906 .unindent(),
1907 buffer_marked_text: "
1908 ZERO
1909 one
1910 «TWO_HUNDRED
1911 THREE_HUNDRED»
1912 four
1913 five
1914 SIX
1915 "
1916 .unindent(),
1917 final_index_text: "
1918 one
1919 TWO_HUNDRED
1920 THREE_HUNDRED
1921 four
1922 five
1923 "
1924 .unindent(),
1925 },
1926 Example {
1927 name: "uncommitted hunk strictly contains unstaged hunks",
1928 head_text: "
1929 one
1930 two
1931 three
1932 four
1933 five
1934 six
1935 seven
1936 "
1937 .unindent(),
1938 index_text: "
1939 one
1940 TWO
1941 THREE
1942 FOUR
1943 FIVE
1944 SIX
1945 seven
1946 "
1947 .unindent(),
1948 buffer_marked_text: "
1949 one
1950 TWO
1951 «THREE_HUNDRED
1952 FOUR
1953 FIVE_HUNDRED»
1954 SIX
1955 seven
1956 "
1957 .unindent(),
1958 final_index_text: "
1959 one
1960 TWO
1961 THREE_HUNDRED
1962 FOUR
1963 FIVE_HUNDRED
1964 SIX
1965 seven
1966 "
1967 .unindent(),
1968 },
1969 Example {
1970 name: "uncommitted deletion hunk",
1971 head_text: "
1972 one
1973 two
1974 three
1975 four
1976 five
1977 "
1978 .unindent(),
1979 index_text: "
1980 one
1981 two
1982 three
1983 four
1984 five
1985 "
1986 .unindent(),
1987 buffer_marked_text: "
1988 one
1989 ˇfive
1990 "
1991 .unindent(),
1992 final_index_text: "
1993 one
1994 five
1995 "
1996 .unindent(),
1997 },
1998 Example {
1999 name: "one unstaged hunk that contains two uncommitted hunks",
2000 head_text: "
2001 one
2002 two
2003
2004 three
2005 four
2006 "
2007 .unindent(),
2008 index_text: "
2009 one
2010 two
2011 three
2012 four
2013 "
2014 .unindent(),
2015 buffer_marked_text: "
2016 «one
2017
2018 three // modified
2019 four»
2020 "
2021 .unindent(),
2022 final_index_text: "
2023 one
2024
2025 three // modified
2026 four
2027 "
2028 .unindent(),
2029 },
2030 Example {
2031 name: "one uncommitted hunk that contains two unstaged hunks",
2032 head_text: "
2033 one
2034 two
2035 three
2036 four
2037 five
2038 "
2039 .unindent(),
2040 index_text: "
2041 ZERO
2042 one
2043 TWO
2044 THREE
2045 FOUR
2046 five
2047 "
2048 .unindent(),
2049 buffer_marked_text: "
2050 «one
2051 TWO_HUNDRED
2052 THREE
2053 FOUR_HUNDRED
2054 five»
2055 "
2056 .unindent(),
2057 final_index_text: "
2058 ZERO
2059 one
2060 TWO_HUNDRED
2061 THREE
2062 FOUR_HUNDRED
2063 five
2064 "
2065 .unindent(),
2066 },
2067 ];
2068
2069 for example in table {
2070 let (buffer_text, ranges) = marked_text_ranges(&example.buffer_marked_text, false);
2071 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2072 let hunk_range =
2073 buffer.anchor_before(ranges[0].start)..buffer.anchor_before(ranges[0].end);
2074
2075 let unstaged_diff =
2076 cx.new(|cx| BufferDiff::new_with_base_text(&example.index_text, &buffer, cx));
2077
2078 let uncommitted_diff = cx.new(|cx| {
2079 let mut diff = BufferDiff::new_with_base_text(&example.head_text, &buffer, cx);
2080 diff.set_secondary_diff(unstaged_diff);
2081 diff
2082 });
2083
2084 uncommitted_diff.update(cx, |diff, cx| {
2085 let hunks = diff
2086 .snapshot(cx)
2087 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2088 .collect::<Vec<_>>();
2089 for hunk in &hunks {
2090 assert_ne!(
2091 hunk.secondary_status,
2092 DiffHunkSecondaryStatus::NoSecondaryHunk
2093 )
2094 }
2095
2096 let new_index_text = diff
2097 .stage_or_unstage_hunks(true, &hunks, &buffer, true, cx)
2098 .unwrap()
2099 .to_string();
2100
2101 let hunks = diff
2102 .snapshot(cx)
2103 .hunks_intersecting_range(hunk_range.clone(), &buffer)
2104 .collect::<Vec<_>>();
2105 for hunk in &hunks {
2106 assert_eq!(
2107 hunk.secondary_status,
2108 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2109 )
2110 }
2111
2112 pretty_assertions::assert_eq!(
2113 new_index_text,
2114 example.final_index_text,
2115 "example: {}",
2116 example.name
2117 );
2118 });
2119 }
2120 }
2121
2122 #[gpui::test]
2123 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
2124 let head_text = "
2125 one
2126 two
2127 three
2128 "
2129 .unindent();
2130 let index_text = head_text.clone();
2131 let buffer_text = "
2132 one
2133 three
2134 "
2135 .unindent();
2136
2137 let buffer = Buffer::new(
2138 ReplicaId::LOCAL,
2139 BufferId::new(1).unwrap(),
2140 buffer_text.clone(),
2141 );
2142 let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2143 let uncommitted_diff = cx.new(|cx| {
2144 let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2145 diff.set_secondary_diff(unstaged_diff.clone());
2146 diff
2147 });
2148
2149 uncommitted_diff.update(cx, |diff, cx| {
2150 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2151
2152 let new_index_text = diff
2153 .stage_or_unstage_hunks(true, std::slice::from_ref(&hunk), &buffer, true, cx)
2154 .unwrap()
2155 .to_string();
2156 assert_eq!(new_index_text, buffer_text);
2157
2158 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2159 assert_eq!(
2160 hunk.secondary_status,
2161 DiffHunkSecondaryStatus::SecondaryHunkRemovalPending
2162 );
2163
2164 let index_text = diff
2165 .stage_or_unstage_hunks(false, &[hunk], &buffer, true, cx)
2166 .unwrap()
2167 .to_string();
2168 assert_eq!(index_text, head_text);
2169
2170 let hunk = diff.snapshot(cx).hunks(&buffer).next().unwrap();
2171 // optimistically unstaged (fine, could also be HasSecondaryHunk)
2172 assert_eq!(
2173 hunk.secondary_status,
2174 DiffHunkSecondaryStatus::SecondaryHunkAdditionPending
2175 );
2176 });
2177 }
2178
2179 #[gpui::test]
2180 async fn test_buffer_diff_compare(cx: &mut TestAppContext) {
2181 let base_text = "
2182 zero
2183 one
2184 two
2185 three
2186 four
2187 five
2188 six
2189 seven
2190 eight
2191 nine
2192 "
2193 .unindent();
2194
2195 let buffer_text_1 = "
2196 one
2197 three
2198 four
2199 five
2200 SIX
2201 seven
2202 eight
2203 NINE
2204 "
2205 .unindent();
2206
2207 let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text_1);
2208
2209 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
2210 let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2211 let (range, base_text_range) =
2212 compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
2213 let range = range.unwrap();
2214 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2215 let base_text_range = base_text_range.unwrap();
2216 assert_eq!(
2217 base_text_range.to_point(diff_1.base_text()),
2218 Point::new(0, 0)..Point::new(10, 0)
2219 );
2220
2221 // Edit does affects the diff because it recalculates word diffs.
2222 buffer.edit_via_marked_text(
2223 &"
2224 one
2225 three
2226 four
2227 five
2228 «SIX.5»
2229 seven
2230 eight
2231 NINE
2232 "
2233 .unindent(),
2234 );
2235 let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2236 let (range, base_text_range) =
2237 compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer);
2238 assert_eq!(
2239 range.unwrap().to_point(&buffer),
2240 Point::new(4, 0)..Point::new(5, 0),
2241 );
2242 assert_eq!(
2243 base_text_range.unwrap().to_point(diff_2.base_text()),
2244 Point::new(6, 0)..Point::new(7, 0),
2245 );
2246
2247 // Edit turns a deletion hunk into a modification.
2248 buffer.edit_via_marked_text(
2249 &"
2250 one
2251 «THREE»
2252 four
2253 five
2254 SIX.5
2255 seven
2256 eight
2257 NINE
2258 "
2259 .unindent(),
2260 );
2261 let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2262 let (range, base_text_range) =
2263 compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
2264 let range = range.unwrap();
2265 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2266 let base_text_range = base_text_range.unwrap();
2267 assert_eq!(
2268 base_text_range.to_point(diff_3.base_text()),
2269 Point::new(2, 0)..Point::new(4, 0)
2270 );
2271
2272 // Edit turns a modification hunk into a deletion.
2273 buffer.edit_via_marked_text(
2274 &"
2275 one
2276 THREE
2277 four
2278 five«»
2279 seven
2280 eight
2281 NINE
2282 "
2283 .unindent(),
2284 );
2285 let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2286 let (range, base_text_range) =
2287 compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
2288 let range = range.unwrap();
2289 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2290 let base_text_range = base_text_range.unwrap();
2291 assert_eq!(
2292 base_text_range.to_point(diff_4.base_text()),
2293 Point::new(6, 0)..Point::new(7, 0)
2294 );
2295
2296 // Edit introduces a new insertion hunk.
2297 buffer.edit_via_marked_text(
2298 &"
2299 one
2300 THREE
2301 four«
2302 FOUR.5
2303 »five
2304 seven
2305 eight
2306 NINE
2307 "
2308 .unindent(),
2309 );
2310 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2311 let (range, base_text_range) =
2312 compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
2313 let range = range.unwrap();
2314 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2315 let base_text_range = base_text_range.unwrap();
2316 assert_eq!(
2317 base_text_range.to_point(diff_5.base_text()),
2318 Point::new(5, 0)..Point::new(5, 0)
2319 );
2320
2321 // Edit removes a hunk.
2322 buffer.edit_via_marked_text(
2323 &"
2324 one
2325 THREE
2326 four
2327 FOUR.5
2328 five
2329 seven
2330 eight
2331 «nine»
2332 "
2333 .unindent(),
2334 );
2335 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2336 let (range, base_text_range) =
2337 compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
2338 let range = range.unwrap();
2339 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2340 let base_text_range = base_text_range.unwrap();
2341 assert_eq!(
2342 base_text_range.to_point(diff_6.base_text()),
2343 Point::new(9, 0)..Point::new(10, 0)
2344 );
2345 }
2346
2347 #[gpui::test(iterations = 100)]
2348 async fn test_staging_and_unstaging_hunks(cx: &mut TestAppContext, mut rng: StdRng) {
2349 fn gen_line(rng: &mut StdRng) -> String {
2350 if rng.random_bool(0.2) {
2351 "\n".to_owned()
2352 } else {
2353 let c = rng.random_range('A'..='Z');
2354 format!("{c}{c}{c}\n")
2355 }
2356 }
2357
2358 fn gen_working_copy(rng: &mut StdRng, head: &str) -> String {
2359 let mut old_lines = {
2360 let mut old_lines = Vec::new();
2361 let old_lines_iter = head.lines();
2362 for line in old_lines_iter {
2363 assert!(!line.ends_with("\n"));
2364 old_lines.push(line.to_owned());
2365 }
2366 if old_lines.last().is_some_and(|line| line.is_empty()) {
2367 old_lines.pop();
2368 }
2369 old_lines.into_iter()
2370 };
2371 let mut result = String::new();
2372 let unchanged_count = rng.random_range(0..=old_lines.len());
2373 result +=
2374 &old_lines
2375 .by_ref()
2376 .take(unchanged_count)
2377 .fold(String::new(), |mut s, line| {
2378 writeln!(&mut s, "{line}").unwrap();
2379 s
2380 });
2381 while old_lines.len() > 0 {
2382 let deleted_count = rng.random_range(0..=old_lines.len());
2383 let _advance = old_lines
2384 .by_ref()
2385 .take(deleted_count)
2386 .map(|line| line.len() + 1)
2387 .sum::<usize>();
2388 let minimum_added = if deleted_count == 0 { 1 } else { 0 };
2389 let added_count = rng.random_range(minimum_added..=5);
2390 let addition = (0..added_count).map(|_| gen_line(rng)).collect::<String>();
2391 result += &addition;
2392
2393 if old_lines.len() > 0 {
2394 let blank_lines = old_lines.clone().take_while(|line| line.is_empty()).count();
2395 if blank_lines == old_lines.len() {
2396 break;
2397 };
2398 let unchanged_count =
2399 rng.random_range((blank_lines + 1).max(1)..=old_lines.len());
2400 result += &old_lines.by_ref().take(unchanged_count).fold(
2401 String::new(),
2402 |mut s, line| {
2403 writeln!(&mut s, "{line}").unwrap();
2404 s
2405 },
2406 );
2407 }
2408 }
2409 result
2410 }
2411
2412 fn uncommitted_diff(
2413 working_copy: &language::BufferSnapshot,
2414 index_text: &Rope,
2415 head_text: String,
2416 cx: &mut TestAppContext,
2417 ) -> Entity<BufferDiff> {
2418 let secondary = cx.new(|cx| {
2419 BufferDiff::new_with_base_text(&index_text.to_string(), &working_copy.text, cx)
2420 });
2421 cx.new(|cx| {
2422 let mut diff = BufferDiff::new_with_base_text(&head_text, &working_copy.text, cx);
2423 diff.secondary_diff = Some(secondary);
2424 diff
2425 })
2426 }
2427
2428 let operations = std::env::var("OPERATIONS")
2429 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
2430 .unwrap_or(10);
2431
2432 let rng = &mut rng;
2433 let head_text = ('a'..='z').fold(String::new(), |mut s, c| {
2434 writeln!(&mut s, "{c}{c}{c}").unwrap();
2435 s
2436 });
2437 let working_copy = gen_working_copy(rng, &head_text);
2438 let working_copy = cx.new(|cx| {
2439 language::Buffer::local_normalized(
2440 Rope::from(working_copy.as_str()),
2441 text::LineEnding::default(),
2442 cx,
2443 )
2444 });
2445 let working_copy = working_copy.read_with(cx, |working_copy, _| working_copy.snapshot());
2446 let mut index_text = if rng.random() {
2447 Rope::from(head_text.as_str())
2448 } else {
2449 working_copy.as_rope().clone()
2450 };
2451
2452 let mut diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2453 let mut hunks = diff.update(cx, |diff, cx| {
2454 diff.snapshot(cx)
2455 .hunks_intersecting_range(
2456 Anchor::min_max_range_for_buffer(diff.buffer_id),
2457 &working_copy,
2458 )
2459 .collect::<Vec<_>>()
2460 });
2461 if hunks.is_empty() {
2462 return;
2463 }
2464
2465 for _ in 0..operations {
2466 let i = rng.random_range(0..hunks.len());
2467 let hunk = &mut hunks[i];
2468 let hunk_to_change = hunk.clone();
2469 let stage = match hunk.secondary_status {
2470 DiffHunkSecondaryStatus::HasSecondaryHunk => {
2471 hunk.secondary_status = DiffHunkSecondaryStatus::NoSecondaryHunk;
2472 true
2473 }
2474 DiffHunkSecondaryStatus::NoSecondaryHunk => {
2475 hunk.secondary_status = DiffHunkSecondaryStatus::HasSecondaryHunk;
2476 false
2477 }
2478 _ => unreachable!(),
2479 };
2480
2481 index_text = diff.update(cx, |diff, cx| {
2482 diff.stage_or_unstage_hunks(stage, &[hunk_to_change], &working_copy, true, cx)
2483 .unwrap()
2484 });
2485
2486 diff = uncommitted_diff(&working_copy, &index_text, head_text.clone(), cx);
2487 let found_hunks = diff.update(cx, |diff, cx| {
2488 diff.snapshot(cx)
2489 .hunks_intersecting_range(
2490 Anchor::min_max_range_for_buffer(diff.buffer_id),
2491 &working_copy,
2492 )
2493 .collect::<Vec<_>>()
2494 });
2495 assert_eq!(hunks.len(), found_hunks.len());
2496
2497 for (expected_hunk, found_hunk) in hunks.iter().zip(&found_hunks) {
2498 assert_eq!(
2499 expected_hunk.buffer_range.to_point(&working_copy),
2500 found_hunk.buffer_range.to_point(&working_copy)
2501 );
2502 assert_eq!(
2503 expected_hunk.diff_base_byte_range,
2504 found_hunk.diff_base_byte_range
2505 );
2506 assert_eq!(expected_hunk.secondary_status, found_hunk.secondary_status);
2507 }
2508 hunks = found_hunks;
2509 }
2510 }
2511
2512 #[gpui::test]
2513 async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
2514 let base_text = "
2515 zero
2516 one
2517 two
2518 three
2519 four
2520 five
2521 six
2522 seven
2523 eight
2524 "
2525 .unindent();
2526 let buffer_text = "
2527 zero
2528 ONE
2529 two
2530 NINE
2531 five
2532 seven
2533 "
2534 .unindent();
2535
2536 // zero
2537 // - one
2538 // + ONE
2539 // two
2540 // - three
2541 // - four
2542 // + NINE
2543 // five
2544 // - six
2545 // seven
2546 // + eight
2547
2548 let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2549 let buffer_snapshot = buffer.snapshot();
2550 let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
2551 let expected_results = [
2552 // main buffer row, base text row (right bias), base text row (left bias)
2553 (0, 0, 0),
2554 (1, 2, 1),
2555 (2, 2, 2),
2556 (3, 5, 3),
2557 (4, 5, 5),
2558 (5, 7, 7),
2559 (6, 9, 9),
2560 ];
2561 for (buffer_row, expected_right, expected_left) in expected_results {
2562 assert_eq!(
2563 diff.row_to_base_text_row(buffer_row, Bias::Right, &buffer_snapshot),
2564 expected_right,
2565 "{buffer_row}"
2566 );
2567 assert_eq!(
2568 diff.row_to_base_text_row(buffer_row, Bias::Left, &buffer_snapshot),
2569 expected_left,
2570 "{buffer_row}"
2571 );
2572 }
2573 }
2574
2575 #[gpui::test]
2576 async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
2577 let base_text = "
2578 one
2579 two
2580 three
2581 four
2582 five
2583 six
2584 "
2585 .unindent();
2586 let buffer_text = "
2587 one
2588 TWO
2589 three
2590 four
2591 FIVE
2592 six
2593 "
2594 .unindent();
2595 let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
2596 let diff = cx.new(|cx| {
2597 BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
2598 });
2599 cx.run_until_parked();
2600 let (tx, rx) = mpsc::channel();
2601 let subscription =
2602 cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
2603
2604 let snapshot = buffer.update(cx, |buffer, cx| {
2605 buffer.set_text(
2606 "
2607 ONE
2608 TWO
2609 THREE
2610 FOUR
2611 FIVE
2612 SIX
2613 "
2614 .unindent(),
2615 cx,
2616 );
2617 buffer.text_snapshot()
2618 });
2619 let update = diff
2620 .update(cx, |diff, cx| {
2621 diff.update_diff(
2622 snapshot.clone(),
2623 Some(base_text.as_str().into()),
2624 false,
2625 None,
2626 cx,
2627 )
2628 })
2629 .await;
2630 diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
2631 .await;
2632 cx.run_until_parked();
2633 drop(subscription);
2634 let events = rx.into_iter().collect::<Vec<_>>();
2635 match events.as_slice() {
2636 [
2637 BufferDiffEvent::DiffChanged {
2638 changed_range: _,
2639 base_text_changed_range,
2640 },
2641 ] => {
2642 // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
2643 // assert_eq!(
2644 // *changed_range,
2645 // Some(Anchor::min_max_range_for_buffer(
2646 // buffer.read_with(cx, |buffer, _| buffer.remote_id())
2647 // ))
2648 // );
2649 assert_eq!(*base_text_changed_range, Some(0..base_text.len()));
2650 }
2651 _ => panic!("unexpected events: {:?}", events),
2652 }
2653 }
2654}