diff.rs

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