Implement `activate_workspace_for_project`

Antonio Scandurra created

Change summary

crates/gpui2/src/app.rs               | 32 +++++++++++++++-
crates/gpui2/src/app/async_context.rs | 43 ++++++++++++++++++++-
crates/gpui2/src/view.rs              | 34 ++++++++++++-----
crates/gpui2/src/window.rs            | 57 +++++++++++++++++-----------
crates/workspace2/src/workspace2.rs   | 29 ++++++++------
5 files changed, 146 insertions(+), 49 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -17,8 +17,8 @@ use crate::{
     AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Executor,
     FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
     Pixels, Platform, Point, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
-    TextStyle, TextStyleRefinement, TextSystem, View, Window, WindowContext, WindowHandle,
-    WindowId,
+    TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
+    WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -303,6 +303,20 @@ impl AppContext {
         })
     }
 
+    pub fn update_window_root<V, R>(
+        &mut self,
+        handle: &WindowHandle<V>,
+        update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R,
+    ) -> Result<R>
+    where
+        V: 'static,
+    {
+        self.update_window(handle.any_handle, |cx| {
+            let root_view = cx.window.root_view.as_ref().unwrap().downcast().unwrap();
+            root_view.update(cx, update)
+        })
+    }
+
     pub(crate) fn push_effect(&mut self, effect: Effect) {
         match &effect {
             Effect::Notify { emitter } => {
@@ -841,6 +855,20 @@ impl MainThread<AppContext> {
         })
     }
 
+    pub fn update_window_root<V, R>(
+        &mut self,
+        handle: &WindowHandle<V>,
+        update: impl FnOnce(&mut V, &mut MainThread<ViewContext<'_, '_, V>>) -> R,
+    ) -> Result<R>
+    where
+        V: 'static,
+    {
+        self.update_window(handle.any_handle, |cx| {
+            let root_view = cx.window.root_view.as_ref().unwrap().downcast().unwrap();
+            root_view.update(cx, update)
+        })
+    }
+
     /// Opens a new window with the given option and the root view returned by the given function.
     /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
     /// functionality.

crates/gpui2/src/app/async_context.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    AnyWindowHandle, AppContext, Context, Executor, Handle, MainThread, ModelContext, Result, Task,
-    WindowContext,
+    AnyWindowHandle, AppContext, Component, Context, Executor, Handle, MainThread, ModelContext,
+    Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle,
 };
 use anyhow::Context as _;
 use derive_more::{Deref, DerefMut};
@@ -78,6 +78,19 @@ impl AsyncAppContext {
         app_context.update_window(handle, update)
     }
 
+    pub fn update_window_root<V, R>(
+        &mut self,
+        handle: &WindowHandle<V>,
+        update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R,
+    ) -> Result<R>
+    where
+        V: 'static,
+    {
+        let app = self.app.upgrade().context("app was released")?;
+        let mut app_context = app.lock();
+        app_context.update_window_root(handle, update)
+    }
+
     pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut + Send + 'static) -> Task<R>
     where
         Fut: Future<Output = R> + Send + 'static,
@@ -245,6 +258,32 @@ impl Context for AsyncWindowContext {
     }
 }
 
+impl VisualContext for AsyncWindowContext {
+    type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>;
+
+    fn build_view<E, V>(
+        &mut self,
+        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
+        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+    ) -> Self::Result<View<V>>
+    where
+        E: Component<V>,
+        V: 'static + Send,
+    {
+        self.app
+            .update_window(self.window, |cx| cx.build_view(build_entity, render))
+    }
+
+    fn update_view<V: 'static, R>(
+        &mut self,
+        view: &View<V>,
+        update: impl FnOnce(&mut V, &mut Self::ViewContext<'_, '_, V>) -> R,
+    ) -> Self::Result<R> {
+        self.app
+            .update_window(self.window, |cx| cx.update_view(view, update))
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

crates/gpui2/src/view.rs 🔗

@@ -1,11 +1,12 @@
 use crate::{
     AnyBox, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
-    EntityId, Handle, LayoutId, Pixels, Size, ViewContext, VisualContext, WeakHandle,
+    EntityId, Flatten, Handle, LayoutId, Pixels, Size, ViewContext, VisualContext, WeakHandle,
     WindowContext,
 };
 use anyhow::{Context, Result};
 use parking_lot::Mutex;
 use std::{
+    any::Any,
     marker::PhantomData,
     sync::{Arc, Weak},
 };
@@ -128,13 +129,17 @@ impl<V: 'static> WeakView<V> {
         Some(View { state, render })
     }
 
-    pub fn update<R>(
+    pub fn update<C, R>(
         &self,
-        cx: &mut WindowContext,
-        f: impl FnOnce(&mut V, &mut ViewContext<V>) -> R,
-    ) -> Result<R> {
+        cx: &mut C,
+        f: impl FnOnce(&mut V, &mut C::ViewContext<'_, '_, V>) -> R,
+    ) -> Result<R>
+    where
+        C: VisualContext,
+        Result<C::Result<R>>: Flatten<R>,
+    {
         let view = self.upgrade().context("error upgrading view")?;
-        Ok(view.update(cx, f))
+        Ok(view.update(cx, f)).flatten()
     }
 }
 
@@ -201,15 +206,16 @@ trait ViewObject: Send + Sync {
     fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
     fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
     fn paint(&self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
+    fn as_any(&self) -> &dyn Any;
 }
 
 impl<V: 'static> ViewObject for View<V> {
     fn entity_id(&self) -> EntityId {
-        self.state.entity_id()
+        self.state.entity_id
     }
 
     fn initialize(&self, cx: &mut WindowContext) -> AnyBox {
-        cx.with_element_id(self.entity_id(), |_global_id, cx| {
+        cx.with_element_id(self.state.entity_id, |_global_id, cx| {
             self.update(cx, |state, cx| {
                 let mut any_element = Box::new((self.render.lock())(state, cx));
                 any_element.initialize(state, cx);
@@ -219,7 +225,7 @@ impl<V: 'static> ViewObject for View<V> {
     }
 
     fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
-        cx.with_element_id(self.entity_id(), |_global_id, cx| {
+        cx.with_element_id(self.state.entity_id, |_global_id, cx| {
             self.update(cx, |state, cx| {
                 let element = element.downcast_mut::<AnyElement<V>>().unwrap();
                 element.layout(state, cx)
@@ -228,19 +234,27 @@ impl<V: 'static> ViewObject for View<V> {
     }
 
     fn paint(&self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
-        cx.with_element_id(self.entity_id(), |_global_id, cx| {
+        cx.with_element_id(self.state.entity_id, |_global_id, cx| {
             self.update(cx, |state, cx| {
                 let element = element.downcast_mut::<AnyElement<V>>().unwrap();
                 element.paint(state, cx);
             });
         });
     }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
 }
 
 #[derive(Clone)]
 pub struct AnyView(Arc<dyn ViewObject>);
 
 impl AnyView {
+    pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
+        self.0.as_any().downcast_ref().cloned()
+    }
+
     pub(crate) fn draw(&self, available_space: Size<AvailableSpace>, cx: &mut WindowContext) {
         let mut rendered_element = self.0.initialize(cx);
         let layout_id = self.0.layout(&mut rendered_element, cx);

crates/gpui2/src/window.rs 🔗

@@ -1237,16 +1237,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     }
 }
 
-impl<'a, 'w> MainThread<WindowContext<'a, 'w>> {
-    fn platform_window(&self) -> &dyn PlatformWindow {
-        self.window.platform_window.borrow_on_main_thread().as_ref()
-    }
-
-    pub fn activate_window(&self) {
-        self.platform_window().activate();
-    }
-}
-
 impl Context for WindowContext<'_, '_> {
     type EntityContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
@@ -1864,6 +1854,16 @@ where
     }
 }
 
+impl<'a, 'w, V: 'static> MainThread<ViewContext<'a, 'w, V>> {
+    fn platform_window(&self) -> &dyn PlatformWindow {
+        self.window.platform_window.borrow_on_main_thread().as_ref()
+    }
+
+    pub fn activate_window(&self) {
+        self.platform_window().activate();
+    }
+}
+
 impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
     type EntityContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
@@ -1934,38 +1934,40 @@ impl WindowId {
     }
 }
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Deref, DerefMut)]
 pub struct WindowHandle<V> {
-    id: WindowId,
+    #[deref]
+    #[deref_mut]
+    pub(crate) any_handle: AnyWindowHandle,
     state_type: PhantomData<V>,
 }
 
-impl<S> Copy for WindowHandle<S> {}
+impl<V> Copy for WindowHandle<V> {}
 
-impl<S> Clone for WindowHandle<S> {
+impl<V> Clone for WindowHandle<V> {
     fn clone(&self) -> Self {
         WindowHandle {
-            id: self.id,
+            any_handle: self.any_handle,
             state_type: PhantomData,
         }
     }
 }
 
-impl<S> WindowHandle<S> {
+impl<V: 'static> WindowHandle<V> {
     pub fn new(id: WindowId) -> Self {
         WindowHandle {
-            id,
+            any_handle: AnyWindowHandle {
+                id,
+                state_type: TypeId::of::<V>(),
+            },
             state_type: PhantomData,
         }
     }
 }
 
-impl<S: 'static> Into<AnyWindowHandle> for WindowHandle<S> {
+impl<V: 'static> Into<AnyWindowHandle> for WindowHandle<V> {
     fn into(self) -> AnyWindowHandle {
-        AnyWindowHandle {
-            id: self.id,
-            state_type: TypeId::of::<S>(),
-        }
+        self.any_handle
     }
 }
 
@@ -1979,6 +1981,17 @@ impl AnyWindowHandle {
     pub fn window_id(&self) -> WindowId {
         self.id
     }
+
+    pub fn downcast<T: 'static>(&self) -> Option<WindowHandle<T>> {
+        if TypeId::of::<T>() == self.state_type {
+            Some(WindowHandle {
+                any_handle: *self,
+                state_type: PhantomData,
+            })
+        } else {
+            None
+        }
+    }
 }
 
 #[cfg(any(test, feature = "test-support"))]

crates/workspace2/src/workspace2.rs 🔗

@@ -4103,25 +4103,28 @@ pub struct Workspace {
 pub async fn activate_workspace_for_project(
     cx: &mut AsyncAppContext,
     predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
-) -> Option<WeakHandle<Workspace>> {
+) -> Option<WindowHandle<Workspace>> {
     cx.run_on_main(move |cx| {
         for window in cx.windows() {
-            let handle = cx
-                .update_window(window, |cx| {
-                    if let Some(workspace_handle) = cx.root_view()?.downcast::<Workspace>() {
-                        let project = workspace_handle.read(cx).project.clone();
-                        if project.update(cx, |project, cx| predicate(project, cx)) {
-                            cx.activate_window();
-                            return Some(workspace_handle.clone());
-                        }
+            let Some(workspace) = window.downcast::<Workspace>() else {
+                continue;
+            };
+
+            let predicate = cx
+                .update_window_root(&workspace, |workspace, cx| {
+                    let project = workspace.project.read(cx);
+                    if predicate(project, cx) {
+                        cx.activate_window();
+                        true
+                    } else {
+                        false
                     }
-                    None
                 })
                 .log_err()
-                .flatten();
+                .unwrap_or(false);
 
-            if let Some(handle) = handle {
-                return Some(handle.downgrade());
+            if predicate {
+                return Some(workspace);
             }
         }