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::{
10 cmp::Reverse,
11 ops::Range,
12 path::{Path, PathBuf},
13 sync::Arc,
14};
15use util::ResultExt;
16
17pub enum Diff {
18 Pending(PendingDiff),
19 Finalized(FinalizedDiff),
20}
21
22impl Diff {
23 pub fn finalized(
24 path: PathBuf,
25 old_text: Option<String>,
26 new_text: String,
27 language_registry: Arc<LanguageRegistry>,
28 cx: &mut Context<Self>,
29 ) -> Self {
30 let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
31 let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
32 let base_text = old_text.clone().unwrap_or(String::new()).into();
33 let task = cx.spawn({
34 let multibuffer = multibuffer.clone();
35 let path = path.clone();
36 let buffer = new_buffer.clone();
37 async move |_, cx| {
38 let language = language_registry
39 .language_for_file_path(&path)
40 .await
41 .log_err();
42
43 buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx))?;
44
45 let diff = build_buffer_diff(
46 old_text.unwrap_or("".into()).into(),
47 &buffer,
48 Some(language_registry.clone()),
49 cx,
50 )
51 .await?;
52
53 multibuffer
54 .update(cx, |multibuffer, cx| {
55 let hunk_ranges = {
56 let buffer = buffer.read(cx);
57 let diff = diff.read(cx);
58 diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
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.read(cx).file().map(|file| file.path().as_ref()),
156 Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_path()),
157 };
158 format!(
159 "Diff: {}\n```\n{}\n```\n",
160 path.unwrap_or(Path::new("untitled")).display(),
161 buffer_text
162 )
163 }
164
165 pub fn has_revealed_range(&self, cx: &App) -> bool {
166 self.multibuffer().read(cx).excerpt_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_str() != 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_str() != 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<String>,
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 diff_snapshot = BufferDiff::update_diff(
209 buffer_diff.clone(),
210 text_snapshot.clone(),
211 Some(base_text),
212 false,
213 false,
214 None,
215 None,
216 cx,
217 )
218 .await?;
219 buffer_diff.update(cx, |diff, cx| {
220 diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
221 diff.secondary_diff().unwrap().update(cx, |diff, cx| {
222 diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
223 });
224 })?;
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 language_registry = self.new_buffer.read(cx).language_registry();
242
243 let path = self
244 .new_buffer
245 .read(cx)
246 .file()
247 .map(|file| file.path().as_ref())
248 .unwrap_or(Path::new("untitled"))
249 .into();
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 0,
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 diff = self.diff.read(cx);
320 let mut ranges = diff
321 .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer, cx)
322 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
323 .collect::<Vec<_>>();
324 ranges.extend(
325 self.revealed_ranges
326 .iter()
327 .map(|range| range.to_point(buffer)),
328 );
329 ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
330
331 // Merge adjacent ranges
332 let mut ranges = ranges.into_iter().peekable();
333 let mut merged_ranges = Vec::new();
334 while let Some(mut range) = ranges.next() {
335 while let Some(next_range) = ranges.peek() {
336 if range.end >= next_range.start {
337 range.end = range.end.max(next_range.end);
338 ranges.next();
339 } else {
340 break;
341 }
342 }
343
344 merged_ranges.push(range);
345 }
346 merged_ranges
347 }
348}
349
350pub struct FinalizedDiff {
351 path: PathBuf,
352 base_text: Arc<String>,
353 new_buffer: Entity<Buffer>,
354 multibuffer: Entity<MultiBuffer>,
355 _update_diff: Task<Result<()>>,
356}
357
358async fn build_buffer_diff(
359 old_text: Arc<String>,
360 buffer: &Entity<Buffer>,
361 language_registry: Option<Arc<LanguageRegistry>>,
362 cx: &mut AsyncApp,
363) -> Result<Entity<BufferDiff>> {
364 let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
365
366 let old_text_rope = cx
367 .background_spawn({
368 let old_text = old_text.clone();
369 async move { Rope::from(old_text.as_str()) }
370 })
371 .await;
372 let base_buffer = cx
373 .update(|cx| {
374 Buffer::build_snapshot(
375 old_text_rope,
376 buffer.language().cloned(),
377 language_registry,
378 cx,
379 )
380 })?
381 .await;
382
383 let diff_snapshot = cx
384 .update(|cx| {
385 BufferDiffSnapshot::new_with_base_buffer(
386 buffer.text.clone(),
387 Some(old_text),
388 base_buffer,
389 cx,
390 )
391 })?
392 .await;
393
394 let secondary_diff = cx.new(|cx| {
395 let mut diff = BufferDiff::new(&buffer, cx);
396 diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
397 diff
398 })?;
399
400 cx.new(|cx| {
401 let mut diff = BufferDiff::new(&buffer.text, cx);
402 diff.set_snapshot(diff_snapshot, &buffer, cx);
403 diff.set_secondary_diff(secondary_diff);
404 diff
405 })
406}
407
408#[cfg(test)]
409mod tests {
410 use gpui::{AppContext as _, TestAppContext};
411 use language::Buffer;
412
413 use crate::Diff;
414
415 #[gpui::test]
416 async fn test_pending_diff(cx: &mut TestAppContext) {
417 let buffer = cx.new(|cx| Buffer::local("hello!", cx));
418 let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
419 buffer.update(cx, |buffer, cx| {
420 buffer.set_text("HELLO!", cx);
421 });
422 cx.run_until_parked();
423 }
424}