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