diff.rs

  1use anyhow::Result;
  2use buffer_diff::{BufferDiff, BufferDiffSnapshot};
  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, Rope, TextBuffer,
  8};
  9use std::{
 10    cmp::Reverse,
 11    ops::Range,
 12    path::{Path, PathBuf},
 13    sync::Arc,
 14};
 15use util::ResultExt;
 16
 17pub enum Diff {
 18    Pending(PendingDiff),
 19    Finalized(FinalizedDiff),
 20}
 21
 22impl Diff {
 23    pub fn finalized(
 24        path: PathBuf,
 25        old_text: Option<String>,
 26        new_text: String,
 27        language_registry: Arc<LanguageRegistry>,
 28        cx: &mut Context<Self>,
 29    ) -> Self {
 30        let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
 31        let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
 32        let base_text = old_text.clone().unwrap_or(String::new()).into();
 33        let task = cx.spawn({
 34            let multibuffer = multibuffer.clone();
 35            let path = path.clone();
 36            let buffer = new_buffer.clone();
 37            async move |_, cx| {
 38                let language = language_registry
 39                    .language_for_file_path(&path)
 40                    .await
 41                    .log_err();
 42
 43                buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx))?;
 44
 45                let diff = build_buffer_diff(
 46                    old_text.unwrap_or("".into()).into(),
 47                    &buffer,
 48                    Some(language_registry.clone()),
 49                    cx,
 50                )
 51                .await?;
 52
 53                multibuffer
 54                    .update(cx, |multibuffer, cx| {
 55                        let hunk_ranges = {
 56                            let buffer = buffer.read(cx);
 57                            let diff = diff.read(cx);
 58                            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
 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 base_text_snapshot = buffer.read(cx).snapshot();
 90        let base_text = base_text_snapshot.text();
 91        debug_assert_eq!(buffer_text_snapshot.text(), base_text);
 92        let buffer_diff = cx.new(|cx| {
 93            let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, base_text_snapshot);
 94            let snapshot = diff.snapshot(cx);
 95            let secondary_diff = cx.new(|cx| {
 96                let mut diff = BufferDiff::new(&buffer_text_snapshot, cx);
 97                diff.set_snapshot(snapshot, &buffer_text_snapshot, cx);
 98                diff
 99            });
100            diff.set_secondary_diff(secondary_diff);
101            diff
102        });
103
104        let multibuffer = cx.new(|cx| {
105            let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
106            multibuffer.add_diff(buffer_diff.clone(), cx);
107            multibuffer
108        });
109
110        Self::Pending(PendingDiff {
111            multibuffer,
112            base_text: Arc::new(base_text),
113            _subscription: cx.observe(&buffer, |this, _, cx| {
114                if let Diff::Pending(diff) = this {
115                    diff.update(cx);
116                }
117            }),
118            new_buffer: buffer,
119            diff: buffer_diff,
120            revealed_ranges: Vec::new(),
121            update_diff: Task::ready(Ok(())),
122        })
123    }
124
125    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
126        if let Self::Pending(diff) = self {
127            diff.reveal_range(range, cx);
128        }
129    }
130
131    pub fn finalize(&mut self, cx: &mut Context<Self>) {
132        if let Self::Pending(diff) = self {
133            *self = Self::Finalized(diff.finalize(cx));
134        }
135    }
136
137    pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
138        match self {
139            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
140            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
141        }
142    }
143
144    pub fn to_markdown(&self, cx: &App) -> String {
145        let buffer_text = self
146            .multibuffer()
147            .read(cx)
148            .all_buffers()
149            .iter()
150            .map(|buffer| buffer.read(cx).text())
151            .join("\n");
152        let path = match self {
153            Diff::Pending(PendingDiff {
154                new_buffer: buffer, ..
155            }) => buffer.read(cx).file().map(|file| file.path().as_ref()),
156            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()),
157        };
158        format!(
159            "Diff: {}\n```\n{}\n```\n",
160            path.unwrap_or(Path::new("untitled")).display(),
161            buffer_text
162        )
163    }
164
165    pub fn has_revealed_range(&self, cx: &App) -> bool {
166        self.multibuffer().read(cx).excerpt_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_str() != 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_str() != 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<String>,
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 diff_snapshot = BufferDiff::update_diff(
209                buffer_diff.clone(),
210                text_snapshot.clone(),
211                Some(base_text),
212                false,
213                false,
214                None,
215                None,
216                cx,
217            )
218            .await?;
219            buffer_diff.update(cx, |diff, cx| {
220                diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
221                diff.secondary_diff().unwrap().update(cx, |diff, cx| {
222                    diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
223                });
224            })?;
225            diff.update(cx, |diff, cx| {
226                if let Diff::Pending(diff) = diff {
227                    diff.update_visible_ranges(cx);
228                }
229            })
230        });
231    }
232
233    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
234        self.revealed_ranges.push(range);
235        self.update_visible_ranges(cx);
236    }
237
238    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
239        let ranges = self.excerpt_ranges(cx);
240        let base_text = self.base_text.clone();
241        let language_registry = self.new_buffer.read(cx).language_registry();
242
243        let path = self
244            .new_buffer
245            .read(cx)
246            .file()
247            .map(|file| file.path().as_ref())
248            .unwrap_or(Path::new("untitled"))
249            .into();
250
251        // Replace the buffer in the multibuffer with the snapshot
252        let buffer = cx.new(|cx| {
253            let language = self.new_buffer.read(cx).language().cloned();
254            let buffer = TextBuffer::new_normalized(
255                0,
256                cx.entity_id().as_non_zero_u64().into(),
257                self.new_buffer.read(cx).line_ending(),
258                self.new_buffer.read(cx).as_rope().clone(),
259            );
260            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
261            buffer.set_language(language, cx);
262            buffer
263        });
264
265        let buffer_diff = cx.spawn({
266            let buffer = buffer.clone();
267            async move |_this, cx| {
268                build_buffer_diff(base_text, &buffer, language_registry, cx).await
269            }
270        });
271
272        let update_diff = cx.spawn(async move |this, cx| {
273            let buffer_diff = buffer_diff.await?;
274            this.update(cx, |this, cx| {
275                this.multibuffer().update(cx, |multibuffer, cx| {
276                    let path_key = PathKey::for_buffer(&buffer, cx);
277                    multibuffer.clear(cx);
278                    multibuffer.set_excerpts_for_path(
279                        path_key,
280                        buffer,
281                        ranges,
282                        multibuffer_context_lines(cx),
283                        cx,
284                    );
285                    multibuffer.add_diff(buffer_diff.clone(), cx);
286                });
287
288                cx.notify();
289            })
290        });
291
292        FinalizedDiff {
293            path,
294            base_text: self.base_text.clone(),
295            multibuffer: self.multibuffer.clone(),
296            new_buffer: self.new_buffer.clone(),
297            _update_diff: update_diff,
298        }
299    }
300
301    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
302        let ranges = self.excerpt_ranges(cx);
303        self.multibuffer.update(cx, |multibuffer, cx| {
304            multibuffer.set_excerpts_for_path(
305                PathKey::for_buffer(&self.new_buffer, cx),
306                self.new_buffer.clone(),
307                ranges,
308                multibuffer_context_lines(cx),
309                cx,
310            );
311            let end = multibuffer.len(cx);
312            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
313        });
314        cx.notify();
315    }
316
317    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
318        let buffer = self.new_buffer.read(cx);
319        let diff = self.diff.read(cx);
320        let mut ranges = diff
321            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
322            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
323            .collect::<Vec<_>>();
324        ranges.extend(
325            self.revealed_ranges
326                .iter()
327                .map(|range| range.to_point(buffer)),
328        );
329        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
330
331        // Merge adjacent ranges
332        let mut ranges = ranges.into_iter().peekable();
333        let mut merged_ranges = Vec::new();
334        while let Some(mut range) = ranges.next() {
335            while let Some(next_range) = ranges.peek() {
336                if range.end >= next_range.start {
337                    range.end = range.end.max(next_range.end);
338                    ranges.next();
339                } else {
340                    break;
341                }
342            }
343
344            merged_ranges.push(range);
345        }
346        merged_ranges
347    }
348}
349
350pub struct FinalizedDiff {
351    path: PathBuf,
352    base_text: Arc<String>,
353    new_buffer: Entity<Buffer>,
354    multibuffer: Entity<MultiBuffer>,
355    _update_diff: Task<Result<()>>,
356}
357
358async fn build_buffer_diff(
359    old_text: Arc<String>,
360    buffer: &Entity<Buffer>,
361    language_registry: Option<Arc<LanguageRegistry>>,
362    cx: &mut AsyncApp,
363) -> Result<Entity<BufferDiff>> {
364    let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
365
366    let old_text_rope = cx
367        .background_spawn({
368            let old_text = old_text.clone();
369            async move { Rope::from(old_text.as_str()) }
370        })
371        .await;
372    let base_buffer = cx
373        .update(|cx| {
374            Buffer::build_snapshot(
375                old_text_rope,
376                buffer.language().cloned(),
377                language_registry,
378                cx,
379            )
380        })?
381        .await;
382
383    let diff_snapshot = cx
384        .update(|cx| {
385            BufferDiffSnapshot::new_with_base_buffer(
386                buffer.text.clone(),
387                Some(old_text),
388                base_buffer,
389                cx,
390            )
391        })?
392        .await;
393
394    let secondary_diff = cx.new(|cx| {
395        let mut diff = BufferDiff::new(&buffer, cx);
396        diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
397        diff
398    })?;
399
400    cx.new(|cx| {
401        let mut diff = BufferDiff::new(&buffer.text, cx);
402        diff.set_snapshot(diff_snapshot, &buffer, cx);
403        diff.set_secondary_diff(secondary_diff);
404        diff
405    })
406}
407
408#[cfg(test)]
409mod tests {
410    use gpui::{AppContext as _, TestAppContext};
411    use language::Buffer;
412
413    use crate::Diff;
414
415    #[gpui::test]
416    async fn test_pending_diff(cx: &mut TestAppContext) {
417        let buffer = cx.new(|cx| Buffer::local("hello!", cx));
418        let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
419        buffer.update(cx, |buffer, cx| {
420            buffer.set_text("HELLO!", cx);
421        });
422        cx.run_until_parked();
423    }
424}