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