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