diff.rs

  1use anyhow::Result;
  2use buffer_diff::BufferDiff;
  3use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
  4use itertools::Itertools;
  5use language::{
  6    Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,
  7};
  8use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
  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.update(cx, |multibuffer, cx| {
 49                    let hunk_ranges = {
 50                        let buffer = buffer.read(cx);
 51                        diff.read(cx)
 52                            .snapshot(cx)
 53                            .hunks_intersecting_range(
 54                                Anchor::min_for_buffer(buffer.remote_id())
 55                                    ..Anchor::max_for_buffer(buffer.remote_id()),
 56                                buffer,
 57                            )
 58                            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
 59                            .collect::<Vec<_>>()
 60                    };
 61
 62                    multibuffer.set_excerpts_for_path(
 63                        PathKey::for_buffer(&buffer, cx),
 64                        buffer.clone(),
 65                        hunk_ranges,
 66                        excerpt_context_lines(cx),
 67                        cx,
 68                    );
 69                    multibuffer.add_diff(diff, cx);
 70                });
 71
 72                anyhow::Ok(())
 73            }
 74        });
 75
 76        Self::Finalized(FinalizedDiff {
 77            multibuffer,
 78            path,
 79            base_text,
 80            new_buffer,
 81            _update_diff: task,
 82        })
 83    }
 84
 85    pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
 86        let buffer_text_snapshot = buffer.read(cx).text_snapshot();
 87        let language = buffer.read(cx).language().cloned();
 88        let language_registry = buffer.read(cx).language_registry();
 89        let buffer_diff = cx.new(|cx| {
 90            let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
 91            diff.language_changed(language.clone(), language_registry.clone(), cx);
 92            let secondary_diff = cx.new(|cx| {
 93                // For the secondary diff buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
 94                // it. As a result, by skipping it we are potentially shaving off a lot of RSS plus we get a snappier feel for large diff
 95                // view multibuffers.
 96                BufferDiff::new_unchanged(&buffer_text_snapshot, cx)
 97            });
 98            diff.set_secondary_diff(secondary_diff);
 99            diff
100        });
101
102        let multibuffer = cx.new(|cx| {
103            let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
104            multibuffer.add_diff(buffer_diff.clone(), cx);
105            multibuffer
106        });
107
108        Self::Pending(PendingDiff {
109            multibuffer,
110            base_text: Arc::from(buffer_text_snapshot.text().as_str()),
111            _subscription: cx.observe(&buffer, |this, _, cx| {
112                if let Diff::Pending(diff) = this {
113                    diff.update(cx);
114                }
115            }),
116            new_buffer: buffer,
117            diff: buffer_diff,
118            revealed_ranges: Vec::new(),
119            update_diff: Task::ready(Ok(())),
120        })
121    }
122
123    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
124        if let Self::Pending(diff) = self {
125            diff.reveal_range(range, cx);
126        }
127    }
128
129    pub fn finalize(&mut self, cx: &mut Context<Self>) {
130        if let Self::Pending(diff) = self {
131            *self = Self::Finalized(diff.finalize(cx));
132        }
133    }
134
135    /// Returns the original text before any edits were applied.
136    pub fn base_text(&self) -> &Arc<str> {
137        match self {
138            Self::Pending(PendingDiff { base_text, .. }) => base_text,
139            Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
140        }
141    }
142
143    /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
144    pub fn buffer(&self) -> &Entity<Buffer> {
145        match self {
146            Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
147            Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
148        }
149    }
150
151    pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
152        match self {
153            Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
154            Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
155        }
156    }
157
158    pub fn to_markdown(&self, cx: &App) -> String {
159        let buffer_text = self
160            .multibuffer()
161            .read(cx)
162            .all_buffers()
163            .iter()
164            .map(|buffer| buffer.read(cx).text())
165            .join("\n");
166        let path = match self {
167            Diff::Pending(PendingDiff {
168                new_buffer: buffer, ..
169            }) => buffer
170                .read(cx)
171                .file()
172                .map(|file| file.path().display(file.path_style(cx))),
173            Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
174        };
175        format!(
176            "Diff: {}\n```\n{}\n```\n",
177            path.unwrap_or("untitled".into()),
178            buffer_text
179        )
180    }
181
182    pub fn has_revealed_range(&self, cx: &App) -> bool {
183        self.multibuffer().read(cx).paths().next().is_some()
184    }
185
186    pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
187        match self {
188            Diff::Pending(PendingDiff {
189                base_text,
190                new_buffer,
191                ..
192            }) => {
193                base_text.as_ref() != old_text
194                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
195            }
196            Diff::Finalized(FinalizedDiff {
197                base_text,
198                new_buffer,
199                ..
200            }) => {
201                base_text.as_ref() != old_text
202                    || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
203            }
204        }
205    }
206}
207
208pub struct PendingDiff {
209    multibuffer: Entity<MultiBuffer>,
210    base_text: Arc<str>,
211    new_buffer: Entity<Buffer>,
212    diff: Entity<BufferDiff>,
213    revealed_ranges: Vec<Range<Anchor>>,
214    _subscription: Subscription,
215    update_diff: Task<Result<()>>,
216}
217
218impl PendingDiff {
219    pub fn update(&mut self, cx: &mut Context<Diff>) {
220        let buffer = self.new_buffer.clone();
221        let buffer_diff = self.diff.clone();
222        let base_text = self.base_text.clone();
223        self.update_diff = cx.spawn(async move |diff, cx| {
224            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
225            let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
226            let update = buffer_diff
227                .update(cx, |diff, cx| {
228                    diff.update_diff(
229                        text_snapshot.clone(),
230                        Some(base_text.clone()),
231                        None,
232                        language,
233                        cx,
234                    )
235                })
236                .await;
237            let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
238                let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
239                let task2 = diff
240                    .secondary_diff()
241                    .unwrap()
242                    .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
243                (task1, task2)
244            });
245            task1.await;
246            task2.await;
247            diff.update(cx, |diff, cx| {
248                if let Diff::Pending(diff) = diff {
249                    diff.update_visible_ranges(cx);
250                }
251            })
252        });
253    }
254
255    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
256        self.revealed_ranges.push(range);
257        self.update_visible_ranges(cx);
258    }
259
260    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
261        let ranges = self.excerpt_ranges(cx);
262        let base_text = self.base_text.clone();
263        let new_buffer = self.new_buffer.read(cx);
264        let language_registry = new_buffer.language_registry();
265
266        let path = new_buffer
267            .file()
268            .map(|file| file.path().display(file.path_style(cx)))
269            .unwrap_or("untitled".into())
270            .into();
271        let replica_id = new_buffer.replica_id();
272
273        // Replace the buffer in the multibuffer with the snapshot
274        let buffer = cx.new(|cx| {
275            let language = self.new_buffer.read(cx).language().cloned();
276            let buffer = TextBuffer::new_normalized(
277                replica_id,
278                cx.entity_id().as_non_zero_u64().into(),
279                self.new_buffer.read(cx).line_ending(),
280                self.new_buffer.read(cx).as_rope().clone(),
281            );
282            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
283            buffer.set_language(language, cx);
284            buffer
285        });
286
287        let buffer_diff = cx.spawn({
288            let buffer = buffer.clone();
289            async move |_this, cx| {
290                build_buffer_diff(base_text, &buffer, language_registry, cx).await
291            }
292        });
293
294        let update_diff = cx.spawn(async move |this, cx| {
295            let buffer_diff = buffer_diff.await?;
296            this.update(cx, |this, cx| {
297                this.multibuffer().update(cx, |multibuffer, cx| {
298                    let path_key = PathKey::for_buffer(&buffer, cx);
299                    multibuffer.clear(cx);
300                    multibuffer.set_excerpts_for_path(
301                        path_key,
302                        buffer,
303                        ranges,
304                        excerpt_context_lines(cx),
305                        cx,
306                    );
307                    multibuffer.add_diff(buffer_diff.clone(), cx);
308                });
309
310                cx.notify();
311            })
312        });
313
314        FinalizedDiff {
315            path,
316            base_text: self.base_text.clone(),
317            multibuffer: self.multibuffer.clone(),
318            new_buffer: self.new_buffer.clone(),
319            _update_diff: update_diff,
320        }
321    }
322
323    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
324        let ranges = self.excerpt_ranges(cx);
325        self.multibuffer.update(cx, |multibuffer, cx| {
326            multibuffer.set_excerpts_for_path(
327                PathKey::for_buffer(&self.new_buffer, cx),
328                self.new_buffer.clone(),
329                ranges,
330                excerpt_context_lines(cx),
331                cx,
332            );
333            let end = multibuffer.len(cx);
334            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
335        });
336        cx.notify();
337    }
338
339    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
340        let buffer = self.new_buffer.read(cx);
341        let mut ranges = self
342            .diff
343            .read(cx)
344            .snapshot(cx)
345            .hunks_intersecting_range(
346                Anchor::min_for_buffer(buffer.remote_id())
347                    ..Anchor::max_for_buffer(buffer.remote_id()),
348                buffer,
349            )
350            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
351            .collect::<Vec<_>>();
352        ranges.extend(
353            self.revealed_ranges
354                .iter()
355                .map(|range| range.to_point(buffer)),
356        );
357        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
358
359        // Merge adjacent ranges
360        let mut ranges = ranges.into_iter().peekable();
361        let mut merged_ranges = Vec::new();
362        while let Some(mut range) = ranges.next() {
363            while let Some(next_range) = ranges.peek() {
364                if range.end >= next_range.start {
365                    range.end = range.end.max(next_range.end);
366                    ranges.next();
367                } else {
368                    break;
369                }
370            }
371
372            merged_ranges.push(range);
373        }
374        merged_ranges
375    }
376}
377
378pub struct FinalizedDiff {
379    path: String,
380    base_text: Arc<str>,
381    new_buffer: Entity<Buffer>,
382    multibuffer: Entity<MultiBuffer>,
383    _update_diff: Task<Result<()>>,
384}
385
386async fn build_buffer_diff(
387    old_text: Arc<str>,
388    buffer: &Entity<Buffer>,
389    language_registry: Option<Arc<LanguageRegistry>>,
390    cx: &mut AsyncApp,
391) -> Result<Entity<BufferDiff>> {
392    let language = cx.update(|cx| buffer.read(cx).language().cloned());
393    let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
394    let buffer = cx.update(|cx| buffer.read(cx).snapshot());
395
396    let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
397
398    let update = secondary_diff
399        .update(cx, |secondary_diff, cx| {
400            secondary_diff.update_diff(
401                text_snapshot.clone(),
402                Some(old_text),
403                Some(false),
404                language.clone(),
405                cx,
406            )
407        })
408        .await;
409
410    secondary_diff
411        .update(cx, |secondary_diff, cx| {
412            secondary_diff.set_snapshot(update.clone(), &buffer, cx)
413        })
414        .await;
415
416    let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
417    diff.update(cx, |diff, cx| {
418        diff.language_changed(language, language_registry, cx);
419        diff.set_secondary_diff(secondary_diff);
420        diff.set_snapshot(update.clone(), &buffer, cx)
421    })
422    .await;
423    Ok(diff)
424}
425
426#[cfg(test)]
427mod tests {
428    use gpui::{AppContext as _, TestAppContext};
429    use language::Buffer;
430
431    use crate::Diff;
432
433    #[gpui::test]
434    async fn test_pending_diff(cx: &mut TestAppContext) {
435        let buffer = cx.new(|cx| Buffer::local("hello!", cx));
436        let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
437        buffer.update(cx, |buffer, cx| {
438            buffer.set_text("HELLO!", cx);
439        });
440        cx.run_until_parked();
441    }
442}