WIP

Nathan Sobo created

Change summary

crates/editor/src/editor_tests.rs          |  98 ++++----
crates/editor/src/element.rs               |   6 
crates/editor/src/inlay_hint_cache.rs      |  14 
crates/file_finder/src/file_finder.rs      |  20 
crates/gpui/src/app.rs                     | 264 +++++++++++++----------
crates/gpui/src/app/test_app_context.rs    |  10 
crates/gpui/src/app/window.rs              |  10 
crates/language_tools/src/lsp_log_tests.rs |   2 
crates/project_panel/src/project_panel.rs  |   8 
crates/search/src/project_search.rs        |   4 
crates/terminal_view/src/terminal_view.rs  |   2 
crates/workspace/src/workspace.rs          | 108 ++++-----
crates/zed/src/zed.rs                      |   6 
13 files changed, 295 insertions(+), 257 deletions(-)

Detailed changes

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

@@ -64,7 +64,7 @@ fn test_edit_events(cx: &mut TestAppContext) {
                 Editor::for_buffer(buffer.clone(), None, cx)
             }
         })
-        .detach(cx);
+        .root(cx);
     let editor2 = cx
         .add_window({
             let events = events.clone();
@@ -81,7 +81,7 @@ fn test_edit_events(cx: &mut TestAppContext) {
                 Editor::for_buffer(buffer.clone(), None, cx)
             }
         })
-        .detach(cx);
+        .root(cx);
     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 
     // Mutating editor 1 will emit an `Edited` event only for that editor.
@@ -179,7 +179,7 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
     let editor = cx
         .add_window(|cx| build_editor(buffer.clone(), cx))
-        .detach(cx);
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         editor.start_transaction_at(now, cx);
@@ -354,7 +354,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     editor.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
     });
@@ -423,7 +423,7 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@@ -471,7 +471,7 @@ fn test_clone(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple(&text, cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@@ -489,7 +489,7 @@ fn test_clone(cx: &mut TestAppContext) {
         .update(cx, |editor, cx| {
             cx.add_window(Default::default(), |cx| editor.clone(cx))
         })
-        .detach(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));
@@ -639,7 +639,7 @@ fn test_cancel(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@@ -704,7 +704,7 @@ fn test_fold_action(cx: &mut TestAppContext) {
             );
             build_editor(buffer.clone(), cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -774,7 +774,7 @@ fn test_move_cursor(cx: &mut TestAppContext) {
     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))
-        .detach(cx);
+        .root(cx);
 
     buffer.update(cx, |buffer, cx| {
         buffer.edit(
@@ -854,7 +854,7 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγΓε\n", cx);
             build_editor(buffer.clone(), cx)
         })
-        .detach(cx);
+        .root(cx);
 
     assert_eq!('ⓐ'.len_utf8(), 3);
     assert_eq!('α'.len_utf8(), 2);
@@ -961,7 +961,7 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
             build_editor(buffer.clone(), cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@@ -1013,7 +1013,7 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\n  def", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1178,7 +1178,7 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1233,7 +1233,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
                 MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.set_wrap_width(Some(140.), cx);
@@ -1568,7 +1568,7 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("one two three four", cx);
             build_editor(buffer.clone(), cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -1606,7 +1606,7 @@ fn test_newline(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
             build_editor(buffer.clone(), cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -1651,7 +1651,7 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) {
             });
             editor
         })
-        .detach(cx);
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         // Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -1863,7 +1863,7 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) {
             editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
             editor
         })
-        .detach(cx);
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         // Edit the buffer directly, deleting ranges surrounding the editor's selections
@@ -2375,7 +2375,7 @@ fn test_delete_line(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2400,7 +2400,7 @@ fn test_delete_line(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(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)])
@@ -2704,7 +2704,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2732,7 +2732,7 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2761,7 +2761,7 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
@@ -2862,7 +2862,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     editor.update(cx, |editor, cx| {
         let snapshot = editor.buffer.read(cx).snapshot(cx);
         editor.insert_blocks(
@@ -3182,7 +3182,7 @@ fn test_select_all(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.select_all(&SelectAll, cx);
         assert_eq!(
@@ -3201,7 +3201,7 @@ fn test_select_line(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -3250,7 +3250,7 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
     view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
@@ -3323,7 +3323,7 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
             build_editor(buffer, cx)
         })
-        .detach(cx);
+        .root(cx);
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -3608,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)).detach(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;
 
@@ -3771,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)).detach(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;
@@ -4334,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)).detach(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;
 
@@ -4482,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)).detach(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;
@@ -4572,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)).detach(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();
@@ -4702,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)).detach(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)));
 
@@ -4814,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)).detach(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)));
 
@@ -4928,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)).detach(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| {
@@ -5706,7 +5706,7 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(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| {
@@ -5776,7 +5776,7 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
         multibuffer
     });
 
-    let view = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(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! {"
@@ -5869,7 +5869,7 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
             );
             editor
         })
-        .detach(cx);
+        .root(cx);
 
     // Refreshing selections is a no-op when excerpts haven't changed.
     editor.update(cx, |editor, cx| {
@@ -5950,7 +5950,7 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
             );
             editor
         })
-        .detach(cx);
+        .root(cx);
 
     multibuffer.update(cx, |multibuffer, cx| {
         multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
@@ -6013,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)).detach(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;
 
@@ -6054,7 +6054,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
             let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
             build_editor(buffer.clone(), cx)
         })
-        .detach(cx);
+        .root(cx);
 
     editor.update(cx, |editor, cx| {
         struct Type1;
@@ -6145,7 +6145,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
     });
     let leader = cx
         .add_window(|cx| build_editor(buffer.clone(), cx))
-        .detach(cx);
+        .root(cx);
     let follower = cx
         .update(|cx| {
             cx.add_window(
@@ -6156,7 +6156,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
                 |cx| build_editor(buffer.clone(), cx),
             )
         })
-        .detach(cx);
+        .root(cx);
 
     let is_still_following = Rc::new(RefCell::new(true));
     let follower_edit_event_count = Rc::new(RefCell::new(0));
@@ -6289,7 +6289,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
     let workspace = cx
         .add_window(|cx| Workspace::test_new(project.clone(), cx))
-        .detach(cx);
+        .root(cx);
     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
     let leader = pane.update(cx, |_, cx| {
@@ -7033,7 +7033,7 @@ async fn test_copilot_multibuffer(
         );
         multibuffer
     });
-    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(cx);
+    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).root(cx);
 
     handle_copilot_completion_request(
         &copilot_lsp,
@@ -7163,7 +7163,7 @@ async fn test_copilot_disabled_globs(
         );
         multibuffer
     });
-    let editor = cx.add_window(|cx| build_editor(multibuffer, cx)).detach(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 {
@@ -7244,7 +7244,7 @@ async fn test_on_type_formatting_not_triggered(cx: &mut gpui::TestAppContext) {
     project.update(cx, |project, _| project.languages().add(Arc::new(language)));
     let workspace = cx
         .add_window(|cx| Workspace::test_new(project.clone(), cx))
-        .detach(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 šŸ”—

@@ -3007,7 +3007,7 @@ mod tests {
                 let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
                 Editor::new(EditorMode::Full, buffer, None, None, cx)
             })
-            .detach(cx);
+            .root(cx);
         let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
 
         let layouts = editor.update(cx, |editor, cx| {
@@ -3028,7 +3028,7 @@ mod tests {
                 let buffer = MultiBuffer::build_simple("", cx);
                 Editor::new(EditorMode::Full, buffer, None, None, cx)
             })
-            .detach(cx);
+            .root(cx);
 
         editor.update(cx, |editor, cx| {
             editor.set_placeholder_text("hello", cx);
@@ -3240,7 +3240,7 @@ mod tests {
                 let buffer = MultiBuffer::build_simple(&input_text, cx);
                 Editor::new(editor_mode, buffer, None, None, cx)
             })
-            .detach(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 šŸ”—

@@ -1138,7 +1138,7 @@ mod tests {
         let project = Project::test(fs, ["/a".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(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()
@@ -1840,7 +1840,7 @@ mod tests {
         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(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()
@@ -1995,7 +1995,7 @@ mod tests {
         });
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(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()
@@ -2083,7 +2083,7 @@ mod tests {
         cx.foreground().run_until_parked();
         let editor = cx
             .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
-            .detach(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);
@@ -2337,7 +2337,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
         });
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(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()
@@ -2384,7 +2384,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
         cx.foreground().run_until_parked();
         let editor = cx
             .add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx))
-            .detach(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);
@@ -2574,7 +2574,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(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/file_finder/src/file_finder.rs šŸ”—

@@ -842,7 +842,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
         let finder = cx
             .add_window(|cx| {
                 Picker::new(
@@ -856,7 +856,7 @@ mod tests {
                     cx,
                 )
             })
-            .detach(cx);
+            .root(cx);
 
         let query = test_path_like("hi");
         finder
@@ -940,7 +940,7 @@ mod tests {
         .await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
         let finder = cx
             .add_window(|cx| {
                 Picker::new(
@@ -954,7 +954,7 @@ mod tests {
                     cx,
                 )
             })
-            .detach(cx);
+            .root(cx);
         finder
             .update(cx, |f, cx| {
                 f.delegate_mut().spawn_search(test_path_like("hi"), cx)
@@ -980,7 +980,7 @@ mod tests {
         .await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
         let finder = cx
             .add_window(|cx| {
                 Picker::new(
@@ -994,7 +994,7 @@ mod tests {
                     cx,
                 )
             })
-            .detach(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.
@@ -1051,7 +1051,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
@@ -1078,7 +1078,7 @@ mod tests {
                     cx,
                 )
             })
-            .detach(cx);
+            .root(cx);
 
         finder
             .update(cx, |f, cx| {
@@ -1117,7 +1117,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
         let finder = cx
             .add_window(|cx| {
                 Picker::new(
@@ -1131,7 +1131,7 @@ mod tests {
                     cx,
                 )
             })
-            .detach(cx);
+            .root(cx);
         finder
             .update(cx, |f, cx| {
                 f.delegate_mut().spawn_search(test_path_like("dir"), cx)

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

@@ -130,12 +130,12 @@ pub trait BorrowAppContext {
 }
 
 pub trait BorrowWindowContext {
-    type Return<T>;
+    type Result<T>;
 
-    fn read_with<T, F>(&self, window_id: usize, f: F) -> Self::Return<T>
+    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&WindowContext) -> T;
-    fn update<T, F>(&mut self, window_id: usize, f: F) -> Self::Return<T>
+    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T;
 }
@@ -458,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);
 
@@ -2162,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),
@@ -3360,14 +3398,18 @@ impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
 }
 
 impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
-    type Return<T> = T;
+    type Result<T> = T;
 
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_with(&*self.window_context, window_id, f)
+    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)
     }
 }
 
@@ -3467,14 +3509,18 @@ impl<V: View> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
 }
 
 impl<V: View> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
-    type Return<T> = T;
+    type Result<T> = T;
 
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_with(&*self.view_context, window_id, f)
+    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)
     }
 }
 
@@ -3521,14 +3567,18 @@ impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
 }
 
 impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
-    type Return<T> = T;
+    type Result<T> = T;
 
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        BorrowWindowContext::read_with(&*self.view_context, window_id, f)
+    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)
     }
 }
 
@@ -3830,32 +3880,16 @@ impl<V: View> WindowHandle<V> {
         self.any_handle.id()
     }
 
-    pub fn root(&self, cx: &impl BorrowAppContext) -> ViewHandle<V> {
+    pub fn root<C: BorrowWindowContext>(&self, cx: &C) -> C::Result<ViewHandle<V>> {
         self.read_with(cx, |cx| cx.root_view().clone().downcast().unwrap())
     }
 
-    /// Keep this window open until it's explicitly closed.
-    //
-    // TODO: Implement window dropping behavior when we don't call this.
-    pub fn detach(mut self, cx: &impl BorrowAppContext) -> ViewHandle<V> {
-        let root = self.root(cx);
-        let ref_counts = self.any_handle.ref_counts.take();
-        #[cfg(any(test, feature = "test-support"))]
-        ref_counts
-            .unwrap()
-            .lock()
-            .leak_detector
-            .lock()
-            .handle_dropped(self.id(), self.any_handle.handle_id);
-        root
-    }
-
-    pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> R
+    pub fn read_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
     where
-        C: BorrowAppContext,
+        C: BorrowWindowContext,
         F: FnOnce(&WindowContext) -> R,
     {
-        cx.read_with(|cx| cx.read_window(self.id(), read).unwrap())
+        cx.read_window_with(self.id(), |cx| read(cx))
     }
 
     pub fn update<C, F, R>(&self, cx: &mut C, update: F) -> R
@@ -3891,9 +3925,9 @@ impl<V: View> WindowHandle<V> {
         root_view.read(cx)
     }
 
-    pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> R
+    pub fn read_root_with<C, F, R>(&self, cx: &C, read: F) -> C::Result<R>
     where
-        C: BorrowAppContext,
+        C: BorrowWindowContext,
         F: FnOnce(&V, &ViewContext<V>) -> R,
     {
         self.read_with(cx, |cx| {
@@ -4021,25 +4055,25 @@ impl<T: View> ViewHandle<T> {
         cx.read_view(self)
     }
 
-    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::Return<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) -> C::Return<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)
@@ -5005,7 +5039,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>>,
         }
@@ -5048,7 +5082,7 @@ mod tests {
         let model = cx.add_model(|_| Model {
             released: model_released.clone(),
         });
-        let window = cx.add_window(Default::default(), |_| View {
+        let window = cx.add_window(|_| View {
             released: view_released.clone(),
         });
         let view = window.root(cx);
@@ -5056,16 +5090,18 @@ mod tests {
         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);
@@ -5795,7 +5831,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>,
@@ -5846,68 +5882,70 @@ 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.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: &Action, cx| {
-                assert_eq!(action.0, "bar");
-                cx.propagate_action();
-                actions.borrow_mut().push(format!("{} a", view.id));
-            }
-        });
+            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.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, 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));
                 }
-                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 ViewB, _: &Action, cx| {
+                    cx.propagate_action();
+                    actions.borrow_mut().push(format!("{} c", 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!("{} d", 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.capture_action({
+                let actions = actions.clone();
+                move |view: &mut ViewA, _: &Action, cx| {
+                    cx.propagate_action();
+                    actions.borrow_mut().push(format!("{} capture", 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.observe_actions({
+                let observed_actions = observed_actions.clone();
+                move |action_id, _| observed_actions.borrow_mut().push(action_id)
+            })
+            .detach();
+        });
 
-        let window = cx.add_window(Default::default(), |_| ViewA { id: 1, child: None });
+        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 });
@@ -5956,7 +5994,7 @@ mod tests {
 
         // Remove view_1, which doesn't propagate the action
 
-        let window = cx.add_window(Default::default(), |_| ViewB { id: 2, child: None });
+        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 });
@@ -6457,7 +6495,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 {
@@ -6474,7 +6512,7 @@ mod tests {
             }
         }
 
-        let window = cx.add_window(Default::default(), |_| View(0));
+        let window = cx.add_window(|_| View(0));
         let root_view = window.root(cx);
         window.update(cx, |cx| {
             assert_eq!(

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

@@ -406,16 +406,20 @@ impl BorrowAppContext for TestAppContext {
 }
 
 impl BorrowWindowContext for TestAppContext {
-    type Return<T> = T;
+    type Result<T> = T;
 
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> 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 šŸ”—

@@ -142,9 +142,9 @@ impl BorrowAppContext for WindowContext<'_> {
 }
 
 impl BorrowWindowContext for WindowContext<'_> {
-    type Return<T> = T;
+    type Result<T> = T;
 
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> 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 {
@@ -152,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 {

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

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

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

@@ -1782,7 +1782,7 @@ mod tests {
         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))
-            .detach(cx);
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
         assert_eq!(
             visible_entries_as_strings(&panel, 0..50, cx),
@@ -2327,7 +2327,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(cx);
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         panel.update(cx, |panel, cx| {
@@ -2641,7 +2641,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(cx);
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         let new_search_events_count = Arc::new(AtomicUsize::new(0));
@@ -2730,7 +2730,7 @@ mod tests {
         let project = Project::test(fs.clone(), ["/project_root".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project.clone(), cx))
-            .detach(cx);
+            .root(cx);
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         panel.update(cx, |panel, cx| {

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

@@ -1449,7 +1449,7 @@ pub mod tests {
         let search = cx.add_model(|cx| ProjectSearch::new(project, cx));
         let search_view = cx
             .add_window(|cx| ProjectSearchView::new(search.clone(), cx))
-            .detach(cx);
+            .root(cx);
 
         search_view.update(cx, |search_view, cx| {
             search_view
@@ -1754,7 +1754,7 @@ pub mod tests {
         });
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
 
         let active_item = cx.read(|cx| {
             workspace

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

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

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

@@ -793,68 +793,60 @@ impl Workspace {
                 DB.next_id().await.unwrap_or(0)
             };
 
-            let window = requesting_window_id
-                .and_then(|window_id| {
-                    cx.update(|cx| {
-                        cx.replace_root_view(window_id, |cx| {
-                            Workspace::new(
-                                workspace_id,
-                                project_handle.clone(),
-                                app_state.clone(),
-                                cx,
-                            )
-                        })
+            let window = 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,
-                            )
-                        },
-                    )
-                });
+                            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();
 
-            let workspace = window.root(&cx);
             (app_state.initialize_workspace)(
                 workspace.downgrade(),
                 serialized_workspace.is_some(),
@@ -3985,7 +3977,7 @@ pub fn join_remote_project(
                 ),
                 |cx| Workspace::new(0, project, app_state.clone(), cx),
             );
-            let workspace = window.root(&cx);
+            let workspace = window.root(&cx).unwrap();
             (app_state.initialize_workspace)(
                 workspace.downgrade(),
                 false,

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

@@ -985,7 +985,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -1566,7 +1566,7 @@ mod tests {
         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))
-            .detach(cx);
+            .root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
@@ -1845,7 +1845,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let workspace = cx
             .add_window(|cx| Workspace::test_new(project, cx))
-            .detach(cx);
+            .root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));