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