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 file_path(&self, cx: &App) -> Option<String> {
153        match self {
154            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer
155                .read(cx)
156                .file()
157                .map(|file| file.full_path(cx).to_string_lossy().into_owned()),
158            Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()),
159        }
160    }
161
162    pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
163        match self {
164            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
165            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
166        }
167    }
168
169    pub fn to_markdown(&self, cx: &App) -> String {
170        let buffer_text = self
171            .multibuffer()
172            .read(cx)
173            .all_buffers()
174            .iter()
175            .map(|buffer| buffer.read(cx).text())
176            .join("\n");
177        let path = match self {
178            Diff::Pending(PendingDiff {
179                new_buffer: buffer, ..
180            }) => buffer
181                .read(cx)
182                .file()
183                .map(|file| file.path().display(file.path_style(cx))),
184            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
185        };
186        format!(
187            "Diff: {}\n```\n{}\n```\n",
188            path.unwrap_or("untitled".into()),
189            buffer_text
190        )
191    }
192
193    pub fn has_revealed_range(&self, cx: &App) -> bool {
194        self.multibuffer().read(cx).paths().next().is_some()
195    }
196
197    pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
198        match self {
199            Diff::Pending(PendingDiff {
200                base_text,
201                new_buffer,
202                ..
203            }) => {
204                base_text.as_ref() != old_text
205                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
206            }
207            Diff::Finalized(FinalizedDiff {
208                base_text,
209                new_buffer,
210                ..
211            }) => {
212                base_text.as_ref() != old_text
213                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
214            }
215        }
216    }
217}
218
219pub struct PendingDiff {
220    multibuffer: Entity<MultiBuffer>,
221    base_text: Arc<str>,
222    new_buffer: Entity<Buffer>,
223    diff: Entity<BufferDiff>,
224    revealed_ranges: Vec<Range<Anchor>>,
225    _subscription: Subscription,
226    update_diff: Task<Result<()>>,
227}
228
229impl PendingDiff {
230    pub fn update(&mut self, cx: &mut Context<Diff>) {
231        let buffer = self.new_buffer.clone();
232        let buffer_diff = self.diff.clone();
233        let base_text = self.base_text.clone();
234        self.update_diff = cx.spawn(async move |diff, cx| {
235            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
236            let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
237            let update = buffer_diff
238                .update(cx, |diff, cx| {
239                    diff.update_diff(
240                        text_snapshot.clone(),
241                        Some(base_text.clone()),
242                        None,
243                        language,
244                        cx,
245                    )
246                })
247                .await;
248            let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
249                let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
250                let task2 = diff
251                    .secondary_diff()
252                    .unwrap()
253                    .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
254                (task1, task2)
255            });
256            task1.await;
257            task2.await;
258            diff.update(cx, |diff, cx| {
259                if let Diff::Pending(diff) = diff {
260                    diff.update_visible_ranges(cx);
261                }
262            })
263        });
264    }
265
266    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
267        self.revealed_ranges.push(range);
268        self.update_visible_ranges(cx);
269    }
270
271    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
272        let ranges = self.excerpt_ranges(cx);
273        let base_text = self.base_text.clone();
274        let new_buffer = self.new_buffer.read(cx);
275        let language_registry = new_buffer.language_registry();
276
277        let path = new_buffer
278            .file()
279            .map(|file| file.path().display(file.path_style(cx)))
280            .unwrap_or("untitled".into())
281            .into();
282        let replica_id = new_buffer.replica_id();
283
284        // Replace the buffer in the multibuffer with the snapshot
285        let buffer = cx.new(|cx| {
286            let language = self.new_buffer.read(cx).language().cloned();
287            let buffer = TextBuffer::new_normalized(
288                replica_id,
289                cx.entity_id().as_non_zero_u64().into(),
290                self.new_buffer.read(cx).line_ending(),
291                self.new_buffer.read(cx).as_rope().clone(),
292            );
293            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
294            buffer.set_language(language, cx);
295            buffer
296        });
297
298        let buffer_diff = cx.spawn({
299            let buffer = buffer.clone();
300            async move |_this, cx| {
301                buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
302                build_buffer_diff(base_text, &buffer, language_registry, cx).await
303            }
304        });
305
306        let update_diff = cx.spawn(async move |this, cx| {
307            let buffer_diff = buffer_diff.await?;
308            this.update(cx, |this, cx| {
309                this.multibuffer().update(cx, |multibuffer, cx| {
310                    let path_key = PathKey::for_buffer(&buffer, cx);
311                    multibuffer.clear(cx);
312                    multibuffer.set_excerpts_for_path(
313                        path_key,
314                        buffer,
315                        ranges,
316                        excerpt_context_lines(cx),
317                        cx,
318                    );
319                    multibuffer.add_diff(buffer_diff.clone(), cx);
320                });
321
322                cx.notify();
323            })
324        });
325
326        FinalizedDiff {
327            path,
328            base_text: self.base_text.clone(),
329            multibuffer: self.multibuffer.clone(),
330            new_buffer: self.new_buffer.clone(),
331            _update_diff: update_diff,
332        }
333    }
334
335    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
336        let ranges = self.excerpt_ranges(cx);
337        self.multibuffer.update(cx, |multibuffer, cx| {
338            multibuffer.set_excerpts_for_path(
339                PathKey::for_buffer(&self.new_buffer, cx),
340                self.new_buffer.clone(),
341                ranges,
342                excerpt_context_lines(cx),
343                cx,
344            );
345            let end = multibuffer.len(cx);
346            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
347        });
348        cx.notify();
349    }
350
351    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
352        let buffer = self.new_buffer.read(cx);
353        let mut ranges = self
354            .diff
355            .read(cx)
356            .snapshot(cx)
357            .hunks_intersecting_range(
358                Anchor::min_for_buffer(buffer.remote_id())
359                    ..Anchor::max_for_buffer(buffer.remote_id()),
360                buffer,
361            )
362            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
363            .collect::<Vec<_>>();
364        ranges.extend(
365            self.revealed_ranges
366                .iter()
367                .map(|range| range.to_point(buffer)),
368        );
369        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
370
371        // Merge adjacent ranges
372        let mut ranges = ranges.into_iter().peekable();
373        let mut merged_ranges = Vec::new();
374        while let Some(mut range) = ranges.next() {
375            while let Some(next_range) = ranges.peek() {
376                if range.end >= next_range.start {
377                    range.end = range.end.max(next_range.end);
378                    ranges.next();
379                } else {
380                    break;
381                }
382            }
383
384            merged_ranges.push(range);
385        }
386        merged_ranges
387    }
388}
389
390pub struct FinalizedDiff {
391    path: String,
392    base_text: Arc<str>,
393    new_buffer: Entity<Buffer>,
394    multibuffer: Entity<MultiBuffer>,
395    _update_diff: Task<Result<()>>,
396}
397
398async fn build_buffer_diff(
399    old_text: Arc<str>,
400    buffer: &Entity<Buffer>,
401    language_registry: Option<Arc<LanguageRegistry>>,
402    cx: &mut AsyncApp,
403) -> Result<Entity<BufferDiff>> {
404    let language = cx.update(|cx| buffer.read(cx).language().cloned());
405    let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
406    let buffer = cx.update(|cx| buffer.read(cx).snapshot());
407
408    let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
409
410    let update = secondary_diff
411        .update(cx, |secondary_diff, cx| {
412            secondary_diff.update_diff(
413                text_snapshot.clone(),
414                Some(old_text),
415                Some(false),
416                language.clone(),
417                cx,
418            )
419        })
420        .await;
421
422    secondary_diff
423        .update(cx, |secondary_diff, cx| {
424            secondary_diff.set_snapshot(update.clone(), &buffer, cx)
425        })
426        .await;
427
428    let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
429    diff.update(cx, |diff, cx| {
430        diff.language_changed(language, language_registry, cx);
431        diff.set_secondary_diff(secondary_diff);
432        diff.set_snapshot(update.clone(), &buffer, cx)
433    })
434    .await;
435    Ok(diff)
436}
437
438#[cfg(test)]
439mod tests {
440    use gpui::{AppContext as _, TestAppContext};
441    use language::Buffer;
442
443    use crate::Diff;
444
445    #[gpui::test]
446    async fn test_pending_diff(cx: &mut TestAppContext) {
447        let buffer = cx.new(|cx| Buffer::local("hello!", cx));
448        let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
449        buffer.update(cx, |buffer, cx| {
450            buffer.set_text("HELLO!", cx);
451        });
452        cx.run_until_parked();
453    }
454}