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