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