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 if !self.auto_update {
387 return;
388 }
389
390 let buffer = self.new_buffer.clone();
391 let buffer_diff = self.diff.clone();
392 let base_text = self.base_text.clone();
393 self.update_diff = cx.spawn(async move |diff, cx| {
394 let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
395 let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
396 let update = buffer_diff
397 .update(cx, |diff, cx| {
398 diff.update_diff(
399 text_snapshot.clone(),
400 Some(base_text.clone()),
401 None,
402 language,
403 cx,
404 )
405 })
406 .await;
407 let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
408 let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
409 let task2 = diff
410 .secondary_diff()
411 .unwrap()
412 .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
413 (task1, task2)
414 });
415 task1.await;
416 task2.await;
417 diff.update(cx, |diff, cx| {
418 if let Diff::Pending(diff) = diff {
419 diff.update_visible_ranges(cx);
420 }
421 })
422 });
423 }
424
425 fn flush_pending_update(&mut self, cx: &mut Context<Diff>) {
426 let Some(PendingUpdate {
427 operations,
428 base_snapshot,
429 text_snapshot,
430 }) = self.pending_update.take()
431 else {
432 self.is_updating = false;
433 return;
434 };
435 self.is_updating = true;
436
437 let buffer_diff = self.diff.clone();
438 let base_text = self.base_text.clone();
439 let language = self.new_buffer.read(cx).language().cloned();
440 self.update_diff = cx.spawn(async move |diff, cx| {
441 let snapshot = text_snapshot.clone();
442 let update = buffer_diff
443 .update(cx, |diff, cx| {
444 diff.update_diff_impl(
445 text_snapshot.clone(),
446 Some(base_text.clone()),
447 None,
448 language,
449 move |_d, _b, _o| compute_hunks(&base_snapshot, &text_snapshot, operations),
450 cx,
451 )
452 })
453 .await;
454 let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
455 let task1 = diff.set_snapshot(update.clone(), &snapshot, cx);
456 let task2 = diff
457 .secondary_diff()
458 .unwrap()
459 .update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx));
460 (task1, task2)
461 });
462 task1.await;
463 task2.await;
464 diff.update(cx, |diff, cx| {
465 if let Diff::Pending(diff) = diff {
466 diff.update_visible_ranges(cx);
467 // Pick up any update that arrived while this task was running.
468 diff.flush_pending_update(cx);
469 }
470 })
471 });
472 }
473
474 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) {
475 self.revealed_ranges.push(range);
476 self.update_visible_ranges(cx);
477 }
478
479 fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff {
480 let ranges = self.excerpt_ranges(cx);
481 let base_text = self.base_text.clone();
482 let new_buffer = self.new_buffer.read(cx);
483 let language_registry = new_buffer.language_registry();
484
485 let path = new_buffer
486 .file()
487 .map(|file| file.path().display(file.path_style(cx)))
488 .unwrap_or("untitled".into())
489 .into();
490 let replica_id = new_buffer.replica_id();
491
492 // Replace the buffer in the multibuffer with the snapshot
493 let buffer = cx.new(|cx| {
494 let language = self.new_buffer.read(cx).language().cloned();
495 let buffer = TextBuffer::new_normalized(
496 replica_id,
497 cx.entity_id().as_non_zero_u64().into(),
498 self.new_buffer.read(cx).line_ending(),
499 self.new_buffer.read(cx).as_rope().clone(),
500 );
501 let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
502 buffer.set_language(language, cx);
503 buffer
504 });
505
506 let buffer_diff = cx.spawn({
507 let buffer = buffer.clone();
508 async move |_this, cx| {
509 buffer.update(cx, |buffer, _| buffer.parsing_idle()).await;
510 build_buffer_diff(base_text, &buffer, language_registry, cx).await
511 }
512 });
513
514 let update_diff = cx.spawn(async move |this, cx| {
515 let buffer_diff = buffer_diff.await?;
516 this.update(cx, |this, cx| {
517 this.multibuffer().update(cx, |multibuffer, cx| {
518 let path_key = PathKey::for_buffer(&buffer, cx);
519 multibuffer.clear(cx);
520 multibuffer.set_excerpts_for_path(
521 path_key,
522 buffer,
523 ranges,
524 excerpt_context_lines(cx),
525 cx,
526 );
527 multibuffer.add_diff(buffer_diff.clone(), cx);
528 });
529
530 cx.notify();
531 })
532 });
533
534 FinalizedDiff {
535 path,
536 base_text: self.base_text.clone(),
537 multibuffer: self.multibuffer.clone(),
538 new_buffer: self.new_buffer.clone(),
539 _update_diff: update_diff,
540 }
541 }
542
543 fn update_visible_ranges(&mut self, cx: &mut Context<Diff>) {
544 let ranges = self.excerpt_ranges(cx);
545 self.multibuffer.update(cx, |multibuffer, cx| {
546 multibuffer.set_excerpts_for_path(
547 PathKey::for_buffer(&self.new_buffer, cx),
548 self.new_buffer.clone(),
549 ranges,
550 excerpt_context_lines(cx),
551 cx,
552 );
553 let end = multibuffer.len(cx);
554 Some(multibuffer.snapshot(cx).offset_to_point(end).row + 1)
555 });
556 cx.notify();
557 }
558
559 fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
560 let buffer = self.new_buffer.read(cx);
561 let mut ranges = self
562 .diff
563 .read(cx)
564 .snapshot(cx)
565 .hunks_intersecting_range(
566 Anchor::min_for_buffer(buffer.remote_id())
567 ..Anchor::max_for_buffer(buffer.remote_id()),
568 buffer,
569 )
570 .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
571 .collect::<Vec<_>>();
572 ranges.extend(
573 self.revealed_ranges
574 .iter()
575 .map(|range| range.to_point(buffer)),
576 );
577 ranges.sort_unstable_by_key(|range| (range.start, Reverse(range.end)));
578
579 // Merge adjacent ranges
580 let mut ranges = ranges.into_iter().peekable();
581 let mut merged_ranges = Vec::new();
582 while let Some(mut range) = ranges.next() {
583 while let Some(next_range) = ranges.peek() {
584 if range.end >= next_range.start {
585 range.end = range.end.max(next_range.end);
586 ranges.next();
587 } else {
588 break;
589 }
590 }
591
592 merged_ranges.push(range);
593 }
594 merged_ranges
595 }
596}
597
598pub struct FinalizedDiff {
599 path: String,
600 base_text: Arc<str>,
601 new_buffer: Entity<Buffer>,
602 multibuffer: Entity<MultiBuffer>,
603 _update_diff: Task<Result<()>>,
604}
605
606async fn build_buffer_diff(
607 old_text: Arc<str>,
608 buffer: &Entity<Buffer>,
609 language_registry: Option<Arc<LanguageRegistry>>,
610 cx: &mut AsyncApp,
611) -> Result<Entity<BufferDiff>> {
612 let language = cx.update(|cx| buffer.read(cx).language().cloned());
613 let text_snapshot = cx.update(|cx| buffer.read(cx).text_snapshot());
614 let buffer = cx.update(|cx| buffer.read(cx).snapshot());
615
616 let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
617
618 let update = secondary_diff
619 .update(cx, |secondary_diff, cx| {
620 secondary_diff.update_diff(
621 text_snapshot.clone(),
622 Some(old_text),
623 Some(false),
624 language.clone(),
625 cx,
626 )
627 })
628 .await;
629
630 secondary_diff
631 .update(cx, |secondary_diff, cx| {
632 secondary_diff.set_snapshot(update.clone(), &buffer, cx)
633 })
634 .await;
635
636 let diff = cx.new(|cx| BufferDiff::new(&buffer, cx));
637 diff.update(cx, |diff, cx| {
638 diff.language_changed(language, language_registry, cx);
639 diff.set_secondary_diff(secondary_diff);
640 diff.set_snapshot(update.clone(), &buffer, cx)
641 })
642 .await;
643 Ok(diff)
644}
645
646#[cfg(test)]
647mod tests {
648 use gpui::{AppContext as _, TestAppContext};
649 use language::Buffer;
650
651 use crate::Diff;
652
653 #[gpui::test]
654 async fn test_pending_diff(cx: &mut TestAppContext) {
655 let buffer = cx.new(|cx| Buffer::local("hello!", cx));
656 let _diff = cx.new(|cx| Diff::new(buffer.clone(), cx));
657 buffer.update(cx, |buffer, cx| {
658 buffer.set_text("HELLO!", cx);
659 });
660 cx.run_until_parked();
661 }
662}