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