1use anyhow::Result;
2use buffer_diff::{BufferDiff, BufferDiffSnapshot};
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, Rope, 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| {
39 buffer.set_language_immediate(language.clone(), cx)
40 })?;
41
42 let diff = build_buffer_diff(
43 old_text.unwrap_or("".into()).into(),
44 &buffer,
45 Some(language_registry.clone()),
46 cx,
47 )
48 .await?;
49
50 multibuffer
51 .update(cx, |multibuffer, cx| {
52 let hunk_ranges = {
53 let buffer = buffer.read(cx);
54 let diff = diff.read(cx);
55 diff.hunks_intersecting_range(
56 Anchor::min_for_buffer(buffer.remote_id())
57 ..Anchor::max_for_buffer(buffer.remote_id()),
58 buffer,
59 cx,
60 )
61 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
62 .collect::<Vec<_>>()
63 };
64
65 multibuffer.set_excerpts_for_path(
66 PathKey::for_buffer(&buffer, cx),
67 buffer.clone(),
68 hunk_ranges,
69 multibuffer_context_lines(cx),
70 cx,
71 );
72 multibuffer.add_diff(diff, cx);
73 })
74 .log_err();
75
76 anyhow::Ok(())
77 }
78 });
79
80 Self::Finalized(FinalizedDiff {
81 multibuffer,
82 path,
83 base_text,
84 new_buffer,
85 _update_diff: task,
86 })
87 }
88
89 pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
90 let buffer_text_snapshot = buffer.read(cx).text_snapshot();
91 let base_text_snapshot = buffer.read(cx).snapshot();
92 let base_text = base_text_snapshot.text();
93 debug_assert_eq!(buffer_text_snapshot.text(), base_text);
94 let buffer_diff = cx.new(|cx| {
95 let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, base_text_snapshot);
96 let snapshot = diff.snapshot(cx);
97 let secondary_diff = cx.new(|cx| {
98 let mut diff = BufferDiff::new(&buffer_text_snapshot, cx);
99 diff.set_snapshot(snapshot, &buffer_text_snapshot, cx);
100 diff
101 });
102 diff.set_secondary_diff(secondary_diff);
103 diff
104 });
105
106 let multibuffer = cx.new(|cx| {
107 let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
108 multibuffer.add_diff(buffer_diff.clone(), cx);
109 multibuffer
110 });
111
112 Self::Pending(PendingDiff {
113 multibuffer,
114 base_text: Arc::new(base_text),
115 _subscription: cx.observe(&buffer, |this, _, cx| {
116 if let Diff::Pending(diff) = this {
117 diff.update(cx);
118 }
119 }),
120 new_buffer: buffer,
121 diff: buffer_diff,
122 revealed_ranges: Vec::new(),
123 update_diff: Task::ready(Ok(())),
124 })
125 }
126
127 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
128 if let Self::Pending(diff) = self {
129 diff.reveal_range(range, cx);
130 }
131 }
132
133 pub fn finalize(&mut self, cx: &mut Context<Self>) {
134 if let Self::Pending(diff) = self {
135 *self = Self::Finalized(diff.finalize(cx));
136 }
137 }
138
139 pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
140 match self {
141 Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
142 Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
143 }
144 }
145
146 pub fn to_markdown(&self, cx: &App) -> String {
147 let buffer_text = self
148 .multibuffer()
149 .read(cx)
150 .all_buffers()
151 .iter()
152 .map(|buffer| buffer.read(cx).text())
153 .join("\n");
154 let path = match self {
155 Diff::Pending(PendingDiff {
156 new_buffer: buffer, ..
157 }) => buffer
158 .read(cx)
159 .file()
160 .map(|file| file.path().display(file.path_style(cx))),
161 Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
162 };
163 format!(
164 "Diff: {}\n```\n{}\n```\n",
165 path.unwrap_or("untitled".into()),
166 buffer_text
167 )
168 }
169
170 pub fn has_revealed_range(&self, cx: &App) -> bool {
171 self.multibuffer().read(cx).excerpt_paths().next().is_some()
172 }
173
174 pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
175 match self {
176 Diff::Pending(PendingDiff {
177 base_text,
178 new_buffer,
179 ..
180 }) => {
181 base_text.as_str() != old_text
182 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
183 }
184 Diff::Finalized(FinalizedDiff {
185 base_text,
186 new_buffer,
187 ..
188 }) => {
189 base_text.as_str() != old_text
190 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
191 }
192 }
193 }
194}
195
196pub struct PendingDiff {
197 multibuffer: Entity<MultiBuffer>,
198 base_text: Arc<String>,
199 new_buffer: Entity<Buffer>,
200 diff: Entity<BufferDiff>,
201 revealed_ranges: Vec<Range<Anchor>>,
202 _subscription: Subscription,
203 update_diff: Task<Result<()>>,
204}
205
206impl PendingDiff {
207 pub fn update(&mut self, cx: &mut Context<Diff>) {
208 let buffer = self.new_buffer.clone();
209 let buffer_diff = self.diff.clone();
210 let base_text = self.base_text.clone();
211 self.update_diff = cx.spawn(async move |diff, cx| {
212 let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot())?;
213 let diff_snapshot = BufferDiff::update_diff(
214 buffer_diff.clone(),
215 text_snapshot.clone(),
216 Some(base_text),
217 false,
218 false,
219 None,
220 None,
221 cx,
222 )
223 .await?;
224 buffer_diff.update(cx, |diff, cx| {
225 diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
226 diff.secondary_diff().unwrap().update(cx, |diff, cx| {
227 diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
228 });
229 })?;
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_immediate(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 diff = self.diff.read(cx);
325 let mut ranges = diff
326 .hunks_intersecting_range(
327 Anchor::min_for_buffer(buffer.remote_id())
328 ..Anchor::max_for_buffer(buffer.remote_id()),
329 buffer,
330 cx,
331 )
332 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
333 .collect::<Vec<_>>();
334 ranges.extend(
335 self.revealed_ranges
336 .iter()
337 .map(|range| range.to_point(buffer)),
338 );
339 ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
340
341 // Merge adjacent ranges
342 let mut ranges = ranges.into_iter().peekable();
343 let mut merged_ranges = Vec::new();
344 while let Some(mut range) = ranges.next() {
345 while let Some(next_range) = ranges.peek() {
346 if range.end >= next_range.start {
347 range.end = range.end.max(next_range.end);
348 ranges.next();
349 } else {
350 break;
351 }
352 }
353
354 merged_ranges.push(range);
355 }
356 merged_ranges
357 }
358}
359
360pub struct FinalizedDiff {
361 path: String,
362 base_text: Arc<String>,
363 new_buffer: Entity<Buffer>,
364 multibuffer: Entity<MultiBuffer>,
365 _update_diff: Task<Result<()>>,
366}
367
368async fn build_buffer_diff(
369 old_text: Arc<String>,
370 buffer: &Entity<Buffer>,
371 language_registry: Option<Arc<LanguageRegistry>>,
372 cx: &mut AsyncApp,
373) -> Result<Entity<BufferDiff>> {
374 let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
375
376 let old_text_rope = cx
377 .background_spawn({
378 let old_text = old_text.clone();
379 async move { Rope::from(old_text.as_str()) }
380 })
381 .await;
382 let base_buffer = cx
383 .update(|cx| {
384 Buffer::build_snapshot(
385 old_text_rope,
386 buffer.language().cloned(),
387 language_registry,
388 cx,
389 )
390 })?
391 .await;
392
393 let diff_snapshot = cx
394 .update(|cx| {
395 BufferDiffSnapshot::new_with_base_buffer(
396 buffer.text.clone(),
397 Some(old_text),
398 base_buffer,
399 cx,
400 )
401 })?
402 .await;
403
404 let secondary_diff = cx.new(|cx| {
405 let mut diff = BufferDiff::new(&buffer, cx);
406 diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
407 diff
408 })?;
409
410 cx.new(|cx| {
411 let mut diff = BufferDiff::new(&buffer.text, cx);
412 diff.set_snapshot(diff_snapshot, &buffer, cx);
413 diff.set_secondary_diff(secondary_diff);
414 diff
415 })
416}
417
418#[cfg(test)]
419mod tests {
420 use gpui::{AppContext as _, TestAppContext};
421 use language::Buffer;
422
423 use crate::Diff;
424
425 #[gpui::test]
426 async fn test_pending_diff(cx: &mut TestAppContext) {
427 let buffer = cx.new(|cx| Buffer::local("hello!", cx));
428 let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
429 buffer.update(cx, |buffer, cx| {
430 buffer.set_text("HELLO!", cx);
431 });
432 cx.run_until_parked();
433 }
434}