diff.rs

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