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        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                            editor::DEFAULT_MULTIBUFFER_CONTEXT,
 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_snapshot = buffer.read(cx).snapshot();
 89        let base_text = buffer_snapshot.text();
 90        let language_registry = buffer.read(cx).language_registry();
 91        let text_snapshot = buffer.read(cx).text_snapshot();
 92        let buffer_diff = cx.new(|cx| {
 93            let mut diff = BufferDiff::new(&text_snapshot, cx);
 94            let _ = diff.set_base_text(
 95                buffer_snapshot.clone(),
 96                language_registry,
 97                text_snapshot,
 98                cx,
 99            );
100            let snapshot = diff.snapshot(cx);
101
102            let secondary_diff = cx.new(|cx| {
103                let mut diff = BufferDiff::new(&buffer_snapshot, cx);
104                diff.set_snapshot(snapshot, &buffer_snapshot, cx);
105                diff
106            });
107            diff.set_secondary_diff(secondary_diff);
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            new_buffer: 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 {
162                new_buffer: buffer, ..
163            }) => buffer.read(cx).file().map(|file| file.path().as_ref()),
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    pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
178        match self {
179            Diff::Pending(PendingDiff {
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            Diff::Finalized(FinalizedDiff {
188                base_text,
189                new_buffer,
190                ..
191            }) => {
192                base_text.as_str() != old_text
193                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
194            }
195        }
196    }
197}
198
199pub struct PendingDiff {
200    multibuffer: Entity<MultiBuffer>,
201    base_text: Arc<String>,
202    new_buffer: Entity<Buffer>,
203    diff: Entity<BufferDiff>,
204    revealed_ranges: Vec<Range<Anchor>>,
205    _subscription: Subscription,
206    update_diff: Task<Result<()>>,
207}
208
209impl PendingDiff {
210    pub fn update(&mut self, cx: &mut Context<Diff>) {
211        let buffer = self.new_buffer.clone();
212        let buffer_diff = self.diff.clone();
213        let base_text = self.base_text.clone();
214        self.update_diff = cx.spawn(async move |diff, cx| {
215            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot())?;
216            let diff_snapshot = BufferDiff::update_diff(
217                buffer_diff.clone(),
218                text_snapshot.clone(),
219                Some(base_text),
220                false,
221                false,
222                None,
223                None,
224                cx,
225            )
226            .await?;
227            buffer_diff.update(cx, |diff, cx| {
228                diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
229                diff.secondary_diff().unwrap().update(cx, |diff, cx| {
230                    diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
231                });
232            })?;
233            diff.update(cx, |diff, cx| {
234                if let Diff::Pending(diff) = diff {
235                    diff.update_visible_ranges(cx);
236                }
237            })
238        });
239    }
240
241    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
242        self.revealed_ranges.push(range);
243        self.update_visible_ranges(cx);
244    }
245
246    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
247        let ranges = self.excerpt_ranges(cx);
248        let base_text = self.base_text.clone();
249        let language_registry = self.new_buffer.read(cx).language_registry();
250
251        let path = self
252            .new_buffer
253            .read(cx)
254            .file()
255            .map(|file| file.path().as_ref())
256            .unwrap_or(Path::new("untitled"))
257            .into();
258
259        // Replace the buffer in the multibuffer with the snapshot
260        let buffer = cx.new(|cx| {
261            let language = self.new_buffer.read(cx).language().cloned();
262            let buffer = TextBuffer::new_normalized(
263                0,
264                cx.entity_id().as_non_zero_u64().into(),
265                self.new_buffer.read(cx).line_ending(),
266                self.new_buffer.read(cx).as_rope().clone(),
267            );
268            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
269            buffer.set_language(language, cx);
270            buffer
271        });
272
273        let buffer_diff = cx.spawn({
274            let buffer = buffer.clone();
275            async move |_this, cx| {
276                build_buffer_diff(base_text, &buffer, language_registry, cx).await
277            }
278        });
279
280        let update_diff = cx.spawn(async move |this, cx| {
281            let buffer_diff = buffer_diff.await?;
282            this.update(cx, |this, cx| {
283                this.multibuffer().update(cx, |multibuffer, cx| {
284                    let path_key = PathKey::for_buffer(&buffer, cx);
285                    multibuffer.clear(cx);
286                    multibuffer.set_excerpts_for_path(
287                        path_key,
288                        buffer,
289                        ranges,
290                        editor::DEFAULT_MULTIBUFFER_CONTEXT,
291                        cx,
292                    );
293                    multibuffer.add_diff(buffer_diff.clone(), cx);
294                });
295
296                cx.notify();
297            })
298        });
299
300        FinalizedDiff {
301            path,
302            base_text: self.base_text.clone(),
303            multibuffer: self.multibuffer.clone(),
304            new_buffer: self.new_buffer.clone(),
305            _update_diff: update_diff,
306        }
307    }
308
309    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
310        let ranges = self.excerpt_ranges(cx);
311        self.multibuffer.update(cx, |multibuffer, cx| {
312            multibuffer.set_excerpts_for_path(
313                PathKey::for_buffer(&self.new_buffer, cx),
314                self.new_buffer.clone(),
315                ranges,
316                editor::DEFAULT_MULTIBUFFER_CONTEXT,
317                cx,
318            );
319            let end = multibuffer.len(cx);
320            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
321        });
322        cx.notify();
323    }
324
325    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
326        let buffer = self.new_buffer.read(cx);
327        let diff = self.diff.read(cx);
328        let mut ranges = diff
329            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
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: PathBuf,
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}