use anyhow::Result;use buffer_diff::{BufferDiff, BufferDiffUpdate};use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity};use itertools::Itertools;use language::{ Anchor, Buffer, Capability, DiffOptions, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,};use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines};use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};use streaming_diff::LineOperation;use text::{Edit, Patch};use util::ResultExt; pub enum Diff { Pending(PendingDiff), Finalized(FinalizedDiff),} impl Diff { pub fn finalized( path: String, old_text: Option<String>, new_text: String, language_registry: Arc<LanguageRegistry>, cx: &mut Context<Self>, ) -> Self { let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly)); let new_buffer = cx.new(|cx| Buffer::local(new_text, cx)); let base_text = old_text.clone().unwrap_or(String::new()).into(); let task = cx.spawn({ let multibuffer = multibuffer.clone(); let path = path.clone(); let buffer = new_buffer.clone(); async move |_, cx| { let language = language_registry .load_language_for_file_path(Path::new(&path)) .await .log_err(); buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx)); buffer.update(cx, |buffer, _| buffer.parsing_idle()).await; let diff = build_buffer_diff( old_text.unwrap_or("".into()).into(), &buffer, Some(language_registry.clone()), cx, ) .await?; multibuffer.update(cx, |multibuffer, cx| { let hunk_ranges = { let buffer = buffer.read(cx); diff.read(cx) .snapshot(cx) .hunks_intersecting_range( Anchor::min_for_buffer(buffer.remote_id()) ..Anchor::max_for_buffer(buffer.remote_id()), buffer, ) .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::<Vec<_>>() }; multibuffer.set_excerpts_for_path( PathKey::for_buffer(&buffer, cx), buffer.clone(), hunk_ranges, excerpt_context_lines(cx), cx, ); multibuffer.add_diff(diff, cx); }); anyhow::Ok(()) } }); Self::Finalized(FinalizedDiff { multibuffer, path, base_text, new_buffer, _update_diff: task, }) } pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self { let subscription = cx.observe(&buffer, |this, _, cx| { if let Diff::Pending(diff) = this { diff.auto_update(cx); } }); Self::new_inner( buffer, UpdateStrategy::Auto { _subscription: subscription, }, cx, ) } pub fn manual(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self { Self::new_inner( buffer, UpdateStrategy::Manual { pending_update: None, }, cx, ) } fn new_inner( buffer: Entity<Buffer>, update_strategy: UpdateStrategy, cx: &mut Context<Self>, ) -> Self { let buffer_text_snapshot = buffer.read(cx).text_snapshot(); let language = buffer.read(cx).language().cloned(); let language_registry = buffer.read(cx).language_registry(); let buffer_diff = cx.new(|cx| { let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx); diff.language_changed(language.clone(), language_registry.clone(), cx); let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx)); diff.set_secondary_diff(secondary_diff); diff }); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly); multibuffer.set_all_diff_hunks_expanded(cx); multibuffer.add_diff(buffer_diff.clone(), cx); multibuffer }); Self::Pending(PendingDiff { multibuffer, base_text: Arc::from(buffer_text_snapshot.text().as_str()), new_buffer: buffer, diff: buffer_diff, revealed_ranges: Vec::new(), update_strategy, update_diff: Task::ready(Ok(())), }) } pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) { if let Self::Pending(diff) = self { diff.reveal_range(range, cx); } } pub fn finalize(&mut self, cx: &mut Context<Self>) { if let Self::Pending(diff) = self { *self = Self::Finalized(diff.finalize(cx)); } } /// Returns the original text before any edits were applied. pub fn base_text(&self) -> &Arc<str> { match self { Self::Pending(PendingDiff { base_text, .. }) => base_text, Self::Finalized(FinalizedDiff { base_text, .. }) => base_text, } } /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs). pub fn buffer(&self) -> &Entity<Buffer> { match self { Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer, Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer, } } pub fn file_path(&self, cx: &App) -> Option<String> { match self { Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer .read(cx) .file() .map(|file| file.full_path(cx).to_string_lossy().into_owned()), Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()), } } pub fn multibuffer(&self) -> &Entity<MultiBuffer> { match self { Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer, Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer, } } pub fn to_markdown(&self, cx: &App) -> String { let buffer_text = self .multibuffer() .read(cx) .all_buffers() .iter() .map(|buffer| buffer.read(cx).text()) .join("\n"); let path = match self { Diff::Pending(PendingDiff { new_buffer: buffer, .. }) => buffer .read(cx) .file() .map(|file| file.path().display(file.path_style(cx))), Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()), }; format!( "Diff: {}\n```\n{}\n```\n", path.unwrap_or("untitled".into()), buffer_text ) } pub fn has_revealed_range(&self, cx: &App) -> bool { self.multibuffer().read(cx).paths().next().is_some() } pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool { match self { Diff::Pending(PendingDiff { base_text, new_buffer, .. }) => { base_text.as_ref() != old_text || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text) } Diff::Finalized(FinalizedDiff { base_text, new_buffer, .. }) => { base_text.as_ref() != old_text || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text) } } } pub fn push_line_operations( &mut self, operations: Vec<LineOperation>, base_snapshot: text::BufferSnapshot, cx: &mut Context<Diff>, ) { if let Diff::Pending(diff) = self { diff.push_line_operations(operations, base_snapshot, cx); } }} enum UpdateStrategy { Auto { _subscription: Subscription, }, Manual { pending_update: Option<PendingUpdate>, },} pub struct PendingDiff { multibuffer: Entity<MultiBuffer>, base_text: Arc<str>, new_buffer: Entity<Buffer>, diff: Entity<BufferDiff>, revealed_ranges: Vec<Range<Anchor>>, update_strategy: UpdateStrategy, update_diff: Task<Result<()>>,} struct PendingUpdate { operations: Vec<LineOperation>, base_snapshot: text::BufferSnapshot, text_snapshot: text::BufferSnapshot,} fn compute_hunks( diff_base: &text::BufferSnapshot, buffer: &text::BufferSnapshot, line_operations: Vec<LineOperation>,) -> Patch<usize> { let mut patch = Patch::default(); let mut old_row = 0u32; let mut new_row = 0u32; // Merge adjacent Delete+Insert into a single Modified hunk let mut pending_delete_lines: Option<u32> = None; let flush_delete = |pending_delete_lines: &mut Option<u32>, old_row: &mut u32, new_row: u32, diff_base: &text::BufferSnapshot, buffer: &text::BufferSnapshot| { if let Some(deleted_lines) = pending_delete_lines.take() { let old_start = diff_base.point_to_offset(Point::new(*old_row, 0).min(diff_base.max_point())); let old_end = diff_base.point_to_offset( Point::new(*old_row + deleted_lines, 0).min(diff_base.max_point()), ); let new_pos = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point())); let edit = Edit { old: old_start..old_end, new: new_pos..new_pos, }; *old_row += deleted_lines; Some(edit) } else { None } }; for operation in line_operations { match operation { LineOperation::Delete { lines } => { // Accumulate deletions — they might be followed by an Insert (= modification) *pending_delete_lines.get_or_insert(0) += lines; } LineOperation::Insert { lines } => { let old_start = diff_base.point_to_offset(Point::new(old_row, 0).min(diff_base.max_point())); let (old_end, deleted_lines) = if let Some(deleted_lines) = pending_delete_lines.take() { // Delete followed by Insert = Modified hunk let old_end = diff_base.point_to_offset( Point::new(old_row + deleted_lines, 0).min(diff_base.max_point()), ); (old_end, deleted_lines) } else { // Pure insertion (old_start, 0) }; let new_start = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point())); let new_end = buffer.point_to_offset(Point::new(new_row + lines, 0).min(buffer.max_point())); patch.push(Edit { old: old_start..old_end, new: new_start..new_end, }); old_row += deleted_lines; new_row += lines; } LineOperation::Keep { lines } => { // Flush any pending deletion before a Keep if let Some(edit) = flush_delete( &mut pending_delete_lines, &mut old_row, new_row, diff_base, buffer, ) { patch.push(edit); } // Keep = unchanged, no hunk to push old_row += lines; new_row += lines; } } } // Flush any trailing deletion if let Some(edit) = flush_delete( &mut pending_delete_lines, &mut old_row, new_row, diff_base, buffer, ) { patch.push(edit); } patch} /// Applies a `BufferDiffUpdate` to both the primary and secondary diffs, then/// refreshes the visible excerpt ranges in the multibuffer.async fn apply_diff_update( update: BufferDiffUpdate, text_snapshot: &text::BufferSnapshot, buffer_diff: &Entity<BufferDiff>, diff: &WeakEntity<Diff>, cx: &mut AsyncApp,) -> Result<()> { // todo: we can get away without having a whole secondary diff here let (task1, task2) = buffer_diff.update(cx, |diff, cx| { let task1 = diff.set_snapshot(update.clone(), text_snapshot, cx); let task2 = diff .secondary_diff() .expect("PendingDiff should always have a secondary diff") .update(cx, |diff, cx| diff.set_snapshot(update, text_snapshot, cx)); (task1, task2) }); task1.await; task2.await; diff.update(cx, |diff, cx| { if let Diff::Pending(diff) = diff { diff.update_visible_ranges(cx); } })} impl PendingDiff { fn push_line_operations( &mut self, operations: Vec<LineOperation>, base_snapshot: text::BufferSnapshot, cx: &mut Context<Diff>, ) { let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else { return; }; let text_snapshot = self.new_buffer.read(cx).text_snapshot(); *pending_update = Some(PendingUpdate { operations, base_snapshot, text_snapshot, }); if self.update_diff.is_ready() { self.flush_pending_update(cx); } } fn auto_update(&mut self, cx: &mut Context<Diff>) { let buffer = self.new_buffer.clone(); let buffer_diff = self.diff.clone(); let base_text = self.base_text.clone(); self.update_diff = cx.spawn(async move |diff, cx| { let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot()); let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned()); let update = buffer_diff .update(cx, |diff, cx| { diff.update_diff( text_snapshot.clone(), Some(base_text.clone()), None, language, cx, ) }) .await; apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await }); } fn flush_pending_update(&mut self, cx: &mut Context<Diff>) { let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else { return; }; let Some(PendingUpdate { operations, base_snapshot, text_snapshot, }) = pending_update.take() else { return; }; let buffer_diff = self.diff.clone(); let base_text = self.base_text.clone(); self.update_diff = cx.spawn(async move |diff, cx| { let update = cx .background_spawn({ let snapshot = text_snapshot.clone(); async move { let hunks = compute_hunks(&base_snapshot, &snapshot, operations); BufferDiffUpdate::from_hunks( base_text, base_snapshot.as_rope(), snapshot, hunks, Some(DiffOptions::default()), ) } }) .await; apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await?; diff.update(cx, |diff, cx| { if let Diff::Pending(diff) = diff { // Pick up any update that arrived while this task was running. diff.flush_pending_update(cx); } }) }); } pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) { self.revealed_ranges.push(range); self.update_visible_ranges(cx); } fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff { let ranges = self.excerpt_ranges(cx); let base_text = self.base_text.clone();Lines 1–500 of 681. Load more
use anyhow::Result; use buffer_diff::{BufferDiff, BufferDiffUpdate}; use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity}; use itertools::Itertools; use language::{ Anchor, Buffer, Capability, DiffOptions, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer, }; use multi_buffer::{MultiBuffer, PathKey, excerpt_context_lines}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use streaming_diff::LineOperation; use text::{Edit, Patch}; use util::ResultExt; pub enum Diff { Pending(PendingDiff), Finalized(FinalizedDiff), } impl Diff { pub fn finalized( path: String, old_text: Option<String>, new_text: String, language_registry: Arc<LanguageRegistry>, cx: &mut Context<Self>, ) -> Self { let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly)); let new_buffer = cx.new(|cx| Buffer::local(new_text, cx)); let base_text = old_text.clone().unwrap_or(String::new()).into(); let task = cx.spawn({ let multibuffer = multibuffer.clone(); let path = path.clone(); let buffer = new_buffer.clone(); async move |_, cx| { let language = language_registry .load_language_for_file_path(Path::new(&path)) .await .log_err(); buffer.update(cx, |buffer, cx| buffer.set_language(language.clone(), cx)); buffer.update(cx, |buffer, _| buffer.parsing_idle()).await; let diff = build_buffer_diff( old_text.unwrap_or("".into()).into(), &buffer, Some(language_registry.clone()), cx, ) .await?; multibuffer.update(cx, |multibuffer, cx| { let hunk_ranges = { let buffer = buffer.read(cx); diff.read(cx) .snapshot(cx) .hunks_intersecting_range( Anchor::min_for_buffer(buffer.remote_id()) ..Anchor::max_for_buffer(buffer.remote_id()), buffer, ) .map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer)) .collect::<Vec<_>>() }; multibuffer.set_excerpts_for_path( PathKey::for_buffer(&buffer, cx), buffer.clone(), hunk_ranges, excerpt_context_lines(cx), cx, ); multibuffer.add_diff(diff, cx); }); anyhow::Ok(()) } }); Self::Finalized(FinalizedDiff { multibuffer, path, base_text, new_buffer, _update_diff: task, }) } pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self { let subscription = cx.observe(&buffer, |this, _, cx| { if let Diff::Pending(diff) = this { diff.auto_update(cx); } }); Self::new_inner( buffer, UpdateStrategy::Auto { _subscription: subscription, }, cx, ) } pub fn manual(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self { Self::new_inner( buffer, UpdateStrategy::Manual { pending_update: None, }, cx, ) } fn new_inner( buffer: Entity<Buffer>, update_strategy: UpdateStrategy, cx: &mut Context<Self>, ) -> Self { let buffer_text_snapshot = buffer.read(cx).text_snapshot(); let language = buffer.read(cx).language().cloned(); let language_registry = buffer.read(cx).language_registry(); let buffer_diff = cx.new(|cx| { let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx); diff.language_changed(language.clone(), language_registry.clone(), cx); let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx)); diff.set_secondary_diff(secondary_diff); diff }); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::without_headers(Capability::ReadOnly); multibuffer.set_all_diff_hunks_expanded(cx); multibuffer.add_diff(buffer_diff.clone(), cx); multibuffer }); Self::Pending(PendingDiff { multibuffer, base_text: Arc::from(buffer_text_snapshot.text().as_str()), new_buffer: buffer, diff: buffer_diff, revealed_ranges: Vec::new(), update_strategy, update_diff: Task::ready(Ok(())), }) } pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) { if let Self::Pending(diff) = self { diff.reveal_range(range, cx); } } pub fn finalize(&mut self, cx: &mut Context<Self>) { if let Self::Pending(diff) = self { *self = Self::Finalized(diff.finalize(cx)); } } /// Returns the original text before any edits were applied. pub fn base_text(&self) -> &Arc<str> { match self { Self::Pending(PendingDiff { base_text, .. }) => base_text, Self::Finalized(FinalizedDiff { base_text, .. }) => base_text, } } /// Returns the buffer being edited (for pending diffs) or the snapshot buffer (for finalized diffs). pub fn buffer(&self) -> &Entity<Buffer> { match self { Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer, Self::Finalized(FinalizedDiff { new_buffer, .. }) => new_buffer, } } pub fn file_path(&self, cx: &App) -> Option<String> { match self { Self::Pending(PendingDiff { new_buffer, .. }) => new_buffer .read(cx) .file() .map(|file| file.full_path(cx).to_string_lossy().into_owned()), Self::Finalized(FinalizedDiff { path, .. }) => Some(path.clone()), } } pub fn multibuffer(&self) -> &Entity<MultiBuffer> { match self { Self::Pending(PendingDiff { multibuffer, .. }) => multibuffer, Self::Finalized(FinalizedDiff { multibuffer, .. }) => multibuffer, } } pub fn to_markdown(&self, cx: &App) -> String { let buffer_text = self .multibuffer() .read(cx) .all_buffers() .iter() .map(|buffer| buffer.read(cx).text()) .join("\n"); let path = match self { Diff::Pending(PendingDiff { new_buffer: buffer, .. }) => buffer .read(cx) .file() .map(|file| file.path().display(file.path_style(cx))), Diff::Finalized(FinalizedDiff { path, .. }) => Some(path.as_str().into()), }; format!( "Diff: {}\n```\n{}\n```\n", path.unwrap_or("untitled".into()), buffer_text ) } pub fn has_revealed_range(&self, cx: &App) -> bool { self.multibuffer().read(cx).paths().next().is_some() } pub fn needs_update(&self, old_text: &str, new_text: &str, cx: &App) -> bool { match self { Diff::Pending(PendingDiff { base_text, new_buffer, .. }) => { base_text.as_ref() != old_text || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text) } Diff::Finalized(FinalizedDiff { base_text, new_buffer, .. }) => { base_text.as_ref() != old_text || !new_buffer.read(cx).as_rope().chunks().equals_str(new_text) } } } pub fn push_line_operations( &mut self, operations: Vec<LineOperation>, base_snapshot: text::BufferSnapshot, cx: &mut Context<Diff>, ) { if let Diff::Pending(diff) = self { diff.push_line_operations(operations, base_snapshot, cx); } } } enum UpdateStrategy { Auto { _subscription: Subscription, }, Manual { pending_update: Option<PendingUpdate>, }, } pub struct PendingDiff { multibuffer: Entity<MultiBuffer>, base_text: Arc<str>, new_buffer: Entity<Buffer>, diff: Entity<BufferDiff>, revealed_ranges: Vec<Range<Anchor>>, update_strategy: UpdateStrategy, update_diff: Task<Result<()>>, } struct PendingUpdate { operations: Vec<LineOperation>, base_snapshot: text::BufferSnapshot, text_snapshot: text::BufferSnapshot, } fn compute_hunks( diff_base: &text::BufferSnapshot, buffer: &text::BufferSnapshot, line_operations: Vec<LineOperation>, ) -> Patch<usize> { let mut patch = Patch::default(); let mut old_row = 0u32; let mut new_row = 0u32; // Merge adjacent Delete+Insert into a single Modified hunk let mut pending_delete_lines: Option<u32> = None; let flush_delete = |pending_delete_lines: &mut Option<u32>, old_row: &mut u32, new_row: u32, diff_base: &text::BufferSnapshot, buffer: &text::BufferSnapshot| { if let Some(deleted_lines) = pending_delete_lines.take() { let old_start = diff_base.point_to_offset(Point::new(*old_row, 0).min(diff_base.max_point())); let old_end = diff_base.point_to_offset( Point::new(*old_row + deleted_lines, 0).min(diff_base.max_point()), ); let new_pos = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point())); let edit = Edit { old: old_start..old_end, new: new_pos..new_pos, }; *old_row += deleted_lines; Some(edit) } else { None } }; for operation in line_operations { match operation { LineOperation::Delete { lines } => { // Accumulate deletions — they might be followed by an Insert (= modification) *pending_delete_lines.get_or_insert(0) += lines; } LineOperation::Insert { lines } => { let old_start = diff_base.point_to_offset(Point::new(old_row, 0).min(diff_base.max_point())); let (old_end, deleted_lines) = if let Some(deleted_lines) = pending_delete_lines.take() { // Delete followed by Insert = Modified hunk let old_end = diff_base.point_to_offset( Point::new(old_row + deleted_lines, 0).min(diff_base.max_point()), ); (old_end, deleted_lines) } else { // Pure insertion (old_start, 0) }; let new_start = buffer.point_to_offset(Point::new(new_row, 0).min(buffer.max_point())); let new_end = buffer.point_to_offset(Point::new(new_row + lines, 0).min(buffer.max_point())); patch.push(Edit { old: old_start..old_end, new: new_start..new_end, }); old_row += deleted_lines; new_row += lines; } LineOperation::Keep { lines } => { // Flush any pending deletion before a Keep if let Some(edit) = flush_delete( &mut pending_delete_lines, &mut old_row, new_row, diff_base, buffer, ) { patch.push(edit); } // Keep = unchanged, no hunk to push old_row += lines; new_row += lines; } } } // Flush any trailing deletion if let Some(edit) = flush_delete( &mut pending_delete_lines, &mut old_row, new_row, diff_base, buffer, ) { patch.push(edit); } patch } /// Applies a `BufferDiffUpdate` to both the primary and secondary diffs, then /// refreshes the visible excerpt ranges in the multibuffer. async fn apply_diff_update( update: BufferDiffUpdate, text_snapshot: &text::BufferSnapshot, buffer_diff: &Entity<BufferDiff>, diff: &WeakEntity<Diff>, cx: &mut AsyncApp, ) -> Result<()> { // todo: we can get away without having a whole secondary diff here let (task1, task2) = buffer_diff.update(cx, |diff, cx| { let task1 = diff.set_snapshot(update.clone(), text_snapshot, cx); let task2 = diff .secondary_diff() .expect("PendingDiff should always have a secondary diff") .update(cx, |diff, cx| diff.set_snapshot(update, text_snapshot, cx)); (task1, task2) }); task1.await; task2.await; diff.update(cx, |diff, cx| { if let Diff::Pending(diff) = diff { diff.update_visible_ranges(cx); } }) } impl PendingDiff { fn push_line_operations( &mut self, operations: Vec<LineOperation>, base_snapshot: text::BufferSnapshot, cx: &mut Context<Diff>, ) { let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else { return; }; let text_snapshot = self.new_buffer.read(cx).text_snapshot(); *pending_update = Some(PendingUpdate { operations, base_snapshot, text_snapshot, }); if self.update_diff.is_ready() { self.flush_pending_update(cx); } } fn auto_update(&mut self, cx: &mut Context<Diff>) { let buffer = self.new_buffer.clone(); let buffer_diff = self.diff.clone(); let base_text = self.base_text.clone(); self.update_diff = cx.spawn(async move |diff, cx| { let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot()); let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned()); let update = buffer_diff .update(cx, |diff, cx| { diff.update_diff( text_snapshot.clone(), Some(base_text.clone()), None, language, cx, ) }) .await; apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await }); } fn flush_pending_update(&mut self, cx: &mut Context<Diff>) { let UpdateStrategy::Manual { pending_update } = &mut self.update_strategy else { return; }; let Some(PendingUpdate { operations, base_snapshot, text_snapshot, }) = pending_update.take() else { return; }; let buffer_diff = self.diff.clone(); let base_text = self.base_text.clone(); self.update_diff = cx.spawn(async move |diff, cx| { let update = cx .background_spawn({ let snapshot = text_snapshot.clone(); async move { let hunks = compute_hunks(&base_snapshot, &snapshot, operations); BufferDiffUpdate::from_hunks( base_text, base_snapshot.as_rope(), snapshot, hunks, Some(DiffOptions::default()), ) } }) .await; apply_diff_update(update, &text_snapshot, &buffer_diff, &diff, cx).await?; diff.update(cx, |diff, cx| { if let Diff::Pending(diff) = diff { // Pick up any update that arrived while this task was running. diff.flush_pending_update(cx); } }) }); } pub fn reveal_range(&mut self, range: Range<Anchor>, cx: &mut Context<Diff>) { self.revealed_ranges.push(range); self.update_visible_ranges(cx); } fn finalize(&self, cx: &mut Context<Diff>) -> FinalizedDiff { let ranges = self.excerpt_ranges(cx); let base_text = self.base_text.clone();