1use anyhow::Result;
2use buffer_diff::{BufferDiff, BufferDiffUpdate};
3use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
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 buffer_text_snapshot = buffer.read(cx).text_snapshot();
91 let language = buffer.read(cx).language().cloned();
92 let language_registry = buffer.read(cx).language_registry();
93 let buffer_diff = cx.new(|cx| {
94 let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
95 diff.language_changed(language.clone(), language_registry.clone(), cx);
96 let secondary_diff = cx.new(|cx| {
97 // For the secondary diff buffer we skip assigning the language as we do not really need to perform any syntax highlighting on
98 // 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
99 // view multibuffers.
100 BufferDiff::new_unchanged(&buffer_text_snapshot, cx)
101 });
102 diff.set_secondary_diff(secondary_diff);
103 diff
104 });
105
106 let multibuffer = cx.new(|cx| {
107 let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly);
108 multibuffer.set_all_diff_hunks_expanded(cx);
109 multibuffer.add_diff(buffer_diff.clone(), cx);
110 multibuffer
111 });
112
113 Self::Pending(PendingDiff {
114 multibuffer,
115 base_text: Arc::from(buffer_text_snapshot.text().as_str()),
116 _subscription: cx.observe(&buffer, |this, _, cx| {
117 if let Diff::Pending(diff) = this {
118 diff.update(cx);
119 }
120 }),
121 new_buffer: buffer,
122 diff: buffer_diff,
123 revealed_ranges: Vec::new(),
124 update_diff: Task::ready(Ok(())),
125 pending_update: None,
126 is_updating: false,
127 auto_update: false,
128 })
129 }
130
131 pub fn disable_auto_update(&mut self) {
132 if let Self::Pending(diff) = self {
133 diff.auto_update = false;
134 }
135 }
136
137 pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
138 if let Self::Pending(diff) = self {
139 diff.reveal_range(range, cx);
140 }
141 }
142
143 pub fn finalize(&mut self, cx: &mut Context<Self>) {
144 if let Self::Pending(diff) = self {
145 *self = Self::Finalized(diff.finalize(cx));
146 }
147 }
148
149 /// Returns the original text before any edits were applied.
150 pub fn base_text(&self) -> &Arc<str> {
151 match self {
152 Self::Pending(PendingDiff { base_text, .. }) => base_text,
153 Self::Finalized(FinalizedDiff { base_text, .. }) => base_text,
154 }
155 }
156
157 /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs).
158 pub fn buffer(&self) -> &Entity<Buffer> {
159 match self {
160 Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer,
161 Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer,
162 }
163 }
164
165 pub fn file_path(&self, cx: &App) -> Option<String> {
166 match self {
167 Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer
168 .read(cx)
169 .file()
170 .map(|file| file.full_path(cx).to_string_lossy().into_owned()),
171 Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()),
172 }
173 }
174
175 pub fn multibuffer(&self) -> &Entity<MultiBuffer> {
176 match self {
177 Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer,
178 Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer,
179 }
180 }
181
182 pub fn to_markdown(&self, cx: &App) -> String {
183 let buffer_text = self
184 .multibuffer()
185 .read(cx)
186 .all_buffers()
187 .iter()
188 .map(|buffer| buffer.read(cx).text())
189 .join("\n");
190 let path = match self {
191 Diff::Pending(PendingDiff {
192 new_buffer: buffer, ..
193 }) => buffer
194 .read(cx)
195 .file()
196 .map(|file| file.path().display(file.path_style(cx))),
197 Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()),
198 };
199 format!(
200 "Diff: {}\n```\n{}\n```\n",
201 path.unwrap_or("untitled".into()),
202 buffer_text
203 )
204 }
205
206 pub fn has_revealed_range(&self, cx: &App) -> bool {
207 self.multibuffer().read(cx).paths().next().is_some()
208 }
209
210 pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool {
211 match self {
212 Diff::Pending(PendingDiff {
213 base_text,
214 new_buffer,
215 ..
216 }) => {
217 base_text.as_ref() != old_text
218 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
219 }
220 Diff::Finalized(FinalizedDiff {
221 base_text,
222 new_buffer,
223 ..
224 }) => {
225 base_text.as_ref() != old_text
226 || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
227 }
228 }
229 }
230
231 pub fn update_pending(
232 &mut self,
233 operations: Vec<LineOperation>,
234 snapshot: text::BufferSnapshot,
235 cx: &mut Context<Diff>,
236 ) {
237 match self {
238 Diff::Pending(diff) => diff.update_manually(operations, snapshot, cx),
239 Diff::Finalized(_) => {}
240 }
241 }
242}
243
244pub struct PendingDiff {
245 multibuffer: Entity<MultiBuffer>,
246 base_text: Arc<str>,
247 new_buffer: Entity<Buffer>,
248 diff: Entity<BufferDiff>,
249 revealed_ranges: Vec<Range<Anchor>>,
250 _subscription: Subscription,
251 auto_update: bool,
252 update_diff: Task<Result<()>>,
253 // The latest update waiting to be processed. Storing only the latest means
254 // intermediate chunks are coalesced when the worker task can't keep up.
255 pending_update: Option<PendingUpdate>,
256 is_updating: bool,
257}
258
259struct PendingUpdate {
260 operations: Vec<LineOperation>,
261 base_snapshot: text::BufferSnapshot,
262 text_snapshot: text::BufferSnapshot,
263}
264
265fn compute_hunks(
266 diff_base: &text::BufferSnapshot,
267 buffer: &text::BufferSnapshot,
268 line_operations: Vec<LineOperation>,
269) -> Patch<usize> {
270 let mut patch = Patch::default();
271
272 let mut old_row = 0u32;
273 let mut new_row = 0u32;
274
275 // Merge adjacent Delete+Insert into a single Modified hunk
276 let mut pending_delete_lines: Option<u32> = None;
277
278 let flush_delete = |pending_delete_lines: &mut Option<u32>,
279 old_row: &mut u32,
280 new_row: u32,
281 diff_base: &text::BufferSnapshot,
282 buffer: &text::BufferSnapshot| {
283 if let Some(deleted_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.point_to_offset(
287 Point::new(*old_row + deleted_lines, 0).min(diff_base.max_point()),
288 );
289 let new_pos = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));
290 let edit = Edit {
291 old: old_start..old_end,
292 new: new_pos..new_pos,
293 };
294 *old_row += deleted_lines;
295 Some(edit)
296 } else {
297 None
298 }
299 };
300
301 for operation in line_operations {
302 match operation {
303 LineOperation::Delete { lines } => {
304 // Accumulate deletions — they might be followed by an Insert (= modification)
305 *pending_delete_lines.get_or_insert(0) += lines;
306 }
307 LineOperation::Insert { lines } => {
308 let old_start =
309 diff_base.point_to_offset(Point::new(old_row, 0).min(diff_base.max_point()));
310 let (old_end, deleted_lines) =
311 if let Some(deleted_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 + deleted_lines, 0).min(diff_base.max_point()),
315 );
316 (old_end, deleted_lines)
317 } else {
318 // Pure insertion
319 (old_start, 0)
320 };
321 let new_start =
322 buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point()));
323 let new_end =
324 buffer.point_to_offset(Point::new(new_row + lines, 0).min(buffer.max_point()));
325 patch.push(Edit {
326 old: old_start..old_end,
327 new: new_start..new_end,
328 });
329 old_row += deleted_lines;
330 new_row += lines;
331 }
332 LineOperation::Keep { lines } => {
333 // Flush any pending deletion before a Keep
334 if let Some(edit) = flush_delete(
335 &mut pending_delete_lines,
336 &mut old_row,
337 new_row,
338 diff_base,
339 buffer,
340 ) {
341 patch.push(edit);
342 }
343 // Keep = unchanged, no hunk to push
344 old_row += lines;
345 new_row += lines;
346 }
347 }
348 }
349
350 // Flush any trailing deletion
351 if let Some(edit) = flush_delete(
352 &mut pending_delete_lines,
353 &mut old_row,
354 new_row,
355 diff_base,
356 buffer,
357 ) {
358 patch.push(edit);
359 }
360
361 patch
362}
363
364impl PendingDiff {
365 pub fn update_manually(
366 &mut self,
367 operations: Vec<LineOperation>,
368 base_snapshot: text::BufferSnapshot,
369 cx: &mut Context<Diff>,
370 ) {
371 let text_snapshot = self.new_buffer.read(cx).text_snapshot();
372 self.pending_update = Some(PendingUpdate {
373 operations,
374 base_snapshot,
375 text_snapshot,
376 });
377 if !self.is_updating {
378 self.flush_pending_update(cx);
379 }
380 }
381
382 pub fn update(&mut self, cx: &mut Context<Diff>) {
383 if !self.auto_update {
384 return;
385 }
386
387 let buffer = self.new_buffer.clone();
388 let buffer_diff = self.diff.clone();
389 let base_text = self.base_text.clone();
390 self.update_diff = cx.spawn(async move |diff, cx| {
391 let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
392 let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned());
393 let update = buffer_diff
394 .update(cx, |diff, cx| {
395 diff.update_diff(
396 text_snapshot.clone(),
397 Some(base_text.clone()),
398 None,
399 language,
400 cx,
401 )
402 })
403 .await;
404 // FIXME we can get away without having a whole secondary diff here
405 let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
406 let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
407 let task2 = diff
408 .secondary_diff()
409 .unwrap()
410 .update(cx, |diff, cx| diff.set_snapshot(update, &text_snapshot, cx));
411 (task1, task2)
412 });
413 task1.await;
414 task2.await;
415 diff.update(cx, |diff, cx| {
416 if let Diff::Pending(diff) = diff {
417 diff.update_visible_ranges(cx);
418 }
419 })
420 });
421 }
422
423 fn flush_pending_update(&mut self, cx: &mut Context<Diff>) {
424 let Some(PendingUpdate {
425 operations,
426 base_snapshot,
427 text_snapshot,
428 }) = self.pending_update.take()
429 else {
430 self.is_updating = false;
431 return;
432 };
433 self.is_updating = true;
434
435 let buffer_diff = self.diff.clone();
436 let base_text = self.base_text.clone();
437 self.update_diff = cx.spawn(async move |diff, cx| {
438 let update = cx
439 .background_spawn({
440 let snapshot = text_snapshot.clone();
441 async move {
442 let hunks = compute_hunks(&base_snapshot, &snapshot, operations);
443 BufferDiffUpdate::from_hunks(
444 base_text,
445 base_snapshot.as_rope(),
446 snapshot,
447 hunks,
448 Some(DiffOptions::default()),
449 )
450 }
451 })
452 .await;
453
454 let (task1, task2) = buffer_diff.update(cx, |diff, cx| {
455 let task1 = diff.set_snapshot(update.clone(), &text_snapshot, cx);
456 let task2 = diff
457 .secondary_diff()
458 .unwrap()
459 .update(cx, |diff, cx| diff.set_snapshot(update, &text_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}