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    pub fn has_revealed_range(&self, cx: &App) -> bool {
179        self.multibuffer().read(cx).excerpt_paths().next().is_some()
180    }
181}
182
183pub struct PendingDiff {
184    multibuffer: Entity<MultiBuffer>,
185    base_text: Arc<String>,
186    buffer: Entity<Buffer>,
187    diff: Entity<BufferDiff>,
188    revealed_ranges: Vec<Range<Anchor>>,
189    _subscription: Subscription,
190    update_diff: Task<Result<()>>,
191}
192
193impl PendingDiff {
194    pub fn update(&mut self, cx: &mut Context<Diff>) {
195        let buffer = self.buffer.clone();
196        let buffer_diff = self.diff.clone();
197        let base_text = self.base_text.clone();
198        self.update_diff = cx.spawn(async move |diff, cx| {
199            let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot())?;
200            let diff_snapshot = BufferDiff::update_diff(
201                buffer_diff.clone(),
202                text_snapshot.clone(),
203                Some(base_text),
204                false,
205                false,
206                None,
207                None,
208                cx,
209            )
210            .await?;
211            buffer_diff.update(cx, |diff, cx| {
212                diff.set_snapshot(diff_snapshot, &text_snapshot, cx)
213            })?;
214            diff.update(cx, |diff, cx| {
215                if let Diff::Pending(diff) = diff {
216                    diff.update_visible_ranges(cx);
217                }
218            })
219        });
220    }
221
222    pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
223        self.revealed_ranges.push(range);
224        self.update_visible_ranges(cx);
225    }
226
227    fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
228        let ranges = self.excerpt_ranges(cx);
229        let base_text = self.base_text.clone();
230        let language_registry = self.buffer.read(cx).language_registry().clone();
231
232        let path = self
233            .buffer
234            .read(cx)
235            .file()
236            .map(|file| file.path().as_ref())
237            .unwrap_or(Path::new("untitled"))
238            .into();
239
240        // Replace the buffer in the multibuffer with the snapshot
241        let buffer = cx.new(|cx| {
242            let language = self.buffer.read(cx).language().cloned();
243            let buffer = TextBuffer::new_normalized(
244                0,
245                cx.entity_id().as_non_zero_u64().into(),
246                self.buffer.read(cx).line_ending(),
247                self.buffer.read(cx).as_rope().clone(),
248            );
249            let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
250            buffer.set_language(language, cx);
251            buffer
252        });
253
254        let buffer_diff = cx.spawn({
255            let buffer = buffer.clone();
256            let language_registry = language_registry.clone();
257            async move |_this, cx| {
258                build_buffer_diff(base_text, &buffer, language_registry, cx).await
259            }
260        });
261
262        let update_diff = cx.spawn(async move |this, cx| {
263            let buffer_diff = buffer_diff.await?;
264            this.update(cx, |this, cx| {
265                this.multibuffer().update(cx, |multibuffer, cx| {
266                    let path_key = PathKey::for_buffer(&buffer, cx);
267                    multibuffer.clear(cx);
268                    multibuffer.set_excerpts_for_path(
269                        path_key,
270                        buffer,
271                        ranges,
272                        editor::DEFAULT_MULTIBUFFER_CONTEXT,
273                        cx,
274                    );
275                    multibuffer.add_diff(buffer_diff.clone(), cx);
276                });
277
278                cx.notify();
279            })
280        });
281
282        FinalizedDiff {
283            path,
284            multibuffer: self.multibuffer.clone(),
285            _update_diff: update_diff,
286        }
287    }
288
289    fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
290        let ranges = self.excerpt_ranges(cx);
291        self.multibuffer.update(cx, |multibuffer, cx| {
292            multibuffer.set_excerpts_for_path(
293                PathKey::for_buffer(&self.buffer, cx),
294                self.buffer.clone(),
295                ranges,
296                editor::DEFAULT_MULTIBUFFER_CONTEXT,
297                cx,
298            );
299            let end = multibuffer.len(cx);
300            Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
301        });
302        cx.notify();
303    }
304
305    fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
306        let buffer = self.buffer.read(cx);
307        let diff = self.diff.read(cx);
308        let mut ranges = diff
309            .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
310            .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
311            .collect::<Vec<_>>();
312        ranges.extend(
313            self.revealed_ranges
314                .iter()
315                .map(|range| range.to_point(buffer)),
316        );
317        ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
318
319        // Merge adjacent ranges
320        let mut ranges = ranges.into_iter().peekable();
321        let mut merged_ranges = Vec::new();
322        while let Some(mut range) = ranges.next() {
323            while let Some(next_range) = ranges.peek() {
324                if range.end >= next_range.start {
325                    range.end = range.end.max(next_range.end);
326                    ranges.next();
327                } else {
328                    break;
329                }
330            }
331
332            merged_ranges.push(range);
333        }
334        merged_ranges
335    }
336}
337
338pub struct FinalizedDiff {
339    path: PathBuf,
340    multibuffer: Entity<MultiBuffer>,
341    _update_diff: Task<Result<()>>,
342}
343
344async fn build_buffer_diff(
345    old_text: Arc<String>,
346    buffer: &Entity<Buffer>,
347    language_registry: Option<Arc<LanguageRegistry>>,
348    cx: &mut AsyncApp,
349) -> Result<Entity<BufferDiff>> {
350    let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
351
352    let old_text_rope = cx
353        .background_spawn({
354            let old_text = old_text.clone();
355            async move { Rope::from(old_text.as_str()) }
356        })
357        .await;
358    let base_buffer = cx
359        .update(|cx| {
360            Buffer::build_snapshot(
361                old_text_rope,
362                buffer.language().cloned(),
363                language_registry,
364                cx,
365            )
366        })?
367        .await;
368
369    let diff_snapshot = cx
370        .update(|cx| {
371            BufferDiffSnapshot::new_with_base_buffer(
372                buffer.text.clone(),
373                Some(old_text),
374                base_buffer,
375                cx,
376            )
377        })?
378        .await;
379
380    let secondary_diff = cx.new(|cx| {
381        let mut diff = BufferDiff::new(&buffer, cx);
382        diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
383        diff
384    })?;
385
386    cx.new(|cx| {
387        let mut diff = BufferDiff::new(&buffer.text, cx);
388        diff.set_snapshot(diff_snapshot, &buffer, cx);
389        diff.set_secondary_diff(secondary_diff);
390        diff
391    })
392}