diff.rs

  1use anyhow::Result;
  2use buffer_diff::{BufferDiff, BufferDiffUpdate};
  3use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};
  4use itertools::Itertools;
  5use language::{
  6    Anchor, Buffer, Capability, DiffOptions, LanguageRegistry, OffsetRangeExt as _, Point,
  7    TextBuffer,
  8};
  9use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
 10use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
 11use streaming_diff::LineOperation;
 12use text::{Edit, Patch};
 13use util::ResultExt;
 14
 15pub enum Diff {
 16    Pending(PendingDiff),
 17    Finalized(FinalizedDiff),
 18}
 19
 20impl Diff {
 21    pub fn finalized(
 22        path: String,
 23        old_text: Option<String>,
 24        new_text: String,
 25        language_registry: Arc<LanguageRegistry>,
 26        cx: &mut Context<Self>,
 27    ) -> Self {
 28        let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
 29        let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
 30        let base_text = old_text.clone().unwrap_or(String::new()).into();
 31        let task = cx.spawn({
 32            let multibuffer = multibuffer.clone();
 33            let path = path.clone();
 34            let buffer = new_buffer.clone();
 35            async move |_, cx| {
 36                let language = language_registry
 37                    .load_language_for_file_path(Path::new(&path))
 38                    .await
 39                    .log_err();
 40
 41                buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx));
 42                buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
 43
 44                let diff = build_buffer_diff(
 45                    old_text.unwrap_or("".into()).into(),
 46                    &buffer,
 47                    Some(language_registry.clone()),
 48                    cx,
 49                )
 50                .await?;
 51
 52                multibuffer.update(cx, |multibuffer, cx| {
 53                    let hunk_ranges = {
 54                        let buffer = buffer.read(cx);
 55                        diff.read(cx)
 56                            .snapshot(cx)
 57                            .hunks_intersecting_range(
 58                                Anchor::min_for_buffer(buffer.remote_id())
 59                                    ..Anchor::max_for_buffer(buffer.remote_id()),
 60                                buffer,
 61                            )
 62                            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
 63                            .collect::<Vec<_>>()
 64                    };
 65
 66                    multibuffer.set_excerpts_for_path(
 67                        PathKey::for_buffer(&buffer, cx),
 68                        buffer.clone(),
 69                        hunk_ranges,
 70                        excerpt_context_lines(cx),
 71                        cx,
 72                    );
 73                    multibuffer.add_diff(diff, cx);
 74                });
 75
 76                anyhow::Ok(())
 77            }
 78        });
 79
 80        Self::Finalized(FinalizedDiff {
 81            multibuffer,
 82            path,
 83            base_text,
 84            new_buffer,
 85            _update_diff: task,
 86        })
 87    }
 88
 89    pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
 90        let subscription = cx.observe(&buffer, |this, _, cx| {
 91            if let Diff::Pending(diff) = this {
 92                diff.auto_update(cx);
 93            }
 94        });
 95        Self::new_inner(
 96            buffer,
 97            UpdateStrategy::Auto {
 98                _subscription: subscription,
 99            },
100            cx,
101        )
102    }
103
104    pub fn manual(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
105        Self::new_inner(
106            buffer,
107            UpdateStrategy::Manual {
108                pending_update: None,
109            },
110            cx,
111        )
112    }
113
114    fn new_inner(
115        buffer: Entity<Buffer>,
116        update_strategy: UpdateStrategy,
117        cx: &mut Context<Self>,
118    ) -> Self {
119        let buffer_text_snapshot = buffer.read(cx).text_snapshot();
120        let language = buffer.read(cx).language().cloned();
121        let language_registry = buffer.read(cx).language_registry();
122        let buffer_diff = cx.new(|cx| {
123            let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
124            diff.language_changed(language.clone(), language_registry.clone(), cx);
125            let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx));
126            diff.set_secondary_diff(secondary_diff);
127            diff
128        });
129
130        let multibuffer = cx.new(|cx| {
131            let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
132            multibuffer.set_all_diff_hunks_expanded(cx);
133            multibuffer.add_diff(buffer_diff.clone(), cx);
134            multibuffer
135        });
136
137        Self::Pending(PendingDiff {
138            multibuffer,
139            base_text: Arc::from(buffer_text_snapshot.text().as_str()),
140            new_buffer: buffer,
141            diff: buffer_diff,
142            revealed_ranges: Vec::new(),
143            update_strategy,
144            update_diff: Task::ready(Ok(())),
145        })
146    }
147
148    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
149        if let Self::Pending(diff) = self {
150            diff.reveal_range(range, cx);
151        }
152    }
153
154    pub fn finalize(&mut self, cx: &mut Context<Self>) {
155        if let Self::Pending(diff) = self {
156            *self = Self::Finalized(diff.finalize(cx));
157        }
158    }
159
160    /// Returns the original text before any edits were applied.
161    pub fn base_text(&self) -> &Arc<str> {
162        match self {
163            Self::Pending(PendingDiff { base_text, .. }) => base_text,
164            Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
165        }
166    }
167
168    /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
169    pub fn buffer(&self) -> &Entity<Buffer> {
170        match self {
171            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
172            Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
173        }
174    }
175
176    pub fn file_path(&self, cx: &App) -> Option<String> {
177        match self {
178            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer
179                .read(cx)
180                .file()
181                .map(|file| file.full_path(cx).to_string_lossy().into_owned()),
182            Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()),
183        }
184    }
185
186    pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
187        match self {
188            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
189            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
190        }
191    }
192
193    pub fn to_markdown(&self, cx: &App) -> String {
194        let buffer_text = self
195            .multibuffer()
196            .read(cx)
197            .all_buffers()
198            .iter()
199            .map(|buffer| buffer.read(cx).text())
200            .join("\n");
201        let path = match self {
202            Diff::Pending(PendingDiff {
203                new_buffer: buffer, ..
204            }) => buffer
205                .read(cx)
206                .file()
207                .map(|file| file.path().display(file.path_style(cx))),
208            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
209        };
210        format!(
211            "Diff: {}\n```\n{}\n```\n",
212            path.unwrap_or("untitled".into()),
213            buffer_text
214        )
215    }
216
217    pub fn has_revealed_range(&self, cx: &App) -> bool {
218        self.multibuffer().read(cx).paths().next().is_some()
219    }
220
221    pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
222        match self {
223            Diff::Pending(PendingDiff {
224                base_text,
225                new_buffer,
226                ..
227            }) => {
228                base_text.as_ref() != old_text
229                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
230            }
231            Diff::Finalized(FinalizedDiff {
232                base_text,
233                new_buffer,
234                ..
235            }) => {
236                base_text.as_ref() != old_text
237                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
238            }
239        }
240    }
241
242    pub fn push_line_operations(
243        &mut self,
244        operations: Vec<LineOperation>,
245        base_snapshot: text::BufferSnapshot,
246        cx: &mut Context<Diff>,
247    ) {
248        if let Diff::Pending(diff) = self {
249            diff.push_line_operations(operations, base_snapshot, cx);
250        }
251    }
252}
253
254enum UpdateStrategy {
255    Auto {
256        _subscription: Subscription,
257    },
258    Manual {
259        pending_update: Option<PendingUpdate>,
260    },
261}
262
263pub struct PendingDiff {
264    multibuffer: Entity<MultiBuffer>,
265    base_text: Arc<str>,
266    new_buffer: Entity<Buffer>,
267    diff: Entity<BufferDiff>,
268    revealed_ranges: Vec<Range<Anchor>>,
269    update_strategy: UpdateStrategy,
270    update_diff: Task<Result<()>>,
271}
272
273struct PendingUpdate {
274    operations: Vec<LineOperation>,
275    base_snapshot: text::BufferSnapshot,
276    text_snapshot: text::BufferSnapshot,
277}
278
279fn compute_hunks(
280    diff_base: &text::BufferSnapshot,
281    buffer: &text::BufferSnapshot,
282    line_operations: Vec<LineOperation>,
283) -> Patch<usize> {
284    let mut patch = Patch::default();
285
286    let mut old_row = 0u32;
287    let mut new_row = 0u32;
288
289    // Merge adjacent Delete+Insert into a single Modified hunk
290    let mut pending_delete_lines: Option<u32> = None;
291
292    let flush_delete = |pending_delete_lines: &mut Option<u32>,
293                        old_row: &mut u32,
294                        new_row: u32,
295                        diff_base: &text::BufferSnapshot,
296                        buffer: &text::BufferSnapshot| {
297        if let Some(deleted_lines) = pending_delete_lines.take() {
298            let old_start =
299                diff_base.point_to_offset(Point::new(*old_row, 0).min(diff_base.max_point()));
300            let old_end = diff_base.point_to_offset(
301                Point::new(*old_row + deleted_lines, 0).min(diff_base.max_point()),
302            );
303            let new_pos = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));
304            let edit = Edit {
305                old: old_start..old_end,
306                new: new_pos..new_pos,
307            };
308            *old_row += deleted_lines;
309            Some(edit)
310        } else {
311            None
312        }
313    };
314
315    for operation in line_operations {
316        match operation {
317            LineOperation::Delete { lines } => {
318                // Accumulate deletions — they might be followed by an Insert (= modification)
319                *pending_delete_lines.get_or_insert(0) += lines;
320            }
321            LineOperation::Insert { lines } => {
322                let old_start =
323                    diff_base.point_to_offset(Point::new(old_row, 0).min(diff_base.max_point()));
324                let (old_end, deleted_lines) =
325                    if let Some(deleted_lines) = pending_delete_lines.take() {
326                        // Delete followed by Insert = Modified hunk
327                        let old_end = diff_base.point_to_offset(
328                            Point::new(old_row + deleted_lines, 0).min(diff_base.max_point()),
329                        );
330                        (old_end, deleted_lines)
331                    } else {
332                        // Pure insertion
333                        (old_start, 0)
334                    };
335                let new_start =
336                    buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));
337                let new_end =
338                    buffer.point_to_offset(Point::new(new_row + lines, 0).min(buffer.max_point()));
339                patch.push(Edit {
340                    old: old_start..old_end,
341                    new: new_start..new_end,
342                });
343                old_row += deleted_lines;
344                new_row += lines;
345            }
346            LineOperation::Keep { lines } => {
347                // Flush any pending deletion before a Keep
348                if let Some(edit) = flush_delete(
349                    &mut pending_delete_lines,
350                    &mut old_row,
351                    new_row,
352                    diff_base,
353                    buffer,
354                ) {
355                    patch.push(edit);
356                }
357                // Keep = unchanged, no hunk to push
358                old_row += lines;
359                new_row += lines;
360            }
361        }
362    }
363
364    // Flush any trailing deletion
365    if let Some(edit) = flush_delete(
366        &mut pending_delete_lines,
367        &mut old_row,
368        new_row,
369        diff_base,
370        buffer,
371    ) {
372        patch.push(edit);
373    }
374
375    patch
376}
377
378/// Applies a `BufferDiffUpdate` to both the primary and secondary diffs, then
379/// refreshes the visible excerpt ranges in the multibuffer.
380async fn apply_diff_update(
381    update: BufferDiffUpdate,
382    text_snapshot: &text::BufferSnapshot,
383    buffer_diff: &Entity<BufferDiff>,
384    diff: &WeakEntity<Diff>,
385    cx: &mut AsyncApp,
386) -> Result<()> {
387    // FIXME we can get away without having a whole secondary diff here
388    let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
389        let task1 = diff.set_snapshot(update.clone(), text_snapshot, cx);
390        let task2 = diff
391            .secondary_diff()
392            .expect("PendingDiff should always have a secondary diff")
393            .update(cx, |diff, cx| diff.set_snapshot(update, text_snapshot, cx));
394        (task1, task2)
395    });
396    task1.await;
397    task2.await;
398    diff.update(cx, |diff, cx| {
399        if let Diff::Pending(diff) = diff {
400            diff.update_visible_ranges(cx);
401        }
402    })
403}
404
405impl PendingDiff {
406    fn push_line_operations(
407        &mut self,
408        operations: Vec<LineOperation>,
409        base_snapshot: text::BufferSnapshot,
410        cx: &mut Context<Diff>,
411    ) {
412        let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else {
413            return;
414        };
415
416        let text_snapshot = self.new_buffer.read(cx).text_snapshot();
417        *pending_update = Some(PendingUpdate {
418            operations,
419            base_snapshot,
420            text_snapshot,
421        });
422        if self.update_diff.is_ready() {
423            self.flush_pending_update(cx);
424        }
425    }
426
427    fn auto_update(&mut self, cx: &mut Context<Diff>) {
428        let buffer = self.new_buffer.clone();
429        let buffer_diff = self.diff.clone();
430        let base_text = self.base_text.clone();
431        self.update_diff = cx.spawn(async move |diff, cx| {
432            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
433            let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
434            let update = buffer_diff
435                .update(cx, |diff, cx| {
436                    diff.update_diff(
437                        text_snapshot.clone(),
438                        Some(base_text.clone()),
439                        None,
440                        language,
441                        cx,
442                    )
443                })
444                .await;
445            apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await
446        });
447    }
448
449    fn flush_pending_update(&mut self, cx: &mut Context<Diff>) {
450        let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else {
451            return;
452        };
453
454        let Some(PendingUpdate {
455            operations,
456            base_snapshot,
457            text_snapshot,
458        }) = pending_update.take()
459        else {
460            return;
461        };
462
463        let buffer_diff = self.diff.clone();
464        let base_text = self.base_text.clone();
465        self.update_diff = cx.spawn(async move |diff, cx| {
466            let update = cx
467                .background_spawn({
468                    let snapshot = text_snapshot.clone();
469                    async move {
470                        let hunks = compute_hunks(&base_snapshot, &snapshot, operations);
471                        BufferDiffUpdate::from_hunks(
472                            base_text,
473                            base_snapshot.as_rope(),
474                            snapshot,
475                            hunks,
476                            Some(DiffOptions::default()),
477                        )
478                    }
479                })
480                .await;
481
482            apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await?;
483
484            diff.update(cx, |diff, cx| {
485                if let Diff::Pending(diff) = diff {
486                    // Pick up any update that arrived while this task was running.
487                    diff.flush_pending_update(cx);
488                }
489            })
490        });
491    }
492
493    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
494        self.revealed_ranges.push(range);
495        self.update_visible_ranges(cx);
496    }
497
498    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
499        let ranges = self.excerpt_ranges(cx);
500        let base_text = self.base_text.clone();
501        let new_buffer = self.new_buffer.read(cx);
502        let language_registry = new_buffer.language_registry();
503
504        let path = new_buffer
505            .file()
506            .map(|file| file.path().display(file.path_style(cx)))
507            .unwrap_or("untitled".into())
508            .into();
509        let replica_id = new_buffer.replica_id();
510
511        // Replace the buffer in the multibuffer with the snapshot
512        let buffer = cx.new(|cx| {
513            let language = self.new_buffer.read(cx).language().cloned();
514            let buffer = TextBuffer::new_normalized(
515                replica_id,
516                cx.entity_id().as_non_zero_u64().into(),
517                self.new_buffer.read(cx).line_ending(),
518                self.new_buffer.read(cx).as_rope().clone(),
519            );
520            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
521            buffer.set_language(language, cx);
522            buffer
523        });
524
525        let buffer_diff = cx.spawn({
526            let buffer = buffer.clone();
527            async move |_this, cx| {
528                buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
529                build_buffer_diff(base_text, &buffer, language_registry, cx).await
530            }
531        });
532
533        let update_diff = cx.spawn(async move |this, cx| {
534            let buffer_diff = buffer_diff.await?;
535            this.update(cx, |this, cx| {
536                this.multibuffer().update(cx, |multibuffer, cx| {
537                    let path_key = PathKey::for_buffer(&buffer, cx);
538                    multibuffer.clear(cx);
539                    multibuffer.set_excerpts_for_path(
540                        path_key,
541                        buffer,
542                        ranges,
543                        excerpt_context_lines(cx),
544                        cx,
545                    );
546                    multibuffer.add_diff(buffer_diff.clone(), cx);
547                });
548
549                cx.notify();
550            })
551        });
552
553        FinalizedDiff {
554            path,
555            base_text: self.base_text.clone(),
556            multibuffer: self.multibuffer.clone(),
557            new_buffer: self.new_buffer.clone(),
558            _update_diff: update_diff,
559        }
560    }
561
562    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
563        let ranges = self.excerpt_ranges(cx);
564        self.multibuffer.update(cx, |multibuffer, cx| {
565            multibuffer.set_excerpts_for_path(
566                PathKey::for_buffer(&self.new_buffer, cx),
567                self.new_buffer.clone(),
568                ranges,
569                excerpt_context_lines(cx),
570                cx,
571            );
572            let end = multibuffer.len(cx);
573            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
574        });
575        cx.notify();
576    }
577
578    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
579        let buffer = self.new_buffer.read(cx);
580        let mut ranges = self
581            .diff
582            .read(cx)
583            .snapshot(cx)
584            .hunks_intersecting_range(
585                Anchor::min_for_buffer(buffer.remote_id())
586                    ..Anchor::max_for_buffer(buffer.remote_id()),
587                buffer,
588            )
589            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
590            .collect::<Vec<_>>();
591        ranges.extend(
592            self.revealed_ranges
593                .iter()
594                .map(|range| range.to_point(buffer)),
595        );
596        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
597
598        // Merge adjacent ranges
599        let mut ranges = ranges.into_iter().peekable();
600        let mut merged_ranges = Vec::new();
601        while let Some(mut range) = ranges.next() {
602            while let Some(next_range) = ranges.peek() {
603                if range.end >= next_range.start {
604                    range.end = range.end.max(next_range.end);
605                    ranges.next();
606                } else {
607                    break;
608                }
609            }
610
611            merged_ranges.push(range);
612        }
613        merged_ranges
614    }
615}
616
617pub struct FinalizedDiff {
618    path: String,
619    base_text: Arc<str>,
620    new_buffer: Entity<Buffer>,
621    multibuffer: Entity<MultiBuffer>,
622    _update_diff: Task<Result<()>>,
623}
624
625async fn build_buffer_diff(
626    old_text: Arc<str>,
627    buffer: &Entity<Buffer>,
628    language_registry: Option<Arc<LanguageRegistry>>,
629    cx: &mut AsyncApp,
630) -> Result<Entity<BufferDiff>> {
631    let language = cx.update(|cx| buffer.read(cx).language().cloned());
632    let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
633    let buffer = cx.update(|cx| buffer.read(cx).snapshot());
634
635    let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
636
637    let update = secondary_diff
638        .update(cx, |secondary_diff, cx| {
639            secondary_diff.update_diff(
640                text_snapshot.clone(),
641                Some(old_text),
642                Some(false),
643                language.clone(),
644                cx,
645            )
646        })
647        .await;
648
649    secondary_diff
650        .update(cx, |secondary_diff, cx| {
651            secondary_diff.set_snapshot(update.clone(), &buffer, cx)
652        })
653        .await;
654
655    let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
656    diff.update(cx, |diff, cx| {
657        diff.language_changed(language, language_registry, cx);
658        diff.set_secondary_diff(secondary_diff);
659        diff.set_snapshot(update.clone(), &buffer, cx)
660    })
661    .await;
662    Ok(diff)
663}
664
665#[cfg(test)]
666mod tests {
667    use gpui::{AppContext as _, TestAppContext};
668    use language::Buffer;
669
670    use crate::Diff;
671
672    #[gpui::test]
673    async fn test_pending_diff(cx: &mut TestAppContext) {
674        let buffer = cx.new(|cx| Buffer::local("hello!", cx));
675        let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
676        buffer.update(cx, |buffer, cx| {
677            buffer.set_text("HELLO!", cx);
678        });
679        cx.run_until_parked();
680    }
681}