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