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