diff.rs

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