diff.rs

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