Project level git diff recalc handling

This avoids an issue where in a many-buffer multi-buffer, each modified
buffer could complete its recalc independently, causing a cascade of
repeated notifies

Now all recalcs started at the same time must complete before
 A: Starting another recalc pass
 B: The master notify occurring

Each buffer can still show its new diff if something else triggers it
to notify earlier, this is desirable and does not have the same negative
effects as the notify cascade as those re-layouts would need to happen
anyway

Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Julia 2023-05-18 18:25:58 -04:00
parent 54421b11f3
commit cede296b04
4 changed files with 64 additions and 37 deletions

View file

@ -343,15 +343,15 @@ impl MultiBuffer {
self.read(cx).symbols_containing(offset, theme) self.read(cx).symbols_containing(offset, theme)
} }
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) { pub fn git_diff_recalc(&mut self, _: &mut ModelContext<Self>) {
let buffers = self.buffers.borrow(); // let buffers = self.buffers.borrow();
for buffer_state in buffers.values() { // for buffer_state in buffers.values() {
if buffer_state.buffer.read(cx).needs_git_diff_recalc() { // if buffer_state.buffer.read(cx).needs_git_diff_recalc() {
buffer_state // buffer_state
.buffer // .buffer
.update(cx, |buffer, cx| buffer.git_diff_recalc(cx)) // .update(cx, |buffer, cx| buffer.git_diff_recalc(cx))
} // }
} // }
} }
pub fn edit<I, S, T>( pub fn edit<I, S, T>(

View file

@ -682,21 +682,29 @@ impl Buffer {
self.git_diff_status.diff.needs_update(self) self.git_diff_status.diff.needs_update(self)
} }
fn git_diff_recalc_2(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> { pub fn git_diff_recalc_2(&mut self, cx: &mut ModelContext<Self>) -> Option<Task<()>> {
let diff_base = &self.diff_base?; let diff_base = self.diff_base.clone()?; // TODO: Make this an Arc
let snapshot = self.snapshot(); let snapshot = self.snapshot();
let handle = cx.weak_handle();
let mut diff = self.git_diff_status.diff.clone(); let mut diff = self.git_diff_status.diff.clone();
Some(cx.background().spawn(async move { let diff = cx.background().spawn(async move {
diff.update(&diff_base, &snapshot).await; diff.update(&diff_base, &snapshot).await;
if let Some(this) = handle.upgrade(cx) { diff
// this.update(cx) });
let handle = cx.weak_handle();
Some(cx.spawn_weak(|_, mut cx| async move {
let buffer_diff = diff.await;
if let Some(this) = handle.upgrade(&mut cx) {
this.update(&mut cx, |this, _| {
this.git_diff_status.diff = buffer_diff;
this.git_diff_update_count += 1;
})
} }
})) }))
} }
pub fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) { fn git_diff_recalc(&mut self, cx: &mut ModelContext<Self>) {
if self.git_diff_status.update_in_progress { if self.git_diff_status.update_in_progress {
self.git_diff_status.update_requested = true; self.git_diff_status.update_requested = true;
return; return;

View file

@ -1610,6 +1610,7 @@ impl Project {
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Result<()> { ) -> Result<()> {
self.request_buffer_diff_recalculation(buffer, cx);
buffer.update(cx, |buffer, _| { buffer.update(cx, |buffer, _| {
buffer.set_language_registry(self.languages.clone()) buffer.set_language_registry(self.languages.clone())
}); });
@ -1928,17 +1929,7 @@ impl Project {
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Option<()> { ) -> Option<()> {
if matches!(event, BufferEvent::Edited { .. } | BufferEvent::Reloaded) { if matches!(event, BufferEvent::Edited { .. } | BufferEvent::Reloaded) {
self.buffers_needing_diff.insert(buffer.downgrade()); self.request_buffer_diff_recalculation(&buffer, cx);
if self.buffers_needing_diff.len() == 1 {
let this = cx.weak_handle();
cx.defer(move |cx| {
if let Some(this) = this.upgrade(cx) {
this.update(cx, |this, cx| {
this.recalculate_buffer_diffs(cx);
});
}
});
}
} }
match event { match event {
@ -2080,11 +2071,33 @@ impl Project {
None None
} }
fn request_buffer_diff_recalculation(
&mut self,
buffer: &ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) {
self.buffers_needing_diff.insert(buffer.downgrade());
if self.buffers_needing_diff.len() == 1 {
let this = cx.weak_handle();
cx.defer(move |cx| {
if let Some(this) = this.upgrade(cx) {
this.update(cx, |this, cx| {
this.recalculate_buffer_diffs(cx);
});
}
});
}
}
fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) { fn recalculate_buffer_diffs(&mut self, cx: &mut ModelContext<Self>) {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let tasks: Vec<_> = this.update(&mut cx, |this, cx| { let buffers: Vec<_> = this.update(&mut cx, |this, _| {
this.buffers_needing_diff this.buffers_needing_diff.drain().collect()
.drain() });
let tasks: Vec<_> = this.update(&mut cx, |_, cx| {
buffers
.iter()
.filter_map(|buffer| { .filter_map(|buffer| {
let buffer = buffer.upgrade(cx)?; let buffer = buffer.upgrade(cx)?;
buffer.update(cx, |buffer, cx| buffer.git_diff_recalc_2(cx)) buffer.update(cx, |buffer, cx| buffer.git_diff_recalc_2(cx))
@ -2092,11 +2105,18 @@ impl Project {
.collect() .collect()
}); });
let updates = futures::future::join_all(tasks).await; futures::future::join_all(tasks).await;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if !this.buffers_needing_diff.is_empty() { if !this.buffers_needing_diff.is_empty() {
this.recalculate_buffer_diffs(cx); this.recalculate_buffer_diffs(cx);
} else {
// TODO: Would a `ModelContext<Project>.notify()` suffice here?
for buffer in buffers {
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, |_, cx| cx.notify());
}
}
} }
}); });
}) })
@ -6229,11 +6249,13 @@ impl Project {
let Some(this) = this.upgrade(&cx) else { let Some(this) = this.upgrade(&cx) else {
return Err(anyhow!("project dropped")); return Err(anyhow!("project dropped"));
}; };
let buffer = this.read_with(&cx, |this, cx| { let buffer = this.read_with(&cx, |this, cx| {
this.opened_buffers this.opened_buffers
.get(&id) .get(&id)
.and_then(|buffer| buffer.upgrade(cx)) .and_then(|buffer| buffer.upgrade(cx))
}); });
if let Some(buffer) = buffer { if let Some(buffer) = buffer {
break buffer; break buffer;
} else if this.read_with(&cx, |this, _| this.is_read_only()) { } else if this.read_with(&cx, |this, _| this.is_read_only()) {
@ -6244,12 +6266,13 @@ impl Project {
this.incomplete_remote_buffers.entry(id).or_default(); this.incomplete_remote_buffers.entry(id).or_default();
}); });
drop(this); drop(this);
opened_buffer_rx opened_buffer_rx
.next() .next()
.await .await
.ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?; .ok_or_else(|| anyhow!("project dropped while waiting for buffer"))?;
}; };
buffer.update(&mut cx, |buffer, cx| buffer.git_diff_recalc(cx));
Ok(buffer) Ok(buffer)
}) })
} }

View file

@ -719,11 +719,7 @@ impl LocalWorktree {
.background() .background()
.spawn(async move { text::Buffer::new(0, id, contents) }) .spawn(async move { text::Buffer::new(0, id, contents) })
.await; .await;
Ok(cx.add_model(|cx| { Ok(cx.add_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file)))))
let mut buffer = Buffer::build(text_buffer, diff_base, Some(Arc::new(file)));
buffer.git_diff_recalc(cx);
buffer
}))
}) })
} }