From 8b04c5d3acea27a7f5c2c2e1a898662c898cfa03 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 22 Jan 2022 13:14:25 -0700 Subject: [PATCH 1/3] Add a ViewContext::defer This takes a closure that will be enqueued as an effect to ensure there are no entities on the stack. --- crates/gpui/src/app.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 11a8760700ba4323ad6ffe2ddc6ba7edf8b7d7ea..37b73b9ca0f45265133c5e700b69a7911e3a3dae 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1060,6 +1060,10 @@ impl MutableAppContext { } } + fn defer(&mut self, callback: Box) { + self.pending_effects.push_back(Effect::Deferred(callback)) + } + pub(crate) fn notify_model(&mut self, model_id: usize) { if self.pending_notifications.insert(model_id) { self.pending_effects @@ -1443,6 +1447,7 @@ impl MutableAppContext { Effect::ViewNotification { window_id, view_id } => { self.notify_view_observers(window_id, view_id) } + Effect::Deferred(callback) => callback(self), Effect::Release { entity_id } => self.notify_release_observers(entity_id), Effect::Focus { window_id, view_id } => { self.focus(window_id, view_id); @@ -1876,6 +1881,7 @@ pub enum Effect { window_id: usize, view_id: usize, }, + Deferred(Box), Release { entity_id: usize, }, @@ -1905,6 +1911,7 @@ impl Debug for Effect { .field("window_id", window_id) .field("view_id", view_id) .finish(), + Effect::Deferred(_) => f.debug_struct("Effect::Deferred").finish(), Effect::Release { entity_id } => f .debug_struct("Effect::Release") .field("entity_id", entity_id) @@ -2410,6 +2417,15 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.notify_view(self.window_id, self.view_id); } + pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut T, &mut ViewContext)) { + let handle = self.handle(); + self.app.defer(Box::new(move |cx| { + handle.update(cx, |view, cx| { + callback(view, cx); + }) + })) + } + pub fn propagate_action(&mut self) { self.halt_action_dispatch = false; } From b755b2d6024615852cc7d70f815293f10479c499 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 22 Jan 2022 13:21:59 -0700 Subject: [PATCH 2/3] Add ViewHandle::defer It's like update, but happens after the current effect instead of synchronously. Also, it doesn't allow the callback to return a value because there would be nothing to do with it. --- crates/gpui/src/app.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 37b73b9ca0f45265133c5e700b69a7911e3a3dae..31efb4991ea73b1b83cc03fbdd6546acb4d126e2 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -2938,6 +2938,17 @@ impl ViewHandle { }) } + pub fn defer(&self, cx: &mut C, update: F) + where + C: AsMut, + F: 'static + FnOnce(&mut T, &mut ViewContext), + { + let this = self.clone(); + cx.as_mut().defer(Box::new(move |cx| { + this.update(cx, |view, cx| update(view, cx)); + })); + } + pub fn is_focused(&self, cx: &AppContext) -> bool { cx.focused_view_id(self.window_id) .map_or(false, |focused_id| focused_id == self.view_id) From e61a5b172cc1f6c2ff9e1b5ad65a9c85828bdd61 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 22 Jan 2022 13:23:08 -0700 Subject: [PATCH 3/3] Defer pane interaction when opening excerpts in diagnostics view Activating a new item causes the current item to be deactivated. We're the current item, but we're on the stack, so we panic if we try to do this synchronously. If we use defer to wait until we're off the stack it works. --- crates/diagnostics/src/diagnostics.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index eedb77c9be1fae401b1dfdf829fec775c501aa49..62fc3bf321809b6074a59552b84ec290d3c8f0f7 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -193,7 +193,10 @@ impl ProjectDiagnosticsEditor { } } - workspace.update(cx, |workspace, cx| { + // We defer the pane interaction because we ourselves are a workspace item + // and activating a new item causes the pane to call a method on us reentrantly, + // which panics if we're on the stack. + workspace.defer(cx, |workspace, cx| { for (buffer, ranges) in new_selections_by_buffer { let buffer = BufferItemHandle(buffer); if !workspace.activate_pane_for_item(&buffer, cx) {