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