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