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::{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| {
 39                    buffer.set_language_immediate(language.clone(), cx)
 40                })?;
 41
 42                let diff = build_buffer_diff(
 43                    old_text.unwrap_or("".into()).into(),
 44                    &buffer,
 45                    Some(language_registry.clone()),
 46                    cx,
 47                )
 48                .await?;
 49
 50                multibuffer
 51                    .update(cx, |multibuffer, cx| {
 52                        let hunk_ranges = {
 53                            let buffer = buffer.read(cx);
 54                            let diff = diff.read(cx);
 55                            diff.hunks_intersecting_range(
 56                                Anchor::min_for_buffer(buffer.remote_id())
 57                                    ..Anchor::max_for_buffer(buffer.remote_id()),
 58                                buffer,
 59                                cx,
 60                            )
 61                            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
 62                            .collect::<Vec<_>>()
 63                        };
 64
 65                        multibuffer.set_excerpts_for_path(
 66                            PathKey::for_buffer(&buffer, cx),
 67                            buffer.clone(),
 68                            hunk_ranges,
 69                            multibuffer_context_lines(cx),
 70                            cx,
 71                        );
 72                        multibuffer.add_diff(diff, cx);
 73                    })
 74                    .log_err();
 75
 76                anyhow::Ok(())
 77            }
 78        });
 79
 80        Self::Finalized(FinalizedDiff {
 81            multibuffer,
 82            path,
 83            base_text,
 84            new_buffer,
 85            _update_diff: task,
 86        })
 87    }
 88
 89    pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
 90        let buffer_text_snapshot = buffer.read(cx).text_snapshot();
 91        let base_text_snapshot = buffer.read(cx).snapshot();
 92        let base_text = base_text_snapshot.text();
 93        debug_assert_eq!(buffer_text_snapshot.text(), base_text);
 94        let buffer_diff = cx.new(|cx| {
 95            let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, base_text_snapshot);
 96            let snapshot = diff.snapshot(cx);
 97            let secondary_diff = cx.new(|cx| {
 98                let mut diff = BufferDiff::new(&buffer_text_snapshot, cx);
 99                diff.set_snapshot(snapshot, &buffer_text_snapshot, cx);
100                diff
101            });
102            diff.set_secondary_diff(secondary_diff);
103            diff
104        });
105
106        let multibuffer = cx.new(|cx| {
107            let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
108            multibuffer.add_diff(buffer_diff.clone(), cx);
109            multibuffer
110        });
111
112        Self::Pending(PendingDiff {
113            multibuffer,
114            base_text: Arc::new(base_text),
115            _subscription: cx.observe(&buffer, |this, _, cx| {
116                if let Diff::Pending(diff) = this {
117                    diff.update(cx);
118                }
119            }),
120            new_buffer: buffer,
121            diff: buffer_diff,
122            revealed_ranges: Vec::new(),
123            update_diff: Task::ready(Ok(())),
124        })
125    }
126
127    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
128        if let Self::Pending(diff) = self {
129            diff.reveal_range(range, cx);
130        }
131    }
132
133    pub fn finalize(&mut self, cx: &mut Context<Self>) {
134        if let Self::Pending(diff) = self {
135            *self = Self::Finalized(diff.finalize(cx));
136        }
137    }
138
139    pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
140        match self {
141            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
142            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
143        }
144    }
145
146    pub fn to_markdown(&self, cx: &App) -> String {
147        let buffer_text = self
148            .multibuffer()
149            .read(cx)
150            .all_buffers()
151            .iter()
152            .map(|buffer| buffer.read(cx).text())
153            .join("\n");
154        let path = match self {
155            Diff::Pending(PendingDiff {
156                new_buffer: buffer, ..
157            }) => buffer
158                .read(cx)
159                .file()
160                .map(|file| file.path().display(file.path_style(cx))),
161            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
162        };
163        format!(
164            "Diff: {}\n```\n{}\n```\n",
165            path.unwrap_or("untitled".into()),
166            buffer_text
167        )
168    }
169
170    pub fn has_revealed_range(&self, cx: &App) -> bool {
171        self.multibuffer().read(cx).excerpt_paths().next().is_some()
172    }
173
174    pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
175        match self {
176            Diff::Pending(PendingDiff {
177                base_text,
178                new_buffer,
179                ..
180            }) => {
181                base_text.as_str() != old_text
182                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
183            }
184            Diff::Finalized(FinalizedDiff {
185                base_text,
186                new_buffer,
187                ..
188            }) => {
189                base_text.as_str() != old_text
190                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
191            }
192        }
193    }
194}
195
196pub struct PendingDiff {
197    multibuffer: Entity<MultiBuffer>,
198    base_text: Arc<String>,
199    new_buffer: Entity<Buffer>,
200    diff: Entity<BufferDiff>,
201    revealed_ranges: Vec<Range<Anchor>>,
202    _subscription: Subscription,
203    update_diff: Task<Result<()>>,
204}
205
206impl PendingDiff {
207    pub fn update(&mut self, cx: &mut Context<Diff>) {
208        let buffer = self.new_buffer.clone();
209        let buffer_diff = self.diff.clone();
210        let base_text = self.base_text.clone();
211        self.update_diff = cx.spawn(async move |diff, cx| {
212            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot())?;
213            let diff_snapshot = BufferDiff::update_diff(
214                buffer_diff.clone(),
215                text_snapshot.clone(),
216                Some(base_text),
217                false,
218                false,
219                None,
220                None,
221                cx,
222            )
223            .await?;
224            buffer_diff.update(cx, |diff, cx| {
225                diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
226                diff.secondary_diff().unwrap().update(cx, |diff, cx| {
227                    diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
228                });
229            })?;
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_immediate(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 diff = self.diff.read(cx);
325        let mut ranges = diff
326            .hunks_intersecting_range(
327                Anchor::min_for_buffer(buffer.remote_id())
328                    ..Anchor::max_for_buffer(buffer.remote_id()),
329                buffer,
330                cx,
331            )
332            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
333            .collect::<Vec<_>>();
334        ranges.extend(
335            self.revealed_ranges
336                .iter()
337                .map(|range| range.to_point(buffer)),
338        );
339        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
340
341        // Merge adjacent ranges
342        let mut ranges = ranges.into_iter().peekable();
343        let mut merged_ranges = Vec::new();
344        while let Some(mut range) = ranges.next() {
345            while let Some(next_range) = ranges.peek() {
346                if range.end >= next_range.start {
347                    range.end = range.end.max(next_range.end);
348                    ranges.next();
349                } else {
350                    break;
351                }
352            }
353
354            merged_ranges.push(range);
355        }
356        merged_ranges
357    }
358}
359
360pub struct FinalizedDiff {
361    path: String,
362    base_text: Arc<String>,
363    new_buffer: Entity<Buffer>,
364    multibuffer: Entity<MultiBuffer>,
365    _update_diff: Task<Result<()>>,
366}
367
368async fn build_buffer_diff(
369    old_text: Arc<String>,
370    buffer: &Entity<Buffer>,
371    language_registry: Option<Arc<LanguageRegistry>>,
372    cx: &mut AsyncApp,
373) -> Result<Entity<BufferDiff>> {
374    let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
375
376    let old_text_rope = cx
377        .background_spawn({
378            let old_text = old_text.clone();
379            async move { Rope::from(old_text.as_str()) }
380        })
381        .await;
382    let base_buffer = cx
383        .update(|cx| {
384            Buffer::build_snapshot(
385                old_text_rope,
386                buffer.language().cloned(),
387                language_registry,
388                cx,
389            )
390        })?
391        .await;
392
393    let diff_snapshot = cx
394        .update(|cx| {
395            BufferDiffSnapshot::new_with_base_buffer(
396                buffer.text.clone(),
397                Some(old_text),
398                base_buffer,
399                cx,
400            )
401        })?
402        .await;
403
404    let secondary_diff = cx.new(|cx| {
405        let mut diff = BufferDiff::new(&buffer, cx);
406        diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
407        diff
408    })?;
409
410    cx.new(|cx| {
411        let mut diff = BufferDiff::new(&buffer.text, cx);
412        diff.set_snapshot(diff_snapshot, &buffer, cx);
413        diff.set_secondary_diff(secondary_diff);
414        diff
415    })
416}
417
418#[cfg(test)]
419mod tests {
420    use gpui::{AppContext as _, TestAppContext};
421    use language::Buffer;
422
423    use crate::Diff;
424
425    #[gpui::test]
426    async fn test_pending_diff(cx: &mut TestAppContext) {
427        let buffer = cx.new(|cx| Buffer::local("hello!", cx));
428        let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
429        buffer.update(cx, |buffer, cx| {
430            buffer.set_text("HELLO!", cx);
431        });
432        cx.run_until_parked();
433    }
434}