WIP: Return WindowHandle<V: View> from AppContext::add_window (#2820)

Nathan Sobo created

Instead of returning a usize for the window id, I'm instead returning a
`WindowHandle<V: View>` where `V` is the type of the window's root view.
@as-cii helped me with a cool technique using generic associated types
where methods on `WindowHandle` can return either T or Option<T>
depending on the `BorrowWindowContext::Result` associated type.

Some example usage...

```rs
let window = cx.add_window(|cx| MyView::new(cx));
let my_view = window.root(cx); // If cx is TestAppContext, returns MyView. Otherwise returns Option<MyView>, because the window could be closed.
```


This isn't insanely beneficial on its own, but I think it will help
clean up our testing story. I'm planning on making `window` more useful
in tests for laying out elements, etc.

- [x] Rework tests that call `add_window` šŸ˜… to expect only a window in
return.
- [x] Get tests passing
- [x] 🚬  test

Change summary

crates/collab/src/tests.rs                          |   5 
crates/collab/src/tests/integration_tests.rs        |  52 
crates/collab_ui/src/incoming_call_notification.rs  |   4 
crates/collab_ui/src/project_shared_notification.rs |   4 
crates/command_palette/src/command_palette.rs       |   4 
crates/copilot/src/sign_in.rs                       |  55 
crates/diagnostics/src/diagnostics.rs               |   8 
crates/editor/src/editor_tests.rs                   | 625 ++++++++------
crates/editor/src/element.rs                        |  30 
crates/editor/src/inlay_hint_cache.rs               |  30 
crates/editor/src/test/editor_lsp_test_context.rs   |   5 
crates/editor/src/test/editor_test_context.rs       |  12 
crates/file_finder/src/file_finder.rs               | 230 ++---
crates/gpui/src/app.rs                              | 577 ++++++++-----
crates/gpui/src/app/test_app_context.rs             |  23 
crates/gpui/src/app/window.rs                       |  20 
crates/language_tools/src/lsp_log_tests.rs          |   4 
crates/project_panel/src/project_panel.rs           |  32 
crates/project_symbols/src/project_symbols.rs       |   4 
crates/search/src/buffer_search.rs                  |  19 
crates/search/src/project_search.rs                 |  16 
crates/terminal_view/src/terminal_view.rs           |   4 
crates/workspace/src/pane.rs                        |  30 
crates/workspace/src/workspace.rs                   | 210 ++--
crates/zed/src/zed.rs                               |  34 
25 files changed, 1,157 insertions(+), 880 deletions(-)

Detailed changes

crates/collab/src/tests.rs šŸ”—

@@ -495,8 +495,9 @@ impl TestClient {
 
         // We use a workspace container so that we don't need to remove the window in order to
         // drop the workspace and we can use a ViewHandle instead.
-        let (window_id, container) = cx.add_window(|_| WorkspaceContainer { workspace: None });
-        let workspace = cx.add_view(window_id, |cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|_| WorkspaceContainer { workspace: None });
+        let container = window.root(cx);
+        let workspace = window.add_view(cx, |cx| Workspace::test_new(project.clone(), cx));
         container.update(cx, |container, cx| {
             container.workspace = Some(workspace.downgrade());
             cx.notify();

crates/collab/src/tests/integration_tests.rs šŸ”—

@@ -7,8 +7,7 @@ use client::{User, RECEIVE_TIMEOUT};
 use collections::HashSet;
 use editor::{
     test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
-    ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToOffset, ToggleCodeActions,
-    Undo,
+    ConfirmRename, Editor, ExcerptRange, MultiBuffer, Redo, Rename, ToggleCodeActions, Undo,
 };
 use fs::{repository::GitFileStatus, FakeFs, Fs as _, LineEnding, RemoveOptions};
 use futures::StreamExt as _;
@@ -1208,7 +1207,7 @@ async fn test_share_project(
     cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
-    let (window_b, _) = cx_b.add_window(|_| EmptyView);
+    let window_b = cx_b.add_window(|_| EmptyView);
     let mut server = TestServer::start(&deterministic).await;
     let client_a = server.create_client(cx_a, "user_a").await;
     let client_b = server.create_client(cx_b, "user_b").await;
@@ -1316,7 +1315,7 @@ async fn test_share_project(
         .await
         .unwrap();
 
-    let editor_b = cx_b.add_view(window_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+    let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
 
     // Client A sees client B's selection
     deterministic.run_until_parked();
@@ -1499,8 +1498,8 @@ async fn test_host_disconnect(
     deterministic.run_until_parked();
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
-    let (window_id_b, workspace_b) =
-        cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let workspace_b = window_b.root(cx_b);
     let editor_b = workspace_b
         .update(cx_b, |workspace, cx| {
             workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -1509,9 +1508,7 @@ async fn test_host_disconnect(
         .unwrap()
         .downcast::<Editor>()
         .unwrap();
-    assert!(cx_b
-        .read_window(window_id_b, |cx| editor_b.is_focused(cx))
-        .unwrap());
+    assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
     assert!(cx_b.is_window_edited(workspace_b.window_id()));
 
@@ -1525,7 +1522,7 @@ async fn test_host_disconnect(
     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 
     // Ensure client B's edited state is reset and that the whole window is blurred.
-    cx_b.read_window(window_id_b, |cx| {
+    window_b.read_with(cx_b, |cx| {
         assert_eq!(cx.focused_view_id(), None);
     });
     assert!(!cx_b.is_window_edited(workspace_b.window_id()));
@@ -3445,13 +3442,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
         .await
         .unwrap();
-    let (window_a, _) = cx_a.add_window(|_| EmptyView);
-    let editor_a = cx_a.add_view(window_a, |cx| {
-        Editor::for_buffer(buffer_a, Some(project_a), cx)
-    });
+    let window_a = cx_a.add_window(|_| EmptyView);
+    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_a.window_id(),
         editor: editor_a,
     };
 
@@ -3460,13 +3455,11 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
         .await
         .unwrap();
-    let (window_b, _) = cx_b.add_window(|_| EmptyView);
-    let editor_b = cx_b.add_view(window_b, |cx| {
-        Editor::for_buffer(buffer_b, Some(project_b), cx)
-    });
+    let window_b = cx_b.add_window(|_| EmptyView);
+    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_b.window_id(),
         editor: editor_b,
     };
 
@@ -4205,8 +4198,8 @@ async fn test_collaborating_with_completion(
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
         .await
         .unwrap();
-    let (window_b, _) = cx_b.add_window(|_| EmptyView);
-    let editor_b = cx_b.add_view(window_b, |cx| {
+    let window_b = cx_b.add_window(|_| EmptyView);
+    let editor_b = window_b.add_view(cx_b, |cx| {
         Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
     });
 
@@ -5316,7 +5309,8 @@ async fn test_collaborating_with_code_actions(
 
     // Join the project as client B.
     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let workspace_b = window_b.root(cx_b);
     let editor_b = workspace_b
         .update(cx_b, |workspace, cx| {
             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -5540,7 +5534,8 @@ async fn test_collaborating_with_renames(
         .unwrap();
     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 
-    let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let window_b = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let workspace_b = window_b.root(cx_b);
     let editor_b = workspace_b
         .update(cx_b, |workspace, cx| {
             workspace.open_path((worktree_id, "one.rs"), None, true, cx)
@@ -5571,6 +5566,7 @@ async fn test_collaborating_with_renames(
         .unwrap();
     prepare_rename.await.unwrap();
     editor_b.update(cx_b, |editor, cx| {
+        use editor::ToOffset;
         let rename = editor.pending_rename().unwrap();
         let buffer = editor.buffer().read(cx).snapshot(cx);
         assert_eq!(
@@ -7601,8 +7597,8 @@ async fn test_on_input_format_from_host_to_guest(
         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
         .await
         .unwrap();
-    let (window_a, _) = cx_a.add_window(|_| EmptyView);
-    let editor_a = cx_a.add_view(window_a, |cx| {
+    let window_a = cx_a.add_window(|_| EmptyView);
+    let editor_a = window_a.add_view(cx_a, |cx| {
         Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
     });
 
@@ -7730,8 +7726,8 @@ async fn test_on_input_format_from_guest_to_host(
         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
         .await
         .unwrap();
-    let (window_b, _) = cx_b.add_window(|_| EmptyView);
-    let editor_b = cx_b.add_view(window_b, |cx| {
+    let window_b = cx_b.add_window(|_| EmptyView);
+    let editor_b = window_b.add_view(cx_b, |cx| {
         Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
     });
 

crates/collab_ui/src/incoming_call_notification.rs šŸ”—

@@ -31,7 +31,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
 
                 for screen in cx.platform().screens() {
                     let screen_bounds = screen.bounds();
-                    let (window_id, _) = cx.add_window(
+                    let window = cx.add_window(
                         WindowOptions {
                             bounds: WindowBounds::Fixed(RectF::new(
                                 screen_bounds.upper_right()
@@ -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_id);
+                    notification_windows.push(window.window_id());
                 }
             }
         }

crates/collab_ui/src/project_shared_notification.rs šŸ”—

@@ -26,7 +26,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
 
             for screen in cx.platform().screens() {
                 let screen_bounds = screen.bounds();
-                let (window_id, _) = cx.add_window(
+                let window = cx.add_window(
                     WindowOptions {
                         bounds: WindowBounds::Fixed(RectF::new(
                             screen_bounds.upper_right() - vec2f(PADDING + window_size.x(), PADDING),
@@ -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_id);
+                    .push(window.window_id());
             }
         }
         room::Event::RemoteProjectUnshared { project_id } => {

crates/command_palette/src/command_palette.rs šŸ”—

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

crates/copilot/src/sign_in.rs šŸ”—

@@ -4,7 +4,7 @@ use gpui::{
     geometry::rect::RectF,
     platform::{WindowBounds, WindowKind, WindowOptions},
     AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
-    ViewHandle,
+    WindowHandle,
 };
 use theme::ui::modal;
 
@@ -18,43 +18,43 @@ const COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
 
 pub fn init(cx: &mut AppContext) {
     if let Some(copilot) = Copilot::global(cx) {
-        let mut code_verification: Option<ViewHandle<CopilotCodeVerification>> = None;
+        let mut verification_window: Option<WindowHandle<CopilotCodeVerification>> = None;
         cx.observe(&copilot, move |copilot, cx| {
             let status = copilot.read(cx).status();
 
             match &status {
                 crate::Status::SigningIn { prompt } => {
-                    if let Some(code_verification_handle) = code_verification.as_mut() {
-                        let window_id = code_verification_handle.window_id();
-                        let updated = cx.update_window(window_id, |cx| {
-                            code_verification_handle.update(cx, |code_verification, cx| {
-                                code_verification.set_status(status.clone(), cx)
-                            });
-                            cx.activate_window();
-                        });
-                        if updated.is_none() {
-                            code_verification = Some(create_copilot_auth_window(cx, &status));
+                    if let Some(window) = verification_window.as_mut() {
+                        let updated = window
+                            .root(cx)
+                            .map(|root| {
+                                root.update(cx, |verification, cx| {
+                                    verification.set_status(status.clone(), cx);
+                                    cx.activate_window();
+                                })
+                            })
+                            .is_some();
+                        if !updated {
+                            verification_window = Some(create_copilot_auth_window(cx, &status));
                         }
                     } else if let Some(_prompt) = prompt {
-                        code_verification = Some(create_copilot_auth_window(cx, &status));
+                        verification_window = Some(create_copilot_auth_window(cx, &status));
                     }
                 }
                 Status::Authorized | Status::Unauthorized => {
-                    if let Some(code_verification) = code_verification.as_ref() {
-                        let window_id = code_verification.window_id();
-                        cx.update_window(window_id, |cx| {
-                            code_verification.update(cx, |code_verification, cx| {
-                                code_verification.set_status(status, cx)
+                    if let Some(window) = verification_window.as_ref() {
+                        if let Some(verification) = window.root(cx) {
+                            verification.update(cx, |verification, cx| {
+                                verification.set_status(status, cx);
+                                cx.platform().activate(true);
+                                cx.activate_window();
                             });
-
-                            cx.platform().activate(true);
-                            cx.activate_window();
-                        });
+                        }
                     }
                 }
                 _ => {
-                    if let Some(code_verification) = code_verification.take() {
-                        cx.update_window(code_verification.window_id(), |cx| cx.remove_window());
+                    if let Some(code_verification) = verification_window.take() {
+                        code_verification.update(cx, |cx| cx.remove_window());
                     }
                 }
             }
@@ -66,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
 fn create_copilot_auth_window(
     cx: &mut AppContext,
     status: &Status,
-) -> ViewHandle<CopilotCodeVerification> {
+) -> WindowHandle<CopilotCodeVerification> {
     let window_size = theme::current(cx).copilot.modal.dimensions();
     let window_options = WindowOptions {
         bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@@ -78,10 +78,9 @@ fn create_copilot_auth_window(
         is_movable: true,
         screen: None,
     };
-    let (_, view) = cx.add_window(window_options, |_cx| {
+    cx.add_window(window_options, |_cx| {
         CopilotCodeVerification::new(status.clone())
-    });
-    view
+    })
 }
 
 pub struct CopilotCodeVerification {

crates/diagnostics/src/diagnostics.rs šŸ”—

@@ -855,7 +855,9 @@ mod tests {
 
         let language_server_id = LanguageServerId(0);
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
 
         // Create some diagnostics
         project.update(cx, |project, cx| {
@@ -1248,7 +1250,9 @@ mod tests {
         let server_id_1 = LanguageServerId(100);
         let server_id_2 = LanguageServerId(101);
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
 
         let view = cx.add_view(window_id, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)

crates/editor/src/editor_tests.rs šŸ”—

@@ -48,36 +48,40 @@ fn test_edit_events(cx: &mut TestAppContext) {
     });
 
     let events = Rc::new(RefCell::new(Vec::new()));
-    let (_, editor1) = cx.add_window({
-        let events = events.clone();
-        |cx| {
-            cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                if matches!(
-                    event,
-                    Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                ) {
-                    events.borrow_mut().push(("editor1", event.clone()));
-                }
-            })
-            .detach();
-            Editor::for_buffer(buffer.clone(), None, cx)
-        }
-    });
-    let (_, editor2) = cx.add_window({
-        let events = events.clone();
-        |cx| {
-            cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                if matches!(
-                    event,
-                    Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                ) {
-                    events.borrow_mut().push(("editor2", event.clone()));
-                }
-            })
-            .detach();
-            Editor::for_buffer(buffer.clone(), None, cx)
-        }
-    });
+    let editor1 = cx
+        .add_window({
+            let events = events.clone();
+            |cx| {
+                cx.subscribe(&cx.handle(), move |_, _, event, _| {
+                    if matches!(
+                        event,
+                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
+                    ) {
+                        events.borrow_mut().push(("editor1", event.clone()));
+                    }
+                })
+                .detach();
+                Editor::for_buffer(buffer.clone(), None, cx)
+            }
+        })
+        .root(cx);
+    let editor2 = cx
+        .add_window({
+            let events = events.clone();
+            |cx| {
+                cx.subscribe(&cx.handle(), move |_, _, event, _| {
+                    if matches!(
+                        event,
+                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
+                    ) {
+                        events.borrow_mut().push(("editor2", event.clone()));
+                    }
+                })
+                .detach();
+                Editor::for_buffer(buffer.clone(), None, cx)
+            }
+        })
+        .root(cx);
     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 
     // Mutating editor 1 will emit an `Edited` event only for that editor.
@@ -173,7 +177,9 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
     let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
     let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+    let editor = cx
+        .add_window(|cx| build_editor(buffer.clone(), cx))
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         editor.start_transaction_at(now, cx);
@@ -343,10 +349,12 @@ fn test_ime_composition(cx: &mut TestAppContext) {
 fn test_selection_with_mouse(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, editor) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
-        build_editor(buffer, cx)
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     editor.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
     });
@@ -410,10 +418,12 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
 fn test_canceling_pending_selection(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@@ -456,10 +466,12 @@ fn test_clone(cx: &mut TestAppContext) {
         true,
     );
 
-    let (_, editor) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(&text, cx);
-        build_editor(buffer, cx)
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&text, cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@@ -473,9 +485,11 @@ fn test_clone(cx: &mut TestAppContext) {
         );
     });
 
-    let (_, cloned_editor) = editor.update(cx, |editor, cx| {
-        cx.add_window(Default::default(), |cx| editor.clone(cx))
-    });
+    let cloned_editor = editor
+        .update(cx, |editor, cx| {
+            cx.add_window(Default::default(), |cx| editor.clone(cx))
+        })
+        .root(cx);
 
     let snapshot = editor.update(cx, |e, cx| e.snapshot(cx));
     let cloned_snapshot = cloned_editor.update(cx, |e, cx| e.snapshot(cx));
@@ -509,7 +523,9 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
 
     let fs = FakeFs::new(cx.background());
     let project = Project::test(fs, [], cx).await;
-    let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+    let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    let workspace = window.root(cx);
+    let window_id = window.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);
@@ -618,10 +634,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
 fn test_cancel(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@@ -661,9 +679,10 @@ fn test_cancel(cx: &mut TestAppContext) {
 fn test_fold_action(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(
-            &"
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(
+                &"
                 impl Foo {
                     // Hello!
 
@@ -680,11 +699,12 @@ fn test_fold_action(cx: &mut TestAppContext) {
                     }
                 }
             "
-            .unindent(),
-            cx,
-        );
-        build_editor(buffer.clone(), cx)
-    });
+                .unindent(),
+                cx,
+            );
+            build_editor(buffer.clone(), cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -752,7 +772,9 @@ fn test_move_cursor(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
     let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
-    let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
+    let view = cx
+        .add_window(|cx| build_editor(buffer.clone(), cx))
+        .root(cx);
 
     buffer.update(cx, |buffer, cx| {
         buffer.edit(
@@ -827,10 +849,12 @@ fn test_move_cursor(cx: &mut TestAppContext) {
 fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγΓε\n", cx);
-        build_editor(buffer.clone(), cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγΓε\n", cx);
+            build_editor(buffer.clone(), cx)
+        })
+        .root(cx);
 
     assert_eq!('ⓐ'.len_utf8(), 3);
     assert_eq!('α'.len_utf8(), 2);
@@ -932,10 +956,12 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
-        build_editor(buffer.clone(), cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+            build_editor(buffer.clone(), cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@@ -982,10 +1008,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 fn test_beginning_end_of_line(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\n  def", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1145,10 +1173,12 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1197,10 +1227,13 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer =
+                MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.set_wrap_width(Some(140.), cx);
@@ -1530,10 +1563,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
 fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("one two three four", cx);
-        build_editor(buffer.clone(), cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("one two three four", cx);
+            build_editor(buffer.clone(), cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -1566,10 +1601,12 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 fn test_newline(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
-        build_editor(buffer.clone(), cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
+            build_editor(buffer.clone(), cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -1589,9 +1626,10 @@ fn test_newline(cx: &mut TestAppContext) {
 fn test_newline_with_old_selections(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, editor) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(
-            "
+    let editor = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(
+                "
                 a
                 b(
                     X
@@ -1600,19 +1638,20 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) {
                     X
                 )
             "
-            .unindent()
-            .as_str(),
-            cx,
-        );
-        let mut editor = build_editor(buffer.clone(), cx);
-        editor.change_selections(None, cx, |s| {
-            s.select_ranges([
-                Point::new(2, 4)..Point::new(2, 5),
-                Point::new(5, 4)..Point::new(5, 5),
-            ])
-        });
-        editor
-    });
+                .unindent()
+                .as_str(),
+                cx,
+            );
+            let mut editor = build_editor(buffer.clone(), cx);
+            editor.change_selections(None, cx, |s| {
+                s.select_ranges([
+                    Point::new(2, 4)..Point::new(2, 5),
+                    Point::new(5, 4)..Point::new(5, 5),
+                ])
+            });
+            editor
+        })
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         // Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -1817,12 +1856,14 @@ async fn test_newline_comments(cx: &mut gpui::TestAppContext) {
 fn test_insert_with_old_selections(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, editor) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
-        let mut editor = build_editor(buffer.clone(), cx);
-        editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
-        editor
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
+            let mut editor = build_editor(buffer.clone(), cx);
+            editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
+            editor
+        })
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         // Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -2329,10 +2370,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
 fn test_delete_line(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2352,10 +2395,12 @@ fn test_delete_line(cx: &mut TestAppContext) {
         );
     });
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
@@ -2654,10 +2699,12 @@ async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 fn test_duplicate_line(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2680,10 +2727,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
         );
     });
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2707,10 +2756,12 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
 fn test_move_line_up_down(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
@@ -2806,10 +2857,12 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
 fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, editor) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-        build_editor(buffer, cx)
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     editor.update(cx, |editor, cx| {
         let snapshot = editor.buffer.read(cx).snapshot(cx);
         editor.insert_blocks(
@@ -2834,102 +2887,94 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 fn test_transpose(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    _ = cx
-        .add_window(|cx| {
-            let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
+    _ = cx.add_window(|cx| {
+        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
 
-            editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bac");
-            assert_eq!(editor.selections.ranges(cx), [2..2]);
+        editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bac");
+        assert_eq!(editor.selections.ranges(cx), [2..2]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bca");
-            assert_eq!(editor.selections.ranges(cx), [3..3]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bca");
+        assert_eq!(editor.selections.ranges(cx), [3..3]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bac");
-            assert_eq!(editor.selections.ranges(cx), [3..3]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bac");
+        assert_eq!(editor.selections.ranges(cx), [3..3]);
 
-            editor
-        })
-        .1;
+        editor
+    });
 
-    _ = cx
-        .add_window(|cx| {
-            let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+    _ = cx.add_window(|cx| {
+        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 
-            editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "acb\nde");
-            assert_eq!(editor.selections.ranges(cx), [3..3]);
+        editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "acb\nde");
+        assert_eq!(editor.selections.ranges(cx), [3..3]);
 
-            editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "acbd\ne");
-            assert_eq!(editor.selections.ranges(cx), [5..5]);
+        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "acbd\ne");
+        assert_eq!(editor.selections.ranges(cx), [5..5]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "acbde\n");
-            assert_eq!(editor.selections.ranges(cx), [6..6]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "acbde\n");
+        assert_eq!(editor.selections.ranges(cx), [6..6]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "acbd\ne");
-            assert_eq!(editor.selections.ranges(cx), [6..6]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "acbd\ne");
+        assert_eq!(editor.selections.ranges(cx), [6..6]);
 
-            editor
-        })
-        .1;
+        editor
+    });
 
-    _ = cx
-        .add_window(|cx| {
-            let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+    _ = cx.add_window(|cx| {
+        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 
-            editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bacd\ne");
-            assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
+        editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bacd\ne");
+        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bcade\n");
-            assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bcade\n");
+        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bcda\ne");
-            assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bcda\ne");
+        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bcade\n");
-            assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bcade\n");
+        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "bcaed\n");
-            assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "bcaed\n");
+        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 
-            editor
-        })
-        .1;
+        editor
+    });
 
-    _ = cx
-        .add_window(|cx| {
-            let mut editor = build_editor(MultiBuffer::build_simple("šŸšŸ€āœ‹", cx), cx);
+    _ = cx.add_window(|cx| {
+        let mut editor = build_editor(MultiBuffer::build_simple("šŸšŸ€āœ‹", cx), cx);
 
-            editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "šŸ€šŸāœ‹");
-            assert_eq!(editor.selections.ranges(cx), [8..8]);
+        editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "šŸ€šŸāœ‹");
+        assert_eq!(editor.selections.ranges(cx), [8..8]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "šŸ€āœ‹šŸ");
-            assert_eq!(editor.selections.ranges(cx), [11..11]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "šŸ€āœ‹šŸ");
+        assert_eq!(editor.selections.ranges(cx), [11..11]);
 
-            editor.transpose(&Default::default(), cx);
-            assert_eq!(editor.text(cx), "šŸ€šŸāœ‹");
-            assert_eq!(editor.selections.ranges(cx), [11..11]);
+        editor.transpose(&Default::default(), cx);
+        assert_eq!(editor.text(cx), "šŸ€šŸāœ‹");
+        assert_eq!(editor.selections.ranges(cx), [11..11]);
 
-            editor
-        })
-        .1;
+        editor
+    });
 }
 
 #[gpui::test]
@@ -3132,10 +3177,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 fn test_select_all(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.select_all(&SelectAll, cx);
         assert_eq!(
@@ -3149,10 +3196,12 @@ fn test_select_all(cx: &mut TestAppContext) {
 fn test_select_line(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -3196,10 +3245,12 @@ fn test_select_line(cx: &mut TestAppContext) {
 fn test_split_selection_into_lines(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
     view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
@@ -3267,10 +3318,12 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 fn test_add_selection_above_below(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, view) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
-        build_editor(buffer, cx)
-    });
+    let view = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+            build_editor(buffer, cx)
+        })
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -3555,7 +3608,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
 
     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
         .await;
 
@@ -3718,7 +3771,7 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 
     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     editor
         .condition(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
         .await;
@@ -4281,7 +4334,7 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
 
     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
         .await;
 
@@ -4429,7 +4482,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
 
     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     editor
         .condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
         .await;
@@ -4519,7 +4572,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) {
     );
 
     let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
 
     editor.update(cx, |editor, cx| {
         let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
@@ -4649,7 +4702,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
     let fake_server = fake_servers.next().await.unwrap();
 
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
     assert!(cx.read(|cx| editor.is_dirty(cx)));
 
@@ -4761,7 +4814,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
     let fake_server = fake_servers.next().await.unwrap();
 
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
     assert!(cx.read(|cx| editor.is_dirty(cx)));
 
@@ -4875,7 +4928,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
     let fake_server = fake_servers.next().await.unwrap();
 
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
+    let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
 
     let format = editor.update(cx, |editor, cx| {
@@ -5653,7 +5706,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
+    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
     view.update(cx, |view, cx| {
         assert_eq!(view.text(cx), "aaaa\nbbbb");
         view.change_selections(None, cx, |s| {
@@ -5723,7 +5776,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
+    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
     view.update(cx, |view, cx| {
         let (expected_text, selection_ranges) = marked_text_ranges(
             indoc! {"
@@ -5799,22 +5852,24 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let (_, editor) = cx.add_window(|cx| {
-        let mut editor = build_editor(multibuffer.clone(), cx);
-        let snapshot = editor.snapshot(cx);
-        editor.change_selections(None, cx, |s| {
-            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
-        });
-        editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
-        assert_eq!(
-            editor.selections.ranges(cx),
-            [
-                Point::new(1, 3)..Point::new(1, 3),
-                Point::new(2, 1)..Point::new(2, 1),
-            ]
-        );
-        editor
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let mut editor = build_editor(multibuffer.clone(), cx);
+            let snapshot = editor.snapshot(cx);
+            editor.change_selections(None, cx, |s| {
+                s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
+            });
+            editor.begin_selection(Point::new(2, 1).to_display_point(&snapshot), true, 1, cx);
+            assert_eq!(
+                editor.selections.ranges(cx),
+                [
+                    Point::new(1, 3)..Point::new(1, 3),
+                    Point::new(2, 1)..Point::new(2, 1),
+                ]
+            );
+            editor
+        })
+        .root(cx);
 
     // Refreshing selections is a no-op when excerpts haven't changed.
     editor.update(cx, |editor, cx| {
@@ -5884,16 +5939,18 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let (_, editor) = cx.add_window(|cx| {
-        let mut editor = build_editor(multibuffer.clone(), cx);
-        let snapshot = editor.snapshot(cx);
-        editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
-        assert_eq!(
-            editor.selections.ranges(cx),
-            [Point::new(1, 3)..Point::new(1, 3)]
-        );
-        editor
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let mut editor = build_editor(multibuffer.clone(), cx);
+            let snapshot = editor.snapshot(cx);
+            editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
+            assert_eq!(
+                editor.selections.ranges(cx),
+                [Point::new(1, 3)..Point::new(1, 3)]
+            );
+            editor
+        })
+        .root(cx);
 
     multibuffer.update(cx, |multibuffer, cx| {
         multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
@@ -5956,7 +6013,7 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 
     let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, view) = cx.add_window(|cx| build_editor(buffer, cx));
+    let view = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
     view.condition(cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
         .await;
 
@@ -5992,10 +6049,12 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 fn test_highlighted_ranges(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let (_, editor) = cx.add_window(|cx| {
-        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
-        build_editor(buffer.clone(), cx)
-    });
+    let editor = cx
+        .add_window(|cx| {
+            let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+            build_editor(buffer.clone(), cx)
+        })
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         struct Type1;
@@ -6084,16 +6143,20 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
             .unwrap();
         cx.add_model(|cx| MultiBuffer::singleton(buffer, cx))
     });
-    let (_, leader) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
-    let (_, follower) = cx.update(|cx| {
-        cx.add_window(
-            WindowOptions {
-                bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
-                ..Default::default()
-            },
-            |cx| build_editor(buffer.clone(), cx),
-        )
-    });
+    let leader = cx
+        .add_window(|cx| build_editor(buffer.clone(), cx))
+        .root(cx);
+    let follower = cx
+        .update(|cx| {
+            cx.add_window(
+                WindowOptions {
+                    bounds: WindowBounds::Fixed(RectF::from_points(vec2f(0., 0.), vec2f(10., 80.))),
+                    ..Default::default()
+                },
+                |cx| build_editor(buffer.clone(), cx),
+            )
+        })
+        .root(cx);
 
     let is_still_following = Rc::new(RefCell::new(true));
     let follower_edit_event_count = Rc::new(RefCell::new(0));
@@ -6224,7 +6287,9 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
 
     let fs = FakeFs::new(cx.background());
     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-    let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+    let workspace = cx
+        .add_window(|cx| Workspace::test_new(project.clone(), cx))
+        .root(cx);
     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
     let leader = pane.update(cx, |_, cx| {
@@ -6968,7 +7033,7 @@ async fn test_copilot_multibuffer(
         );
         multibuffer
     });
-    let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
+    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
 
     handle_copilot_completion_request(
         &copilot_lsp,
@@ -7098,7 +7163,7 @@ async fn test_copilot_disabled_globs(
         );
         multibuffer
     });
-    let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
+    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
 
     let mut copilot_requests = copilot_lsp
         .handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
@@ -7177,7 +7242,9 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
     .await;
     let project = Project::test(fs, ["/a".as_ref()], cx).await;
     project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-    let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+    let workspace = cx
+        .add_window(|cx| Workspace::test_new(project.clone(), cx))
+        .root(cx);
     let worktree_id = workspace.update(cx, |workspace, cx| {
         workspace.project().read_with(cx, |project, cx| {
             project.worktrees(cx).next().unwrap().read(cx).id()

crates/editor/src/element.rs šŸ”—

@@ -3002,10 +3002,12 @@ mod tests {
     fn test_layout_line_numbers(cx: &mut TestAppContext) {
         init_test(cx, |_| {});
 
-        let (_, editor) = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
-            Editor::new(EditorMode::Full, buffer, None, None, cx)
-        });
+        let editor = cx
+            .add_window(|cx| {
+                let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
+                Editor::new(EditorMode::Full, buffer, None, None, cx)
+            })
+            .root(cx);
         let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
 
         let layouts = editor.update(cx, |editor, cx| {
@@ -3021,10 +3023,12 @@ mod tests {
     fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
         init_test(cx, |_| {});
 
-        let (_, editor) = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple("", cx);
-            Editor::new(EditorMode::Full, buffer, None, None, cx)
-        });
+        let editor = cx
+            .add_window(|cx| {
+                let buffer = MultiBuffer::build_simple("", cx);
+                Editor::new(EditorMode::Full, buffer, None, None, cx)
+            })
+            .root(cx);
 
         editor.update(cx, |editor, cx| {
             editor.set_placeholder_text("hello", cx);
@@ -3231,10 +3235,12 @@ mod tests {
         info!(
             "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
         );
-        let (_, editor) = cx.add_window(|cx| {
-            let buffer = MultiBuffer::build_simple(&input_text, cx);
-            Editor::new(editor_mode, buffer, None, None, cx)
-        });
+        let editor = cx
+            .add_window(|cx| {
+                let buffer = MultiBuffer::build_simple(&input_text, cx);
+                Editor::new(editor_mode, buffer, None, None, cx)
+            })
+            .root(cx);
 
         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
         let (_, layout_state) = editor.update(cx, |editor, cx| {

crates/editor/src/inlay_hint_cache.rs šŸ”—

@@ -1135,7 +1135,9 @@ mod tests {
                 )
                 .await;
         let project = Project::test(fs, ["/a".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let worktree_id = workspace.update(cx, |workspace, cx| {
             workspace.project().read_with(cx, |project, cx| {
                 project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1835,7 +1837,9 @@ mod tests {
         .await;
         let project = Project::test(fs, ["/a".as_ref()], cx).await;
         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let worktree_id = workspace.update(cx, |workspace, cx| {
             workspace.project().read_with(cx, |project, cx| {
                 project.worktrees(cx).next().unwrap().read(cx).id()
@@ -1988,7 +1992,9 @@ mod tests {
         project.update(cx, |project, _| {
             project.languages().add(Arc::clone(&language))
         });
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let worktree_id = workspace.update(cx, |workspace, cx| {
             workspace.project().read_with(cx, |project, cx| {
                 project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2074,8 +2080,9 @@ mod tests {
 
         deterministic.run_until_parked();
         cx.foreground().run_until_parked();
-        let (_, editor) =
-            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+        let editor = cx
+            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
+            .root(cx);
         let editor_edited = Arc::new(AtomicBool::new(false));
         let fake_server = fake_servers.next().await.unwrap();
         let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2327,7 +2334,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
         project.update(cx, |project, _| {
             project.languages().add(Arc::clone(&language))
         });
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let worktree_id = workspace.update(cx, |workspace, cx| {
             workspace.project().read_with(cx, |project, cx| {
                 project.worktrees(cx).next().unwrap().read(cx).id()
@@ -2372,8 +2381,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
 
         deterministic.run_until_parked();
         cx.foreground().run_until_parked();
-        let (_, editor) =
-            cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx));
+        let editor = cx
+            .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
+            .root(cx);
         let editor_edited = Arc::new(AtomicBool::new(false));
         let fake_server = fake_servers.next().await.unwrap();
         let closure_editor_edited = Arc::clone(&editor_edited);
@@ -2561,7 +2571,9 @@ all hints should be invalidated and requeried for all of its visible excerpts"
 
         let project = Project::test(fs, ["/a".as_ref()], cx).await;
         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let worktree_id = workspace.update(cx, |workspace, cx| {
             workspace.project().read_with(cx, |project, cx| {
                 project.worktrees(cx).next().unwrap().read(cx).id()

crates/editor/src/test/editor_lsp_test_context.rs šŸ”—

@@ -69,7 +69,8 @@ impl<'a> EditorLspTestContext<'a> {
             .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
             .await;
 
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         project
             .update(cx, |project, cx| {
                 project.find_or_create_local_worktree("/root", true, cx)
@@ -98,7 +99,7 @@ impl<'a> EditorLspTestContext<'a> {
         Self {
             cx: EditorTestContext {
                 cx,
-                window_id,
+                window_id: window.window_id(),
                 editor,
             },
             lsp,

crates/editor/src/test/editor_test_context.rs šŸ”—

@@ -32,16 +32,14 @@ impl<'a> EditorTestContext<'a> {
         let buffer = project
             .update(cx, |project, cx| project.create_buffer("", None, cx))
             .unwrap();
-        let (window_id, editor) = cx.update(|cx| {
-            cx.add_window(Default::default(), |cx| {
-                cx.focus_self();
-                build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
-            })
+        let window = cx.add_window(|cx| {
+            cx.focus_self();
+            build_editor(MultiBuffer::build_from_buffer(buffer, cx), cx)
         });
-
+        let editor = window.root(cx);
         Self {
             cx,
-            window_id,
+            window_id: window.window_id(),
             editor,
         }
     }

crates/file_finder/src/file_finder.rs šŸ”—

@@ -617,8 +617,9 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        cx.dispatch_action(window_id, Toggle);
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        cx.dispatch_action(window.window_id(), Toggle);
 
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
         finder
@@ -631,8 +632,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window_id, SelectNext);
-        cx.dispatch_action(window_id, Confirm);
+        cx.dispatch_action(window.window_id(), SelectNext);
+        cx.dispatch_action(window.window_id(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -671,8 +672,9 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        cx.dispatch_action(window_id, Toggle);
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        cx.dispatch_action(window.window_id(), Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
 
         let file_query = &first_file_name[..3];
@@ -704,8 +706,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window_id, SelectNext);
-        cx.dispatch_action(window_id, Confirm);
+        cx.dispatch_action(window.window_id(), SelectNext);
+        cx.dispatch_action(window.window_id(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -754,8 +756,9 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        cx.dispatch_action(window_id, Toggle);
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        cx.dispatch_action(window.window_id(), Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
 
         let file_query = &first_file_name[..3];
@@ -787,8 +790,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window_id, SelectNext);
-        cx.dispatch_action(window_id, Confirm);
+        cx.dispatch_action(window.window_id(), SelectNext);
+        cx.dispatch_action(window.window_id(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -837,19 +840,23 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) = cx.add_window(|cx| {
-            Picker::new(
-                FileFinderDelegate::new(
-                    workspace.downgrade(),
-                    workspace.read(cx).project().clone(),
-                    None,
-                    Vec::new(),
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
+        let finder = cx
+            .add_window(|cx| {
+                Picker::new(
+                    FileFinderDelegate::new(
+                        workspace.downgrade(),
+                        workspace.read(cx).project().clone(),
+                        None,
+                        Vec::new(),
+                        cx,
+                    ),
                     cx,
-                ),
-                cx,
-            )
-        });
+                )
+            })
+            .root(cx);
 
         let query = test_path_like("hi");
         finder
@@ -931,19 +938,23 @@ mod tests {
             cx,
         )
         .await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) = cx.add_window(|cx| {
-            Picker::new(
-                FileFinderDelegate::new(
-                    workspace.downgrade(),
-                    workspace.read(cx).project().clone(),
-                    None,
-                    Vec::new(),
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
+        let finder = cx
+            .add_window(|cx| {
+                Picker::new(
+                    FileFinderDelegate::new(
+                        workspace.downgrade(),
+                        workspace.read(cx).project().clone(),
+                        None,
+                        Vec::new(),
+                        cx,
+                    ),
                     cx,
-                ),
-                cx,
-            )
-        });
+                )
+            })
+            .root(cx);
         finder
             .update(cx, |f, cx| {
                 f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@@ -967,19 +978,23 @@ mod tests {
             cx,
         )
         .await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) = cx.add_window(|cx| {
-            Picker::new(
-                FileFinderDelegate::new(
-                    workspace.downgrade(),
-                    workspace.read(cx).project().clone(),
-                    None,
-                    Vec::new(),
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
+        let finder = cx
+            .add_window(|cx| {
+                Picker::new(
+                    FileFinderDelegate::new(
+                        workspace.downgrade(),
+                        workspace.read(cx).project().clone(),
+                        None,
+                        Vec::new(),
+                        cx,
+                    ),
                     cx,
-                ),
-                cx,
-            )
-        });
+                )
+            })
+            .root(cx);
 
         // Even though there is only one worktree, that worktree's filename
         // is included in the matching, because the worktree is a single file.
@@ -1015,61 +1030,6 @@ mod tests {
         finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
     }
 
-    #[gpui::test]
-    async fn test_multiple_matches_with_same_relative_path(cx: &mut TestAppContext) {
-        let app_state = init_test(cx);
-        app_state
-            .fs
-            .as_fake()
-            .insert_tree(
-                "/root",
-                json!({
-                    "dir1": { "a.txt": "" },
-                    "dir2": { "a.txt": "" }
-                }),
-            )
-            .await;
-
-        let project = Project::test(
-            app_state.fs.clone(),
-            ["/root/dir1".as_ref(), "/root/dir2".as_ref()],
-            cx,
-        )
-        .await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-
-        let (_, finder) = cx.add_window(|cx| {
-            Picker::new(
-                FileFinderDelegate::new(
-                    workspace.downgrade(),
-                    workspace.read(cx).project().clone(),
-                    None,
-                    Vec::new(),
-                    cx,
-                ),
-                cx,
-            )
-        });
-
-        // Run a search that matches two files with the same relative path.
-        finder
-            .update(cx, |f, cx| {
-                f.delegate_mut().spawn_search(test_path_like("a.t"), cx)
-            })
-            .await;
-
-        // Can switch between different matches with the same relative path.
-        finder.update(cx, |finder, cx| {
-            let delegate = finder.delegate_mut();
-            assert_eq!(delegate.matches.len(), 2);
-            assert_eq!(delegate.selected_index(), 0);
-            delegate.set_selected_index(1, cx);
-            assert_eq!(delegate.selected_index(), 1);
-            delegate.set_selected_index(0, cx);
-            assert_eq!(delegate.selected_index(), 0);
-        });
-    }
-
     #[gpui::test]
     async fn test_path_distance_ordering(cx: &mut TestAppContext) {
         let app_state = init_test(cx);
@@ -1089,7 +1049,9 @@ 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));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
@@ -1103,18 +1065,20 @@ mod tests {
             worktree_id,
             path: Arc::from(Path::new("/root/dir2/b.txt")),
         }));
-        let (_, finder) = cx.add_window(|cx| {
-            Picker::new(
-                FileFinderDelegate::new(
-                    workspace.downgrade(),
-                    workspace.read(cx).project().clone(),
-                    b_path,
-                    Vec::new(),
+        let finder = cx
+            .add_window(|cx| {
+                Picker::new(
+                    FileFinderDelegate::new(
+                        workspace.downgrade(),
+                        workspace.read(cx).project().clone(),
+                        b_path,
+                        Vec::new(),
+                        cx,
+                    ),
                     cx,
-                ),
-                cx,
-            )
-        });
+                )
+            })
+            .root(cx);
 
         finder
             .update(cx, |f, cx| {
@@ -1151,19 +1115,23 @@ 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));
-        let (_, finder) = cx.add_window(|cx| {
-            Picker::new(
-                FileFinderDelegate::new(
-                    workspace.downgrade(),
-                    workspace.read(cx).project().clone(),
-                    None,
-                    Vec::new(),
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
+        let finder = cx
+            .add_window(|cx| {
+                Picker::new(
+                    FileFinderDelegate::new(
+                        workspace.downgrade(),
+                        workspace.read(cx).project().clone(),
+                        None,
+                        Vec::new(),
+                        cx,
+                    ),
                     cx,
-                ),
-                cx,
-            )
-        });
+                )
+            })
+            .root(cx);
         finder
             .update(cx, |f, cx| {
                 f.delegate_mut().spawn_search(test_path_like("dir"), cx)
@@ -1198,7 +1166,9 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
@@ -1404,7 +1374,9 @@ mod tests {
         .detach();
         deterministic.run_until_parked();
 
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1,);

crates/gpui/src/app.rs šŸ”—

@@ -130,8 +130,14 @@ pub trait BorrowAppContext {
 }
 
 pub trait BorrowWindowContext {
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T;
-    fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T;
+    type Result<T>;
+
+    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    where
+        F: FnOnce(&WindowContext) -> T;
+    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    where
+        F: FnOnce(&mut WindowContext) -> T;
 }
 
 #[derive(Clone)]
@@ -402,7 +408,7 @@ impl AsyncAppContext {
         &mut self,
         window_options: WindowOptions,
         build_root_view: F,
-    ) -> (usize, ViewHandle<T>)
+    ) -> WindowHandle<T>
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> T,
@@ -452,6 +458,26 @@ 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>
+    where
+        F: FnOnce(&WindowContext) -> T,
+    {
+        self.0.borrow().read_with(|cx| cx.read_window(window_id, f))
+    }
+
+    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    where
+        F: FnOnce(&mut WindowContext) -> T,
+    {
+        self.0
+            .borrow_mut()
+            .update(|cx| cx.update_window(window_id, f))
+    }
+}
+
 type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
 type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
 
@@ -494,8 +520,8 @@ pub struct AppContext {
     // Action Types -> Action Handlers
     global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
     keystroke_matcher: KeymapMatcher,
-    next_entity_id: usize,
-    next_window_id: usize,
+    next_id: usize,
+    // next_window_id: usize,
     next_subscription_id: usize,
     frame_count: usize,
 
@@ -554,8 +580,7 @@ impl AppContext {
             actions: Default::default(),
             global_actions: Default::default(),
             keystroke_matcher: KeymapMatcher::default(),
-            next_entity_id: 0,
-            next_window_id: 0,
+            next_id: 0,
             next_subscription_id: 0,
             frame_count: 0,
             subscriptions: Default::default(),
@@ -783,7 +808,7 @@ impl AppContext {
         result
     }
 
-    pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(
         &self,
         window_id: usize,
         callback: F,
@@ -1226,7 +1251,7 @@ impl AppContext {
         F: FnOnce(&mut ModelContext<T>) -> T,
     {
         self.update(|this| {
-            let model_id = post_inc(&mut this.next_entity_id);
+            let model_id = post_inc(&mut this.next_id);
             let handle = ModelHandle::new(model_id, &this.ref_counts);
             let mut cx = ModelContext::new(this, model_id);
             let model = build_model(&mut cx);
@@ -1300,20 +1325,19 @@ impl AppContext {
         &mut self,
         window_options: WindowOptions,
         build_root_view: F,
-    ) -> (usize, ViewHandle<V>)
+    ) -> WindowHandle<V>
     where
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window_id = post_inc(&mut this.next_window_id);
+            let window_id = 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);
-            let root_view = window.root_view().clone().downcast::<V>().unwrap();
             this.windows.insert(window_id, window);
-            (window_id, root_view)
+            WindowHandle::new(window_id)
         })
     }
 
@@ -1323,7 +1347,7 @@ impl AppContext {
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window_id = post_inc(&mut this.next_window_id);
+            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();
@@ -1422,7 +1446,7 @@ impl AppContext {
         &mut self,
         window_id: usize,
         build_root_view: F,
-    ) -> Option<ViewHandle<V>>
+    ) -> Option<WindowHandle<V>>
     where
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
@@ -2158,6 +2182,24 @@ 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>
+    where
+        F: FnOnce(&WindowContext) -> T,
+    {
+        AppContext::read_window(self, window_id, f)
+    }
+
+    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    where
+        F: FnOnce(&mut WindowContext) -> T,
+    {
+        AppContext::update_window(self, window_id, f)
+    }
+}
+
 #[derive(Debug)]
 pub enum ParentId {
     View(usize),
@@ -3356,12 +3398,18 @@ impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
 }
 
 impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_with(&*self.window_context, window_id, f)
+    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 update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::update(&mut *self.window_context, window_id, f)
+    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+        &mut self,
+        window_id: usize,
+        f: F,
+    ) -> T {
+        BorrowWindowContext::update_window(&mut *self.window_context, window_id, f)
     }
 }
 
@@ -3461,12 +3509,18 @@ impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
 }
 
 impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_with(&*self.view_context, window_id, f)
+    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 update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::update(&mut *self.view_context, window_id, f)
+    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+        &mut self,
+        window_id: usize,
+        f: F,
+    ) -> T {
+        BorrowWindowContext::update_window(&mut *self.view_context, window_id, f)
     }
 }
 
@@ -3513,12 +3567,18 @@ impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
 }
 
 impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_with(&*self.view_context, window_id, f)
+    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 update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::update(&mut *self.view_context, window_id, f)
+    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+        &mut self,
+        window_id: usize,
+        f: F,
+    ) -> T {
+        BorrowWindowContext::update_window(&mut *self.view_context, window_id, f)
     }
 }
 
@@ -3802,6 +3862,89 @@ impl<T> Clone for WeakModelHandle<T> {
 
 impl<T> Copy for WeakModelHandle<T> {}
 
+pub struct WindowHandle<T> {
+    window_id: usize,
+    root_view_type: PhantomData<T>,
+}
+
+#[allow(dead_code)]
+impl<V: View> WindowHandle<V> {
+    fn new(window_id: usize) -> Self {
+        WindowHandle {
+            window_id,
+            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 update_root<C, F, R>(&self, cx: &mut C, update: F) -> C::Result<Option<R>>
+    // where
+    //     C: BorrowWindowContext,
+    //     F: FnOnce(&mut V, &mut ViewContext<V>) -> R,
+    // {
+    //     cx.update_window(self.window_id, |cx| {
+    //         cx.root_view()
+    //             .clone()
+    //             .downcast::<V>()
+    //             .map(|v| v.update(cx, update))
+    //     })
+    // }
+
+    pub fn read_root<'a>(&self, cx: &'a AppContext) -> &'a V {
+        let root_view = cx
+            .read_window(self.window_id(), |cx| {
+                cx.root_view().clone().downcast().unwrap()
+            })
+            .unwrap();
+        root_view.read(cx)
+    }
+
+    pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
+    where
+        C: BorrowWindowContext,
+        F: FnOnce(&V, &ViewContext<V>) -> R,
+    {
+        self.read_with(cx, |cx| {
+            cx.root_view()
+                .downcast_ref::<V>()
+                .unwrap()
+                .read_with(cx, read)
+        })
+    }
+
+    pub fn add_view<C, U, F>(&self, cx: &mut C, build_view: F) -> C::Result<ViewHandle<U>>
+    where
+        C: BorrowWindowContext,
+        U: View,
+        F: FnOnce(&mut ViewContext<U>) -> U,
+    {
+        self.update(cx, |cx| cx.add_view(build_view))
+    }
+}
+
 #[repr(transparent)]
 pub struct ViewHandle<T> {
     any_handle: AnyViewHandle,
@@ -3849,25 +3992,25 @@ impl<T: View> ViewHandle<T> {
         cx.read_view(self)
     }
 
-    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> S
+    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::Result<S>
     where
         C: BorrowWindowContext,
         F: FnOnce(&T, &ViewContext<T>) -> S,
     {
-        cx.read_with(self.window_id, |cx| {
+        cx.read_window_with(self.window_id, |cx| {
             let cx = ViewContext::immutable(cx, self.view_id);
             read(cx.read_view(self), &cx)
         })
     }
 
-    pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> S
+    pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> C::Result<S>
     where
         C: BorrowWindowContext,
         F: FnOnce(&mut T, &mut ViewContext<T>) -> S,
     {
         let mut update = Some(update);
 
-        cx.update(self.window_id, |cx| {
+        cx.update_window(self.window_id, |cx| {
             cx.update_view(self, &mut |view, cx| {
                 let update = update.take().unwrap();
                 update(view, cx)
@@ -4684,11 +4827,11 @@ mod tests {
             }
         }
 
-        let (_, view) = cx.add_window(|_| View { render_count: 0 });
+        let window = cx.add_window(|_| View { render_count: 0 });
         let called_defer = Rc::new(AtomicBool::new(false));
         let called_after_window_update = Rc::new(AtomicBool::new(false));
 
-        view.update(cx, |this, cx| {
+        window.root(cx).update(cx, |this, cx| {
             assert_eq!(this.render_count, 1);
             cx.defer({
                 let called_defer = called_defer.clone();
@@ -4712,7 +4855,7 @@ mod tests {
 
         assert!(called_defer.load(SeqCst));
         assert!(called_after_window_update.load(SeqCst));
-        assert_eq!(view.read_with(cx, |view, _| view.render_count), 3);
+        assert_eq!(window.read_root_with(cx, |view, _| view.render_count), 3);
     }
 
     #[crate::test(self)]
@@ -4751,9 +4894,9 @@ mod tests {
             }
         }
 
-        let (window_id, _root_view) = cx.add_window(|cx| View::new(None, cx));
-        let handle_1 = cx.add_view(window_id, |cx| View::new(None, cx));
-        let handle_2 = cx.add_view(window_id, |cx| View::new(Some(handle_1.clone()), cx));
+        let window = cx.add_window(|cx| View::new(None, cx));
+        let handle_1 = window.add_view(cx, |cx| View::new(None, cx));
+        let handle_2 = window.add_view(cx, |cx| View::new(Some(handle_1.clone()), cx));
         assert_eq!(cx.read(|cx| cx.views.len()), 3);
 
         handle_1.update(cx, |view, cx| {
@@ -4813,11 +4956,11 @@ mod tests {
         }
 
         let mouse_down_count = Arc::new(AtomicUsize::new(0));
-        let (window_id, _) = cx.add_window(Default::default(), |_| View {
+        let window = cx.add_window(Default::default(), |_| View {
             mouse_down_count: mouse_down_count.clone(),
         });
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             // Ensure window's root element is in a valid lifecycle state.
             cx.dispatch_event(
                 Event::MouseDown(MouseButtonEvent {
@@ -4833,7 +4976,7 @@ mod tests {
     }
 
     #[crate::test(self)]
-    fn test_entity_release_hooks(cx: &mut AppContext) {
+    fn test_entity_release_hooks(cx: &mut TestAppContext) {
         struct Model {
             released: Rc<Cell<bool>>,
         }
@@ -4876,22 +5019,26 @@ mod tests {
         let model = cx.add_model(|_| Model {
             released: model_released.clone(),
         });
-        let (window_id, view) = cx.add_window(Default::default(), |_| View {
+        let window = cx.add_window(|_| View {
             released: view_released.clone(),
         });
+        let view = window.root(cx);
+
         assert!(!model_released.get());
         assert!(!view_released.get());
 
-        cx.observe_release(&model, {
-            let model_release_observed = model_release_observed.clone();
-            move |_, _| model_release_observed.set(true)
-        })
-        .detach();
-        cx.observe_release(&view, {
-            let view_release_observed = view_release_observed.clone();
-            move |_, _| view_release_observed.set(true)
-        })
-        .detach();
+        cx.update(|cx| {
+            cx.observe_release(&model, {
+                let model_release_observed = model_release_observed.clone();
+                move |_, _| model_release_observed.set(true)
+            })
+            .detach();
+            cx.observe_release(&view, {
+                let view_release_observed = view_release_observed.clone();
+                move |_, _| view_release_observed.set(true)
+            })
+            .detach();
+        });
 
         cx.update(move |_| {
             drop(model);
@@ -4900,7 +5047,7 @@ mod tests {
         assert!(model_release_observed.get());
 
         drop(view);
-        cx.update_window(window_id, |cx| cx.remove_window());
+        window.update(cx, |cx| cx.remove_window());
         assert!(view_released.get());
         assert!(view_release_observed.get());
     }
@@ -4913,8 +5060,9 @@ mod tests {
             type Event = String;
         }
 
-        let (window_id, handle_1) = cx.add_window(|_| TestView::default());
-        let handle_2 = cx.add_view(window_id, |_| TestView::default());
+        let window = cx.add_window(|_| TestView::default());
+        let handle_1 = window.root(cx);
+        let handle_2 = window.add_view(cx, |_| TestView::default());
         let handle_3 = cx.add_model(|_| Model);
 
         handle_1.update(cx, |_, cx| {
@@ -5140,9 +5288,9 @@ mod tests {
             type Event = ();
         }
 
-        let (window_id, _root_view) = cx.add_window(|_| TestView::default());
-        let observing_view = cx.add_view(window_id, |_| TestView::default());
-        let emitting_view = cx.add_view(window_id, |_| TestView::default());
+        let window = cx.add_window(|_| TestView::default());
+        let observing_view = window.add_view(cx, |_| TestView::default());
+        let emitting_view = window.add_view(cx, |_| TestView::default());
         let observing_model = cx.add_model(|_| Model);
         let observed_model = cx.add_model(|_| Model);
 
@@ -5165,7 +5313,7 @@ mod tests {
 
     #[crate::test(self)]
     fn test_view_emit_before_subscribe_in_same_update_cycle(cx: &mut AppContext) {
-        let (_, view) = cx.add_window::<TestView, _>(Default::default(), |cx| {
+        let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
             drop(cx.subscribe(&cx.handle(), {
                 move |this, _, _, _| this.events.push("dropped before flush".into())
             }));
@@ -5181,7 +5329,7 @@ mod tests {
             TestView { events: Vec::new() }
         });
 
-        assert_eq!(view.read(cx).events, ["before emit"]);
+        assert_eq!(window.read_root(cx).events, ["before emit"]);
     }
 
     #[crate::test(self)]
@@ -5195,7 +5343,8 @@ mod tests {
             type Event = ();
         }
 
-        let (_, view) = cx.add_window(|_| TestView::default());
+        let window = cx.add_window(|_| TestView::default());
+        let view = window.root(cx);
         let model = cx.add_model(|_| Model {
             state: "old-state".into(),
         });
@@ -5216,7 +5365,7 @@ mod tests {
 
     #[crate::test(self)]
     fn test_view_notify_before_observe_in_same_update_cycle(cx: &mut AppContext) {
-        let (_, view) = cx.add_window::<TestView, _>(Default::default(), |cx| {
+        let window = cx.add_window::<TestView, _>(Default::default(), |cx| {
             drop(cx.observe(&cx.handle(), {
                 move |this, _, _| this.events.push("dropped before flush".into())
             }));
@@ -5232,7 +5381,7 @@ mod tests {
             TestView { events: Vec::new() }
         });
 
-        assert_eq!(view.read(cx).events, ["before notify"]);
+        assert_eq!(window.read_root(cx).events, ["before notify"]);
     }
 
     #[crate::test(self)]
@@ -5243,7 +5392,8 @@ mod tests {
         }
 
         let model = cx.add_model(|_| Model);
-        let (_, view) = cx.add_window(|_| TestView::default());
+        let window = cx.add_window(|_| TestView::default());
+        let view = window.root(cx);
 
         view.update(cx, |_, cx| {
             model.update(cx, |_, cx| cx.notify());
@@ -5267,8 +5417,8 @@ mod tests {
             type Event = ();
         }
 
-        let (window_id, _root_view) = cx.add_window(|_| TestView::default());
-        let observing_view = cx.add_view(window_id, |_| TestView::default());
+        let window = cx.add_window(|_| TestView::default());
+        let observing_view = window.add_view(cx, |_| TestView::default());
         let observing_model = cx.add_model(|_| Model);
         let observed_model = cx.add_model(|_| Model);
 
@@ -5390,9 +5540,9 @@ mod tests {
             }
         }
 
-        let (window_id, _root_view) = cx.add_window(|_| View);
-        let observing_view = cx.add_view(window_id, |_| View);
-        let observed_view = cx.add_view(window_id, |_| View);
+        let window = cx.add_window(|_| View);
+        let observing_view = window.add_view(cx, |_| View);
+        let observed_view = window.add_view(cx, |_| View);
 
         let observation_count = Rc::new(RefCell::new(0));
         observing_view.update(cx, |_, cx| {
@@ -5474,25 +5624,24 @@ mod tests {
         }
 
         let view_events: Arc<Mutex<Vec<String>>> = Default::default();
-        let (window_id, view_1) = cx.add_window(|_| View {
+        let window = cx.add_window(|_| View {
             events: view_events.clone(),
             name: "view 1".to_string(),
             child: None,
         });
-        let view_2 = cx
-            .update_window(window_id, |cx| {
-                let view_2 = cx.add_view(|_| View {
-                    events: view_events.clone(),
-                    name: "view 2".to_string(),
-                    child: None,
-                });
-                view_1.update(cx, |view_1, cx| {
-                    view_1.child = Some(view_2.clone().into_any());
-                    cx.notify();
-                });
-                view_2
-            })
-            .unwrap();
+        let view_1 = window.root(cx);
+        let view_2 = window.update(cx, |cx| {
+            let view_2 = cx.add_view(|_| View {
+                events: view_events.clone(),
+                name: "view 2".to_string(),
+                child: None,
+            });
+            view_1.update(cx, |view_1, cx| {
+                view_1.child = Some(view_2.clone().into_any());
+                cx.notify();
+            });
+            view_2
+        });
 
         let observed_events: Arc<Mutex<Vec<String>>> = Default::default();
         view_1.update(cx, |_, cx| {
@@ -5619,7 +5768,7 @@ mod tests {
     }
 
     #[crate::test(self)]
-    fn test_dispatch_action(cx: &mut AppContext) {
+    fn test_dispatch_action(cx: &mut TestAppContext) {
         struct ViewA {
             id: usize,
             child: Option<AnyViewHandle>,
@@ -5670,101 +5819,97 @@ mod tests {
         impl_actions!(test, [Action]);
 
         let actions = Rc::new(RefCell::new(Vec::new()));
+        let observed_actions = Rc::new(RefCell::new(Vec::new()));
 
-        cx.add_global_action({
-            let actions = actions.clone();
-            move |_: &Action, _: &mut AppContext| {
-                actions.borrow_mut().push("global".to_string());
-            }
-        });
-
-        cx.add_action({
-            let actions = actions.clone();
-            move |view: &mut ViewA, action: &Action, cx| {
-                assert_eq!(action.0, "bar");
-                cx.propagate_action();
-                actions.borrow_mut().push(format!("{} a", view.id));
-            }
-        });
+        cx.update(|cx| {
+            cx.add_global_action({
+                let actions = actions.clone();
+                move |_: &Action, _: &mut AppContext| {
+                    actions.borrow_mut().push("global".to_string());
+                }
+            });
 
-        cx.add_action({
-            let actions = actions.clone();
-            move |view: &mut ViewA, _: &Action, cx| {
-                if view.id != 1 {
-                    cx.add_view(|cx| {
-                        cx.propagate_action(); // Still works on a nested ViewContext
-                        ViewB { id: 5, child: None }
-                    });
+            cx.add_action({
+                let actions = actions.clone();
+                move |view: &mut ViewA, action: &Action, cx| {
+                    assert_eq!(action.0, "bar");
+                    cx.propagate_action();
+                    actions.borrow_mut().push(format!("{} a", view.id));
                 }
-                actions.borrow_mut().push(format!("{} b", view.id));
-            }
-        });
+            });
 
-        cx.add_action({
-            let actions = actions.clone();
-            move |view: &mut ViewB, _: &Action, cx| {
-                cx.propagate_action();
-                actions.borrow_mut().push(format!("{} c", view.id));
-            }
-        });
+            cx.add_action({
+                let actions = actions.clone();
+                move |view: &mut ViewA, _: &Action, cx| {
+                    if view.id != 1 {
+                        cx.add_view(|cx| {
+                            cx.propagate_action(); // Still works on a nested ViewContext
+                            ViewB { id: 5, child: None }
+                        });
+                    }
+                    actions.borrow_mut().push(format!("{} b", view.id));
+                }
+            });
 
-        cx.add_action({
-            let actions = actions.clone();
-            move |view: &mut ViewB, _: &Action, cx| {
-                cx.propagate_action();
-                actions.borrow_mut().push(format!("{} d", view.id));
-            }
-        });
+            cx.add_action({
+                let actions = actions.clone();
+                move |view: &mut ViewB, _: &Action, cx| {
+                    cx.propagate_action();
+                    actions.borrow_mut().push(format!("{} c", view.id));
+                }
+            });
 
-        cx.capture_action({
-            let actions = actions.clone();
-            move |view: &mut ViewA, _: &Action, cx| {
-                cx.propagate_action();
-                actions.borrow_mut().push(format!("{} capture", view.id));
-            }
-        });
+            cx.add_action({
+                let actions = actions.clone();
+                move |view: &mut ViewB, _: &Action, cx| {
+                    cx.propagate_action();
+                    actions.borrow_mut().push(format!("{} d", view.id));
+                }
+            });
 
-        let observed_actions = Rc::new(RefCell::new(Vec::new()));
-        cx.observe_actions({
-            let observed_actions = observed_actions.clone();
-            move |action_id, _| observed_actions.borrow_mut().push(action_id)
-        })
-        .detach();
+            cx.capture_action({
+                let actions = actions.clone();
+                move |view: &mut ViewA, _: &Action, cx| {
+                    cx.propagate_action();
+                    actions.borrow_mut().push(format!("{} capture", view.id));
+                }
+            });
 
-        let (window_id, view_1) =
-            cx.add_window(Default::default(), |_| ViewA { id: 1, child: None });
-        let view_2 = cx
-            .update_window(window_id, |cx| {
-                let child = cx.add_view(|_| ViewB { id: 2, child: None });
-                view_1.update(cx, |view, cx| {
-                    view.child = Some(child.clone().into_any());
-                    cx.notify();
-                });
-                child
-            })
-            .unwrap();
-        let view_3 = cx
-            .update_window(window_id, |cx| {
-                let child = cx.add_view(|_| ViewA { id: 3, child: None });
-                view_2.update(cx, |view, cx| {
-                    view.child = Some(child.clone().into_any());
-                    cx.notify();
-                });
-                child
+            cx.observe_actions({
+                let observed_actions = observed_actions.clone();
+                move |action_id, _| observed_actions.borrow_mut().push(action_id)
             })
-            .unwrap();
-        let view_4 = cx
-            .update_window(window_id, |cx| {
-                let child = cx.add_view(|_| ViewB { id: 4, child: None });
-                view_3.update(cx, |view, cx| {
-                    view.child = Some(child.clone().into_any());
-                    cx.notify();
-                });
-                child
-            })
-            .unwrap();
+            .detach();
+        });
 
-        cx.update_window(window_id, |cx| {
+        let window = cx.add_window(|_| ViewA { id: 1, child: None });
+        let view_1 = window.root(cx);
+        let view_2 = window.update(cx, |cx| {
+            let child = cx.add_view(|_| ViewB { id: 2, child: None });
+            view_1.update(cx, |view, cx| {
+                view.child = Some(child.clone().into_any());
+                cx.notify();
+            });
+            child
+        });
+        let view_3 = window.update(cx, |cx| {
+            let child = cx.add_view(|_| ViewA { id: 3, child: None });
+            view_2.update(cx, |view, cx| {
+                view.child = Some(child.clone().into_any());
+                cx.notify();
+            });
+            child
+        });
+        let view_4 = window.update(cx, |cx| {
+            let child = cx.add_view(|_| ViewB { id: 4, child: None });
+            view_3.update(cx, |view, cx| {
+                view.child = Some(child.clone().into_any());
+                cx.notify();
+            });
+            child
+        });
+
+        window.update(cx, |cx| {
             cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
         });
 
@@ -5786,31 +5931,27 @@ mod tests {
 
         // Remove view_1, which doesn't propagate the action
 
-        let (window_id, view_2) =
-            cx.add_window(Default::default(), |_| ViewB { id: 2, child: None });
-        let view_3 = cx
-            .update_window(window_id, |cx| {
-                let child = cx.add_view(|_| ViewA { id: 3, child: None });
-                view_2.update(cx, |view, cx| {
-                    view.child = Some(child.clone().into_any());
-                    cx.notify();
-                });
-                child
-            })
-            .unwrap();
-        let view_4 = cx
-            .update_window(window_id, |cx| {
-                let child = cx.add_view(|_| ViewB { id: 4, child: None });
-                view_3.update(cx, |view, cx| {
-                    view.child = Some(child.clone().into_any());
-                    cx.notify();
-                });
-                child
-            })
-            .unwrap();
+        let window = cx.add_window(|_| ViewB { id: 2, child: None });
+        let view_2 = window.root(cx);
+        let view_3 = window.update(cx, |cx| {
+            let child = cx.add_view(|_| ViewA { id: 3, child: None });
+            view_2.update(cx, |view, cx| {
+                view.child = Some(child.clone().into_any());
+                cx.notify();
+            });
+            child
+        });
+        let view_4 = window.update(cx, |cx| {
+            let child = cx.add_view(|_| ViewB { id: 4, child: None });
+            view_3.update(cx, |view, cx| {
+                view.child = Some(child.clone().into_any());
+                cx.notify();
+            });
+            child
+        });
 
         actions.borrow_mut().clear();
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             cx.dispatch_action(Some(view_4.id()), &Action("bar".to_string()))
         });
 
@@ -5887,7 +6028,7 @@ mod tests {
         view_3.keymap_context.add_identifier("b");
         view_3.keymap_context.add_identifier("c");
 
-        let (window_id, _view_1) = cx.add_window(Default::default(), |cx| {
+        let window = cx.add_window(Default::default(), |cx| {
             let view_2 = cx.add_view(|cx| {
                 let view_3 = cx.add_view(|cx| {
                     cx.focus_self();
@@ -5947,26 +6088,26 @@ mod tests {
             }
         });
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             cx.dispatch_keystroke(&Keystroke::parse("a").unwrap())
         });
         assert_eq!(&*actions.borrow(), &["2 a"]);
         actions.borrow_mut().clear();
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             cx.dispatch_keystroke(&Keystroke::parse("b").unwrap());
         });
 
         assert_eq!(&*actions.borrow(), &["3 b", "2 b", "1 b", "global b"]);
         actions.borrow_mut().clear();
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             cx.dispatch_keystroke(&Keystroke::parse("c").unwrap());
         });
         assert_eq!(&*actions.borrow(), &["3 c"]);
         actions.borrow_mut().clear();
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             cx.dispatch_keystroke(&Keystroke::parse("d").unwrap());
         });
         assert_eq!(&*actions.borrow(), &["2 d"]);
@@ -6006,13 +6147,14 @@ mod tests {
             }
         }
 
-        let (window_id, view_1) = cx.add_window(|cx| {
+        let window = cx.add_window(|cx| {
             let view_2 = cx.add_view(|cx| {
                 cx.focus_self();
                 View2 {}
             });
             View1 { child: view_2 }
         });
+        let view_1 = window.root(cx);
         let view_2 = view_1.read_with(cx, |view, _| view.child.clone());
 
         cx.update(|cx| {
@@ -6076,7 +6218,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_id, view_1.id(), cx),
+            &available_actions(window.window_id(), view_1.id(), cx),
             &[
                 ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
                 ("test::GlobalAction", vec![])
@@ -6085,7 +6227,7 @@ mod tests {
 
         // Check that view 1 actions and bindings are available even when called from view 2
         assert_eq!(
-            &available_actions(window_id, view_2.id(), cx),
+            &available_actions(window.window_id(), view_2.id(), cx),
             &[
                 ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
                 ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
@@ -6138,7 +6280,8 @@ mod tests {
 
         impl_actions!(test, [ActionWithArg]);
 
-        let (window_id, view) = cx.add_window(|_| View);
+        let window = cx.add_window(|_| View);
+        let view = window.root(cx);
         cx.update(|cx| {
             cx.add_global_action(|_: &ActionWithArg, _| {});
             cx.add_bindings(vec![
@@ -6147,7 +6290,7 @@ mod tests {
             ]);
         });
 
-        let actions = cx.available_actions(window_id, view.id());
+        let actions = cx.available_actions(window.window_id(), view.id());
         assert_eq!(
             actions[0].1.as_any().downcast_ref::<ActionWithArg>(),
             Some(&ActionWithArg { arg: false })
@@ -6250,7 +6393,8 @@ mod tests {
             }
         }
 
-        let (_, view) = cx.add_window(|_| Counter(0));
+        let window = cx.add_window(|_| Counter(0));
+        let view = window.root(cx);
 
         let condition1 = view.condition(cx, |view, _| view.0 == 2);
         let condition2 = view.condition(cx, |view, _| view.0 == 3);
@@ -6272,15 +6416,15 @@ mod tests {
     #[crate::test(self)]
     #[should_panic]
     async fn test_view_condition_timeout(cx: &mut TestAppContext) {
-        let (_, view) = cx.add_window(|_| TestView::default());
-        view.condition(cx, |_, _| false).await;
+        let window = cx.add_window(|_| TestView::default());
+        window.root(cx).condition(cx, |_, _| false).await;
     }
 
     #[crate::test(self)]
     #[should_panic(expected = "view dropped with pending condition")]
     async fn test_view_condition_panic_on_drop(cx: &mut TestAppContext) {
-        let (window_id, _root_view) = cx.add_window(|_| TestView::default());
-        let view = cx.add_view(window_id, |_| TestView::default());
+        let window = cx.add_window(|_| TestView::default());
+        let view = window.add_view(cx, |_| TestView::default());
 
         let condition = view.condition(cx, |_, _| false);
         cx.update(|_| drop(view));
@@ -6288,7 +6432,7 @@ mod tests {
     }
 
     #[crate::test(self)]
-    fn test_refresh_windows(cx: &mut AppContext) {
+    fn test_refresh_windows(cx: &mut TestAppContext) {
         struct View(usize);
 
         impl super::Entity for View {
@@ -6305,22 +6449,21 @@ mod tests {
             }
         }
 
-        let (window_id, root_view) = cx.add_window(Default::default(), |_| View(0));
-        cx.update_window(window_id, |cx| {
+        let window = cx.add_window(|_| View(0));
+        let root_view = window.root(cx);
+        window.update(cx, |cx| {
             assert_eq!(
                 cx.window.rendered_views[&root_view.id()].name(),
                 Some("render count: 0")
             );
         });
 
-        let view = cx
-            .update_window(window_id, |cx| {
-                cx.refresh_windows();
-                cx.add_view(|_| View(0))
-            })
-            .unwrap();
+        let view = window.update(cx, |cx| {
+            cx.refresh_windows();
+            cx.add_view(|_| View(0))
+        });
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             assert_eq!(
                 cx.window.rendered_views[&root_view.id()].name(),
                 Some("render count: 1")

crates/gpui/src/app/test_app_context.rs šŸ”—

@@ -6,7 +6,7 @@ use crate::{
     platform::{Event, InputHandler, KeyDownEvent, Platform},
     Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
     ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
-    WindowContext,
+    WindowContext, WindowHandle,
 };
 use collections::BTreeMap;
 use futures::Future;
@@ -60,7 +60,7 @@ impl TestAppContext {
             RefCounts::new(leak_detector),
             (),
         );
-        cx.next_entity_id = first_entity_id;
+        cx.next_id = first_entity_id;
         let cx = TestAppContext {
             cx: Rc::new(RefCell::new(cx)),
             foreground_platform,
@@ -148,17 +148,18 @@ impl TestAppContext {
         self.cx.borrow_mut().add_model(build_model)
     }
 
-    pub fn add_window<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
+    pub fn add_window<T, F>(&mut self, build_root_view: F) -> WindowHandle<T>
     where
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> T,
     {
-        let (window_id, view) = self
+        let window = self
             .cx
             .borrow_mut()
             .add_window(Default::default(), build_root_view);
-        self.simulate_window_activation(Some(window_id));
-        (window_id, view)
+        self.simulate_window_activation(Some(window.window_id()));
+
+        WindowHandle::new(window.window_id())
     }
 
     pub fn add_view<T, F>(&mut self, window_id: usize, build_view: F) -> ViewHandle<T>
@@ -405,14 +406,20 @@ impl BorrowAppContext for TestAppContext {
 }
 
 impl BorrowWindowContext for TestAppContext {
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+    type Result<T> = T;
+
+    fn read_window_with<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 update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
+    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+        &mut self,
+        window_id: usize,
+        f: F,
+    ) -> T {
         self.cx
             .borrow_mut()
             .update_window(window_id, f)

crates/gpui/src/app/window.rs šŸ”—

@@ -15,7 +15,7 @@ use crate::{
     util::post_inc,
     Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
     Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
-    View, ViewContext, ViewHandle, WindowInvalidation,
+    View, ViewContext, ViewHandle, WindowHandle, WindowInvalidation,
 };
 use anyhow::{anyhow, bail, Result};
 use collections::{HashMap, HashSet};
@@ -142,7 +142,9 @@ impl BorrowAppContext for WindowContext<'_> {
 }
 
 impl BorrowWindowContext for WindowContext<'_> {
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+    type Result<T> = T;
+
+    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         if self.window_id == window_id {
             f(self)
         } else {
@@ -150,7 +152,11 @@ impl BorrowWindowContext for WindowContext<'_> {
         }
     }
 
-    fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T {
+    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+        &mut self,
+        window_id: usize,
+        f: F,
+    ) -> T {
         if self.window_id == window_id {
             f(self)
         } else {
@@ -1151,15 +1157,15 @@ impl<'a> WindowContext<'a> {
         self.window.platform_window.prompt(level, msg, answers)
     }
 
-    pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
+    pub 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.root_view = Some(root_view.clone().into_any());
         self.window.focused_view_id = Some(root_view.id());
-        root_view
+        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>
@@ -1176,7 +1182,7 @@ impl<'a> WindowContext<'a> {
         F: FnOnce(&mut ViewContext<T>) -> Option<T>,
     {
         let window_id = self.window_id;
-        let view_id = post_inc(&mut self.next_entity_id);
+        let view_id = post_inc(&mut self.next_id);
         let mut cx = ViewContext::mutable(self, view_id);
         let handle = if let Some(view) = build_view(&mut cx) {
             let mut keymap_context = KeymapContext::default();

crates/language_tools/src/lsp_log_tests.rs šŸ”—

@@ -61,7 +61,9 @@ async fn test_lsp_logs(cx: &mut TestAppContext) {
         .receive_notification::<lsp::notification::DidOpenTextDocument>()
         .await;
 
-    let (_, log_view) = cx.add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx));
+    let log_view = cx
+        .add_window(|cx| LspLogView::new(project.clone(), log_store.clone(), cx))
+        .root(cx);
 
     language_server.notify::<lsp::notification::LogMessage>(lsp::LogMessageParams {
         message: "hello from the server".into(),

crates/project_panel/src/project_panel.rs šŸ”—

@@ -1780,7 +1780,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
         assert_eq!(
             visible_entries_as_strings(&panel, 0..50, cx),
@@ -1868,7 +1870,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
@@ -2219,7 +2223,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
@@ -2319,7 +2325,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         panel.update(cx, |panel, cx| {
@@ -2392,7 +2400,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         toggle_expand_dir(&panel, "src/test", cx);
@@ -2481,7 +2491,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "src/", cx);
@@ -2627,7 +2639,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         let new_search_events_count = Arc::new(AtomicUsize::new(0));
@@ -2714,7 +2728,9 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         panel.update(cx, |panel, cx| {

crates/project_symbols/src/project_symbols.rs šŸ”—

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

crates/search/src/buffer_search.rs šŸ”—

@@ -849,11 +849,13 @@ mod tests {
                 cx,
             )
         });
-        let (window_id, _root_view) = cx.add_window(|_| EmptyView);
+        let window = cx.add_window(|_| EmptyView);
 
-        let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
+        let editor = cx.add_view(window.window_id(), |cx| {
+            Editor::for_buffer(buffer.clone(), None, cx)
+        });
 
-        let search_bar = cx.add_view(window_id, |cx| {
+        let search_bar = cx.add_view(window.window_id(), |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(cx);
@@ -1229,7 +1231,8 @@ mod tests {
             "Should pick a query with multiple results"
         );
         let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
-        let (window_id, _root_view) = cx.add_window(|_| EmptyView);
+        let window = cx.add_window(|_| EmptyView);
+        let window_id = window.window_id();
 
         let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
 
@@ -1416,11 +1419,13 @@ mod tests {
         "#
         .unindent();
         let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
-        let (window_id, _root_view) = cx.add_window(|_| EmptyView);
+        let window = cx.add_window(|_| EmptyView);
 
-        let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
+        let editor = cx.add_view(window.window_id(), |cx| {
+            Editor::for_buffer(buffer.clone(), None, cx)
+        });
 
-        let search_bar = cx.add_view(window_id, |cx| {
+        let search_bar = cx.add_view(window.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 šŸ”—

@@ -1447,7 +1447,9 @@ pub mod tests {
         .await;
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
         let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
-        let (_, search_view) = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx));
+        let search_view = cx
+            .add_window(|cx| ProjectSearchView::new(search.clone(), cx))
+            .root(cx);
 
         search_view.update(cx, |search_view, cx| {
             search_view
@@ -1564,7 +1566,9 @@ pub mod tests {
         )
         .await;
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
 
         let active_item = cx.read(|cx| {
             workspace
@@ -1748,7 +1752,9 @@ pub mod tests {
         let worktree_id = project.read_with(cx, |project, cx| {
             project.worktrees(cx).next().unwrap().read(cx).id()
         });
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
 
         let active_item = cx.read(|cx| {
             workspace
@@ -1866,7 +1872,9 @@ pub mod tests {
         )
         .await;
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         workspace.update(cx, |workspace, cx| {
             ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
         });

crates/terminal_view/src/terminal_view.rs šŸ”—

@@ -1070,7 +1070,9 @@ mod tests {
         });
 
         let project = Project::test(params.fs.clone(), [], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
 
         (project, workspace)
     }

crates/workspace/src/pane.rs šŸ”—

@@ -1972,7 +1972,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         pane.update(cx, |pane, cx| {
@@ -1987,7 +1988,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         // 1. Add with a destination index
@@ -2065,7 +2067,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         // 1. Add with a destination index
@@ -2141,7 +2144,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         // singleton view
@@ -2209,7 +2213,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         add_labeled_item(&pane, "A", false, cx);
@@ -2256,7 +2261,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@@ -2276,7 +2282,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         add_labeled_item(&pane, "A", true, cx);
@@ -2299,7 +2306,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@@ -2319,7 +2327,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
@@ -2339,7 +2348,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         add_labeled_item(&pane, "A", false, cx);

crates/workspace/src/workspace.rs šŸ”—

@@ -793,67 +793,59 @@ impl Workspace {
                 DB.next_id().await.unwrap_or(0)
             };
 
-            let workspace = 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 = 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)
                     })
                 })
-                .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 = 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;
                                 }
+                            }
 
-                                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,
-                            )
-                        },
-                    )
-                    .1
-                });
+                            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.
+            let workspace = window.root(&cx).unwrap();
 
             (app_state.initialize_workspace)(
                 workspace.downgrade(),
@@ -864,7 +856,7 @@ impl Workspace {
             .await
             .log_err();
 
-            cx.update_window(workspace.window_id(), |cx| cx.activate_window());
+            window.update(&mut cx, |cx| cx.activate_window());
 
             let workspace = workspace.downgrade();
             notify_if_database_failed(&workspace, &mut cx);
@@ -3977,7 +3969,7 @@ pub fn join_remote_project(
                 .await?;
 
             let window_bounds_override = window_bounds_env_override(&cx);
-            let (_, workspace) = cx.add_window(
+            let window = cx.add_window(
                 (app_state.build_window_options)(
                     window_bounds_override,
                     None,
@@ -3985,6 +3977,7 @@ pub fn join_remote_project(
                 ),
                 |cx| Workspace::new(0, project, app_state.clone(), cx),
             );
+            let workspace = window.root(&cx).unwrap();
             (app_state.initialize_workspace)(
                 workspace.downgrade(),
                 false,
@@ -4113,10 +4106,11 @@ mod tests {
 
         let fs = FakeFs::new(cx.background());
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
 
         // Adding an item with no ambiguity renders the tab without detail.
-        let item1 = cx.add_view(window_id, |_| {
+        let item1 = window.add_view(cx, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
             item
@@ -4128,7 +4122,7 @@ mod tests {
 
         // Adding an item that creates ambiguity increases the level of detail on
         // both tabs.
-        let item2 = cx.add_view(window_id, |_| {
+        let item2 = window.add_view(cx, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
             item
@@ -4142,7 +4136,7 @@ mod tests {
         // Adding an item that creates ambiguity increases the level of detail only
         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
         // we stop at the highest detail available.
-        let item3 = cx.add_view(window_id, |_| {
+        let item3 = window.add_view(cx, |_| {
             let mut item = TestItem::new();
             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
             item
@@ -4177,16 +4171,17 @@ mod tests {
         .await;
 
         let project = Project::test(fs, ["root1".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
         let worktree_id = project.read_with(cx, |project, cx| {
             project.worktrees(cx).next().unwrap().read(cx).id()
         });
 
-        let item1 = cx.add_view(window_id, |cx| {
+        let item1 = window.add_view(cx, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
         });
-        let item2 = cx.add_view(window_id, |cx| {
+        let item2 = window.add_view(cx, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
         });
 
@@ -4201,14 +4196,14 @@ mod tests {
             );
         });
         assert_eq!(
-            cx.current_window_title(window_id).as_deref(),
+            cx.current_window_title(window.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_id).as_deref(),
+            cx.current_window_title(window.window_id()).as_deref(),
             Some("two.txt — root1")
         );
         project.read_with(cx, |project, cx| {
@@ -4227,7 +4222,7 @@ mod tests {
         .await
         .unwrap();
         assert_eq!(
-            cx.current_window_title(window_id).as_deref(),
+            cx.current_window_title(window.window_id()).as_deref(),
             Some("one.txt — root1")
         );
         project.read_with(cx, |project, cx| {
@@ -4247,14 +4242,14 @@ mod tests {
             .await
             .unwrap();
         assert_eq!(
-            cx.current_window_title(window_id).as_deref(),
+            cx.current_window_title(window.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_id).as_deref(),
+            cx.current_window_title(window.window_id()).as_deref(),
             Some("one.txt — root2")
         );
     }
@@ -4267,18 +4262,19 @@ mod tests {
         fs.insert_tree("/root", json!({ "one": "" })).await;
 
         let project = Project::test(fs, ["root".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
 
         // When there are no dirty items, there's nothing to do.
-        let item1 = cx.add_view(window_id, |_| TestItem::new());
+        let item1 = window.add_view(cx, |_| TestItem::new());
         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
         assert!(task.await.unwrap());
 
         // When there are dirty untitled items, prompt to save each one. If the user
         // cancels any prompt, then abort.
-        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
-        let item3 = cx.add_view(window_id, |cx| {
+        let item2 = window.add_view(cx, |_| TestItem::new().with_dirty(true));
+        let item3 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -4289,9 +4285,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_id, 2 /* cancel */);
+        cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */);
         cx.foreground().run_until_parked();
-        assert!(!cx.has_pending_prompt(window_id));
+        assert!(!cx.has_pending_prompt(window.window_id()));
         assert!(!task.await.unwrap());
     }
 
@@ -4302,26 +4298,27 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, None, cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
-        let item1 = cx.add_view(window_id, |cx| {
+        let item1 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
         });
-        let item2 = cx.add_view(window_id, |cx| {
+        let item2 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_conflict(true)
                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
         });
-        let item3 = cx.add_view(window_id, |cx| {
+        let item3 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_conflict(true)
                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
         });
-        let item4 = cx.add_view(window_id, |cx| {
+        let item4 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
@@ -4349,10 +4346,10 @@ mod tests {
             assert_eq!(pane.items_len(), 4);
             assert_eq!(pane.active_item().unwrap().id(), item1.id());
         });
-        assert!(cx.has_pending_prompt(window_id));
+        assert!(cx.has_pending_prompt(window.window_id()));
 
         // Confirm saving item 1.
-        cx.simulate_prompt_answer(window_id, 0);
+        cx.simulate_prompt_answer(window.window_id(), 0);
         cx.foreground().run_until_parked();
 
         // Item 1 is saved. There's a prompt to save item 3.
@@ -4363,10 +4360,10 @@ mod tests {
             assert_eq!(pane.items_len(), 3);
             assert_eq!(pane.active_item().unwrap().id(), item3.id());
         });
-        assert!(cx.has_pending_prompt(window_id));
+        assert!(cx.has_pending_prompt(window.window_id()));
 
         // Cancel saving item 3.
-        cx.simulate_prompt_answer(window_id, 1);
+        cx.simulate_prompt_answer(window.window_id(), 1);
         cx.foreground().run_until_parked();
 
         // Item 3 is reloaded. There's a prompt to save item 4.
@@ -4377,10 +4374,10 @@ mod tests {
             assert_eq!(pane.items_len(), 2);
             assert_eq!(pane.active_item().unwrap().id(), item4.id());
         });
-        assert!(cx.has_pending_prompt(window_id));
+        assert!(cx.has_pending_prompt(window.window_id()));
 
         // Confirm saving item 4.
-        cx.simulate_prompt_answer(window_id, 0);
+        cx.simulate_prompt_answer(window.window_id(), 0);
         cx.foreground().run_until_parked();
 
         // There's a prompt for a path for item 4.
@@ -4404,13 +4401,14 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
         // Create several workspace items with single project entries, and two
         // workspace items with multiple project entries.
         let single_entry_items = (0..=4)
             .map(|project_entry_id| {
-                cx.add_view(window_id, |cx| {
+                window.add_view(cx, |cx| {
                     TestItem::new()
                         .with_dirty(true)
                         .with_project_items(&[TestProjectItem::new(
@@ -4421,7 +4419,7 @@ mod tests {
                 })
             })
             .collect::<Vec<_>>();
-        let item_2_3 = cx.add_view(window_id, |cx| {
+        let item_2_3 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_singleton(false)
@@ -4430,7 +4428,7 @@ mod tests {
                     single_entry_items[3].read(cx).project_items[0].clone(),
                 ])
         });
-        let item_3_4 = cx.add_view(window_id, |cx| {
+        let item_3_4 = window.add_view(cx, |cx| {
             TestItem::new()
                 .with_dirty(true)
                 .with_singleton(false)
@@ -4482,7 +4480,7 @@ mod tests {
                 &[ProjectEntryId::from_proto(0)]
             );
         });
-        cx.simulate_prompt_answer(window_id, 0);
+        cx.simulate_prompt_answer(window.window_id(), 0);
 
         cx.foreground().run_until_parked();
         left_pane.read_with(cx, |pane, cx| {
@@ -4491,7 +4489,7 @@ mod tests {
                 &[ProjectEntryId::from_proto(2)]
             );
         });
-        cx.simulate_prompt_answer(window_id, 0);
+        cx.simulate_prompt_answer(window.window_id(), 0);
 
         cx.foreground().run_until_parked();
         close.await.unwrap();
@@ -4507,10 +4505,11 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
-        let item = cx.add_view(window_id, |cx| {
+        let item = window.add_view(cx, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
         });
         let item_id = item.id();
@@ -4550,7 +4549,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_id));
+        cx.simulate_window_activation(Some(window.window_id()));
         item.update(cx, |item, cx| {
             cx.focus_self();
             item.is_dirty = true;
@@ -4592,7 +4591,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_id));
+        assert!(!cx.has_pending_prompt(window.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.
@@ -4613,7 +4612,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_id));
+        assert!(cx.has_pending_prompt(window.window_id()));
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
     }
 
@@ -4624,9 +4623,10 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
-        let item = cx.add_view(window_id, |cx| {
+        let item = window.add_view(cx, |cx| {
             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
         });
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
@@ -4677,7 +4677,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
         let panel = workspace.update(cx, |workspace, cx| {
             let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
@@ -4824,7 +4825,8 @@ mod tests {
         let fs = FakeFs::new(cx.background());
 
         let project = Project::test(fs, [], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
             // Add panel_1 on the left, panel_2 on the right.
@@ -4979,7 +4981,7 @@ mod tests {
 
         // If focus is transferred to another view that's not a panel or another pane, we still show
         // the panel as zoomed.
-        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
+        let focus_receiver = window.add_view(cx, |_| EmptyView);
         focus_receiver.update(cx, |_, cx| cx.focus_self());
         workspace.read_with(cx, |workspace, _| {
             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));

crates/zed/src/zed.rs šŸ”—

@@ -982,7 +982,9 @@ 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));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -1294,7 +1296,9 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
 
         // Open a file within an existing worktree.
         workspace
@@ -1335,7 +1339,9 @@ mod tests {
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         project.update(cx, |project, _| project.languages().add(rust_lang()));
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
         let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
 
         // Create a new untitled buffer
@@ -1428,7 +1434,9 @@ mod tests {
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
         project.update(cx, |project, _| project.languages().add(rust_lang()));
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
 
         // Create a new untitled buffer
         cx.dispatch_action(window_id, NewFile);
@@ -1479,7 +1487,9 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
+        let window_id = window.window_id();
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -1553,7 +1563,9 @@ 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.clone(), cx));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project.clone(), cx))
+            .root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -1830,7 +1842,9 @@ 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));
+        let workspace = cx
+            .add_window(|cx| Workspace::test_new(project, cx))
+            .root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -2072,7 +2086,8 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        let (window_id, _view) = cx.add_window(|_| TestView);
+        let window = cx.add_window(|_| TestView);
+        let window_id = window.window_id();
 
         // Test loading the keymap base at all
         assert_key_bindings_for(
@@ -2242,7 +2257,8 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        let (window_id, _view) = cx.add_window(|_| TestView);
+        let window = cx.add_window(|_| TestView);
+        let window_id = window.window_id();
 
         // Test loading the keymap base at all
         assert_key_bindings_for(