1use anyhow::Result;
2use buffer_diff::{BufferDiff, BufferDiffUpdate};
3use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};
4use itertools::Itertools;
5use language::{
6 Anchor, Buffer, Capability, DiffOptions, LanguageRegistry, OffsetRangeExt as _, Point,
7 TextBuffer,
8};
9use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};
10use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
11use streaming_diff::LineOperation;
12use text::{Edit, Patch};
13use util::ResultExt;
14
15pub enum Diff {
16 Pending(PendingDiff),
17 Finalized(FinalizedDiff),
18}
19
20impl Diff {
21 pub fn finalized(
22 path: String,
23 old_text: Option<String>,
24 new_text: String,
25 language_registry: Arc<LanguageRegistry>,
26 cx: &mut Context<Self>,
27 ) -> Self {
28 let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
29 let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
30 let base_text = old_text.clone().unwrap_or(String::new()).into();
31 let task = cx.spawn({
32 let multibuffer = multibuffer.clone();
33 let path = path.clone();
34 let buffer = new_buffer.clone();
35 async move |_, cx| {
36 let language = language_registry
37 .load_language_for_file_path(Path::new(&path))
38 .await
39 .log_err();
40
41 buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx));
42 buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
43
44 let diff = build_buffer_diff(
45 old_text.unwrap_or("".into()).into(),
46 &buffer,
47 Some(language_registry.clone()),
48 cx,
49 )
50 .await?;
51
52 multibuffer.update(cx, |multibuffer, cx| {
53 let hunk_ranges = {
54 let buffer = buffer.read(cx);
55 diff.read(cx)
56 .snapshot(cx)
57 .hunks_intersecting_range(
58 Anchor::min_for_buffer(buffer.remote_id())
59 ..Anchor::max_for_buffer(buffer.remote_id()),
60 buffer,
61 )
62 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
63 .collect::<Vec<_>>()
64 };
65
66 multibuffer.set_excerpts_for_path(
67 PathKey::for_buffer(&buffer, cx),
68 buffer.clone(),
69 hunk_ranges,
70 excerpt_context_lines(cx),
71 cx,
72 );
73 multibuffer.add_diff(diff, cx);
74 });
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 subscription = cx.observe(&buffer, |this, _, cx| {
91 if let Diff::Pending(diff) = this {
92 diff.auto_update(cx);
93 }
94 });
95 Self::new_inner(
96 buffer,
97 UpdateStrategy::Auto {
98 _subscription: subscription,
99 },
100 cx,
101 )
102 }
103
104 pub fn manual(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
105 Self::new_inner(
106 buffer,
107 UpdateStrategy::Manual {
108 pending_update: None,
109 },
110 cx,
111 )
112 }
113
114 fn new_inner(
115 buffer: Entity<Buffer>,
116 update_strategy: UpdateStrategy,
117 cx: &mut Context<Self>,
118 ) -> Self {
119 let buffer_text_snapshot = buffer.read(cx).text_snapshot();
120 let language = buffer.read(cx).language().cloned();
121 let language_registry = buffer.read(cx).language_registry();
122 let buffer_diff = cx.new(|cx| {
123 let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
124 diff.language_changed(language.clone(), language_registry.clone(), cx);
125 let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx));
126 diff.set_secondary_diff(secondary_diff);
127 diff
128 });
129
130 let multibuffer = cx.new(|cx| {
131 let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
132 multibuffer.set_all_diff_hunks_expanded(cx);
133 multibuffer.add_diff(buffer_diff.clone(), cx);
134 multibuffer
135 });
136
137 Self::Pending(PendingDiff {
138 multibuffer,
139 base_text: Arc::from(buffer_text_snapshot.text().as_str()),
140 new_buffer: buffer,
141 diff: buffer_diff,
142 revealed_ranges: Vec::new(),
143 update_strategy,
144 update_diff: Task::ready(Ok(())),
145 })
146 }
147
148 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
149 if let Self::Pending(diff) = self {
150 diff.reveal_range(range, cx);
151 }
152 }
153
154 pub fn finalize(&mut self, cx: &mut Context<Self>) {
155 if let Self::Pending(diff) = self {
156 *self = Self::Finalized(diff.finalize(cx));
157 }
158 }
159
160 /// Returns the original text before any edits were applied.
161 pub fn base_text(&self) -> &Arc<str> {
162 match self {
163 Self::Pending(PendingDiff { base_text, .. }) => base_text,
164 Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
165 }
166 }
167
168 /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
169 pub fn buffer(&self) -> &Entity<Buffer> {
170 match self {
171 Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
172 Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
173 }
174 }
175
176 pub fn file_path(&self, cx: &App) -> Option<String> {
177 match self {
178 Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer
179 .read(cx)
180 .file()
181 .map(|file| file.full_path(cx).to_string_lossy().into_owned()),
182 Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()),
183 }
184 }
185
186 pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
187 match self {
188 Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
189 Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
190 }
191 }
192
193 pub fn to_markdown(&self, cx: &App) -> String {
194 let buffer_text = self
195 .multibuffer()
196 .read(cx)
197 .all_buffers()
198 .iter()
199 .map(|buffer| buffer.read(cx).text())
200 .join("\n");
201 let path = match self {
202 Diff::Pending(PendingDiff {
203 new_buffer: buffer, ..
204 }) => buffer
205 .read(cx)
206 .file()
207 .map(|file| file.path().display(file.path_style(cx))),
208 Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
209 };
210 format!(
211 "Diff: {}\n```\n{}\n```\n",
212 path.unwrap_or("untitled".into()),
213 buffer_text
214 )
215 }
216
217 pub fn has_revealed_range(&self, cx: &App) -> bool {
218 self.multibuffer().read(cx).paths().next().is_some()
219 }
220
221 pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
222 match self {
223 Diff::Pending(PendingDiff {
224 base_text,
225 new_buffer,
226 ..
227 }) => {
228 base_text.as_ref() != old_text
229 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
230 }
231 Diff::Finalized(FinalizedDiff {
232 base_text,
233 new_buffer,
234 ..
235 }) => {
236 base_text.as_ref() != old_text
237 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
238 }
239 }
240 }
241
242 pub fn push_line_operations(
243 &mut self,
244 operations: Vec<LineOperation>,
245 base_snapshot: text::BufferSnapshot,
246 cx: &mut Context<Diff>,
247 ) {
248 if let Diff::Pending(diff) = self {
249 diff.push_line_operations(operations, base_snapshot, cx);
250 }
251 }
252}
253
254enum UpdateStrategy {
255 Auto {
256 _subscription: Subscription,
257 },
258 Manual {
259 pending_update: Option<PendingUpdate>,
260 },
261}
262
263pub struct PendingDiff {
264 multibuffer: Entity<MultiBuffer>,
265 base_text: Arc<str>,
266 new_buffer: Entity<Buffer>,
267 diff: Entity<BufferDiff>,
268 revealed_ranges: Vec<Range<Anchor>>,
269 update_strategy: UpdateStrategy,
270 update_diff: Task<Result<()>>,
271}
272
273struct PendingUpdate {
274 operations: Vec<LineOperation>,
275 base_snapshot: text::BufferSnapshot,
276 text_snapshot: text::BufferSnapshot,
277}
278
279fn compute_hunks(
280 diff_base: &text::BufferSnapshot,
281 buffer: &text::BufferSnapshot,
282 line_operations: Vec<LineOperation>,
283) -> Patch<usize> {
284 let mut patch = Patch::default();
285
286 let mut old_row = 0u32;
287 let mut new_row = 0u32;
288
289 // Merge adjacent Delete+Insert into a single Modified hunk
290 let mut pending_delete_lines: Option<u32> = None;
291
292 let flush_delete = |pending_delete_lines: &mut Option<u32>,
293 old_row: &mut u32,
294 new_row: u32,
295 diff_base: &text::BufferSnapshot,
296 buffer: &text::BufferSnapshot| {
297 if let Some(deleted_lines) = pending_delete_lines.take() {
298 let old_start =
299 diff_base.point_to_offset(Point::new(*old_row, 0).min(diff_base.max_point()));
300 let old_end = diff_base.point_to_offset(
301 Point::new(*old_row + deleted_lines, 0).min(diff_base.max_point()),
302 );
303 let new_pos = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));
304 let edit = Edit {
305 old: old_start..old_end,
306 new: new_pos..new_pos,
307 };
308 *old_row += deleted_lines;
309 Some(edit)
310 } else {
311 None
312 }
313 };
314
315 for operation in line_operations {
316 match operation {
317 LineOperation::Delete { lines } => {
318 // Accumulate deletions — they might be followed by an Insert (= modification)
319 *pending_delete_lines.get_or_insert(0) += lines;
320 }
321 LineOperation::Insert { lines } => {
322 let old_start =
323 diff_base.point_to_offset(Point::new(old_row, 0).min(diff_base.max_point()));
324 let (old_end, deleted_lines) =
325 if let Some(deleted_lines) = pending_delete_lines.take() {
326 // Delete followed by Insert = Modified hunk
327 let old_end = diff_base.point_to_offset(
328 Point::new(old_row + deleted_lines, 0).min(diff_base.max_point()),
329 );
330 (old_end, deleted_lines)
331 } else {
332 // Pure insertion
333 (old_start, 0)
334 };
335 let new_start =
336 buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));
337 let new_end =
338 buffer.point_to_offset(Point::new(new_row + lines, 0).min(buffer.max_point()));
339 patch.push(Edit {
340 old: old_start..old_end,
341 new: new_start..new_end,
342 });
343 old_row += deleted_lines;
344 new_row += lines;
345 }
346 LineOperation::Keep { lines } => {
347 // Flush any pending deletion before a Keep
348 if let Some(edit) = flush_delete(
349 &mut pending_delete_lines,
350 &mut old_row,
351 new_row,
352 diff_base,
353 buffer,
354 ) {
355 patch.push(edit);
356 }
357 // Keep = unchanged, no hunk to push
358 old_row += lines;
359 new_row += lines;
360 }
361 }
362 }
363
364 // Flush any trailing deletion
365 if let Some(edit) = flush_delete(
366 &mut pending_delete_lines,
367 &mut old_row,
368 new_row,
369 diff_base,
370 buffer,
371 ) {
372 patch.push(edit);
373 }
374
375 patch
376}
377
378/// Applies a `BufferDiffUpdate` to both the primary and secondary diffs, then
379/// refreshes the visible excerpt ranges in the multibuffer.
380async fn apply_diff_update(
381 update: BufferDiffUpdate,
382 text_snapshot: &text::BufferSnapshot,
383 buffer_diff: &Entity<BufferDiff>,
384 diff: &WeakEntity<Diff>,
385 cx: &mut AsyncApp,
386) -> Result<()> {
387 // todo: we can get away without having a whole secondary diff here
388 let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
389 let task1 = diff.set_snapshot(update.clone(), text_snapshot, cx);
390 let task2 = diff
391 .secondary_diff()
392 .expect("PendingDiff should always have a secondary diff")
393 .update(cx, |diff, cx| diff.set_snapshot(update, text_snapshot, cx));
394 (task1, task2)
395 });
396 task1.await;
397 task2.await;
398 diff.update(cx, |diff, cx| {
399 if let Diff::Pending(diff) = diff {
400 diff.update_visible_ranges(cx);
401 }
402 })
403}
404
405impl PendingDiff {
406 fn push_line_operations(
407 &mut self,
408 operations: Vec<LineOperation>,
409 base_snapshot: text::BufferSnapshot,
410 cx: &mut Context<Diff>,
411 ) {
412 let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else {
413 return;
414 };
415
416 let text_snapshot = self.new_buffer.read(cx).text_snapshot();
417 *pending_update = Some(PendingUpdate {
418 operations,
419 base_snapshot,
420 text_snapshot,
421 });
422 if self.update_diff.is_ready() {
423 self.flush_pending_update(cx);
424 }
425 }
426
427 fn auto_update(&mut self, cx: &mut Context<Diff>) {
428 let buffer = self.new_buffer.clone();
429 let buffer_diff = self.diff.clone();
430 let base_text = self.base_text.clone();
431 self.update_diff = cx.spawn(async move |diff, cx| {
432 let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
433 let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
434 let update = buffer_diff
435 .update(cx, |diff, cx| {
436 diff.update_diff(
437 text_snapshot.clone(),
438 Some(base_text.clone()),
439 None,
440 language,
441 cx,
442 )
443 })
444 .await;
445 apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await
446 });
447 }
448
449 fn flush_pending_update(&mut self, cx: &mut Context<Diff>) {
450 let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else {
451 return;
452 };
453
454 let Some(PendingUpdate {
455 operations,
456 base_snapshot,
457 text_snapshot,
458 }) = pending_update.take()
459 else {
460 return;
461 };
462
463 let buffer_diff = self.diff.clone();
464 let base_text = self.base_text.clone();
465 self.update_diff = cx.spawn(async move |diff, cx| {
466 let update = cx
467 .background_spawn({
468 let snapshot = text_snapshot.clone();
469 async move {
470 let hunks = compute_hunks(&base_snapshot, &snapshot, operations);
471 BufferDiffUpdate::from_hunks(
472 base_text,
473 base_snapshot.as_rope(),
474 snapshot,
475 hunks,
476 Some(DiffOptions::default()),
477 )
478 }
479 })
480 .await;
481
482 apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await?;
483
484 diff.update(cx, |diff, cx| {
485 if let Diff::Pending(diff) = diff {
486 // Pick up any update that arrived while this task was running.
487 diff.flush_pending_update(cx);
488 }
489 })
490 });
491 }
492
493 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
494 self.revealed_ranges.push(range);
495 self.update_visible_ranges(cx);
496 }
497
498 fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
499 let ranges = self.excerpt_ranges(cx);
500 let base_text = self.base_text.clone();
501 let new_buffer = self.new_buffer.read(cx);
502 let language_registry = new_buffer.language_registry();
503
504 let path = new_buffer
505 .file()
506 .map(|file| file.path().display(file.path_style(cx)))
507 .unwrap_or("untitled".into())
508 .into();
509 let replica_id = new_buffer.replica_id();
510
511 // Replace the buffer in the multibuffer with the snapshot
512 let buffer = cx.new(|cx| {
513 let language = self.new_buffer.read(cx).language().cloned();
514 let buffer = TextBuffer::new_normalized(
515 replica_id,
516 cx.entity_id().as_non_zero_u64().into(),
517 self.new_buffer.read(cx).line_ending(),
518 self.new_buffer.read(cx).as_rope().clone(),
519 );
520 let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
521 buffer.set_language(language, cx);
522 buffer
523 });
524
525 let buffer_diff = cx.spawn({
526 let buffer = buffer.clone();
527 async move |_this, cx| {
528 buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
529 build_buffer_diff(base_text, &buffer, language_registry, cx).await
530 }
531 });
532
533 let update_diff = cx.spawn(async move |this, cx| {
534 let buffer_diff = buffer_diff.await?;
535 this.update(cx, |this, cx| {
536 this.multibuffer().update(cx, |multibuffer, cx| {
537 let path_key = PathKey::for_buffer(&buffer, cx);
538 multibuffer.clear(cx);
539 multibuffer.set_excerpts_for_path(
540 path_key,
541 buffer,
542 ranges,
543 excerpt_context_lines(cx),
544 cx,
545 );
546 multibuffer.add_diff(buffer_diff.clone(), cx);
547 });
548
549 cx.notify();
550 })
551 });
552
553 FinalizedDiff {
554 path,
555 base_text: self.base_text.clone(),
556 multibuffer: self.multibuffer.clone(),
557 new_buffer: self.new_buffer.clone(),
558 _update_diff: update_diff,
559 }
560 }
561
562 fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
563 let ranges = self.excerpt_ranges(cx);
564 self.multibuffer.update(cx, |multibuffer, cx| {
565 multibuffer.set_excerpts_for_path(
566 PathKey::for_buffer(&self.new_buffer, cx),
567 self.new_buffer.clone(),
568 ranges,
569 excerpt_context_lines(cx),
570 cx,
571 );
572 let end = multibuffer.len(cx);
573 Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
574 });
575 cx.notify();
576 }
577
578 fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
579 let buffer = self.new_buffer.read(cx);
580 let mut ranges = self
581 .diff
582 .read(cx)
583 .snapshot(cx)
584 .hunks_intersecting_range(
585 Anchor::min_for_buffer(buffer.remote_id())
586 ..Anchor::max_for_buffer(buffer.remote_id()),
587 buffer,
588 )
589 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
590 .collect::<Vec<_>>();
591 ranges.extend(
592 self.revealed_ranges
593 .iter()
594 .map(|range| range.to_point(buffer)),
595 );
596 ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
597
598 // Merge adjacent ranges
599 let mut ranges = ranges.into_iter().peekable();
600 let mut merged_ranges = Vec::new();
601 while let Some(mut range) = ranges.next() {
602 while let Some(next_range) = ranges.peek() {
603 if range.end >= next_range.start {
604 range.end = range.end.max(next_range.end);
605 ranges.next();
606 } else {
607 break;
608 }
609 }
610
611 merged_ranges.push(range);
612 }
613 merged_ranges
614 }
615}
616
617pub struct FinalizedDiff {
618 path: String,
619 base_text: Arc<str>,
620 new_buffer: Entity<Buffer>,
621 multibuffer: Entity<MultiBuffer>,
622 _update_diff: Task<Result<()>>,
623}
624
625async fn build_buffer_diff(
626 old_text: Arc<str>,
627 buffer: &Entity<Buffer>,
628 language_registry: Option<Arc<LanguageRegistry>>,
629 cx: &mut AsyncApp,
630) -> Result<Entity<BufferDiff>> {
631 let language = cx.update(|cx| buffer.read(cx).language().cloned());
632 let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
633 let buffer = cx.update(|cx| buffer.read(cx).snapshot());
634
635 let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
636
637 let update = secondary_diff
638 .update(cx, |secondary_diff, cx| {
639 secondary_diff.update_diff(
640 text_snapshot.clone(),
641 Some(old_text),
642 Some(false),
643 language.clone(),
644 cx,
645 )
646 })
647 .await;
648
649 secondary_diff
650 .update(cx, |secondary_diff, cx| {
651 secondary_diff.set_snapshot(update.clone(), &buffer, cx)
652 })
653 .await;
654
655 let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
656 diff.update(cx, |diff, cx| {
657 diff.language_changed(language, language_registry, cx);
658 diff.set_secondary_diff(secondary_diff);
659 diff.set_snapshot(update.clone(), &buffer, cx)
660 })
661 .await;
662 Ok(diff)
663}
664
665#[cfg(test)]
666mod tests {
667 use gpui::{AppContext as _, TestAppContext};
668 use language::Buffer;
669
670 use crate::Diff;
671
672 #[gpui::test]
673 async fn test_pending_diff(cx: &mut TestAppContext) {
674 let buffer = cx.new(|cx| Buffer::local("hello!", cx));
675 let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
676 buffer.update(cx, |buffer, cx| {
677 buffer.set_text("HELLO!", cx);
678 });
679 cx.run_until_parked();
680 }
681}