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