WIP

Nathan Sobo created

Change summary

crates/gpui/src/app.rs                      | 186 +++++++++++++++++++---
crates/gpui/src/app/test_app_context.rs     |  59 ++++--
crates/gpui/src/app/window.rs               |  16 +
crates/gpui/src/app/window_input_handler.rs |  27 +-
crates/workspace/src/workspace.rs           |  11 -
5 files changed, 221 insertions(+), 78 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -133,12 +133,18 @@ pub trait BorrowAppContext {
 pub trait BorrowWindowContext {
     type Result<T>;
 
-    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    fn read_window<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&WindowContext) -> T;
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>;
     fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T;
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>;
 }
 
 #[derive(Clone)]
@@ -449,13 +455,22 @@ impl BorrowAppContext for AsyncAppContext {
 impl BorrowWindowContext for AsyncAppContext {
     type Result<T> = Option<T>;
 
-    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    fn read_window<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&WindowContext) -> T,
     {
         self.0.borrow().read_with(|cx| cx.read_window(window_id, f))
     }
 
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        self.0
+            .borrow_mut()
+            .update(|cx| cx.read_window_optional(window_id, f))
+    }
+
     fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T,
@@ -464,6 +479,15 @@ impl BorrowWindowContext for AsyncAppContext {
             .borrow_mut()
             .update(|cx| cx.update_window(window_id, f))
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        self.0
+            .borrow_mut()
+            .update(|cx| cx.update_window_optional(window_id, f))
+    }
 }
 
 type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
@@ -1303,13 +1327,14 @@ impl AppContext {
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window = WindowHandle::<V>::new(post_inc(&mut this.next_id));
+            let window_id = post_inc(&mut this.next_id);
             let platform_window =
                 this.platform
-                    .open_window(window, window_options, this.foreground.clone());
-            let window = this.build_window(window, platform_window, build_root_view);
-            this.windows.insert(window.into(), window);
-            window
+                    .open_window(window_id, window_options, this.foreground.clone());
+            let handle = WindowHandle::<V>::new(window_id);
+            let window = this.build_window(handle, platform_window, build_root_view);
+            this.windows.insert(window_id, window);
+            handle
         })
     }
 
@@ -1319,11 +1344,11 @@ impl AppContext {
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
-            let platform_window = this.platform.add_status_item(handle.id());
+            let window_id = post_inc(&mut this.next_id);
+            let platform_window = this.platform.add_status_item(window_id);
+            let handle = WindowHandle::<V>::new(window_id);
             let window = this.build_window(handle, platform_window, build_root_view);
-
-            this.windows.insert(handle.into(), window);
+            this.windows.insert(window_id, window);
             handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
             handle
         })
@@ -1340,11 +1365,14 @@ impl AppContext {
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
+        let handle: AnyWindowHandle = handle.into();
+        let window_id = handle.id();
+
         {
             let mut app = self.upgrade();
 
             platform_window.on_event(Box::new(move |event| {
-                app.update_window(handle, |cx| {
+                app.update_window(window_id, |cx| {
                     if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
                         if cx.dispatch_keystroke(keystroke) {
                             return true;
@@ -1360,35 +1388,35 @@ impl AppContext {
         {
             let mut app = self.upgrade();
             platform_window.on_active_status_change(Box::new(move |is_active| {
-                app.update(|cx| cx.window_changed_active_status(handle, is_active))
+                app.update(|cx| cx.window_changed_active_status(window_id, is_active))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_resize(Box::new(move || {
-                app.update(|cx| cx.window_was_resized(handle))
+                app.update(|cx| cx.window_was_resized(window_id))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_moved(Box::new(move || {
-                app.update(|cx| cx.window_was_moved(handle))
+                app.update(|cx| cx.window_was_moved(window_id))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
-                app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
+                app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_close(Box::new(move || {
-                app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
+                app.update(|cx| cx.update_window(window_id, |cx| cx.remove_window()));
             }));
         }
 
@@ -1400,11 +1428,11 @@ impl AppContext {
 
         platform_window.set_input_handler(Box::new(WindowInputHandler {
             app: self.upgrade().0,
-            window_id: handle,
+            window: handle,
         }));
 
-        let mut window = Window::new(handle, platform_window, self, build_root_view);
-        let mut cx = WindowContext::mutable(self, &mut window, handle);
+        let mut window = Window::new(window_id, platform_window, self, build_root_view);
+        let mut cx = WindowContext::mutable(self, &mut window, window_id);
         cx.layout(false).expect("initial layout should not error");
         let scene = cx.paint().expect("initial paint should not error");
         window.platform_window.present_scene(scene);
@@ -2156,13 +2184,20 @@ impl BorrowAppContext for AppContext {
 impl BorrowWindowContext for AppContext {
     type Result<T> = Option<T>;
 
-    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    fn read_window<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&WindowContext) -> T,
     {
         AppContext::read_window(self, window_id, f)
     }
 
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        AppContext::read_window(self, window_id, f).flatten()
+    }
+
     fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T,
@@ -2177,6 +2212,13 @@ impl BorrowWindowContext for AppContext {
             Some(result)
         })
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        AppContext::update_window(self, window_id, f).flatten()
+    }
 }
 
 #[derive(Debug)]
@@ -3379,8 +3421,15 @@ impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
 impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_window_with(&*self.window_context, window_id, f)
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+        BorrowWindowContext::read_window(&*self.window_context, window_id, f)
+    }
+
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window_optional(&*self.window_context, window_id, f)
     }
 
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
@@ -3390,6 +3439,13 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
     ) -> T {
         BorrowWindowContext::update_window(&mut *self.window_context, window_id, f)
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window_optional(&mut *self.window_context, window_id, f)
+    }
 }
 
 pub struct LayoutContext<'a, 'b, 'c, V: View> {
@@ -3490,8 +3546,15 @@ impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
 impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_window_with(&*self.view_context, window_id, f)
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+        BorrowWindowContext::read_window(&*self.view_context, window_id, f)
+    }
+
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window_optional(&*self.view_context, window_id, f)
     }
 
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
@@ -3501,6 +3564,13 @@ impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
     ) -> T {
         BorrowWindowContext::update_window(&mut *self.view_context, window_id, f)
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window_optional(&mut *self.view_context, window_id, f)
+    }
 }
 
 pub struct EventContext<'a, 'b, 'c, V: View> {
@@ -3548,8 +3618,15 @@ impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
 impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_window_with(&*self.view_context, window_id, f)
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+        BorrowWindowContext::read_window(&*self.view_context, window_id, f)
+    }
+
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window_optional(&*self.view_context, window_id, f)
     }
 
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
@@ -3559,6 +3636,13 @@ impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
     ) -> T {
         BorrowWindowContext::update_window(&mut *self.view_context, window_id, f)
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window_optional(&mut *self.view_context, window_id, f)
+    }
 }
 
 pub(crate) enum Reference<'a, T> {
@@ -3841,13 +3925,24 @@ impl<T> Clone for WeakModelHandle<T> {
 
 impl<T> Copy for WeakModelHandle<T> {}
 
-#[derive(Deref, Copy, Clone)]
-pub struct WindowHandle<T> {
+#[derive(Deref)]
+pub struct WindowHandle<V> {
     #[deref]
     any_handle: AnyWindowHandle,
-    root_view_type: PhantomData<T>,
+    root_view_type: PhantomData<V>,
+}
+
+impl<V> Clone for WindowHandle<V> {
+    fn clone(&self) -> Self {
+        Self {
+            any_handle: self.any_handle.clone(),
+            root_view_type: PhantomData,
+        }
+    }
 }
 
+impl<V> Copy for WindowHandle<V> {}
+
 impl<V: View> WindowHandle<V> {
     fn new(window_id: usize) -> Self {
         WindowHandle {
@@ -3933,7 +4028,15 @@ impl AnyWindowHandle {
         C: BorrowWindowContext,
         F: FnOnce(&WindowContext) -> R,
     {
-        cx.read_window_with(self.window_id, |cx| read(cx))
+        cx.read_window(self.window_id, |cx| read(cx))
+    }
+
+    pub fn read_optional_with<C, F, R>(&self, cx: &C, read: F) -> Option<R>
+    where
+        C: BorrowWindowContext,
+        F: FnOnce(&WindowContext) -> Option<R>,
+    {
+        cx.read_window_optional(self.window_id, |cx| read(cx))
     }
 
     pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
@@ -3944,6 +4047,14 @@ impl AnyWindowHandle {
         cx.update_window(self.window_id, update)
     }
 
+    pub fn update_optional<C, F, R>(&self, cx: &mut C, update: F) -> Option<R>
+    where
+        C: BorrowWindowContext,
+        F: FnOnce(&mut WindowContext) -> Option<R>,
+    {
+        cx.update_window_optional(self.window_id, update)
+    }
+
     pub fn add_view<C, U, F>(&self, cx: &mut C, build_view: F) -> C::Result<ViewHandle<U>>
     where
         C: BorrowWindowContext,
@@ -3953,6 +4064,17 @@ impl AnyWindowHandle {
         self.update(cx, |cx| cx.add_view(build_view))
     }
 
+    pub fn downcast<V: View>(self) -> Option<WindowHandle<V>> {
+        if self.root_view_type == TypeId::of::<V>() {
+            Some(WindowHandle {
+                any_handle: self,
+                root_view_type: PhantomData,
+            })
+        } else {
+            None
+        }
+    }
+
     pub fn root_is<V: View>(&self) -> bool {
         self.root_view_type == TypeId::of::<V>()
     }
@@ -4018,7 +4140,7 @@ impl<T: View> ViewHandle<T> {
         C: BorrowWindowContext,
         F: FnOnce(&T, &ViewContext<T>) -> S,
     {
-        cx.read_window_with(self.window_id, |cx| {
+        cx.read_window(self.window_id, |cx| {
             let cx = ViewContext::immutable(cx, self.view_id);
             read(cx.read_view(self), &cx)
         })

crates/gpui/src/app/test_app_context.rs 🔗

@@ -92,33 +92,34 @@ impl TestAppContext {
         self.update(|cx| cx.dispatch_global_action_any(&action));
     }
 
-    pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
-        let handled = self
-            .cx
-            .borrow_mut()
-            .update_window(window_id, |cx| {
-                if cx.dispatch_keystroke(&keystroke) {
-                    return true;
-                }
+    pub fn dispatch_keystroke(
+        &mut self,
+        window: AnyWindowHandle,
+        keystroke: Keystroke,
+        is_held: bool,
+    ) {
+        let handled = window.update(self, |cx| {
+            if cx.dispatch_keystroke(&keystroke) {
+                return true;
+            }
 
-                if cx.dispatch_event(
-                    Event::KeyDown(KeyDownEvent {
-                        keystroke: keystroke.clone(),
-                        is_held,
-                    }),
-                    false,
-                ) {
-                    return true;
-                }
+            if cx.dispatch_event(
+                Event::KeyDown(KeyDownEvent {
+                    keystroke: keystroke.clone(),
+                    is_held,
+                }),
+                false,
+            ) {
+                return true;
+            }
 
-                false
-            })
-            .unwrap_or(false);
+            false
+        });
 
         if !handled && !keystroke.cmd && !keystroke.ctrl {
             WindowInputHandler {
                 app: self.cx.clone(),
-                window_id,
+                window,
             }
             .replace_text_in_range(None, &keystroke.key)
         }
@@ -419,13 +420,20 @@ impl BorrowAppContext for TestAppContext {
 impl BorrowWindowContext for TestAppContext {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         self.cx
             .borrow()
             .read_window(window_id, f)
             .expect("window was closed")
     }
 
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window(self, window_id, f)
+    }
+
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
         window_id: usize,
@@ -436,6 +444,13 @@ impl BorrowWindowContext for TestAppContext {
             .update_window(window_id, f)
             .expect("window was closed")
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window(self, window_id, f)
+    }
 }
 
 impl<T: Entity> ModelHandle<T> {

crates/gpui/src/app/window.rs 🔗

@@ -144,7 +144,7 @@ impl BorrowAppContext for WindowContext<'_> {
 impl BorrowWindowContext for WindowContext<'_> {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         if self.window_id == window_id {
             f(self)
         } else {
@@ -152,6 +152,13 @@ impl BorrowWindowContext for WindowContext<'_> {
         }
     }
 
+    fn read_window_optional<T, F>(&self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window(self, window_id, f)
+    }
+
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
         window_id: usize,
@@ -163,6 +170,13 @@ impl BorrowWindowContext for WindowContext<'_> {
             panic!("update called with id of window that does not belong to this context")
         }
     }
+
+    fn update_window_optional<T, F>(&mut self, window_id: usize, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window_optional(self, window_id, f)
+    }
 }
 
 impl<'a> WindowContext<'a> {

crates/gpui/src/app/window_input_handler.rs 🔗

@@ -2,11 +2,11 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
 
 use pathfinder_geometry::rect::RectF;
 
-use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
+use crate::{platform::InputHandler, window::WindowContext, AnyView, AnyWindowHandle, AppContext};
 
 pub struct WindowInputHandler {
     pub app: Rc<RefCell<AppContext>>,
-    pub window_id: usize,
+    pub window: AnyWindowHandle,
 }
 
 impl WindowInputHandler {
@@ -21,13 +21,12 @@ impl WindowInputHandler {
         //
         // See https://github.com/zed-industries/community/issues/444
         let mut app = self.app.try_borrow_mut().ok()?;
-        app.update_window(self.window_id, |cx| {
+        self.window.update_optional(&mut *app, |cx| {
             let view_id = cx.window.focused_view_id?;
-            let view = cx.views.get(&(self.window_id, view_id))?;
+            let view = cx.views.get(&(self.window.id(), view_id))?;
             let result = f(view.as_ref(), &cx);
             Some(result)
         })
-        .flatten()
     }
 
     fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
@@ -35,11 +34,12 @@ impl WindowInputHandler {
         F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
     {
         let mut app = self.app.try_borrow_mut().ok()?;
-        app.update_window(self.window_id, |cx| {
-            let view_id = cx.window.focused_view_id?;
-            cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
-        })
-        .flatten()
+        self.window
+            .update(&mut *app, |cx| {
+                let view_id = cx.window.focused_view_id?;
+                cx.update_any_view(view_id, |view, cx| f(view, cx, view_id))
+            })
+            .flatten()
     }
 }
 
@@ -83,9 +83,8 @@ impl InputHandler for WindowInputHandler {
     }
 
     fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
-        self.app
-            .borrow()
-            .read_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
-            .flatten()
+        self.window.read_optional_with(&*self.app.borrow(), |cx| {
+            cx.rect_for_text_range(range_utf16)
+        })
     }
 }

crates/workspace/src/workspace.rs 🔗

@@ -4035,16 +4035,9 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
     let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
     cx.spawn(|mut cx| async move {
         let mut workspaces = cx
-            .window_ids()
+            .windows()
             .into_iter()
-            .filter_map(|window_id| {
-                Some(
-                    cx.root_view(window_id)?
-                        .clone()
-                        .downcast::<Workspace>()?
-                        .downgrade(),
-                )
-            })
+            .filter_map(|window| Some(window.downcast::<Workspace>()?.root(&cx)?.downgrade()))
             .collect::<Vec<_>>();
 
         // If multiple windows have unsaved changes, and need a save prompt,