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