diff.rs

  1use anyhow::Result;
  2use buffer_diff::BufferDiff;
  3use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
  4use itertools::Itertools;
  5use language::{
  6    Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,
  7};
  8use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
  9use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
 10use util::ResultExt;
 11
 12pub enum Diff {
 13    Pending(PendingDiff),
 14    Finalized(FinalizedDiff),
 15}
 16
 17impl Diff {
 18    pub fn finalized(
 19        path: String,
 20        old_text: Option<String>,
 21        new_text: String,
 22        language_registry: Arc<LanguageRegistry>,
 23        cx: &mut Context<Self>,
 24    ) -> Self {
 25        let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
 26        let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
 27        let base_text = old_text.clone().unwrap_or(String::new()).into();
 28        let task = cx.spawn({
 29            let multibuffer = multibuffer.clone();
 30            let path = path.clone();
 31            let buffer = new_buffer.clone();
 32            async move |_, cx| {
 33                let language = language_registry
 34                    .load_language_for_file_path(Path::new(&path))
 35                    .await
 36                    .log_err();
 37
 38                buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx));
 39                buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
 40
 41                let diff = build_buffer_diff(
 42                    old_text.unwrap_or("".into()).into(),
 43                    &buffer,
 44                    Some(language_registry.clone()),
 45                    cx,
 46                )
 47                .await?;
 48
 49                multibuffer.update(cx, |multibuffer, cx| {
 50                    let hunk_ranges = {
 51                        let buffer = buffer.read(cx);
 52                        diff.read(cx)
 53                            .snapshot(cx)
 54                            .hunks_intersecting_range(
 55                                Anchor::min_for_buffer(buffer.remote_id())
 56                                    ..Anchor::max_for_buffer(buffer.remote_id()),
 57                                buffer,
 58                            )
 59                            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
 60                            .collect::<Vec<_>>()
 61                    };
 62
 63                    multibuffer.set_excerpts_for_path(
 64                        PathKey::for_buffer(&buffer, cx),
 65                        buffer.clone(),
 66                        hunk_ranges,
 67                        excerpt_context_lines(cx),
 68                        cx,
 69                    );
 70                    multibuffer.add_diff(diff, cx);
 71                });
 72
 73                anyhow::Ok(())
 74            }
 75        });
 76
 77        Self::Finalized(FinalizedDiff {
 78            multibuffer,
 79            path,
 80            base_text,
 81            new_buffer,
 82            _update_diff: task,
 83        })
 84    }
 85
 86    pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
 87        let buffer_text_snapshot = buffer.read(cx).text_snapshot();
 88        let language = buffer.read(cx).language().cloned();
 89        let language_registry = buffer.read(cx).language_registry();
 90        let buffer_diff = cx.new(|cx| {
 91            let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
 92            diff.language_changed(language.clone(), language_registry.clone(), cx);
 93            let secondary_diff = cx.new(|cx| {
 94                // For the secondary diff buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
 95                // 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
 96                // view multibuffers.
 97                BufferDiff::new_unchanged(&buffer_text_snapshot, cx)
 98            });
 99            diff.set_secondary_diff(secondary_diff);
100            diff
101        });
102
103        let multibuffer = cx.new(|cx| {
104            let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
105            multibuffer.add_diff(buffer_diff.clone(), cx);
106            multibuffer
107        });
108
109        Self::Pending(PendingDiff {
110            multibuffer,
111            base_text: Arc::from(buffer_text_snapshot.text().as_str()),
112            _subscription: cx.observe(&buffer, |this, _, cx| {
113                if let Diff::Pending(diff) = this {
114                    diff.update(cx);
115                }
116            }),
117            new_buffer: buffer,
118            diff: buffer_diff,
119            revealed_ranges: Vec::new(),
120            update_diff: Task::ready(Ok(())),
121        })
122    }
123
124    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
125        if let Self::Pending(diff) = self {
126            diff.reveal_range(range, cx);
127        }
128    }
129
130    pub fn finalize(&mut self, cx: &mut Context<Self>) {
131        if let Self::Pending(diff) = self {
132            *self = Self::Finalized(diff.finalize(cx));
133        }
134    }
135
136    /// Returns the original text before any edits were applied.
137    pub fn base_text(&self) -> &Arc<str> {
138        match self {
139            Self::Pending(PendingDiff { base_text, .. }) => base_text,
140            Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
141        }
142    }
143
144    /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
145    pub fn buffer(&self) -> &Entity<Buffer> {
146        match self {
147            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
148            Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
149        }
150    }
151
152    pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
153        match self {
154            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
155            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
156        }
157    }
158
159    pub fn to_markdown(&self, cx: &App) -> String {
160        let buffer_text = self
161            .multibuffer()
162            .read(cx)
163            .all_buffers()
164            .iter()
165            .map(|buffer| buffer.read(cx).text())
166            .join("\n");
167        let path = match self {
168            Diff::Pending(PendingDiff {
169                new_buffer: buffer, ..
170            }) => buffer
171                .read(cx)
172                .file()
173                .map(|file| file.path().display(file.path_style(cx))),
174            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
175        };
176        format!(
177            "Diff: {}\n```\n{}\n```\n",
178            path.unwrap_or("untitled".into()),
179            buffer_text
180        )
181    }
182
183    pub fn has_revealed_range(&self, cx: &App) -> bool {
184        self.multibuffer().read(cx).paths().next().is_some()
185    }
186
187    pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
188        match self {
189            Diff::Pending(PendingDiff {
190                base_text,
191                new_buffer,
192                ..
193            }) => {
194                base_text.as_ref() != old_text
195                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
196            }
197            Diff::Finalized(FinalizedDiff {
198                base_text,
199                new_buffer,
200                ..
201            }) => {
202                base_text.as_ref() != old_text
203                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
204            }
205        }
206    }
207}
208
209pub struct PendingDiff {
210    multibuffer: Entity<MultiBuffer>,
211    base_text: Arc<str>,
212    new_buffer: Entity<Buffer>,
213    diff: Entity<BufferDiff>,
214    revealed_ranges: Vec<Range<Anchor>>,
215    _subscription: Subscription,
216    update_diff: Task<Result<()>>,
217}
218
219impl PendingDiff {
220    pub fn update(&mut self, cx: &mut Context<Diff>) {
221        let buffer = self.new_buffer.clone();
222        let buffer_diff = self.diff.clone();
223        let base_text = self.base_text.clone();
224        self.update_diff = cx.spawn(async move |diff, cx| {
225            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
226            let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
227            let update = buffer_diff
228                .update(cx, |diff, cx| {
229                    diff.update_diff(
230                        text_snapshot.clone(),
231                        Some(base_text.clone()),
232                        None,
233                        language,
234                        cx,
235                    )
236                })
237                .await;
238            let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
239                let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
240                let task2 = diff
241                    .secondary_diff()
242                    .unwrap()
243                    .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
244                (task1, task2)
245            });
246            task1.await;
247            task2.await;
248            diff.update(cx, |diff, cx| {
249                if let Diff::Pending(diff) = diff {
250                    diff.update_visible_ranges(cx);
251                }
252            })
253        });
254    }
255
256    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
257        self.revealed_ranges.push(range);
258        self.update_visible_ranges(cx);
259    }
260
261    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
262        let ranges = self.excerpt_ranges(cx);
263        let base_text = self.base_text.clone();
264        let new_buffer = self.new_buffer.read(cx);
265        let language_registry = new_buffer.language_registry();
266
267        let path = new_buffer
268            .file()
269            .map(|file| file.path().display(file.path_style(cx)))
270            .unwrap_or("untitled".into())
271            .into();
272        let replica_id = new_buffer.replica_id();
273
274        // Replace the buffer in the multibuffer with the snapshot
275        let buffer = cx.new(|cx| {
276            let language = self.new_buffer.read(cx).language().cloned();
277            let buffer = TextBuffer::new_normalized(
278                replica_id,
279                cx.entity_id().as_non_zero_u64().into(),
280                self.new_buffer.read(cx).line_ending(),
281                self.new_buffer.read(cx).as_rope().clone(),
282            );
283            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
284            buffer.set_language(language, cx);
285            buffer
286        });
287
288        let buffer_diff = cx.spawn({
289            let buffer = buffer.clone();
290            async move |_this, cx| {
291                buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
292                build_buffer_diff(base_text, &buffer, language_registry, cx).await
293            }
294        });
295
296        let update_diff = cx.spawn(async move |this, cx| {
297            let buffer_diff = buffer_diff.await?;
298            this.update(cx, |this, cx| {
299                this.multibuffer().update(cx, |multibuffer, cx| {
300                    let path_key = PathKey::for_buffer(&buffer, cx);
301                    multibuffer.clear(cx);
302                    multibuffer.set_excerpts_for_path(
303                        path_key,
304                        buffer,
305                        ranges,
306                        excerpt_context_lines(cx),
307                        cx,
308                    );
309                    multibuffer.add_diff(buffer_diff.clone(), cx);
310                });
311
312                cx.notify();
313            })
314        });
315
316        FinalizedDiff {
317            path,
318            base_text: self.base_text.clone(),
319            multibuffer: self.multibuffer.clone(),
320            new_buffer: self.new_buffer.clone(),
321            _update_diff: update_diff,
322        }
323    }
324
325    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
326        let ranges = self.excerpt_ranges(cx);
327        self.multibuffer.update(cx, |multibuffer, cx| {
328            multibuffer.set_excerpts_for_path(
329                PathKey::for_buffer(&self.new_buffer, cx),
330                self.new_buffer.clone(),
331                ranges,
332                excerpt_context_lines(cx),
333                cx,
334            );
335            let end = multibuffer.len(cx);
336            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
337        });
338        cx.notify();
339    }
340
341    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
342        let buffer = self.new_buffer.read(cx);
343        let mut ranges = self
344            .diff
345            .read(cx)
346            .snapshot(cx)
347            .hunks_intersecting_range(
348                Anchor::min_for_buffer(buffer.remote_id())
349                    ..Anchor::max_for_buffer(buffer.remote_id()),
350                buffer,
351            )
352            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
353            .collect::<Vec<_>>();
354        ranges.extend(
355            self.revealed_ranges
356                .iter()
357                .map(|range| range.to_point(buffer)),
358        );
359        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
360
361        // Merge adjacent ranges
362        let mut ranges = ranges.into_iter().peekable();
363        let mut merged_ranges = Vec::new();
364        while let Some(mut range) = ranges.next() {
365            while let Some(next_range) = ranges.peek() {
366                if range.end >= next_range.start {
367                    range.end = range.end.max(next_range.end);
368                    ranges.next();
369                } else {
370                    break;
371                }
372            }
373
374            merged_ranges.push(range);
375        }
376        merged_ranges
377    }
378}
379
380pub struct FinalizedDiff {
381    path: String,
382    base_text: Arc<str>,
383    new_buffer: Entity<Buffer>,
384    multibuffer: Entity<MultiBuffer>,
385    _update_diff: Task<Result<()>>,
386}
387
388async fn build_buffer_diff(
389    old_text: Arc<str>,
390    buffer: &Entity<Buffer>,
391    language_registry: Option<Arc<LanguageRegistry>>,
392    cx: &mut AsyncApp,
393) -> Result<Entity<BufferDiff>> {
394    let language = cx.update(|cx| buffer.read(cx).language().cloned());
395    let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
396    let buffer = cx.update(|cx| buffer.read(cx).snapshot());
397
398    let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
399
400    let update = secondary_diff
401        .update(cx, |secondary_diff, cx| {
402            secondary_diff.update_diff(
403                text_snapshot.clone(),
404                Some(old_text),
405                Some(false),
406                language.clone(),
407                cx,
408            )
409        })
410        .await;
411
412    secondary_diff
413        .update(cx, |secondary_diff, cx| {
414            secondary_diff.set_snapshot(update.clone(), &buffer, cx)
415        })
416        .await;
417
418    let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
419    diff.update(cx, |diff, cx| {
420        diff.language_changed(language, language_registry, cx);
421        diff.set_secondary_diff(secondary_diff);
422        diff.set_snapshot(update.clone(), &buffer, cx)
423    })
424    .await;
425    Ok(diff)
426}
427
428#[cfg(test)]
429mod tests {
430    use gpui::{AppContext as _, TestAppContext};
431    use language::Buffer;
432
433    use crate::Diff;
434
435    #[gpui::test]
436    async fn test_pending_diff(cx: &mut TestAppContext) {
437        let buffer = cx.new(|cx| Buffer::local("hello!", cx));
438        let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
439        buffer.update(cx, |buffer, cx| {
440            buffer.set_text("HELLO!", cx);
441        });
442        cx.run_until_parked();
443    }
444}