WIP

Nathan Sobo created

Change summary

Cargo.lock                                          |  39 ++
Cargo.toml                                          |   1 
crates/collab/src/tests/integration_tests.rs        |   4 
crates/collab_ui/src/incoming_call_notification.rs  |   2 
crates/collab_ui/src/project_shared_notification.rs |   2 
crates/command_palette/src/command_palette.rs       |   2 
crates/diagnostics/src/diagnostics.rs               |   4 
crates/editor/src/editor_tests.rs                   |   2 
crates/editor/src/test/editor_lsp_test_context.rs   |   2 
crates/editor/src/test/editor_test_context.rs       |   2 
crates/file_finder/src/file_finder.rs               |  22 
crates/gpui/Cargo.toml                              |   1 
crates/gpui/src/app.rs                              | 264 +++++++-------
crates/gpui/src/app/test_app_context.rs             |  33 +
crates/gpui/src/app/window.rs                       |  21 
crates/project_panel/src/project_panel.rs           |   8 
crates/project_symbols/src/project_symbols.rs       |   2 
crates/search/src/buffer_search.rs                  |  10 
crates/search/src/project_search.rs                 |   4 
crates/workspace/src/workspace.rs                   | 165 ++++----
crates/zed/src/zed.rs                               |  20 
21 files changed, 331 insertions(+), 279 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1649,6 +1649,12 @@ dependencies = [
  "theme",
 ]
 
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
 [[package]]
 name = "copilot"
 version = "0.1.0"
@@ -2133,6 +2139,19 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version 0.4.0",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "dhat"
 version = "0.3.2"
@@ -3096,6 +3115,7 @@ dependencies = [
  "core-graphics",
  "core-text",
  "ctor",
+ "derive_more",
  "dhat",
  "env_logger 0.9.3",
  "etagere",
@@ -5106,7 +5126,7 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
 dependencies = [
- "rustc_version",
+ "rustc_version 0.3.3",
 ]
 
 [[package]]
@@ -6276,7 +6296,16 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
 dependencies = [
- "semver",
+ "semver 0.11.0",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver 1.0.18",
 ]
 
 [[package]]
@@ -6716,6 +6745,12 @@ dependencies = [
  "semver-parser",
 ]
 
+[[package]]
+name = "semver"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
+
 [[package]]
 name = "semver-parser"
 version = "0.10.2"

Cargo.toml 🔗

@@ -79,6 +79,7 @@ resolver = "2"
 anyhow = { version = "1.0.57" }
 async-trait = { version = "0.1" }
 ctor = { version = "0.1" }
+derive_more = { version = "0.99.17" }
 env_logger = { version = "0.9" }
 futures = { version = "0.3" }
 globset = { version = "0.4" }

crates/collab/src/tests/integration_tests.rs 🔗

@@ -3446,7 +3446,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
     let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
     let mut editor_cx_a = EditorTestContext {
         cx: cx_a,
-        window_id: window_a.window_id(),
+        window_id: window_a.id(),
         editor: editor_a,
     };
 
@@ -3459,7 +3459,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
     let mut editor_cx_b = EditorTestContext {
         cx: cx_b,
-        window_id: window_b.window_id(),
+        window_id: window_b.id(),
         editor: editor_b,
     };
 

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -49,7 +49,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
                         |_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
                     );
 
-                    notification_windows.push(window.window_id());
+                    notification_windows.push(window.id());
                 }
             }
         }

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -52,7 +52,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
                 notification_windows
                     .entry(*project_id)
                     .or_insert(Vec::new())
-                    .push(window.window_id());
+                    .push(window.id());
             }
         }
         room::Event::RemoteProjectUnshared { project_id } => {

crates/command_palette/src/command_palette.rs 🔗

@@ -297,7 +297,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), [], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let editor = cx.add_view(window_id, |cx| {
             let mut editor = Editor::single_line(None, cx);
             editor.set_text("abc", cx);

crates/diagnostics/src/diagnostics.rs 🔗

@@ -857,7 +857,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         // Create some diagnostics
         project.update(cx, |project, cx| {
@@ -1252,7 +1252,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         let view = cx.add_view(window_id, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)

crates/editor/src/editor_tests.rs 🔗

@@ -525,7 +525,7 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
     let project = Project::test(fs, [], cx).await;
     let window = cx.add_window(|cx| Workspace::test_new(project, cx));
     let workspace = window.root(cx);
-    let window_id = window.window_id();
+    let window_id = window.id();
     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
     cx.add_view(window_id, |cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);

crates/file_finder/src/file_finder.rs 🔗

@@ -619,7 +619,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        cx.dispatch_action(window.window_id(), Toggle);
+        cx.dispatch_action(window.id(), Toggle);
 
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
         finder
@@ -632,8 +632,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.window_id(), SelectNext);
-        cx.dispatch_action(window.window_id(), Confirm);
+        cx.dispatch_action(window.id(), SelectNext);
+        cx.dispatch_action(window.id(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -674,7 +674,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        cx.dispatch_action(window.window_id(), Toggle);
+        cx.dispatch_action(window.id(), Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
 
         let file_query = &first_file_name[..3];
@@ -706,8 +706,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.window_id(), SelectNext);
-        cx.dispatch_action(window.window_id(), Confirm);
+        cx.dispatch_action(window.id(), SelectNext);
+        cx.dispatch_action(window.id(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -758,7 +758,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        cx.dispatch_action(window.window_id(), Toggle);
+        cx.dispatch_action(window.id(), Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
 
         let file_query = &first_file_name[..3];
@@ -790,8 +790,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.window_id(), SelectNext);
-        cx.dispatch_action(window.window_id(), Confirm);
+        cx.dispatch_action(window.id(), SelectNext);
+        cx.dispatch_action(window.id(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -1168,7 +1168,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
@@ -1376,7 +1376,7 @@ mod tests {
 
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1,);

crates/gpui/Cargo.toml 🔗

@@ -22,6 +22,7 @@ sqlez = { path = "../sqlez" }
 async-task = "4.0.3"
 backtrace = { version = "0.3", optional = true }
 ctor.workspace = true
+derive_more.workspace = true
 dhat = { version = "0.3", optional = true }
 env_logger = { version = "0.9", optional = true }
 etagere = "0.2"

crates/gpui/src/app.rs 🔗

@@ -23,6 +23,7 @@ use std::{
 };
 
 use anyhow::{anyhow, Context, Result};
+use derive_more::Deref;
 use parking_lot::Mutex;
 use postage::oneshot;
 use smallvec::SmallVec;
@@ -132,11 +133,13 @@ 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_with<H, T, F>(&self, window_handle: H, f: F) -> Self::Result<T>
     where
+        H: Into<AnyWindowHandle>,
         F: FnOnce(&WindowContext) -> T;
-    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    fn update_window<H, T, F>(&mut self, window_handle: H, f: F) -> Self::Result<T>
     where
+        H: Into<AnyWindowHandle>,
         F: FnOnce(&mut WindowContext) -> T;
 }
 
@@ -300,13 +303,13 @@ impl App {
         result
     }
 
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        callback: F,
-    ) -> Option<T> {
+    fn update_window<H, T, F>(&mut self, handle: H, callback: F) -> Option<T>
+    where
+        H: Into<AnyWindowHandle>,
+        F: FnOnce(&mut WindowContext) -> T,
+    {
         let mut state = self.0.borrow_mut();
-        let result = state.update_window(window_id, callback);
+        let result = state.update_window(handle, callback);
         state.pending_notifications.clear();
         result
     }
@@ -333,6 +336,10 @@ impl AsyncAppContext {
         self.0.borrow_mut().update(callback)
     }
 
+    pub fn windows(&self) -> Vec<AnyWindowHandle> {
+        self.0.borrow().windows().collect()
+    }
+
     pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
         &self,
         window_id: usize,
@@ -343,10 +350,10 @@ impl AsyncAppContext {
 
     pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
-        window_id: usize,
+        handle: AnyWindowHandle,
         callback: F,
     ) -> Option<T> {
-        self.0.borrow_mut().update_window(window_id, callback)
+        self.0.borrow_mut().update_window(handle, callback)
     }
 
     pub fn debug_elements(&self, window_id: usize) -> Option<json::Value> {
@@ -359,14 +366,14 @@ impl AsyncAppContext {
 
     pub fn dispatch_action(
         &mut self,
-        window_id: usize,
+        window: impl Into<AnyWindowHandle>,
         view_id: usize,
         action: &dyn Action,
     ) -> Result<()> {
         self.0
             .borrow_mut()
-            .update_window(window_id, |window| {
-                window.dispatch_action(Some(view_id), action);
+            .update_window(window, |cx| {
+                cx.dispatch_action(Some(view_id), action);
             })
             .ok_or_else(|| anyhow!("window not found"))
     }
@@ -380,22 +387,6 @@ impl AsyncAppContext {
             .unwrap_or_default()
     }
 
-    pub fn has_window(&self, window_id: usize) -> bool {
-        self.read(|cx| cx.windows.contains_key(&window_id))
-    }
-
-    pub fn window_is_active(&self, window_id: usize) -> bool {
-        self.read(|cx| cx.windows.get(&window_id).map_or(false, |w| w.is_active))
-    }
-
-    pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> {
-        self.read(|cx| cx.windows.get(&window_id).map(|w| w.root_view().clone()))
-    }
-
-    pub fn window_ids(&self) -> Vec<usize> {
-        self.read(|cx| cx.windows.keys().copied().collect())
-    }
-
     pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
     where
         T: Entity,
@@ -501,7 +492,7 @@ pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
     views: HashMap<(usize, usize), Box<dyn AnyView>>,
     views_metadata: HashMap<(usize, usize), ViewMetadata>,
-    windows: HashMap<usize, Window>,
+    windows: HashMap<AnyWindowHandle, Window>,
     globals: HashMap<TypeId, Box<dyn Any>>,
     element_states: HashMap<ElementStateId, Box<dyn Any>>,
     background: Arc<executor::Background>,
@@ -818,22 +809,6 @@ impl AppContext {
         Some(callback(&window_context))
     }
 
-    pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        callback: F,
-    ) -> Option<T> {
-        self.update(|app_context| {
-            let mut window = app_context.windows.remove(&window_id)?;
-            let mut window_context = WindowContext::mutable(app_context, &mut window, window_id);
-            let result = callback(&mut window_context);
-            if !window_context.removed {
-                app_context.windows.insert(window_id, window);
-            }
-            Some(result)
-        })
-    }
-
     pub fn update_active_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
         callback: F,
@@ -1331,43 +1306,40 @@ impl AppContext {
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window_id = post_inc(&mut this.next_id);
+            let window = WindowHandle::<V>::new(post_inc(&mut this.next_id));
             let platform_window =
                 this.platform
-                    .open_window(window_id, window_options, this.foreground.clone());
-            let window = this.build_window(window_id, platform_window, build_root_view);
-            this.windows.insert(window_id, window);
-            WindowHandle::new(window_id)
+                    .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
         })
     }
 
-    pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<V>)
+    pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
     where
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window_id = post_inc(&mut this.next_id);
-            let platform_window = this.platform.add_status_item(window_id);
-            let window = this.build_window(window_id, platform_window, build_root_view);
-            let root_view = window.root_view().clone().downcast::<V>().unwrap();
-
-            this.windows.insert(window_id, window);
-            this.update_window(window_id, |cx| {
-                root_view.update(cx, |view, cx| view.focus_in(cx.handle().into_any(), cx))
-            });
+            let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
+            let platform_window = this.platform.add_status_item(handle.id());
+            let window = this.build_window(handle, platform_window, build_root_view);
 
-            (window_id, root_view)
+            this.windows.insert(handle.into(), window);
+            handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
+            handle
         })
     }
 
-    pub fn build_window<V, F>(
+    pub fn build_window<H, V, F>(
         &mut self,
-        window_id: usize,
+        handle: H,
         mut platform_window: Box<dyn platform::Window>,
         build_root_view: F,
     ) -> Window
     where
+        H: Into<AnyWindowHandle>,
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
@@ -1375,7 +1347,7 @@ impl AppContext {
             let mut app = self.upgrade();
 
             platform_window.on_event(Box::new(move |event| {
-                app.update_window(window_id, |cx| {
+                app.update_window(handle, |cx| {
                     if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
                         if cx.dispatch_keystroke(keystroke) {
                             return true;
@@ -1391,35 +1363,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(window_id, is_active))
+                app.update(|cx| cx.window_changed_active_status(handle, is_active))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_resize(Box::new(move || {
-                app.update(|cx| cx.window_was_resized(window_id))
+                app.update(|cx| cx.window_was_resized(handle))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_moved(Box::new(move || {
-                app.update(|cx| cx.window_was_moved(window_id))
+                app.update(|cx| cx.window_was_moved(handle))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
-                app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
+                app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_close(Box::new(move || {
-                app.update(|cx| cx.update_window(window_id, |cx| cx.remove_window()));
+                app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
             }));
         }
 
@@ -1431,27 +1403,20 @@ impl AppContext {
 
         platform_window.set_input_handler(Box::new(WindowInputHandler {
             app: self.upgrade().0,
-            window_id,
+            window_id: handle,
         }));
 
-        let mut window = Window::new(window_id, platform_window, self, build_root_view);
-        let mut cx = WindowContext::mutable(self, &mut window, window_id);
+        let mut window = Window::new(handle, platform_window, self, build_root_view);
+        let mut cx = WindowContext::mutable(self, &mut window, handle);
         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);
         window
     }
 
-    pub(crate) fn replace_root_view<V, F>(
-        &mut self,
-        window_id: usize,
-        build_root_view: F,
-    ) -> Option<WindowHandle<V>>
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        self.update_window(window_id, |cx| cx.replace_root_view(build_root_view))
+    pub fn windows(&self) -> impl Iterator<Item = AnyWindowHandle> {
+        todo!();
+        None.into_iter()
     }
 
     pub fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
@@ -2192,11 +2157,20 @@ impl BorrowWindowContext for AppContext {
         AppContext::read_window(self, window_id, f)
     }
 
-    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    fn update_window<T, F>(&mut self, window: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T,
     {
-        AppContext::update_window(self, window_id, f)
+        self.update(|app_context| {
+            let mut window = app_context.windows.remove(&window_handle)?;
+            let mut window_context =
+                WindowContext::mutable(app_context, &mut window, window_handle);
+            let result = callback(&mut window_context);
+            if !window_context.removed {
+                app_context.windows.insert(window_handle, window);
+            }
+            Some(result)
+        })
     }
 }
 
@@ -3862,44 +3836,28 @@ impl<T> Clone for WeakModelHandle<T> {
 
 impl<T> Copy for WeakModelHandle<T> {}
 
+#[derive(Deref, Copy, Clone)]
 pub struct WindowHandle<T> {
-    window_id: usize,
+    #[deref]
+    any_handle: AnyWindowHandle,
     root_view_type: PhantomData<T>,
 }
 
-#[allow(dead_code)]
 impl<V: View> WindowHandle<V> {
     fn new(window_id: usize) -> Self {
         WindowHandle {
-            window_id,
+            any_handle: AnyWindowHandle {
+                window_id,
+                root_view_type: TypeId::of::<V>(),
+            },
             root_view_type: PhantomData,
         }
     }
 
-    pub fn window_id(&self) -> usize {
-        self.window_id
-    }
-
     pub fn root<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<ViewHandle<V>> {
         self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap())
     }
 
-    pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&WindowContext) -> R,
-    {
-        cx.read_window_with(self.window_id(), |cx| read(cx))
-    }
-
-    pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut WindowContext) -> R,
-    {
-        cx.update_window(self.window_id(), update)
-    }
-
     pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
     where
         C: BorrowWindowContext,
@@ -3918,7 +3876,7 @@ impl<V: View> WindowHandle<V> {
         C: BorrowWindowContext,
         F: FnOnce(&mut V, &mut ViewContext<V>) -> R,
     {
-        cx.update_window(self.window_id, |cx| {
+        cx.update_window(*self, |cx| {
             cx.root_view()
                 .clone()
                 .downcast::<V>()
@@ -3927,6 +3885,53 @@ impl<V: View> WindowHandle<V> {
         })
     }
 
+    pub fn replace_root<C, F>(&self, cx: &mut C, build_root: F) -> C::Result<ViewHandle<V>>
+    where
+        C: BorrowWindowContext,
+        F: FnOnce(&mut ViewContext<V>) -> V,
+    {
+        cx.update_window(self.into_any(), |cx| {
+            let root_view = self.add_view(cx, |cx| build_root(cx));
+            cx.window.root_view = Some(root_view.clone().into_any());
+            cx.window.focused_view_id = Some(root_view.id());
+            root_view
+        })
+    }
+}
+
+impl<V> Into<AnyWindowHandle> for WindowHandle<V> {
+    fn into(self) -> AnyWindowHandle {
+        self.any_handle
+    }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq, Hash)]
+pub struct AnyWindowHandle {
+    window_id: usize,
+    root_view_type: TypeId,
+}
+
+impl AnyWindowHandle {
+    pub fn id(&self) -> usize {
+        self.window_id
+    }
+
+    pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
+    where
+        C: BorrowWindowContext,
+        F: FnOnce(&WindowContext) -> R,
+    {
+        cx.read_window_with(self.window_id, |cx| read(cx))
+    }
+
+    pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<R>
+    where
+        C: BorrowWindowContext,
+        F: FnOnce(&mut WindowContext) -> R,
+    {
+        cx.update_window(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,
@@ -3936,21 +3941,16 @@ impl<V: View> WindowHandle<V> {
         self.update(cx, |cx| cx.add_view(build_view))
     }
 
-    pub(crate) fn replace_root_view<C, F>(
-        &self,
-        cx: &mut C,
-        build_root_view: F,
-    ) -> C::Result<ViewHandle<V>>
-    where
-        C: BorrowWindowContext,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        cx.update_window(self.window_id, |cx| {
-            let root_view = self.add_view(cx, |cx| build_root_view(cx));
-            cx.window.root_view = Some(root_view.clone().into_any());
-            cx.window.focused_view_id = Some(root_view.id());
-            root_view
-        })
+    pub fn root_is<V: View>(&self) -> bool {
+        self.root_view_type == TypeId::of::<V>()
+    }
+
+    pub fn is_active<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<bool> {
+        self.read_with(cx, |cx| cx.window.is_active)
+    }
+
+    pub fn remove<C: BorrowWindowContext>(&self, cx: &mut C) -> C::Result<()> {
+        self.update(cx, |cx| cx.remove_window())
     }
 }
 
@@ -5390,7 +5390,7 @@ mod tests {
             TestView { events: Vec::new() }
         });
 
-        assert_eq!(window.read_root_with(cx, |view, _| assert_eq!(view.events, ["before notify"])));
+        window.read_root_with(cx, |view, _| assert_eq!(view.events, ["before notify"]));
     }
 
     #[crate::test(self)]
@@ -6227,7 +6227,7 @@ mod tests {
 
         // Check that global actions do not have a binding, even if a binding does exist in another view
         assert_eq!(
-            &available_actions(window.window_id(), view_1.id(), cx),
+            &available_actions(window.id(), view_1.id(), cx),
             &[
                 ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
                 ("test::GlobalAction", vec![])
@@ -6236,7 +6236,7 @@ mod tests {
 
         // Check that view 1 actions and bindings are available even when called from view 2
         assert_eq!(
-            &available_actions(window.window_id(), view_2.id(), cx),
+            &available_actions(window.id(), view_2.id(), cx),
             &[
                 ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
                 ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
@@ -6299,7 +6299,7 @@ mod tests {
             ]);
         });
 
-        let actions = cx.available_actions(window.window_id(), view.id());
+        let actions = cx.available_actions(window.id(), view.id());
         assert_eq!(
             actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
             Some(&ActionWithArg { arg: false })
@@ -6585,25 +6585,25 @@ mod tests {
             [("window 2", false), ("window 3", true)]
         );
 
-        cx.simulate_window_activation(Some(window_2.window_id()));
+        cx.simulate_window_activation(Some(window_2.id()));
         assert_eq!(
             mem::take(&mut *events.borrow_mut()),
             [("window 3", false), ("window 2", true)]
         );
 
-        cx.simulate_window_activation(Some(window_1.window_id()));
+        cx.simulate_window_activation(Some(window_1.id()));
         assert_eq!(
             mem::take(&mut *events.borrow_mut()),
             [("window 2", false), ("window 1", true)]
         );
 
-        cx.simulate_window_activation(Some(window_3.window_id()));
+        cx.simulate_window_activation(Some(window_3.id()));
         assert_eq!(
             mem::take(&mut *events.borrow_mut()),
             [("window 1", false), ("window 3", true)]
         );
 
-        cx.simulate_window_activation(Some(window_3.window_id()));
+        cx.simulate_window_activation(Some(window_3.id()));
         assert_eq!(mem::take(&mut *events.borrow_mut()), []);
     }
 

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

@@ -4,9 +4,9 @@ use crate::{
     keymap_matcher::{Binding, Keystroke},
     platform,
     platform::{Event, InputHandler, KeyDownEvent, Platform},
-    Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
-    ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
-    WindowContext, WindowHandle,
+    Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
+    Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
+    WeakHandle, WindowContext, WindowHandle,
 };
 use collections::BTreeMap;
 use futures::Future;
@@ -124,6 +124,13 @@ impl TestAppContext {
         }
     }
 
+    pub fn window<V: View>(&self, window_id: usize) -> Option<WindowHandle<V>> {
+        self.cx
+            .borrow()
+            .read_window(window_id, |cx| cx.window())
+            .flatten()
+    }
+
     pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
         &self,
         window_id: usize,
@@ -157,9 +164,9 @@ impl TestAppContext {
             .cx
             .borrow_mut()
             .add_window(Default::default(), build_root_view);
-        self.simulate_window_activation(Some(window.window_id()));
+        self.simulate_window_activation(Some(window.id()));
 
-        WindowHandle::new(window.window_id())
+        WindowHandle::new(window.id())
     }
 
     pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
@@ -191,10 +198,14 @@ impl TestAppContext {
         self.cx.borrow_mut().subscribe_global(callback)
     }
 
-    pub fn window_ids(&self) -> Vec<usize> {
-        self.cx.borrow().windows.keys().copied().collect()
+    pub fn windows(&self) -> impl Iterator<Item = AnyWindowHandle> {
+        self.cx.borrow().windows()
     }
 
+    // pub fn window_ids(&self) -> Vec<usize> {
+    //     self.cx.borrow().windows.keys().copied().collect()
+    // }
+
     pub fn remove_all_windows(&mut self) {
         self.update(|cx| cx.windows.clear());
     }
@@ -311,15 +322,15 @@ impl TestAppContext {
 
     pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
         self.cx.borrow_mut().update(|cx| {
-            let other_window_ids = cx
+            let other_windows = cx
                 .windows
                 .keys()
-                .filter(|window_id| Some(**window_id) != to_activate)
+                .filter(|window| Some(window.id()) != to_activate)
                 .copied()
                 .collect::<Vec<_>>();
 
-            for window_id in other_window_ids {
-                cx.window_changed_active_status(window_id, false)
+            for window in other_windows {
+                cx.window_changed_active_status(window.id(), false)
             }
 
             if let Some(to_activate) = to_activate {

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

@@ -196,6 +196,16 @@ impl<'a> WindowContext<'a> {
         self.window_id
     }
 
+    pub fn window<V: View>(&self) -> Option<WindowHandle<V>> {
+        self.window.root_view.as_ref().and_then(|root_view| {
+            if root_view.is::<V>() {
+                Some(WindowHandle::new(self.window_id))
+            } else {
+                None
+            }
+        })
+    }
+
     pub fn app_context(&mut self) -> &mut AppContext {
         &mut self.app_context
     }
@@ -1157,17 +1167,6 @@ impl<'a> WindowContext<'a> {
         self.window.platform_window.prompt(level, msg, answers)
     }
 
-    pub(crate) fn replace_root_view<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        let root_view = self.add_view(|cx| build_root_view(cx));
-        self.window.focused_view_id = Some(root_view.id());
-        self.window.root_view = Some(root_view.into_any());
-        WindowHandle::new(self.window_id)
-    }
-
     pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
     where
         T: View,

crates/project_panel/src/project_panel.rs 🔗

@@ -1872,7 +1872,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
@@ -2225,7 +2225,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
@@ -2402,7 +2402,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         toggle_expand_dir(&panel, "src/test", cx);
@@ -2493,7 +2493,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "src/", cx);

crates/project_symbols/src/project_symbols.rs 🔗

@@ -328,7 +328,7 @@ mod tests {
 
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         // Create the project symbols view.
         let symbols = cx.add_view(window_id, |cx| {

crates/search/src/buffer_search.rs 🔗

@@ -851,11 +851,11 @@ mod tests {
         });
         let window = cx.add_window(|_| EmptyView);
 
-        let editor = cx.add_view(window.window_id(), |cx| {
+        let editor = cx.add_view(window.id(), |cx| {
             Editor::for_buffer(buffer.clone(), None, cx)
         });
 
-        let search_bar = cx.add_view(window.window_id(), |cx| {
+        let search_bar = cx.add_view(window.id(), |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(cx);
@@ -1232,7 +1232,7 @@ mod tests {
         );
         let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
         let window = cx.add_window(|_| EmptyView);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
 
@@ -1421,11 +1421,11 @@ mod tests {
         let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
         let window = cx.add_window(|_| EmptyView);
 
-        let editor = cx.add_view(window.window_id(), |cx| {
+        let editor = cx.add_view(window.id(), |cx| {
             Editor::for_buffer(buffer.clone(), None, cx)
         });
 
-        let search_bar = cx.add_view(window.window_id(), |cx| {
+        let search_bar = cx.add_view(window.id(), |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(cx);

crates/search/src/project_search.rs 🔗

@@ -1568,7 +1568,7 @@ pub mod tests {
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         let active_item = cx.read(|cx| {
             workspace
@@ -1874,7 +1874,7 @@ pub mod tests {
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         workspace.update(cx, |workspace, cx| {
             ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
         });

crates/workspace/src/workspace.rs 🔗

@@ -37,7 +37,7 @@ use gpui::{
     },
     AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
     ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle, WindowContext,
+    WeakViewHandle, WindowContext, WindowHandle,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 use itertools::Itertools;
@@ -749,7 +749,7 @@ impl Workspace {
     fn new_local(
         abs_paths: Vec<PathBuf>,
         app_state: Arc<AppState>,
-        requesting_window_id: Option<usize>,
+        requesting_window: Option<WindowHandle<Workspace>>,
         cx: &mut AppContext,
     ) -> Task<(
         WeakViewHandle<Workspace>,
@@ -793,55 +793,60 @@ impl Workspace {
                 DB.next_id().await.unwrap_or(0)
             };
 
-            let window = requesting_window_id.and_then(|window_id| {
-                cx.update(|cx| {
-                    cx.replace_root_view(window_id, |cx| {
-                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-                    })
-                })
-            });
-            let window = window.unwrap_or_else(|| {
-                let window_bounds_override = window_bounds_env_override(&cx);
-                let (bounds, display) = if let Some(bounds) = window_bounds_override {
-                    (Some(bounds), None)
-                } else {
-                    serialized_workspace
-                        .as_ref()
-                        .and_then(|serialized_workspace| {
-                            let display = serialized_workspace.display?;
-                            let mut bounds = serialized_workspace.bounds?;
-
-                            // Stored bounds are relative to the containing display.
-                            // So convert back to global coordinates if that screen still exists
-                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                if let Some(screen) = cx.platform().screen_by_id(display) {
-                                    let screen_bounds = screen.bounds();
-                                    window_bounds.set_origin_x(
-                                        window_bounds.origin_x() + screen_bounds.origin_x(),
-                                    );
-                                    window_bounds.set_origin_y(
-                                        window_bounds.origin_y() + screen_bounds.origin_y(),
-                                    );
-                                    bounds = WindowBounds::Fixed(window_bounds);
-                                } else {
-                                    // Screen no longer exists. Return none here.
-                                    return None;
+            let window = if let Some(window) = requesting_window {
+                window.replace_root(&mut cx, |cx| {
+                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+                });
+                window
+            } else {
+                {
+                    let window_bounds_override = window_bounds_env_override(&cx);
+                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
+                        (Some(bounds), None)
+                    } else {
+                        serialized_workspace
+                            .as_ref()
+                            .and_then(|serialized_workspace| {
+                                let display = serialized_workspace.display?;
+                                let mut bounds = serialized_workspace.bounds?;
+
+                                // Stored bounds are relative to the containing display.
+                                // So convert back to global coordinates if that screen still exists
+                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
+                                    if let Some(screen) = cx.platform().screen_by_id(display) {
+                                        let screen_bounds = screen.bounds();
+                                        window_bounds.set_origin_x(
+                                            window_bounds.origin_x() + screen_bounds.origin_x(),
+                                        );
+                                        window_bounds.set_origin_y(
+                                            window_bounds.origin_y() + screen_bounds.origin_y(),
+                                        );
+                                        bounds = WindowBounds::Fixed(window_bounds);
+                                    } else {
+                                        // Screen no longer exists. Return none here.
+                                        return None;
+                                    }
                                 }
-                            }
 
-                            Some((bounds, display))
-                        })
-                        .unzip()
-                };
-
-                // Use the serialized workspace to construct the new window
-                cx.add_window(
-                    (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
-                    |cx| {
-                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-                    },
-                )
-            });
+                                Some((bounds, display))
+                            })
+                            .unzip()
+                    };
+
+                    // Use the serialized workspace to construct the new window
+                    cx.add_window(
+                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+                        |cx| {
+                            Workspace::new(
+                                workspace_id,
+                                project_handle.clone(),
+                                app_state.clone(),
+                                cx,
+                            )
+                        },
+                    )
+                }
+            };
 
             // We haven't yielded the main thread since obtaining the window handle,
             // so the window exists.
@@ -1227,14 +1232,14 @@ impl Workspace {
 
     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
         cx.spawn(|mut cx| async move {
-            let id = cx
-                .window_ids()
+            let window = cx
+                .windows()
                 .into_iter()
-                .find(|&id| cx.window_is_active(id));
-            if let Some(id) = id {
+                .find(|window| window.is_active(&cx).unwrap_or(false));
+            if let Some(window) = window {
                 //This can only get called when the window's project connection has been lost
                 //so we don't need to prompt the user for anything and instead just close the window
-                cx.remove_window(id);
+                window.remove(&mut cx);
             }
         })
         .detach();
@@ -1265,9 +1270,9 @@ impl Workspace {
 
         cx.spawn(|this, mut cx| async move {
             let workspace_count = cx
-                .window_ids()
+                .windows()
                 .into_iter()
-                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+                .filter(|window| window.root_is::<Workspace>())
                 .count();
 
             if let Some(active_call) = active_call {
@@ -1385,7 +1390,7 @@ impl Workspace {
         paths: Vec<PathBuf>,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
-        let window_id = cx.window_id();
+        let window = cx.window::<Self>();
         let is_remote = self.project.read(cx).is_remote();
         let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
         let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
@@ -1397,15 +1402,15 @@ impl Workspace {
         let app_state = self.app_state.clone();
 
         cx.spawn(|_, mut cx| async move {
-            let window_id_to_replace = if let Some(close_task) = close_task {
+            let window_to_replace = if let Some(close_task) = close_task {
                 if !close_task.await? {
                     return Ok(());
                 }
-                Some(window_id)
+                window
             } else {
                 None
             };
-            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
+            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
                 .await?;
             Ok(())
         })
@@ -3851,7 +3856,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
 pub fn open_paths(
     abs_paths: &[PathBuf],
     app_state: &Arc<AppState>,
-    requesting_window_id: Option<usize>,
+    requesting_window: Option<WindowHandle<Workspace>>,
     cx: &mut AppContext,
 ) -> Task<
     Result<(
@@ -3879,7 +3884,7 @@ pub fn open_paths(
         } else {
             Ok(cx
                 .update(|cx| {
-                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
+                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
                 })
                 .await)
         }
@@ -4196,14 +4201,14 @@ mod tests {
             );
         });
         assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
+            cx.current_window_title(window.id()).as_deref(),
             Some("one.txt — root1")
         );
 
         // Add a second item to a non-empty pane
         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
         assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
+            cx.current_window_title(window.id()).as_deref(),
             Some("two.txt — root1")
         );
         project.read_with(cx, |project, cx| {
@@ -4222,7 +4227,7 @@ mod tests {
         .await
         .unwrap();
         assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
+            cx.current_window_title(window.id()).as_deref(),
             Some("one.txt — root1")
         );
         project.read_with(cx, |project, cx| {
@@ -4242,14 +4247,14 @@ mod tests {
             .await
             .unwrap();
         assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
+            cx.current_window_title(window.id()).as_deref(),
             Some("one.txt — root1, root2")
         );
 
         // Remove a project folder
         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
         assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
+            cx.current_window_title(window.id()).as_deref(),
             Some("one.txt — root2")
         );
     }
@@ -4285,9 +4290,9 @@ mod tests {
         });
         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
         cx.foreground().run_until_parked();
-        cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */);
+        cx.simulate_prompt_answer(window.id(), 2 /* cancel */);
         cx.foreground().run_until_parked();
-        assert!(!cx.has_pending_prompt(window.window_id()));
+        assert!(!cx.has_pending_prompt(window.id()));
         assert!(!task.await.unwrap());
     }
 
@@ -4346,10 +4351,10 @@ mod tests {
             assert_eq!(pane.items_len(), 4);
             assert_eq!(pane.active_item().unwrap().id(), item1.id());
         });
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(cx.has_pending_prompt(window.id()));
 
         // Confirm saving item 1.
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        cx.simulate_prompt_answer(window.id(), 0);
         cx.foreground().run_until_parked();
 
         // Item 1 is saved. There's a prompt to save item 3.
@@ -4360,10 +4365,10 @@ mod tests {
             assert_eq!(pane.items_len(), 3);
             assert_eq!(pane.active_item().unwrap().id(), item3.id());
         });
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(cx.has_pending_prompt(window.id()));
 
         // Cancel saving item 3.
-        cx.simulate_prompt_answer(window.window_id(), 1);
+        cx.simulate_prompt_answer(window.id(), 1);
         cx.foreground().run_until_parked();
 
         // Item 3 is reloaded. There's a prompt to save item 4.
@@ -4374,10 +4379,10 @@ mod tests {
             assert_eq!(pane.items_len(), 2);
             assert_eq!(pane.active_item().unwrap().id(), item4.id());
         });
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(cx.has_pending_prompt(window.id()));
 
         // Confirm saving item 4.
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        cx.simulate_prompt_answer(window.id(), 0);
         cx.foreground().run_until_parked();
 
         // There's a prompt for a path for item 4.
@@ -4480,7 +4485,7 @@ mod tests {
                 &[ProjectEntryId::from_proto(0)]
             );
         });
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        cx.simulate_prompt_answer(window.id(), 0);
 
         cx.foreground().run_until_parked();
         left_pane.read_with(cx, |pane, cx| {
@@ -4489,7 +4494,7 @@ mod tests {
                 &[ProjectEntryId::from_proto(2)]
             );
         });
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        cx.simulate_prompt_answer(window.id(), 0);
 
         cx.foreground().run_until_parked();
         close.await.unwrap();
@@ -4549,7 +4554,7 @@ mod tests {
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
 
         // Deactivating the window still saves the file.
-        cx.simulate_window_activation(Some(window.window_id()));
+        cx.simulate_window_activation(Some(window.id()));
         item.update(cx, |item, cx| {
             cx.focus_self();
             item.is_dirty = true;
@@ -4591,7 +4596,7 @@ mod tests {
         pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
             .await
             .unwrap();
-        assert!(!cx.has_pending_prompt(window.window_id()));
+        assert!(!cx.has_pending_prompt(window.id()));
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
 
         // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
@@ -4612,7 +4617,7 @@ mod tests {
         let _close_items =
             pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
         deterministic.run_until_parked();
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(cx.has_pending_prompt(window.id()));
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
     }
 

crates/zed/src/zed.rs 🔗

@@ -813,11 +813,12 @@ mod tests {
 
         // Replace existing windows
         let window_id = cx.window_ids()[0];
+        let window = cx.read_window(window_id, |cx| cx.window()).flatten();
         cx.update(|cx| {
             open_paths(
                 &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
                 &app_state,
-                Some(window_id),
+                window,
                 cx,
             )
         })
@@ -982,9 +983,8 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -1298,7 +1298,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         // Open a file within an existing worktree.
         workspace
@@ -1341,7 +1341,7 @@ mod tests {
         project.update(cx, |project, _| project.languages().add(rust_lang()));
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
         let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
 
         // Create a new untitled buffer
@@ -1436,7 +1436,7 @@ mod tests {
         project.update(cx, |project, _| project.languages().add(rust_lang()));
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         // Create a new untitled buffer
         cx.dispatch_action(window_id, NewFile);
@@ -1489,7 +1489,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -2087,7 +2087,7 @@ mod tests {
         cx.foreground().run_until_parked();
 
         let window = cx.add_window(|_| TestView);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         // Test loading the keymap base at all
         assert_key_bindings_for(
@@ -2258,7 +2258,7 @@ mod tests {
         cx.foreground().run_until_parked();
 
         let window = cx.add_window(|_| TestView);
-        let window_id = window.window_id();
+        let window_id = window.id();
 
         // Test loading the keymap base at all
         assert_key_bindings_for(