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