buffer_diff: Ensure that base text has finished parsing before completing update (#46054)

Follow-up to #46001 

That initial fix partly addressed the issue for diffs managed by the
`GitStore`, but not for other diffs (e.g. those managed by the
`ActionLog` or `CommitView`). The underlying issue is that we switched
to using a `Buffer` to represent the diff base text, and when updating
the diff we were calling `set_text` on this buffer and not waiting for
reparsing to finish. When the base text was represented by a series of
independent `BufferSnapshot`s, this wasn't an issue because we would
parse the base text in the background as part of computing the diff
update. This PR fixes the issue by waiting on reparsing to finish after
each call to `set_text`.

Release Notes:

- N/A
This commit is contained in:
Cole Miller authored and GitHub committed 2026-01-05 00:56:58 +00:00
1 parent 851aa9eb40
commit 88a0b310d6
9 files changed
+103 -74

No files matched your search

+41 -32
View File
@@ -1148,7 +1148,7 @@ impl BufferDiff {
None,
cx,
));
this.set_snapshot(inner, &buffer, cx);
this.set_snapshot(inner, &buffer, cx).detach();
this
}
@@ -1293,26 +1293,19 @@ impl BufferDiff {
language_registry: Option<Arc<LanguageRegistry>>,
cx: &mut Context<Self>,
) {
let base_text = self.inner.base_text.downgrade();
let fut = self.inner.base_text.update(cx, |base_text, cx| {
if let Some(language_registry) = language_registry {
base_text.set_language_registry(language_registry);
}
base_text.set_language(language, cx);
base_text.parsing_idle()
});
cx.spawn(async move |this, cx| {
let fut = base_text
.update(cx, |base_text, cx| {
if let Some(language_registry) = language_registry {
base_text.set_language_registry(language_registry);
}
base_text.set_language(language, cx);
base_text.parsing_idle()
})
.ok()?;
fut.await;
this.update(cx, |_, cx| {
cx.emit(BufferDiffEvent::LanguageChanged);
})
.ok()?;
Some(())
.ok();
})
.detach();
}
@@ -1322,7 +1315,7 @@ impl BufferDiff {
new_state: BufferDiffUpdate,
buffer: &text::BufferSnapshot,
cx: &mut Context<Self>,
) -> Option<Range<Anchor>> {
) -> Task<Option<Range<Anchor>>> {
self.set_snapshot_with_secondary(new_state, buffer, None, false, cx)
}
@@ -1333,7 +1326,7 @@ impl BufferDiff {
secondary_diff_change: Option<Range<Anchor>>,
clear_pending_hunks: bool,
cx: &mut Context<Self>,
) -> Option<Range<Anchor>> {
) -> Task<Option<Range<Anchor>>> {
log::debug!("set snapshot with secondary {secondary_diff_change:?}");
let old_snapshot = self.snapshot(cx);
@@ -1372,13 +1365,16 @@ impl BufferDiff {
let state = &mut self.inner;
state.base_text_exists = new_state.base_text_exists;
if update.base_text_changed {
let parsing_idle = if update.base_text_changed {
state.base_text.update(cx, |base_text, cx| {
base_text.set_capability(Capability::ReadWrite, cx);
base_text.set_text(new_state.base_text.clone(), cx);
base_text.set_capability(Capability::ReadOnly, cx);
Some(base_text.parsing_idle())
})
}
} else {
None
};
state.hunks = new_state.hunks;
if update.base_text_changed || clear_pending_hunks {
if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
@@ -1402,11 +1398,19 @@ impl BufferDiff {
state.pending_hunks = SumTree::new(buffer);
}
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: changed_range.clone(),
base_text_changed_range,
});
changed_range
cx.spawn(async move |this, cx| {
if let Some(parsing_idle) = parsing_idle {
parsing_idle.await;
}
this.update(cx, |_, cx| {
cx.emit(BufferDiffEvent::DiffChanged {
changed_range: changed_range.clone(),
base_text_changed_range,
});
})
.ok();
changed_range
})
}
pub fn base_text(&self, cx: &App) -> language::BufferSnapshot {
@@ -1454,10 +1458,12 @@ impl BufferDiff {
return;
};
let state = state.await;
this.update(cx, |this, cx| {
this.set_snapshot(state, &buffer, cx);
})
.log_err();
if let Some(task) = this
.update(cx, |this, cx| this.set_snapshot(state, &buffer, cx))
.log_err()
{
task.await;
}
drop(complete_on_drop)
})
.detach();
@@ -1475,8 +1481,9 @@ impl BufferDiff {
let language = self.base_text(cx).language().cloned();
let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
let snapshot = cx.background_executor().block(fut);
self.set_snapshot(snapshot, &buffer, cx);
let executor = cx.background_executor().clone();
let snapshot = executor.block(fut);
self.set_snapshot(snapshot, &buffer, cx).detach();
}
pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
@@ -2589,6 +2596,7 @@ mod tests {
let diff = cx.new(|cx| {
BufferDiff::new_with_base_text(&base_text, &buffer.read(cx).text_snapshot(), cx)
});
cx.run_until_parked();
let (tx, rx) = mpsc::channel();
let subscription =
cx.update(|cx| cx.subscribe(&diff, move |_, event, _| tx.send(event.clone()).unwrap()));
@@ -2619,7 +2627,8 @@ mod tests {
)
})
.await;
diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx));
diff.update(cx, |diff, cx| diff.set_snapshot(update, &snapshot, cx))
.await;
cx.run_until_parked();
drop(subscription);
let events = rx.into_iter().collect::<Vec<_>>();