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}