Fix unnecessarily large edits emitted from multi buffer on diff recalculation (#23753)

This fixes an issue introduced in #22994 where soft wrap would
recalculate for the entire buffer when editing.

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Max Brunsfeld 2025-01-27 18:11:15 -08:00 committed by GitHub
parent 5331418f3a
commit ee5f270f3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 402 additions and 251 deletions

View file

@ -69,6 +69,10 @@ pub struct BufferChangeSet {
pub language_registry: Option<Arc<LanguageRegistry>>,
}
pub enum BufferChangeSetEvent {
DiffChanged { changed_range: Range<text::Anchor> },
}
enum BufferStoreState {
Local(LocalBufferStore),
Remote(RemoteBufferStore),
@ -2201,6 +2205,8 @@ impl BufferStore {
}
}
impl EventEmitter<BufferChangeSetEvent> for BufferChangeSet {}
impl BufferChangeSet {
pub fn new(buffer: &Entity<Buffer>, cx: &mut Context<Self>) -> Self {
cx.subscribe(buffer, |this, buffer, event, cx| match event {
@ -2318,69 +2324,53 @@ impl BufferChangeSet {
let (tx, rx) = oneshot::channel();
self.diff_updated_futures.push(tx);
self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move {
let new_base_text = if base_text_changed {
let base_text_rope: Rope = base_text.as_str().into();
let snapshot = this.update(&mut cx, |this, cx| {
language::Buffer::build_snapshot(
let (old_diff, new_base_text) = this.update(&mut cx, |this, cx| {
let new_base_text = if base_text_changed {
let base_text_rope: Rope = base_text.as_str().into();
let snapshot = language::Buffer::build_snapshot(
base_text_rope,
this.language.clone(),
this.language_registry.clone(),
cx,
)
})?;
Some(cx.background_executor().spawn(snapshot).await)
} else {
None
};
let diff = cx
.background_executor()
.spawn({
let buffer_snapshot = buffer_snapshot.clone();
async move { BufferDiff::build(&base_text, &buffer_snapshot) }
})
.await;
);
cx.background_executor()
.spawn(async move { Some(snapshot.await) })
} else {
Task::ready(None)
};
(this.diff_to_buffer.clone(), new_base_text)
})?;
let diff = cx.background_executor().spawn(async move {
let new_diff = BufferDiff::build(&base_text, &buffer_snapshot);
let changed_range = if base_text_changed {
Some(text::Anchor::MIN..text::Anchor::MAX)
} else {
new_diff.compare(&old_diff, &buffer_snapshot)
};
(new_diff, changed_range)
});
let (new_base_text, (diff, changed_range)) = futures::join!(new_base_text, diff);
this.update(&mut cx, |this, cx| {
if let Some(new_base_text) = new_base_text {
this.base_text = Some(new_base_text)
}
this.diff_to_buffer = diff;
this.recalculate_diff_task.take();
for tx in this.diff_updated_futures.drain(..) {
tx.send(()).ok();
}
cx.notify();
if let Some(changed_range) = changed_range {
cx.emit(BufferChangeSetEvent::DiffChanged { changed_range });
}
})?;
Ok(())
}));
rx
}
#[cfg(any(test, feature = "test-support"))]
pub fn recalculate_diff_sync(
&mut self,
mut base_text: String,
buffer_snapshot: text::BufferSnapshot,
base_text_changed: bool,
cx: &mut Context<Self>,
) {
LineEnding::normalize(&mut base_text);
let diff = BufferDiff::build(&base_text, &buffer_snapshot);
if base_text_changed {
self.base_text = Some(
cx.background_executor()
.clone()
.block(Buffer::build_snapshot(
base_text.into(),
self.language.clone(),
self.language_registry.clone(),
cx,
)),
);
}
self.diff_to_buffer = diff;
self.recalculate_diff_task.take();
cx.notify();
}
}
impl OpenBuffer {