Merge branch 'main' into collab-panel

Mikayla created

Change summary

Cargo.lock                                          |  43 +
Cargo.toml                                          |   1 
assets/fonts/plex/IBMPlexSans-Bold.ttf              |   0 
assets/fonts/plex/IBMPlexSans-Italic.ttf            |   0 
assets/fonts/plex/IBMPlexSans-Regular.ttf           |   0 
assets/fonts/plex/LICENSE.txt                       |  93 ++
crates/collab/src/tests.rs                          |  83 +-
crates/collab/src/tests/integration_tests.rs        |  50 
crates/collab_ui/src/collab_panel.rs                |  12 
crates/collab_ui/src/collab_titlebar_item.rs        |   6 
crates/collab_ui/src/face_pile.rs                   |   4 
crates/collab_ui/src/incoming_call_notification.rs  |  10 
crates/collab_ui/src/project_shared_notification.rs |  14 
crates/collab_ui/src/sharing_status_indicator.rs    |   8 
crates/command_palette/src/command_palette.rs       |  22 
crates/context_menu/src/context_menu.rs             |  22 
crates/diagnostics/src/diagnostics.rs               |   6 
crates/drag_and_drop/src/drag_and_drop.rs           |  40 
crates/editor/src/editor.rs                         |  74 ++
crates/editor/src/editor_tests.rs                   |  12 
crates/editor/src/element.rs                        |  79 -
crates/editor/src/test/editor_lsp_test_context.rs   |   2 
crates/editor/src/test/editor_test_context.rs       |  10 
crates/file_finder/src/file_finder.rs               |  42 
crates/go_to_line/src/go_to_line.rs                 |   2 
crates/gpui/Cargo.toml                              |   1 
crates/gpui/src/app.rs                              | 480 ++++++--------
crates/gpui/src/app/menu.rs                         |   6 
crates/gpui/src/app/ref_counts.rs                   |  14 
crates/gpui/src/app/test_app_context.rs             | 258 +++----
crates/gpui/src/app/window.rs                       | 176 ++--
crates/gpui/src/app/window_input_handler.rs         |  27 
crates/gpui/src/elements.rs                         |  26 
crates/gpui/src/elements/align.rs                   |   5 
crates/gpui/src/elements/canvas.rs                  |   4 
crates/gpui/src/elements/clipped.rs                 |   5 
crates/gpui/src/elements/constrained_box.rs         |   5 
crates/gpui/src/elements/container.rs               |   5 
crates/gpui/src/elements/empty.rs                   |   4 
crates/gpui/src/elements/expanded.rs                |   5 
crates/gpui/src/elements/flex.rs                    |   8 
crates/gpui/src/elements/hook.rs                    |   5 
crates/gpui/src/elements/image.rs                   |   6 
crates/gpui/src/elements/keystroke_label.rs         |   2 
crates/gpui/src/elements/label.rs                   |   4 
crates/gpui/src/elements/list.rs                    |  10 
crates/gpui/src/elements/mouse_event_handler.rs     |   6 
crates/gpui/src/elements/overlay.rs                 |   6 
crates/gpui/src/elements/resizable.rs               |   6 
crates/gpui/src/elements/stack.rs                   |   5 
crates/gpui/src/elements/svg.rs                     |   3 
crates/gpui/src/elements/text.rs                    |   6 
crates/gpui/src/elements/tooltip.rs                 |   6 
crates/gpui/src/elements/uniform_list.rs            |   4 
crates/gpui/src/fonts.rs                            |  26 
crates/gpui/src/platform.rs                         |   8 
crates/gpui/src/platform/mac.rs                     |   2 
crates/gpui/src/platform/mac/platform.rs            |  14 
crates/gpui/src/platform/mac/window.rs              |  32 
crates/gpui/src/platform/test.rs                    |  40 
crates/gpui/src/scene/mouse_region.rs               |   2 
crates/language/src/language.rs                     |  18 
crates/project/src/project.rs                       |  29 
crates/project/src/terminals.rs                     |   6 
crates/project_panel/src/project_panel.rs           |  58 -
crates/project_symbols/src/project_symbols.rs       |   3 
crates/search/src/buffer_search.rs                  |  32 
crates/search/src/project_search.rs                 |  24 
crates/terminal/src/terminal.rs                     |   6 
crates/terminal_view/src/terminal_element.rs        |   7 
crates/terminal_view/src/terminal_panel.rs          |   8 
crates/terminal_view/src/terminal_view.rs           |   8 
crates/theme/src/ui.rs                              |   1 
crates/util/src/paths.rs                            |  39 +
crates/vim/src/editor_events.rs                     |   8 
crates/vim/src/mode_indicator.rs                    |   2 
crates/vim/src/test/vim_test_context.rs             |   4 
crates/workspace/src/dock.rs                        |  11 
crates/workspace/src/item.rs                        |  13 
crates/workspace/src/pane.rs                        |  10 
crates/workspace/src/pane/dragged_item_receiver.rs  |  12 
crates/workspace/src/pane_group.rs                  |   4 
crates/workspace/src/searchable.rs                  |   6 
crates/workspace/src/status_bar.rs                  |   6 
crates/workspace/src/workspace.rs                   | 262 +++----
crates/zed/Cargo.toml                               |   2 
crates/zed/src/languages/bash/config.toml           |   2 
crates/zed/src/languages/rust.rs                    |   4 
crates/zed/src/languages/toml/config.toml           |   2 
crates/zed/src/main.rs                              |  38 -
crates/zed/src/zed.rs                               | 180 ++---
styles/src/common.ts                                |   1 
styles/src/theme/create_theme.ts                    |  37 +
93 files changed, 1,435 insertions(+), 1,265 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1652,6 +1652,12 @@ dependencies = [
  "theme",
 ]
 
+[[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
 [[package]]
 name = "convert_case"
 version = "0.6.0"
@@ -2145,6 +2151,19 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "derive_more"
+version = "0.99.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
+dependencies = [
+ "convert_case 0.4.0",
+ "proc-macro2",
+ "quote",
+ "rustc_version 0.4.0",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "dhat"
 version = "0.3.2"
@@ -2326,7 +2345,7 @@ dependencies = [
  "clock",
  "collections",
  "context_menu",
- "convert_case",
+ "convert_case 0.6.0",
  "copilot",
  "ctor",
  "db",
@@ -3109,6 +3128,7 @@ dependencies = [
  "core-graphics",
  "core-text",
  "ctor",
+ "derive_more",
  "dhat",
  "env_logger 0.9.3",
  "etagere",
@@ -5119,7 +5139,7 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff"
 dependencies = [
- "rustc_version",
+ "rustc_version 0.3.3",
 ]
 
 [[package]]
@@ -6289,7 +6309,16 @@ version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
 dependencies = [
- "semver",
+ "semver 0.11.0",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver 1.0.18",
 ]
 
 [[package]]
@@ -6729,6 +6758,12 @@ dependencies = [
  "semver-parser",
 ]
 
+[[package]]
+name = "semver"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
+
 [[package]]
 name = "semver-parser"
 version = "0.10.2"
@@ -9828,7 +9863,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.99.0"
+version = "0.100.0"
 dependencies = [
  "activity_indicator",
  "ai",

Cargo.toml 🔗

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

assets/fonts/plex/LICENSE.txt 🔗

@@ -0,0 +1,93 @@
+Copyright © 2017 IBM Corp. with Reserved Font Name "Plex"

+

+This Font Software is licensed under the SIL Open Font License, Version 1.1.

+

+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL

+

+

+-----------------------------------------------------------

+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

+-----------------------------------------------------------

+

+PREAMBLE

+The goals of the Open Font License (OFL) are to stimulate worldwide

+development of collaborative font projects, to support the font creation

+efforts of academic and linguistic communities, and to provide a free and

+open framework in which fonts may be shared and improved in partnership

+with others.

+

+The OFL allows the licensed fonts to be used, studied, modified and

+redistributed freely as long as they are not sold by themselves. The

+fonts, including any derivative works, can be bundled, embedded, 

+redistributed and/or sold with any software provided that any reserved

+names are not used by derivative works. The fonts and derivatives,

+however, cannot be released under any other type of license. The

+requirement for fonts to remain under this license does not apply

+to any document created using the fonts or their derivatives.

+

+DEFINITIONS

+"Font Software" refers to the set of files released by the Copyright

+Holder(s) under this license and clearly marked as such. This may

+include source files, build scripts and documentation.

+

+"Reserved Font Name" refers to any names specified as such after the

+copyright statement(s).

+

+"Original Version" refers to the collection of Font Software components as

+distributed by the Copyright Holder(s).

+

+"Modified Version" refers to any derivative made by adding to, deleting,

+or substituting -- in part or in whole -- any of the components of the

+Original Version, by changing formats or by porting the Font Software to a

+new environment.

+

+"Author" refers to any designer, engineer, programmer, technical

+writer or other person who contributed to the Font Software.

+

+PERMISSION & CONDITIONS

+Permission is hereby granted, free of charge, to any person obtaining

+a copy of the Font Software, to use, study, copy, merge, embed, modify,

+redistribute, and sell modified and unmodified copies of the Font

+Software, subject to the following conditions:

+

+1) Neither the Font Software nor any of its individual components,

+in Original or Modified Versions, may be sold by itself.

+

+2) Original or Modified Versions of the Font Software may be bundled,

+redistributed and/or sold with any software, provided that each copy

+contains the above copyright notice and this license. These can be

+included either as stand-alone text files, human-readable headers or

+in the appropriate machine-readable metadata fields within text or

+binary files as long as those fields can be easily viewed by the user.

+

+3) No Modified Version of the Font Software may use the Reserved Font

+Name(s) unless explicit written permission is granted by the corresponding

+Copyright Holder. This restriction only applies to the primary font name as

+presented to the users.

+

+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font

+Software shall not be used to promote, endorse or advertise any

+Modified Version, except to acknowledge the contribution(s) of the

+Copyright Holder(s) and the Author(s) or with their explicit written

+permission.

+

+5) The Font Software, modified or unmodified, in part or in whole,

+must be distributed entirely under this license, and must not be

+distributed under any other license. The requirement for fonts to

+remain under this license does not apply to any document created

+using the Font Software.

+

+TERMINATION

+This license becomes null and void if any of the above conditions are

+not met.

+

+DISCLAIMER

+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,

+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF

+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT

+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE

+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,

+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL

+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING

+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM

+OTHER DEALINGS IN THE FONT SOFTWARE.

crates/collab/src/tests.rs 🔗

@@ -541,45 +541,50 @@ impl TestClient {
         &self,
         project: &ModelHandle<Project>,
         cx: &mut TestAppContext,
-    ) -> ViewHandle<Workspace> {
-        struct WorkspaceContainer {
-            workspace: Option<WeakViewHandle<Workspace>>,
-        }
-
-        impl Entity for WorkspaceContainer {
-            type Event = ();
-        }
-
-        impl View for WorkspaceContainer {
-            fn ui_name() -> &'static str {
-                "WorkspaceContainer"
-            }
-
-            fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-                if let Some(workspace) = self
-                    .workspace
-                    .as_ref()
-                    .and_then(|workspace| workspace.upgrade(cx))
-                {
-                    ChildView::new(&workspace, cx).into_any()
-                } else {
-                    Empty::new().into_any()
-                }
-            }
-        }
-
-        // 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 = cx.add_window(|_| WorkspaceContainer { workspace: None });
-        let container = window.root(cx);
-        let workspace = window.add_view(cx, |cx| {
-            Workspace::new(0, project.clone(), self.app_state.clone(), cx)
-        });
-        container.update(cx, |container, cx| {
-            container.workspace = Some(workspace.downgrade());
-            cx.notify();
-        });
-        workspace
+        // <<<<<<< HEAD
+        //     ) -> ViewHandle<Workspace> {
+        //         struct WorkspaceContainer {
+        //             workspace: Option<WeakViewHandle<Workspace>>,
+        //         }
+
+        //         impl Entity for WorkspaceContainer {
+        //             type Event = ();
+        //         }
+
+        //         impl View for WorkspaceContainer {
+        //             fn ui_name() -> &'static str {
+        //                 "WorkspaceContainer"
+        //             }
+
+        //             fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+        //                 if let Some(workspace) = self
+        //                     .workspace
+        //                     .as_ref()
+        //                     .and_then(|workspace| workspace.upgrade(cx))
+        //                 {
+        //                     ChildView::new(&workspace, cx).into_any()
+        //                 } else {
+        //                     Empty::new().into_any()
+        //                 }
+        //             }
+        //         }
+
+        //         // 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 = cx.add_window(|_| WorkspaceContainer { workspace: None });
+        //         let container = window.root(cx);
+        //         let workspace = window.add_view(cx, |cx| {
+        //             Workspace::new(0, project.clone(), self.app_state.clone(), cx)
+        //         });
+        //         container.update(cx, |container, cx| {
+        //             container.workspace = Some(workspace.downgrade());
+        //             cx.notify();
+        //         });
+        //         workspace
+        // =======
+    ) -> WindowHandle<Workspace> {
+        cx.add_window(|cx| Workspace::test_new(project.clone(), cx))
+        // >>>>>>> main
     }
 }
 

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

@@ -1511,7 +1511,7 @@ async fn test_host_disconnect(
         .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()));
+    assert!(window_b.is_edited(cx_b));
 
     // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
     server.forbid_connections();
@@ -1526,7 +1526,7 @@ async fn test_host_disconnect(
     window_b.read_with(cx_b, |cx| {
         assert_eq!(cx.focused_view_id(), None);
     });
-    assert!(!cx_b.is_window_edited(workspace_b.window_id()));
+    assert!(!window_b.is_edited(cx_b));
 
     // Ensure client B is not prompted to save edits when closing window after disconnecting.
     let can_close = workspace_b
@@ -3442,7 +3442,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
     let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
     let mut editor_cx_a = EditorTestContext {
         cx: cx_a,
-        window_id: window_a.window_id(),
+        window: window_a.into(),
         editor: editor_a,
     };
 
@@ -3455,7 +3455,7 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
     let mut editor_cx_b = EditorTestContext {
         cx: cx_b,
-        window_id: window_b.window_id(),
+        window: window_b.into(),
         editor: editor_b,
     };
 
@@ -6440,8 +6440,10 @@ async fn test_basic_following(
         .await
         .unwrap();
 
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let window_a = client_a.build_workspace(&project_a, cx_a);
+    let workspace_a = window_a.root(cx_a);
+    let window_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_b = window_b.root(cx_b);
 
     // Client A opens some editors.
     let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
@@ -6524,7 +6526,8 @@ async fn test_basic_following(
     cx_c.foreground().run_until_parked();
     let active_call_c = cx_c.read(ActiveCall::global);
     let project_c = client_c.build_remote_project(project_id, cx_c).await;
-    let workspace_c = client_c.build_workspace(&project_c, cx_c);
+    let window_c = client_c.build_workspace(&project_c, cx_c);
+    let workspace_c = window_c.root(cx_c);
     active_call_c
         .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
         .await
@@ -6542,7 +6545,7 @@ async fn test_basic_following(
     cx_d.foreground().run_until_parked();
     let active_call_d = cx_d.read(ActiveCall::global);
     let project_d = client_d.build_remote_project(project_id, cx_d).await;
-    let workspace_d = client_d.build_workspace(&project_d, cx_d);
+    let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
     active_call_d
         .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
         .await
@@ -6640,6 +6643,7 @@ async fn test_basic_following(
     }
 
     // Client C closes the project.
+    window_c.remove(cx_c);
     cx_c.drop_last(workspace_c);
 
     // Clients A and B see that client B is following A, and client C is not present in the followers.
@@ -6869,9 +6873,7 @@ async fn test_basic_following(
     });
 
     // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
-    let panel = cx_b.add_view(workspace_b.window_id(), |_| {
-        TestPanel::new(DockPosition::Left)
-    });
+    let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
     workspace_b.update(cx_b, |workspace, cx| {
         workspace.add_panel(panel, cx);
         workspace.toggle_panel_focus::<TestPanel>(cx);
@@ -6899,7 +6901,7 @@ async fn test_basic_following(
 
     // Client B activates an item that doesn't implement following,
     // so the previously-opened screen-sharing item gets activated.
-    let unfollowable_item = cx_b.add_view(workspace_b.window_id(), |_| TestItem::new());
+    let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
     workspace_b.update(cx_b, |workspace, cx| {
         workspace.active_pane().update(cx, |pane, cx| {
             pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
@@ -7061,10 +7063,10 @@ async fn test_following_tab_order(
         .await
         .unwrap();
 
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
     let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
 
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
     let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
 
     let client_b_id = project_a.read_with(cx_a, |project, _| {
@@ -7187,7 +7189,7 @@ async fn test_peers_following_each_other(
         .unwrap();
 
     // Client A opens some editors.
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
     let pane_a1 = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
     let _editor_a1 = workspace_a
         .update(cx_a, |workspace, cx| {
@@ -7199,7 +7201,7 @@ async fn test_peers_following_each_other(
         .unwrap();
 
     // Client B opens an editor.
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
     let pane_b1 = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
     let _editor_b1 = workspace_b
         .update(cx_b, |workspace, cx| {
@@ -7358,7 +7360,7 @@ async fn test_auto_unfollowing(
         .unwrap();
 
     // Client A opens some editors.
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
     let _editor_a1 = workspace_a
         .update(cx_a, |workspace, cx| {
             workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -7369,7 +7371,7 @@ async fn test_auto_unfollowing(
         .unwrap();
 
     // Client B starts following client A.
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
     let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
     let leader_id = project_b.read_with(cx_b, |project, _| {
         project.collaborators().values().next().unwrap().peer_id
@@ -7497,14 +7499,14 @@ async fn test_peers_simultaneously_following_each_other(
 
     client_a.fs().insert_tree("/a", json!({})).await;
     let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
     let project_id = active_call_a
         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
         .await
         .unwrap();
 
     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
 
     deterministic.run_until_parked();
     let client_a_id = project_b.read_with(cx_b, |project, _| {
@@ -7886,7 +7888,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
         .await
         .unwrap();
 
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
     cx_a.foreground().start_waiting();
 
     let _buffer_a = project_a
@@ -7954,7 +7956,7 @@ async fn test_mutual_editor_inlay_hint_cache_update(
             "Host editor update the cache version after every cache/view change",
         );
     });
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
     let editor_b = workspace_b
         .update(cx_b, |workspace, cx| {
             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
@@ -8193,8 +8195,8 @@ async fn test_inlay_hint_refresh_is_forwarded(
         .await
         .unwrap();
 
-    let workspace_a = client_a.build_workspace(&project_a, cx_a);
-    let workspace_b = client_b.build_workspace(&project_b, cx_b);
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
     cx_a.foreground().start_waiting();
     cx_b.foreground().start_waiting();
 

crates/collab_ui/src/collab_panel.rs 🔗

@@ -1891,18 +1891,18 @@ impl CollabPanel {
             );
             let mut answer =
                 cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-            let window_id = cx.window_id();
+            let window = cx.window();
             cx.spawn(|_, mut cx| async move {
                 if answer.next().await == Some(0) {
                     if let Err(e) = channel_store
                         .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
                         .await
                     {
-                        cx.prompt(
-                            window_id,
+                        window.prompt(
                             PromptLevel::Info,
                             &format!("Failed to remove channel: {}", e),
                             &["Ok"],
+                            &mut cx,
                         );
                     }
                 }
@@ -1921,18 +1921,18 @@ impl CollabPanel {
             github_login
         );
         let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-        let window_id = cx.window_id();
+        let window = cx.window();
         cx.spawn(|_, mut cx| async move {
             if answer.next().await == Some(0) {
                 if let Err(e) = user_store
                     .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
                     .await
                 {
-                    cx.prompt(
-                        window_id,
+                    window.prompt(
                         PromptLevel::Info,
                         &format!("Failed to remove contact: {}", e),
                         &["Ok"],
+                        &mut cx,
                     );
                 }
             }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -13,8 +13,8 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f, PathBuilder},
     json::{self, ToJson},
     platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ImageData, LayoutContext, ModelHandle, SceneBuilder, Subscription, View,
-    ViewContext, ViewHandle, WeakViewHandle,
+    AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
+    Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use picker::PickerEvent;
 use project::{Project, RepositoryEntry};
@@ -1190,7 +1190,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
         _: RectF,
         _: &mut Self::LayoutState,
         _: &mut CollabTitlebarItem,
-        _: &mut ViewContext<CollabTitlebarItem>,
+        _: &mut PaintContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let mut path = PathBuilder::new();
         path.reset(bounds.lower_left());

crates/collab_ui/src/face_pile.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     },
     json::ToJson,
     serde_json::{self, json},
-    AnyElement, Axis, Element, LayoutContext, SceneBuilder, View, ViewContext,
+    AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
 };
 
 pub(crate) struct FacePile<V: View> {
@@ -58,7 +58,7 @@ impl<V: View> Element<V> for FacePile<V> {
         visible_bounds: RectF,
         _layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
-    AnyElement, AppContext, Entity, View, ViewContext,
+    AnyElement, AppContext, Entity, View, ViewContext, WindowHandle,
 };
 use util::ResultExt;
 use workspace::AppState;
@@ -16,10 +16,10 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
     let app_state = Arc::downgrade(app_state);
     let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
     cx.spawn(|mut cx| async move {
-        let mut notification_windows = Vec::new();
+        let mut notification_windows: Vec<WindowHandle<IncomingCallNotification>> = Vec::new();
         while let Some(incoming_call) = incoming_call.next().await {
-            for window_id in notification_windows.drain(..) {
-                cx.remove_window(window_id);
+            for window in notification_windows.drain(..) {
+                window.remove(&mut cx);
             }
 
             if let Some(incoming_call) = incoming_call {
@@ -49,7 +49,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
                         |_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
                     );
 
-                    notification_windows.push(window.window_id());
+                    notification_windows.push(window);
                 }
             }
         }

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -52,20 +52,20 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
                 notification_windows
                     .entry(*project_id)
                     .or_insert(Vec::new())
-                    .push(window.window_id());
+                    .push(window);
             }
         }
         room::Event::RemoteProjectUnshared { project_id } => {
-            if let Some(window_ids) = notification_windows.remove(&project_id) {
-                for window_id in window_ids {
-                    cx.update_window(window_id, |cx| cx.remove_window());
+            if let Some(windows) = notification_windows.remove(&project_id) {
+                for window in windows {
+                    window.remove(cx);
                 }
             }
         }
         room::Event::Left => {
-            for (_, window_ids) in notification_windows.drain() {
-                for window_id in window_ids {
-                    cx.update_window(window_id, |cx| cx.remove_window());
+            for (_, windows) in notification_windows.drain() {
+                for window in windows {
+                    window.remove(cx);
                 }
             }
         }

crates/collab_ui/src/sharing_status_indicator.rs 🔗

@@ -20,11 +20,11 @@ pub fn init(cx: &mut AppContext) {
                 {
                     status_indicator = Some(cx.add_status_bar_item(|_| SharingStatusIndicator));
                 }
-            } else if let Some((window_id, _)) = status_indicator.take() {
-                cx.update_window(window_id, |cx| cx.remove_window());
+            } else if let Some(window) = status_indicator.take() {
+                window.update(cx, |cx| cx.remove_window());
             }
-        } else if let Some((window_id, _)) = status_indicator.take() {
-            cx.update_window(window_id, |cx| cx.remove_window());
+        } else if let Some(window) = status_indicator.take() {
+            window.update(cx, |cx| cx.remove_window());
         }
     })
     .detach();

crates/command_palette/src/command_palette.rs 🔗

@@ -1,8 +1,8 @@
 use collections::CommandPaletteFilter;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Element, MouseState,
-    ViewContext,
+    actions, anyhow::anyhow, elements::*, keymap_matcher::Keystroke, Action, AnyWindowHandle,
+    AppContext, Element, MouseState, ViewContext,
 };
 use picker::{Picker, PickerDelegate, PickerEvent};
 use std::cmp;
@@ -28,7 +28,7 @@ pub struct CommandPaletteDelegate {
 pub enum Event {
     Dismissed,
     Confirmed {
-        window_id: usize,
+        window: AnyWindowHandle,
         focused_view_id: usize,
         action: Box<dyn Action>,
     },
@@ -80,12 +80,13 @@ impl PickerDelegate for CommandPaletteDelegate {
         query: String,
         cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
-        let window_id = cx.window_id();
         let view_id = self.focused_view_id;
+        let window = cx.window();
         cx.spawn(move |picker, mut cx| async move {
-            let actions = cx
-                .available_actions(window_id, view_id)
+            let actions = window
+                .available_actions(view_id, &cx)
                 .into_iter()
+                .flatten()
                 .filter_map(|(name, action, bindings)| {
                     let filtered = cx.read(|cx| {
                         if cx.has_global::<CommandPaletteFilter>() {
@@ -162,13 +163,15 @@ impl PickerDelegate for CommandPaletteDelegate {
 
     fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
         if !self.matches.is_empty() {
-            let window_id = cx.window_id();
+            let window = cx.window();
             let focused_view_id = self.focused_view_id;
             let action_ix = self.matches[self.selected_ix].candidate_id;
             let action = self.actions.remove(action_ix).action;
             cx.app_context()
                 .spawn(move |mut cx| async move {
-                    cx.dispatch_action(window_id, focused_view_id, action.as_ref())
+                    window
+                        .dispatch_action(focused_view_id, action.as_ref(), &mut cx)
+                        .ok_or_else(|| anyhow!("window was closed"))
                 })
                 .detach_and_log_err(cx);
         }
@@ -297,8 +300,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), [], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
-        let editor = cx.add_view(window_id, |cx| {
+        let editor = window.add_view(cx, |cx| {
             let mut editor = Editor::single_line(None, cx);
             editor.set_text("abc", cx);
             editor

crates/context_menu/src/context_menu.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::{
-    anyhow,
+    anyhow::{self, anyhow},
     elements::*,
     geometry::vector::Vector2F,
     keymap_matcher::KeymapContext,
@@ -218,12 +218,14 @@ impl ContextMenu {
             if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
                 match action {
                     ContextMenuItemAction::Action(action) => {
-                        let window_id = cx.window_id();
+                        let window = cx.window();
                         let view_id = self.parent_view_id;
                         let action = action.boxed_clone();
                         cx.app_context()
                             .spawn(|mut cx| async move {
-                                cx.dispatch_action(window_id, view_id, action.as_ref())
+                                window
+                                    .dispatch_action(view_id, action.as_ref(), &mut cx)
+                                    .ok_or_else(|| anyhow!("window was closed"))
                             })
                             .detach_and_log_err(cx);
                     }
@@ -480,17 +482,19 @@ impl ContextMenu {
                             .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
                             .on_click(MouseButton::Left, move |_, menu, cx| {
                                 menu.cancel(&Default::default(), cx);
-                                let window_id = cx.window_id();
+                                let window = cx.window();
                                 match &action {
                                     ContextMenuItemAction::Action(action) => {
                                         let action = action.boxed_clone();
                                         cx.app_context()
                                             .spawn(|mut cx| async move {
-                                                cx.dispatch_action(
-                                                    window_id,
-                                                    view_id,
-                                                    action.as_ref(),
-                                                )
+                                                window
+                                                    .dispatch_action(
+                                                        view_id,
+                                                        action.as_ref(),
+                                                        &mut cx,
+                                                    )
+                                                    .ok_or_else(|| anyhow!("window was closed"))
                                             })
                                             .detach_and_log_err(cx);
                                     }

crates/diagnostics/src/diagnostics.rs 🔗

@@ -857,7 +857,6 @@ mod tests {
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
 
         // Create some diagnostics
         project.update(cx, |project, cx| {
@@ -944,7 +943,7 @@ mod tests {
         });
 
         // Open the project diagnostics view while there are already diagnostics.
-        let view = cx.add_view(window_id, |cx| {
+        let view = window.add_view(cx, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 
@@ -1252,9 +1251,8 @@ mod tests {
         let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
 
-        let view = cx.add_view(window_id, |cx| {
+        let view = window.add_view(cx, |cx| {
             ProjectDiagnosticsEditor::new(project.clone(), workspace.downgrade(), cx)
         });
 

crates/drag_and_drop/src/drag_and_drop.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     geometry::{rect::RectF, vector::Vector2F},
     platform::{CursorStyle, MouseButton},
     scene::{MouseDown, MouseDrag},
-    AnyElement, Element, View, ViewContext, WeakViewHandle, WindowContext,
+    AnyElement, AnyWindowHandle, Element, View, ViewContext, WeakViewHandle, WindowContext,
 };
 
 const DEAD_ZONE: f32 = 4.;
@@ -21,7 +21,7 @@ enum State<V: View> {
         region: RectF,
     },
     Dragging {
-        window_id: usize,
+        window: AnyWindowHandle,
         position: Vector2F,
         region_offset: Vector2F,
         region: RectF,
@@ -49,14 +49,14 @@ impl<V: View> Clone for State<V> {
                 region,
             },
             State::Dragging {
-                window_id,
+                window,
                 position,
                 region_offset,
                 region,
                 payload,
                 render,
             } => Self::Dragging {
-                window_id: window_id.clone(),
+                window: window.clone(),
                 position: position.clone(),
                 region_offset: region_offset.clone(),
                 region: region.clone(),
@@ -87,16 +87,16 @@ impl<V: View> DragAndDrop<V> {
         self.containers.insert(handle);
     }
 
-    pub fn currently_dragged<T: Any>(&self, window_id: usize) -> Option<(Vector2F, Rc<T>)> {
+    pub fn currently_dragged<T: Any>(&self, window: AnyWindowHandle) -> Option<(Vector2F, Rc<T>)> {
         self.currently_dragged.as_ref().and_then(|state| {
             if let State::Dragging {
                 position,
                 payload,
-                window_id: window_dragged_from,
+                window: window_dragged_from,
                 ..
             } = state
             {
-                if &window_id != window_dragged_from {
+                if &window != window_dragged_from {
                     return None;
                 }
 
@@ -126,9 +126,9 @@ impl<V: View> DragAndDrop<V> {
         cx: &mut WindowContext,
         render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> AnyElement<V>>,
     ) {
-        let window_id = cx.window_id();
+        let window = cx.window();
         cx.update_global(|this: &mut Self, cx| {
-            this.notify_containers_for_window(window_id, cx);
+            this.notify_containers_for_window(window, cx);
 
             match this.currently_dragged.as_ref() {
                 Some(&State::Down {
@@ -141,7 +141,7 @@ impl<V: View> DragAndDrop<V> {
                 }) => {
                     if (event.position - (region.origin() + region_offset)).length() > DEAD_ZONE {
                         this.currently_dragged = Some(State::Dragging {
-                            window_id,
+                            window,
                             region_offset,
                             region,
                             position: event.position,
@@ -163,7 +163,7 @@ impl<V: View> DragAndDrop<V> {
                     ..
                 }) => {
                     this.currently_dragged = Some(State::Dragging {
-                        window_id,
+                        window,
                         region_offset,
                         region,
                         position: event.position,
@@ -188,14 +188,14 @@ impl<V: View> DragAndDrop<V> {
                     State::Down { .. } => None,
                     State::DeadZone { .. } => None,
                     State::Dragging {
-                        window_id,
+                        window,
                         region_offset,
                         position,
                         region,
                         payload,
                         render,
                     } => {
-                        if cx.window_id() != window_id {
+                        if cx.window() != window {
                             return None;
                         }
 
@@ -260,27 +260,27 @@ impl<V: View> DragAndDrop<V> {
 
     pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
         if let Some(State::Dragging {
-            payload, window_id, ..
+            payload, window, ..
         }) = &self.currently_dragged
         {
             if payload.is::<P>() {
-                let window_id = *window_id;
+                let window = *window;
                 self.currently_dragged = Some(State::Canceled);
-                self.notify_containers_for_window(window_id, cx);
+                self.notify_containers_for_window(window, cx);
             }
         }
     }
 
     fn finish_dragging(&mut self, cx: &mut WindowContext) {
-        if let Some(State::Dragging { window_id, .. }) = self.currently_dragged.take() {
-            self.notify_containers_for_window(window_id, cx);
+        if let Some(State::Dragging { window, .. }) = self.currently_dragged.take() {
+            self.notify_containers_for_window(window, cx);
         }
     }
 
-    fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
+    fn notify_containers_for_window(&mut self, window: AnyWindowHandle, cx: &mut WindowContext) {
         self.containers.retain(|container| {
             if let Some(container) = container.upgrade(cx) {
-                if container.window_id() == window_id {
+                if container.window() == window {
                     container.update(cx, |_, cx| cx.notify());
                 }
                 true

crates/editor/src/editor.rs 🔗

@@ -90,7 +90,7 @@ use std::{
     cmp::{self, Ordering, Reverse},
     mem,
     num::NonZeroU32,
-    ops::{ControlFlow, Deref, DerefMut, Range},
+    ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
     path::Path,
     sync::Arc,
     time::{Duration, Instant},
@@ -7549,6 +7549,78 @@ impl Editor {
         results
     }
 
+    pub fn background_highlight_row_ranges<T: 'static>(
+        &self,
+        search_range: Range<Anchor>,
+        display_snapshot: &DisplaySnapshot,
+        count: usize,
+    ) -> Vec<RangeInclusive<DisplayPoint>> {
+        let mut results = Vec::new();
+        let buffer = &display_snapshot.buffer_snapshot;
+        let Some((_, ranges)) = self.background_highlights
+            .get(&TypeId::of::<T>()) else {
+                return vec![];
+            };
+
+        let start_ix = match ranges.binary_search_by(|probe| {
+            let cmp = probe.end.cmp(&search_range.start, buffer);
+            if cmp.is_gt() {
+                Ordering::Greater
+            } else {
+                Ordering::Less
+            }
+        }) {
+            Ok(i) | Err(i) => i,
+        };
+        let mut push_region = |start: Option<Point>, end: Option<Point>| {
+            if let (Some(start_display), Some(end_display)) = (start, end) {
+                results.push(
+                    start_display.to_display_point(display_snapshot)
+                        ..=end_display.to_display_point(display_snapshot),
+                );
+            }
+        };
+        let mut start_row: Option<Point> = None;
+        let mut end_row: Option<Point> = None;
+        if ranges.len() > count {
+            return vec![];
+        }
+        for range in &ranges[start_ix..] {
+            if range.start.cmp(&search_range.end, buffer).is_ge() {
+                break;
+            }
+            let end = range.end.to_point(buffer);
+            if let Some(current_row) = &end_row {
+                if end.row == current_row.row {
+                    continue;
+                }
+            }
+            let start = range.start.to_point(buffer);
+
+            if start_row.is_none() {
+                assert_eq!(end_row, None);
+                start_row = Some(start);
+                end_row = Some(end);
+                continue;
+            }
+            if let Some(current_end) = end_row.as_mut() {
+                if start.row > current_end.row + 1 {
+                    push_region(start_row, end_row);
+                    start_row = Some(start);
+                    end_row = Some(end);
+                } else {
+                    // Merge two hunks.
+                    *current_end = end;
+                }
+            } else {
+                unreachable!();
+            }
+        }
+        // We might still have a hunk that was not rendered (if there was a search hit on the last line)
+        push_region(start_row, end_row);
+        results
+    }
+
     pub fn highlight_text<T: 'static>(
         &mut self,
         ranges: Vec<Range<Anchor>>,

crates/editor/src/editor_tests.rs 🔗

@@ -525,9 +525,8 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
     let project = Project::test(fs, [], cx).await;
     let window = cx.add_window(|cx| Workspace::test_new(project, cx));
     let workspace = window.root(cx);
-    let window_id = window.window_id();
     let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-    cx.add_view(window_id, |cx| {
+    window.add_view(cx, |cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
         let mut editor = build_editor(buffer.clone(), cx);
         let handle = cx.handle();
@@ -1290,7 +1289,8 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppCon
     let mut cx = EditorTestContext::new(cx).await;
 
     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-    cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
+    let window = cx.window;
+    window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
 
     cx.set_state(
         &r#"ˇone
@@ -1401,7 +1401,8 @@ async fn test_scroll_page_up_page_down(cx: &mut gpui::TestAppContext) {
     init_test(cx, |_| {});
     let mut cx = EditorTestContext::new(cx).await;
     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-    cx.simulate_window_resize(cx.window_id, vec2f(1000., 4. * line_height + 0.5));
+    let window = cx.window;
+    window.simulate_resize(vec2f(1000., 4. * line_height + 0.5), &mut cx);
 
     cx.set_state(
         &r#"ˇone
@@ -1439,7 +1440,8 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
     let mut cx = EditorTestContext::new(cx).await;
 
     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
-    cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
+    let window = cx.window;
+    window.simulate_resize(vec2f(100., 4. * line_height), &mut cx);
 
     cx.set_state(
         &r#"

crates/editor/src/element.rs 🔗

@@ -32,7 +32,7 @@ use gpui::{
     platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
     text_layout::{self, Line, RunStyle, TextLayoutCache},
     AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
-    MouseRegion, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
+    MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use json::json;
@@ -1107,8 +1107,6 @@ impl EditorElement {
             if layout.is_singleton && scrollbar_settings.selections {
                 let start_anchor = Anchor::min();
                 let end_anchor = Anchor::max();
-                let mut start_row = None;
-                let mut end_row = None;
                 let color = scrollbar_theme.selections;
                 let border = Border {
                     width: 1.,
@@ -1119,54 +1117,32 @@ impl EditorElement {
                     bottom: false,
                     left: true,
                 };
-                let mut push_region = |start, end| {
-                    if let (Some(start_display), Some(end_display)) = (start, end) {
-                        let start_y = y_for_row(start_display as f32);
-                        let mut end_y = y_for_row(end_display as f32);
-                        if end_y - start_y < 1. {
-                            end_y = start_y + 1.;
-                        }
-                        let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
-
-                        scene.push_quad(Quad {
-                            bounds,
-                            background: Some(color),
-                            border,
-                            corner_radius: style.thumb.corner_radius,
-                        })
+                let mut push_region = |start: DisplayPoint, end: DisplayPoint| {
+                    let start_y = y_for_row(start.row() as f32);
+                    let mut end_y = y_for_row(end.row() as f32);
+                    if end_y - start_y < 1. {
+                        end_y = start_y + 1.;
                     }
+                    let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
+
+                    scene.push_quad(Quad {
+                        bounds,
+                        background: Some(color),
+                        border,
+                        corner_radius: style.thumb.corner_radius,
+                    })
                 };
-                for (row, _) in &editor
-                    .background_highlights_in_range_for::<crate::items::BufferSearchHighlights>(
+                let background_ranges = editor
+                    .background_highlight_row_ranges::<crate::items::BufferSearchHighlights>(
                         start_anchor..end_anchor,
                         &layout.position_map.snapshot,
-                        &theme,
-                    )
-                {
-                    let start_display = row.start;
-                    let end_display = row.end;
-
-                    if start_row.is_none() {
-                        assert_eq!(end_row, None);
-                        start_row = Some(start_display.row());
-                        end_row = Some(end_display.row());
-                        continue;
-                    }
-                    if let Some(current_end) = end_row.as_mut() {
-                        if start_display.row() > *current_end + 1 {
-                            push_region(start_row, end_row);
-                            start_row = Some(start_display.row());
-                            end_row = Some(end_display.row());
-                        } else {
-                            // Merge two hunks.
-                            *current_end = end_display.row();
-                        }
-                    } else {
-                        unreachable!();
-                    }
+                        50000,
+                    );
+                for row in background_ranges {
+                    let start = row.start();
+                    let end = row.end();
+                    push_region(*start, *end);
                 }
-                // We might still have a hunk that was not rendered (if there was a search hit on the last line)
-                push_region(start_row, end_row);
             }
 
             if layout.is_singleton && scrollbar_settings.git_diff {
@@ -2479,7 +2455,7 @@ impl Element<Editor> for EditorElement {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         editor: &mut Editor,
-        cx: &mut ViewContext<Editor>,
+        cx: &mut PaintContext<Editor>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
         scene.push_layer(Some(visible_bounds));
@@ -3079,7 +3055,14 @@ mod tests {
         let mut scene = SceneBuilder::new(1.0);
         let bounds = RectF::new(Default::default(), size);
         editor.update(cx, |editor, cx| {
-            element.paint(&mut scene, bounds, bounds, &mut state, editor, cx);
+            element.paint(
+                &mut scene,
+                bounds,
+                bounds,
+                &mut state,
+                editor,
+                &mut PaintContext::new(cx),
+            );
         });
     }
 

crates/editor/src/test/editor_test_context.rs 🔗

@@ -3,7 +3,8 @@ use crate::{
 };
 use futures::Future;
 use gpui::{
-    keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
+    keymap_matcher::Keystroke, AnyWindowHandle, AppContext, ContextHandle, ModelContext,
+    ViewContext, ViewHandle,
 };
 use indoc::indoc;
 use language::{Buffer, BufferSnapshot};
@@ -21,7 +22,7 @@ use super::build_editor;
 
 pub struct EditorTestContext<'a> {
     pub cx: &'a mut gpui::TestAppContext,
-    pub window_id: usize,
+    pub window: AnyWindowHandle,
     pub editor: ViewHandle<Editor>,
 }
 
@@ -39,7 +40,7 @@ impl<'a> EditorTestContext<'a> {
         let editor = window.root(cx);
         Self {
             cx,
-            window_id: window.window_id(),
+            window: window.into(),
             editor,
         }
     }
@@ -111,7 +112,8 @@ impl<'a> EditorTestContext<'a> {
         let keystroke_under_test_handle =
             self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
         let keystroke = Keystroke::parse(keystroke_text).unwrap();
-        self.cx.dispatch_keystroke(self.window_id, keystroke, false);
+
+        self.cx.dispatch_keystroke(self.window, keystroke, false);
         keystroke_under_test_handle
     }
 

crates/file_finder/src/file_finder.rs 🔗

@@ -619,7 +619,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        cx.dispatch_action(window.window_id(), Toggle);
+        cx.dispatch_action(window.into(), Toggle);
 
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
         finder
@@ -632,8 +632,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.window_id(), SelectNext);
-        cx.dispatch_action(window.window_id(), Confirm);
+        cx.dispatch_action(window.into(), SelectNext);
+        cx.dispatch_action(window.into(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -674,7 +674,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        cx.dispatch_action(window.window_id(), Toggle);
+        cx.dispatch_action(window.into(), Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
 
         let file_query = &first_file_name[..3];
@@ -706,8 +706,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.window_id(), SelectNext);
-        cx.dispatch_action(window.window_id(), Confirm);
+        cx.dispatch_action(window.into(), SelectNext);
+        cx.dispatch_action(window.into(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -758,7 +758,7 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        cx.dispatch_action(window.window_id(), Toggle);
+        cx.dispatch_action(window.into(), Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
 
         let file_query = &first_file_name[..3];
@@ -790,8 +790,8 @@ mod tests {
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
-        cx.dispatch_action(window.window_id(), SelectNext);
-        cx.dispatch_action(window.window_id(), Confirm);
+        cx.dispatch_action(window.into(), SelectNext);
+        cx.dispatch_action(window.into(), Confirm);
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())
             .await;
@@ -1168,7 +1168,6 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
@@ -1186,7 +1185,7 @@ mod tests {
             "fir",
             1,
             "first.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1201,7 +1200,7 @@ mod tests {
             "sec",
             1,
             "second.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1223,7 +1222,7 @@ mod tests {
             "thi",
             1,
             "third.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1255,7 +1254,7 @@ mod tests {
             "sec",
             1,
             "second.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1294,7 +1293,7 @@ mod tests {
             "thi",
             1,
             "third.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1376,7 +1375,6 @@ mod tests {
 
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let worktree_id = cx.read(|cx| {
             let worktrees = workspace.read(cx).worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1,);
@@ -1411,7 +1409,7 @@ mod tests {
             "sec",
             1,
             "second.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1433,7 +1431,7 @@ mod tests {
             "fir",
             1,
             "first.rs",
-            window_id,
+            window.into(),
             &workspace,
             &deterministic,
             cx,
@@ -1465,12 +1463,12 @@ mod tests {
         input: &str,
         expected_matches: usize,
         expected_editor_title: &str,
-        window_id: usize,
+        window: gpui::AnyWindowHandle,
         workspace: &ViewHandle<Workspace>,
         deterministic: &gpui::executor::Deterministic,
         cx: &mut gpui::TestAppContext,
     ) -> Vec<FoundPath> {
-        cx.dispatch_action(window_id, Toggle);
+        cx.dispatch_action(window, Toggle);
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
         finder
             .update(cx, |finder, cx| {
@@ -1487,8 +1485,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, SelectNext);
+        cx.dispatch_action(window, Confirm);
         deterministic.run_until_parked();
         active_pane
             .condition(cx, |pane, _| pane.active_item().is_some())

crates/go_to_line/src/go_to_line.rs 🔗

@@ -135,7 +135,7 @@ impl Entity for GoToLine {
 
     fn release(&mut self, cx: &mut AppContext) {
         let scroll_position = self.prev_scroll_position.take();
-        cx.update_window(self.active_editor.window_id(), |cx| {
+        self.active_editor.window().update(cx, |cx| {
             self.active_editor.update(cx, |editor, cx| {
                 editor.highlight_rows(None);
                 if let Some(scroll_position) = scroll_position {

crates/gpui/Cargo.toml 🔗

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

crates/gpui/src/app.rs 🔗

@@ -23,6 +23,8 @@ use std::{
 };
 
 use anyhow::{anyhow, Context, Result};
+
+use derive_more::Deref;
 use parking_lot::Mutex;
 use postage::oneshot;
 use smallvec::SmallVec;
@@ -44,6 +46,7 @@ use window_input_handler::WindowInputHandler;
 use crate::{
     elements::{AnyElement, AnyRootElement, RootElement},
     executor::{self, Task},
+    fonts::TextStyle,
     json,
     keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
     platform::{
@@ -132,12 +135,18 @@ pub trait BorrowAppContext {
 pub trait BorrowWindowContext {
     type Result<T>;
 
-    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    fn read_window<T, F>(&self, window: AnyWindowHandle, 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>
+    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>;
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T;
+    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>;
 }
 
 #[derive(Clone)]
@@ -300,13 +309,12 @@ impl App {
         result
     }
 
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        callback: F,
-    ) -> Option<T> {
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, callback: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> T,
+    {
         let mut state = self.0.borrow_mut();
-        let result = state.update_window(window_id, callback);
+        let result = state.update_window(window, callback);
         state.pending_notifications.clear();
         result
     }
@@ -333,67 +341,8 @@ impl AsyncAppContext {
         self.0.borrow_mut().update(callback)
     }
 
-    pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
-        &self,
-        window_id: usize,
-        callback: F,
-    ) -> Option<T> {
-        self.0.borrow_mut().read_window(window_id, callback)
-    }
-
-    pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        callback: F,
-    ) -> Option<T> {
-        self.0.borrow_mut().update_window(window_id, callback)
-    }
-
-    pub fn debug_elements(&self, window_id: usize) -> Option<json::Value> {
-        self.0.borrow().read_window(window_id, |cx| {
-            let root_view = cx.window.root_view();
-            let root_element = cx.window.rendered_views.get(&root_view.id())?;
-            root_element.debug(cx).log_err()
-        })?
-    }
-
-    pub fn dispatch_action(
-        &mut self,
-        window_id: usize,
-        view_id: usize,
-        action: &dyn Action,
-    ) -> Result<()> {
-        self.0
-            .borrow_mut()
-            .update_window(window_id, |window| {
-                window.dispatch_action(Some(view_id), action);
-            })
-            .ok_or_else(|| anyhow!("window not found"))
-    }
-
-    pub fn available_actions(
-        &self,
-        window_id: usize,
-        view_id: usize,
-    ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
-        self.read_window(window_id, |cx| cx.available_actions(view_id))
-            .unwrap_or_default()
-    }
-
-    pub fn has_window(&self, window_id: usize) -> bool {
-        self.read(|cx| cx.windows.contains_key(&window_id))
-    }
-
-    pub fn window_is_active(&self, window_id: usize) -> bool {
-        self.read(|cx| cx.windows.get(&window_id).map_or(false, |w| w.is_active))
-    }
-
-    pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> {
-        self.read(|cx| cx.windows.get(&window_id).map(|w| w.root_view().clone()))
-    }
-
-    pub fn window_ids(&self) -> Vec<usize> {
-        self.read(|cx| cx.windows.keys().copied().collect())
+    pub fn windows(&self) -> Vec<AnyWindowHandle> {
+        self.0.borrow().windows().collect()
     }
 
     pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
@@ -416,25 +365,6 @@ impl AsyncAppContext {
         self.update(|cx| cx.add_window(window_options, build_root_view))
     }
 
-    pub fn remove_window(&mut self, window_id: usize) {
-        self.update_window(window_id, |cx| cx.remove_window());
-    }
-
-    pub fn activate_window(&mut self, window_id: usize) {
-        self.update_window(window_id, |cx| cx.activate_window());
-    }
-
-    // TODO: Can we eliminate this method and move it to WindowContext then call it with update_window?s
-    pub fn prompt(
-        &mut self,
-        window_id: usize,
-        level: PromptLevel,
-        msg: &str,
-        answers: &[&str],
-    ) -> Option<oneshot::Receiver<usize>> {
-        self.update_window(window_id, |cx| cx.prompt(level, msg, answers))
-    }
-
     pub fn platform(&self) -> Arc<dyn Platform> {
         self.0.borrow().platform().clone()
     }
@@ -461,20 +391,36 @@ impl BorrowAppContext for AsyncAppContext {
 impl BorrowWindowContext for AsyncAppContext {
     type Result<T> = Option<T>;
 
-    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
     where
         F: FnOnce(&WindowContext) -> T,
     {
-        self.0.borrow().read_with(|cx| cx.read_window(window_id, f))
+        self.0.borrow().read_with(|cx| cx.read_window(window, f))
+    }
+
+    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        self.0
+            .borrow_mut()
+            .update(|cx| cx.read_window_optional(window, f))
     }
 
-    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T,
+    {
+        self.0.borrow_mut().update(|cx| cx.update_window(window, f))
+    }
+
+    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
     {
         self.0
             .borrow_mut()
-            .update(|cx| cx.update_window(window_id, f))
+            .update(|cx| cx.update_window_optional(window, f))
     }
 }
 
@@ -499,9 +445,9 @@ type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> b
 
 pub struct AppContext {
     models: HashMap<usize, Box<dyn AnyModel>>,
-    views: HashMap<(usize, usize), Box<dyn AnyView>>,
-    views_metadata: HashMap<(usize, usize), ViewMetadata>,
-    windows: HashMap<usize, Window>,
+    views: HashMap<(AnyWindowHandle, usize), Box<dyn AnyView>>,
+    views_metadata: HashMap<(AnyWindowHandle, usize), ViewMetadata>,
+    windows: HashMap<AnyWindowHandle, Window>,
     globals: HashMap<TypeId, Box<dyn Any>>,
     element_states: HashMap<ElementStateId, Box<dyn Any>>,
     background: Arc<executor::Background>,
@@ -521,7 +467,7 @@ pub struct AppContext {
     global_actions: HashMap<TypeId, Box<GlobalActionCallback>>,
     keystroke_matcher: KeymapMatcher,
     next_id: usize,
-    // next_window_id: usize,
+    // next_window: AnyWindowHandle,
     next_subscription_id: usize,
     frame_count: usize,
 
@@ -532,10 +478,10 @@ pub struct AppContext {
     focus_observations: CallbackCollection<usize, FocusObservationCallback>,
     release_observations: CallbackCollection<usize, ReleaseObservationCallback>,
     action_dispatch_observations: CallbackCollection<(), ActionObservationCallback>,
-    window_activation_observations: CallbackCollection<usize, WindowActivationCallback>,
-    window_fullscreen_observations: CallbackCollection<usize, WindowFullscreenCallback>,
-    window_bounds_observations: CallbackCollection<usize, WindowBoundsCallback>,
-    keystroke_observations: CallbackCollection<usize, KeystrokeCallback>,
+    window_activation_observations: CallbackCollection<AnyWindowHandle, WindowActivationCallback>,
+    window_fullscreen_observations: CallbackCollection<AnyWindowHandle, WindowFullscreenCallback>,
+    window_bounds_observations: CallbackCollection<AnyWindowHandle, WindowBoundsCallback>,
+    keystroke_observations: CallbackCollection<AnyWindowHandle, KeystrokeCallback>,
     active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
 
     foreground: Rc<executor::Foreground>,
@@ -781,13 +727,13 @@ impl AppContext {
         }
     }
 
-    pub fn view_ui_name(&self, window_id: usize, view_id: usize) -> Option<&'static str> {
-        Some(self.views.get(&(window_id, view_id))?.ui_name())
+    pub fn view_ui_name(&self, window: AnyWindowHandle, view_id: usize) -> Option<&'static str> {
+        Some(self.views.get(&(window, view_id))?.ui_name())
     }
 
-    pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option<TypeId> {
+    pub fn view_type_id(&self, window: AnyWindowHandle, view_id: usize) -> Option<TypeId> {
         self.views_metadata
-            .get(&(window_id, view_id))
+            .get(&(window, view_id))
             .map(|metadata| metadata.type_id)
     }
 
@@ -810,37 +756,20 @@ impl AppContext {
 
     fn read_window<T, F: FnOnce(&WindowContext) -> T>(
         &self,
-        window_id: usize,
+        handle: AnyWindowHandle,
         callback: F,
     ) -> Option<T> {
-        let window = self.windows.get(&window_id)?;
-        let window_context = WindowContext::immutable(self, &window, window_id);
+        let window = self.windows.get(&handle)?;
+        let window_context = WindowContext::immutable(self, &window, handle);
         Some(callback(&window_context))
     }
 
-    pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        callback: F,
-    ) -> Option<T> {
-        self.update(|app_context| {
-            let mut window = app_context.windows.remove(&window_id)?;
-            let mut window_context = WindowContext::mutable(app_context, &mut window, window_id);
-            let result = callback(&mut window_context);
-            if !window_context.removed {
-                app_context.windows.insert(window_id, window);
-            }
-            Some(result)
-        })
-    }
-
     pub fn update_active_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
         callback: F,
     ) -> Option<T> {
-        self.platform
-            .main_window_id()
-            .and_then(|id| self.update_window(id, callback))
+        self.active_window()
+            .and_then(|window| window.update(self, callback))
     }
 
     pub fn prompt_for_paths(
@@ -1078,10 +1007,10 @@ impl AppContext {
         }
     }
 
-    fn notify_view(&mut self, window_id: usize, view_id: usize) {
+    fn notify_view(&mut self, window: AnyWindowHandle, view_id: usize) {
         if self.pending_notifications.insert(view_id) {
             self.pending_effects
-                .push_back(Effect::ViewNotification { window_id, view_id });
+                .push_back(Effect::ViewNotification { window, view_id });
         }
     }
 
@@ -1099,13 +1028,13 @@ impl AppContext {
     pub fn is_action_available(&self, action: &dyn Action) -> bool {
         let mut available_in_window = false;
         let action_id = action.id();
-        if let Some(window_id) = self.platform.main_window_id() {
+        if let Some(window) = self.active_window() {
             available_in_window = self
-                .read_window(window_id, |cx| {
+                .read_window(window, |cx| {
                     if let Some(focused_view_id) = cx.focused_view_id() {
                         for view_id in cx.ancestors(focused_view_id) {
                             if let Some(view_metadata) =
-                                cx.views_metadata.get(&(window_id, view_id))
+                                cx.views_metadata.get(&(cx.window_handle, view_id))
                             {
                                 if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
                                     if actions.contains_key(&action_id) {
@@ -1331,39 +1260,34 @@ impl AppContext {
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window_id = post_inc(&mut this.next_id);
+            let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
             let platform_window =
                 this.platform
-                    .open_window(window_id, window_options, this.foreground.clone());
-            let window = this.build_window(window_id, platform_window, build_root_view);
-            this.windows.insert(window_id, window);
-            WindowHandle::new(window_id)
+                    .open_window(handle.into(), window_options, this.foreground.clone());
+            let window = this.build_window(handle.into(), platform_window, build_root_view);
+            this.windows.insert(handle.into(), window);
+            handle
         })
     }
 
-    pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<V>)
+    pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
     where
         V: View,
         F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
-            let window_id = post_inc(&mut this.next_id);
-            let platform_window = this.platform.add_status_item(window_id);
-            let window = this.build_window(window_id, platform_window, build_root_view);
-            let root_view = window.root_view().clone().downcast::<V>().unwrap();
-
-            this.windows.insert(window_id, window);
-            this.update_window(window_id, |cx| {
-                root_view.update(cx, |view, cx| view.focus_in(cx.handle().into_any(), cx))
-            });
-
-            (window_id, root_view)
+            let handle = WindowHandle::<V>::new(post_inc(&mut this.next_id));
+            let platform_window = this.platform.add_status_item(handle.into());
+            let window = this.build_window(handle.into(), platform_window, build_root_view);
+            this.windows.insert(handle.into(), window);
+            handle.update_root(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
+            handle
         })
     }
 
     pub fn build_window<V, F>(
         &mut self,
-        window_id: usize,
+        handle: AnyWindowHandle,
         mut platform_window: Box<dyn platform::Window>,
         build_root_view: F,
     ) -> Window
@@ -1375,7 +1299,7 @@ impl AppContext {
             let mut app = self.upgrade();
 
             platform_window.on_event(Box::new(move |event| {
-                app.update_window(window_id, |cx| {
+                app.update_window(handle, |cx| {
                     if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
                         if cx.dispatch_keystroke(keystroke) {
                             return true;
@@ -1391,35 +1315,35 @@ impl AppContext {
         {
             let mut app = self.upgrade();
             platform_window.on_active_status_change(Box::new(move |is_active| {
-                app.update(|cx| cx.window_changed_active_status(window_id, is_active))
+                app.update(|cx| cx.window_changed_active_status(handle, is_active))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_resize(Box::new(move || {
-                app.update(|cx| cx.window_was_resized(window_id))
+                app.update(|cx| cx.window_was_resized(handle))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_moved(Box::new(move || {
-                app.update(|cx| cx.window_was_moved(window_id))
+                app.update(|cx| cx.window_was_moved(handle))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_fullscreen(Box::new(move |is_fullscreen| {
-                app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
+                app.update(|cx| cx.window_was_fullscreen_changed(handle, is_fullscreen))
             }));
         }
 
         {
             let mut app = self.upgrade();
             platform_window.on_close(Box::new(move || {
-                app.update(|cx| cx.update_window(window_id, |cx| cx.remove_window()));
+                app.update(|cx| cx.update_window(handle, |cx| cx.remove_window()));
             }));
         }
 
@@ -1431,31 +1355,27 @@ impl AppContext {
 
         platform_window.set_input_handler(Box::new(WindowInputHandler {
             app: self.upgrade().0,
-            window_id,
+            window: handle,
         }));
 
-        let mut window = Window::new(window_id, platform_window, self, build_root_view);
-        let mut cx = WindowContext::mutable(self, &mut window, window_id);
+        let mut window = Window::new(handle, platform_window, self, build_root_view);
+        let mut cx = WindowContext::mutable(self, &mut window, handle);
         cx.layout(false).expect("initial layout should not error");
         let scene = cx.paint().expect("initial paint should not error");
         window.platform_window.present_scene(scene);
         window
     }
 
-    pub fn replace_root_view<V, F>(
-        &mut self,
-        window_id: usize,
-        build_root_view: F,
-    ) -> Option<WindowHandle<V>>
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        self.update_window(window_id, |cx| cx.replace_root_view(build_root_view))
+    pub fn active_window(&self) -> Option<AnyWindowHandle> {
+        self.platform.main_window()
+    }
+
+    pub fn windows(&self) -> impl '_ + Iterator<Item = AnyWindowHandle> {
+        self.windows.keys().copied()
     }
 
     pub fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
-        if let Some(view) = self.views.get(&(handle.window_id, handle.view_id)) {
+        if let Some(view) = self.views.get(&(handle.window, handle.view_id)) {
             view.as_any().downcast_ref().expect("downcast is type safe")
         } else {
             panic!("circular view reference for type {}", type_name::<T>());
@@ -1465,7 +1385,7 @@ impl AppContext {
     fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
         if self.ref_counts.lock().is_entity_alive(handle.view_id) {
             Some(ViewHandle::new(
-                handle.window_id,
+                handle.window,
                 handle.view_id,
                 &self.ref_counts,
             ))
@@ -1477,7 +1397,7 @@ impl AppContext {
     fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
         if self.ref_counts.lock().is_entity_alive(handle.view_id) {
             Some(AnyViewHandle::new(
-                handle.window_id,
+                handle.window,
                 handle.view_id,
                 handle.view_type,
                 self.ref_counts.clone(),
@@ -1507,13 +1427,13 @@ impl AppContext {
                     .push_back(Effect::ModelRelease { model_id, model });
             }
 
-            for (window_id, view_id) in dropped_views {
+            for (window, view_id) in dropped_views {
                 self.subscriptions.remove(view_id);
                 self.observations.remove(view_id);
-                self.views_metadata.remove(&(window_id, view_id));
-                let mut view = self.views.remove(&(window_id, view_id)).unwrap();
+                self.views_metadata.remove(&(window, view_id));
+                let mut view = self.views.remove(&(window, view_id)).unwrap();
                 view.release(self);
-                if let Some(window) = self.windows.get_mut(&window_id) {
+                if let Some(window) = self.windows.get_mut(&window) {
                     window.parents.remove(&view_id);
                     window
                         .invalidation
@@ -1541,7 +1461,7 @@ impl AppContext {
 
             let mut refreshing = false;
             let mut updated_windows = HashSet::default();
-            let mut focus_effects = HashMap::<usize, FocusEffect>::default();
+            let mut focus_effects = HashMap::<AnyWindowHandle, FocusEffect>::default();
             loop {
                 self.remove_dropped_entities();
                 if let Some(effect) = self.pending_effects.pop_front() {
@@ -1585,9 +1505,10 @@ impl AppContext {
                             observations.emit(model_id, |callback| callback(self));
                         }
 
-                        Effect::ViewNotification { window_id, view_id } => {
-                            self.handle_view_notification_effect(window_id, view_id)
-                        }
+                        Effect::ViewNotification {
+                            window: window_id,
+                            view_id,
+                        } => self.handle_view_notification_effect(window_id, view_id),
 
                         Effect::GlobalNotification { type_id } => {
                             let mut subscriptions = self.global_observations.clone();
@@ -1618,13 +1539,13 @@ impl AppContext {
 
                         Effect::Focus(mut effect) => {
                             if focus_effects
-                                .get(&effect.window_id())
+                                .get(&effect.window())
                                 .map_or(false, |prev_effect| prev_effect.is_forced())
                             {
                                 effect.force();
                             }
 
-                            focus_effects.insert(effect.window_id(), effect);
+                            focus_effects.insert(effect.window(), effect);
                         }
 
                         Effect::FocusObservation {
@@ -1639,42 +1560,38 @@ impl AppContext {
                             );
                         }
 
-                        Effect::ResizeWindow { window_id } => {
-                            if let Some(window) = self.windows.get_mut(&window_id) {
+                        Effect::ResizeWindow { window } => {
+                            if let Some(window) = self.windows.get_mut(&window) {
                                 window
                                     .invalidation
                                     .get_or_insert(WindowInvalidation::default());
                             }
-                            self.handle_window_moved(window_id);
+                            self.handle_window_moved(window);
                         }
 
-                        Effect::MoveWindow { window_id } => {
-                            self.handle_window_moved(window_id);
+                        Effect::MoveWindow { window } => {
+                            self.handle_window_moved(window);
                         }
 
                         Effect::WindowActivationObservation {
-                            window_id,
+                            window,
                             subscription_id,
                             callback,
                         } => self.window_activation_observations.add_callback(
-                            window_id,
+                            window,
                             subscription_id,
                             callback,
                         ),
 
-                        Effect::ActivateWindow {
-                            window_id,
-                            is_active,
-                        } => {
-                            if self.handle_window_activation_effect(window_id, is_active)
-                                && is_active
+                        Effect::ActivateWindow { window, is_active } => {
+                            if self.handle_window_activation_effect(window, is_active) && is_active
                             {
                                 focus_effects
-                                    .entry(window_id)
+                                    .entry(window)
                                     .or_insert_with(|| FocusEffect::View {
-                                        window_id,
+                                        window,
                                         view_id: self
-                                            .read_window(window_id, |cx| cx.focused_view_id())
+                                            .read_window(window, |cx| cx.focused_view_id())
                                             .flatten(),
                                         is_forced: true,
                                     })
@@ -1683,26 +1600,26 @@ impl AppContext {
                         }
 
                         Effect::WindowFullscreenObservation {
-                            window_id,
+                            window,
                             subscription_id,
                             callback,
                         } => self.window_fullscreen_observations.add_callback(
-                            window_id,
+                            window,
                             subscription_id,
                             callback,
                         ),
 
                         Effect::FullscreenWindow {
-                            window_id,
+                            window,
                             is_fullscreen,
-                        } => self.handle_fullscreen_effect(window_id, is_fullscreen),
+                        } => self.handle_fullscreen_effect(window, is_fullscreen),
 
                         Effect::WindowBoundsObservation {
-                            window_id,
+                            window,
                             subscription_id,
                             callback,
                         } => self.window_bounds_observations.add_callback(
-                            window_id,
+                            window,
                             subscription_id,
                             callback,
                         ),
@@ -1714,18 +1631,15 @@ impl AppContext {
                         Effect::ActionDispatchNotification { action_id } => {
                             self.handle_action_dispatch_notification_effect(action_id)
                         }
-                        Effect::WindowShouldCloseSubscription {
-                            window_id,
-                            callback,
-                        } => {
-                            self.handle_window_should_close_subscription_effect(window_id, callback)
+                        Effect::WindowShouldCloseSubscription { window, callback } => {
+                            self.handle_window_should_close_subscription_effect(window, callback)
                         }
                         Effect::Keystroke {
-                            window_id,
+                            window,
                             keystroke,
                             handled_by,
                             result,
-                        } => self.handle_keystroke_effect(window_id, keystroke, handled_by, result),
+                        } => self.handle_keystroke_effect(window, keystroke, handled_by, result),
                         Effect::ActiveLabeledTasksChanged => {
                             self.handle_active_labeled_tasks_changed_effect()
                         }
@@ -1740,8 +1654,8 @@ impl AppContext {
                     }
                     self.pending_notifications.clear();
                 } else {
-                    for window_id in self.windows.keys().cloned().collect::<Vec<_>>() {
-                        self.update_window(window_id, |cx| {
+                    for window in self.windows().collect::<Vec<_>>() {
+                        self.update_window(window, |cx| {
                             let invalidation = if refreshing {
                                 let mut invalidation =
                                     cx.window.invalidation.take().unwrap_or_default();
@@ -1757,7 +1671,7 @@ impl AppContext {
                                 let appearance = cx.window.platform_window.appearance();
                                 cx.invalidate(invalidation, appearance);
                                 if let Some(old_parents) = cx.layout(refreshing).log_err() {
-                                    updated_windows.insert(window_id);
+                                    updated_windows.insert(window);
 
                                     if let Some(focused_view_id) = cx.focused_view_id() {
                                         let old_ancestors = std::iter::successors(
@@ -1772,15 +1686,14 @@ impl AppContext {
                                         for old_ancestor in old_ancestors.iter().copied() {
                                             if !new_ancestors.contains(&old_ancestor) {
                                                 if let Some(mut view) =
-                                                    cx.views.remove(&(window_id, old_ancestor))
+                                                    cx.views.remove(&(window, old_ancestor))
                                                 {
                                                     view.focus_out(
                                                         focused_view_id,
                                                         cx,
                                                         old_ancestor,
                                                     );
-                                                    cx.views
-                                                        .insert((window_id, old_ancestor), view);
+                                                    cx.views.insert((window, old_ancestor), view);
                                                 }
                                             }
                                         }
@@ -1789,15 +1702,14 @@ impl AppContext {
                                         for new_ancestor in new_ancestors.iter().copied() {
                                             if !old_ancestors.contains(&new_ancestor) {
                                                 if let Some(mut view) =
-                                                    cx.views.remove(&(window_id, new_ancestor))
+                                                    cx.views.remove(&(window, new_ancestor))
                                                 {
                                                     view.focus_in(
                                                         focused_view_id,
                                                         cx,
                                                         new_ancestor,
                                                     );
-                                                    cx.views
-                                                        .insert((window_id, new_ancestor), view);
+                                                    cx.views.insert((window, new_ancestor), view);
                                                 }
                                             }
                                         }
@@ -1806,13 +1718,13 @@ impl AppContext {
                                         // there isn't any pending focus, focus the root view.
                                         let root_view_id = cx.window.root_view().id();
                                         if focused_view_id != root_view_id
-                                            && !cx.views.contains_key(&(window_id, focused_view_id))
-                                            && !focus_effects.contains_key(&window_id)
+                                            && !cx.views.contains_key(&(window, focused_view_id))
+                                            && !focus_effects.contains_key(&window)
                                         {
                                             focus_effects.insert(
-                                                window_id,
+                                                window,
                                                 FocusEffect::View {
-                                                    window_id,
+                                                    window,
                                                     view_id: Some(root_view_id),
                                                     is_forced: false,
                                                 },
@@ -1833,8 +1745,8 @@ impl AppContext {
                             callback(self);
                         }
 
-                        for window_id in updated_windows.drain() {
-                            self.update_window(window_id, |cx| {
+                        for window in updated_windows.drain() {
+                            self.update_window(window, |cx| {
                                 if let Some(scene) = cx.paint().log_err() {
                                     cx.window.platform_window.present_scene(scene);
                                 }
@@ -1855,39 +1767,37 @@ impl AppContext {
         }
     }
 
-    fn window_was_resized(&mut self, window_id: usize) {
+    fn window_was_resized(&mut self, window: AnyWindowHandle) {
         self.pending_effects
-            .push_back(Effect::ResizeWindow { window_id });
+            .push_back(Effect::ResizeWindow { window });
     }
 
-    fn window_was_moved(&mut self, window_id: usize) {
+    fn window_was_moved(&mut self, window: AnyWindowHandle) {
         self.pending_effects
-            .push_back(Effect::MoveWindow { window_id });
+            .push_back(Effect::MoveWindow { window });
     }
 
-    fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) {
+    fn window_was_fullscreen_changed(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
         self.pending_effects.push_back(Effect::FullscreenWindow {
-            window_id,
+            window,
             is_fullscreen,
         });
     }
 
-    fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) {
-        self.pending_effects.push_back(Effect::ActivateWindow {
-            window_id,
-            is_active,
-        });
+    fn window_changed_active_status(&mut self, window: AnyWindowHandle, is_active: bool) {
+        self.pending_effects
+            .push_back(Effect::ActivateWindow { window, is_active });
     }
 
     fn keystroke(
         &mut self,
-        window_id: usize,
+        window: AnyWindowHandle,
         keystroke: Keystroke,
         handled_by: Option<Box<dyn Action>>,
         result: MatchResult,
     ) {
         self.pending_effects.push_back(Effect::Keystroke {
-            window_id,
+            window,
             keystroke,
             handled_by,
             result,
@@ -1910,16 +1820,16 @@ impl AppContext {
 
     fn handle_view_notification_effect(
         &mut self,
-        observed_window_id: usize,
+        observed_window: AnyWindowHandle,
         observed_view_id: usize,
     ) {
-        let view_key = (observed_window_id, observed_view_id);
+        let view_key = (observed_window, observed_view_id);
         if let Some((view, mut view_metadata)) = self
             .views
             .remove(&view_key)
             .zip(self.views_metadata.remove(&view_key))
         {
-            if let Some(window) = self.windows.get_mut(&observed_window_id) {
+            if let Some(window) = self.windows.get_mut(&observed_window) {
                 window
                     .invalidation
                     .get_or_insert_with(Default::default)
@@ -1946,17 +1856,17 @@ impl AppContext {
             })
     }
 
-    fn handle_fullscreen_effect(&mut self, window_id: usize, is_fullscreen: bool) {
-        self.update_window(window_id, |cx| {
+    fn handle_fullscreen_effect(&mut self, window: AnyWindowHandle, is_fullscreen: bool) {
+        self.update_window(window, |cx| {
             cx.window.is_fullscreen = is_fullscreen;
 
             let mut fullscreen_observations = cx.window_fullscreen_observations.clone();
-            fullscreen_observations.emit(window_id, |callback| callback(is_fullscreen, cx));
+            fullscreen_observations.emit(window, |callback| callback(is_fullscreen, cx));
 
             if let Some(uuid) = cx.window_display_uuid() {
                 let bounds = cx.window_bounds();
                 let mut bounds_observations = cx.window_bounds_observations.clone();
-                bounds_observations.emit(window_id, |callback| callback(bounds, uuid, cx));
+                bounds_observations.emit(window, |callback| callback(bounds, uuid, cx));
             }
 
             Some(())
@@ -1965,42 +1875,42 @@ impl AppContext {
 
     fn handle_keystroke_effect(
         &mut self,
-        window_id: usize,
+        window: AnyWindowHandle,
         keystroke: Keystroke,
         handled_by: Option<Box<dyn Action>>,
         result: MatchResult,
     ) {
-        self.update_window(window_id, |cx| {
+        self.update_window(window, |cx| {
             let mut observations = cx.keystroke_observations.clone();
-            observations.emit(window_id, move |callback| {
+            observations.emit(window, move |callback| {
                 callback(&keystroke, &result, handled_by.as_ref(), cx)
             });
         });
     }
 
-    fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) -> bool {
-        self.update_window(window_id, |cx| {
+    fn handle_window_activation_effect(&mut self, window: AnyWindowHandle, active: bool) -> bool {
+        self.update_window(window, |cx| {
             if cx.window.is_active == active {
                 return false;
             }
             cx.window.is_active = active;
 
             let mut observations = cx.window_activation_observations.clone();
-            observations.emit(window_id, |callback| callback(active, cx));
+            observations.emit(window, |callback| callback(active, cx));
             true
         })
         .unwrap_or(false)
     }
 
     fn handle_focus_effect(&mut self, effect: FocusEffect) {
-        let window_id = effect.window_id();
-        self.update_window(window_id, |cx| {
+        let window = effect.window();
+        self.update_window(window, |cx| {
             // Ensure the newly-focused view still exists, otherwise focus
             // the root view instead.
             let focused_id = match effect {
                 FocusEffect::View { view_id, .. } => {
                     if let Some(view_id) = view_id {
-                        if cx.views.contains_key(&(window_id, view_id)) {
+                        if cx.views.contains_key(&(window, view_id)) {
                             Some(view_id)
                         } else {
                             Some(cx.root_view().id())
@@ -2025,9 +1935,9 @@ impl AppContext {
             if focus_changed {
                 if let Some(blurred_id) = blurred_id {
                     for view_id in cx.ancestors(blurred_id).collect::<Vec<_>>() {
-                        if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+                        if let Some(mut view) = cx.views.remove(&(window, view_id)) {
                             view.focus_out(blurred_id, cx, view_id);
-                            cx.views.insert((window_id, view_id), view);
+                            cx.views.insert((window, view_id), view);
                         }
                     }
 
@@ -2039,9 +1949,9 @@ impl AppContext {
             if focus_changed || effect.is_forced() {
                 if let Some(focused_id) = focused_id {
                     for view_id in cx.ancestors(focused_id).collect::<Vec<_>>() {
-                        if let Some(mut view) = cx.views.remove(&(window_id, view_id)) {
+                        if let Some(mut view) = cx.views.remove(&(window, view_id)) {
                             view.focus_in(focused_id, cx, view_id);
-                            cx.views.insert((window_id, view_id), view);
+                            cx.views.insert((window, view_id), view);
                         }
                     }
 
@@ -2063,24 +1973,24 @@ impl AppContext {
 
     fn handle_window_should_close_subscription_effect(
         &mut self,
-        window_id: usize,
+        window: AnyWindowHandle,
         mut callback: WindowShouldCloseSubscriptionCallback,
     ) {
         let mut app = self.upgrade();
-        if let Some(window) = self.windows.get_mut(&window_id) {
+        if let Some(window) = self.windows.get_mut(&window) {
             window
                 .platform_window
                 .on_should_close(Box::new(move || app.update(|cx| callback(cx))))
         }
     }
 
-    fn handle_window_moved(&mut self, window_id: usize) {
-        self.update_window(window_id, |cx| {
+    fn handle_window_moved(&mut self, window: AnyWindowHandle) {
+        self.update_window(window, |cx| {
             if let Some(display) = cx.window_display_uuid() {
                 let bounds = cx.window_bounds();
                 cx.window_bounds_observations
                     .clone()
-                    .emit(window_id, move |callback| {
+                    .emit(window, move |callback| {
                         callback(bounds, display, cx);
                         true
                     });
@@ -2097,10 +2007,10 @@ impl AppContext {
             });
     }
 
-    pub fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
+    pub fn focus(&mut self, window: AnyWindowHandle, view_id: Option<usize>) {
         self.pending_effects
             .push_back(Effect::Focus(FocusEffect::View {
-                window_id,
+                window,
                 view_id,
                 is_forced: false,
             }));
@@ -2185,18 +2095,40 @@ impl BorrowAppContext for AppContext {
 impl BorrowWindowContext for AppContext {
     type Result<T> = Option<T>;
 
-    fn read_window_with<T, F>(&self, window_id: usize, f: F) -> Self::Result<T>
+    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
     where
         F: FnOnce(&WindowContext) -> T,
     {
-        AppContext::read_window(self, window_id, f)
+        AppContext::read_window(self, window, f)
+    }
+
+    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        AppContext::read_window(self, window, f).flatten()
     }
 
-    fn update_window<T, F>(&mut self, window_id: usize, f: F) -> Self::Result<T>
+    fn update_window<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Self::Result<T>
     where
         F: FnOnce(&mut WindowContext) -> T,
     {
-        AppContext::update_window(self, window_id, f)
+        self.update(|cx| {
+            let mut window = cx.windows.remove(&handle)?;
+            let mut window_context = WindowContext::mutable(cx, &mut window, handle);
+            let result = f(&mut window_context);
+            if !window_context.removed {
+                cx.windows.insert(handle, window);
+            }
+            Some(result)
+        })
+    }
+
+    fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        AppContext::update_window(self, handle, f).flatten()
     }
 }
 
@@ -2220,22 +2152,22 @@ pub struct WindowInvalidation {
 #[derive(Debug)]
 pub enum FocusEffect {
     View {
-        window_id: usize,
+        window: AnyWindowHandle,
         view_id: Option<usize>,
         is_forced: bool,
     },
     ViewParent {
-        window_id: usize,
+        window: AnyWindowHandle,
         view_id: usize,
         is_forced: bool,
     },
 }
 
 impl FocusEffect {
-    fn window_id(&self) -> usize {
+    fn window(&self) -> AnyWindowHandle {
         match self {
-            FocusEffect::View { window_id, .. } => *window_id,
-            FocusEffect::ViewParent { window_id, .. } => *window_id,
+            FocusEffect::View { window, .. } => *window,
+            FocusEffect::ViewParent { window, .. } => *window,
         }
     }
 

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

@@ -77,9 +77,9 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
         let cx = app.0.clone();
         move |action| {
             let mut cx = cx.borrow_mut();
-            if let Some(main_window_id) = cx.platform.main_window_id() {
-                let dispatched = cx
-                    .update_window(main_window_id, |cx| {
+            if let Some(main_window) = cx.active_window() {
+                let dispatched = main_window
+                    .update(&mut *cx, |cx| {
                         if let Some(view_id) = cx.focused_view_id() {
                             cx.dispatch_action(Some(view_id), action);
                             true

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

@@ -9,7 +9,7 @@ use collections::{hash_map::Entry, HashMap, HashSet};
 
 #[cfg(any(test, feature = "test-support"))]
 use crate::util::post_inc;
-use crate::ElementStateId;
+use crate::{AnyWindowHandle, ElementStateId};
 
 lazy_static! {
     static ref LEAK_BACKTRACE: bool =
@@ -26,7 +26,7 @@ pub struct RefCounts {
     entity_counts: HashMap<usize, usize>,
     element_state_counts: HashMap<ElementStateId, ElementStateRefCount>,
     dropped_models: HashSet<usize>,
-    dropped_views: HashSet<(usize, usize)>,
+    dropped_views: HashSet<(AnyWindowHandle, usize)>,
     dropped_element_states: HashSet<ElementStateId>,
 
     #[cfg(any(test, feature = "test-support"))]
@@ -55,12 +55,12 @@ impl RefCounts {
         }
     }
 
-    pub fn inc_view(&mut self, window_id: usize, view_id: usize) {
+    pub fn inc_view(&mut self, window: AnyWindowHandle, view_id: usize) {
         match self.entity_counts.entry(view_id) {
             Entry::Occupied(mut entry) => *entry.get_mut() += 1,
             Entry::Vacant(entry) => {
                 entry.insert(1);
-                self.dropped_views.remove(&(window_id, view_id));
+                self.dropped_views.remove(&(window, view_id));
             }
         }
     }
@@ -94,12 +94,12 @@ impl RefCounts {
         }
     }
 
-    pub fn dec_view(&mut self, window_id: usize, view_id: usize) {
+    pub fn dec_view(&mut self, window: AnyWindowHandle, view_id: usize) {
         let count = self.entity_counts.get_mut(&view_id).unwrap();
         *count -= 1;
         if *count == 0 {
             self.entity_counts.remove(&view_id);
-            self.dropped_views.insert((window_id, view_id));
+            self.dropped_views.insert((window, view_id));
         }
     }
 
@@ -120,7 +120,7 @@ impl RefCounts {
         &mut self,
     ) -> (
         HashSet<usize>,
-        HashSet<(usize, usize)>,
+        HashSet<(AnyWindowHandle, usize)>,
         HashSet<ElementStateId>,
     ) {
         (

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

@@ -4,9 +4,9 @@ use crate::{
     keymap_matcher::{Binding, Keystroke},
     platform,
     platform::{Event, InputHandler, KeyDownEvent, Platform},
-    Action, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache, Handle,
-    ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakHandle,
-    WindowContext, WindowHandle,
+    Action, AnyWindowHandle, AppContext, BorrowAppContext, BorrowWindowContext, Entity, FontCache,
+    Handle, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
+    WeakHandle, WindowContext, WindowHandle,
 };
 use collections::BTreeMap;
 use futures::Future;
@@ -72,8 +72,8 @@ impl TestAppContext {
         cx
     }
 
-    pub fn dispatch_action<A: Action>(&mut self, window_id: usize, action: A) {
-        self.update_window(window_id, |window| {
+    pub fn dispatch_action<A: Action>(&mut self, window: AnyWindowHandle, action: A) {
+        self.update_window(window, |window| {
             window.dispatch_action(window.focused_view_id(), &action);
         })
         .expect("window not found");
@@ -81,10 +81,10 @@ impl TestAppContext {
 
     pub fn available_actions(
         &self,
-        window_id: usize,
+        window: AnyWindowHandle,
         view_id: usize,
     ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
-        self.read_window(window_id, |cx| cx.available_actions(view_id))
+        self.read_window(window, |cx| cx.available_actions(view_id))
             .unwrap_or_default()
     }
 
@@ -92,33 +92,34 @@ impl TestAppContext {
         self.update(|cx| cx.dispatch_global_action_any(&action));
     }
 
-    pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
-        let handled = self
-            .cx
-            .borrow_mut()
-            .update_window(window_id, |cx| {
-                if cx.dispatch_keystroke(&keystroke) {
-                    return true;
-                }
+    pub fn dispatch_keystroke(
+        &mut self,
+        window: AnyWindowHandle,
+        keystroke: Keystroke,
+        is_held: bool,
+    ) {
+        let handled = window.update(self, |cx| {
+            if cx.dispatch_keystroke(&keystroke) {
+                return true;
+            }
 
-                if cx.dispatch_event(
-                    Event::KeyDown(KeyDownEvent {
-                        keystroke: keystroke.clone(),
-                        is_held,
-                    }),
-                    false,
-                ) {
-                    return true;
-                }
+            if cx.dispatch_event(
+                Event::KeyDown(KeyDownEvent {
+                    keystroke: keystroke.clone(),
+                    is_held,
+                }),
+                false,
+            ) {
+                return true;
+            }
 
-                false
-            })
-            .unwrap_or(false);
+            false
+        });
 
         if !handled && !keystroke.cmd && !keystroke.ctrl {
             WindowInputHandler {
                 app: self.cx.clone(),
-                window_id,
+                window,
             }
             .replace_text_in_range(None, &keystroke.key)
         }
@@ -126,18 +127,18 @@ impl TestAppContext {
 
     pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
         &self,
-        window_id: usize,
+        window: AnyWindowHandle,
         callback: F,
     ) -> Option<T> {
-        self.cx.borrow().read_window(window_id, callback)
+        self.cx.borrow().read_window(window, callback)
     }
 
     pub fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
-        window_id: usize,
+        window: AnyWindowHandle,
         callback: F,
     ) -> Option<T> {
-        self.cx.borrow_mut().update_window(window_id, callback)
+        self.cx.borrow_mut().update_window(window, callback)
     }
 
     pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
@@ -148,27 +149,17 @@ impl TestAppContext {
         self.cx.borrow_mut().add_model(build_model)
     }
 
-    pub fn add_window<T, F>(&mut self, build_root_view: F) -> WindowHandle<T>
+    pub fn add_window<V, F>(&mut self, build_root_view: F) -> WindowHandle<V>
     where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
+        V: View,
+        F: FnOnce(&mut ViewContext<V>) -> V,
     {
         let window = self
             .cx
             .borrow_mut()
             .add_window(Default::default(), build_root_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>
-    where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
-    {
-        self.update_window(window_id, |cx| cx.add_view(build_view))
-            .expect("window not found")
+        window.simulate_activation(self);
+        window
     }
 
     pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
@@ -191,8 +182,8 @@ impl TestAppContext {
         self.cx.borrow_mut().subscribe_global(callback)
     }
 
-    pub fn window_ids(&self) -> Vec<usize> {
-        self.cx.borrow().windows.keys().copied().collect()
+    pub fn windows(&self) -> Vec<AnyWindowHandle> {
+        self.cx.borrow().windows().collect()
     }
 
     pub fn remove_all_windows(&mut self) {
@@ -262,76 +253,6 @@ impl TestAppContext {
         self.foreground_platform.as_ref().did_prompt_for_new_path()
     }
 
-    pub fn simulate_prompt_answer(&self, window_id: usize, answer: usize) {
-        use postage::prelude::Sink as _;
-
-        let mut done_tx = self
-            .platform_window_mut(window_id)
-            .pending_prompts
-            .borrow_mut()
-            .pop_front()
-            .expect("prompt was not called");
-        done_tx.try_send(answer).ok();
-    }
-
-    pub fn has_pending_prompt(&self, window_id: usize) -> bool {
-        let window = self.platform_window_mut(window_id);
-        let prompts = window.pending_prompts.borrow_mut();
-        !prompts.is_empty()
-    }
-
-    pub fn current_window_title(&self, window_id: usize) -> Option<String> {
-        self.platform_window_mut(window_id).title.clone()
-    }
-
-    pub fn simulate_window_close(&self, window_id: usize) -> bool {
-        let handler = self
-            .platform_window_mut(window_id)
-            .should_close_handler
-            .take();
-        if let Some(mut handler) = handler {
-            let should_close = handler();
-            self.platform_window_mut(window_id).should_close_handler = Some(handler);
-            should_close
-        } else {
-            false
-        }
-    }
-
-    pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
-        let mut window = self.platform_window_mut(window_id);
-        window.size = size;
-        let mut handlers = mem::take(&mut window.resize_handlers);
-        drop(window);
-        for handler in &mut handlers {
-            handler();
-        }
-        self.platform_window_mut(window_id).resize_handlers = handlers;
-    }
-
-    pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
-        self.cx.borrow_mut().update(|cx| {
-            let other_window_ids = cx
-                .windows
-                .keys()
-                .filter(|window_id| Some(**window_id) != to_activate)
-                .copied()
-                .collect::<Vec<_>>();
-
-            for window_id in other_window_ids {
-                cx.window_changed_active_status(window_id, false)
-            }
-
-            if let Some(to_activate) = to_activate {
-                cx.window_changed_active_status(to_activate, true)
-            }
-        });
-    }
-
-    pub fn is_window_edited(&self, window_id: usize) -> bool {
-        self.platform_window_mut(window_id).edited
-    }
-
     pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
         self.cx.borrow().leak_detector()
     }
@@ -352,18 +273,6 @@ impl TestAppContext {
         self.assert_dropped(weak);
     }
 
-    fn platform_window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
-        std::cell::RefMut::map(self.cx.borrow_mut(), |state| {
-            let window = state.windows.get_mut(&window_id).unwrap();
-            let test_window = window
-                .platform_window
-                .as_any_mut()
-                .downcast_mut::<platform::test::Window>()
-                .unwrap();
-            test_window
-        })
-    }
-
     pub fn set_condition_duration(&mut self, duration: Option<Duration>) {
         self.condition_duration = duration;
     }
@@ -408,23 +317,37 @@ impl BorrowAppContext for TestAppContext {
 impl BorrowWindowContext for TestAppContext {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
         self.cx
             .borrow()
-            .read_window(window_id, f)
+            .read_window(window, f)
             .expect("window was closed")
     }
 
+    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window(self, window, f)
+    }
+
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
-        window_id: usize,
+        window: AnyWindowHandle,
         f: F,
     ) -> T {
         self.cx
             .borrow_mut()
-            .update_window(window_id, f)
+            .update_window(window, f)
             .expect("window was closed")
     }
+
+    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window(self, window, f)
+    }
 }
 
 impl<T: Entity> ModelHandle<T> {
@@ -539,6 +462,71 @@ impl<T: Entity> ModelHandle<T> {
     }
 }
 
+impl AnyWindowHandle {
+    pub fn has_pending_prompt(&self, cx: &mut TestAppContext) -> bool {
+        let window = self.platform_window_mut(cx);
+        let prompts = window.pending_prompts.borrow_mut();
+        !prompts.is_empty()
+    }
+
+    pub fn current_title(&self, cx: &mut TestAppContext) -> Option<String> {
+        self.platform_window_mut(cx).title.clone()
+    }
+
+    pub fn simulate_close(&self, cx: &mut TestAppContext) -> bool {
+        let handler = self.platform_window_mut(cx).should_close_handler.take();
+        if let Some(mut handler) = handler {
+            let should_close = handler();
+            self.platform_window_mut(cx).should_close_handler = Some(handler);
+            should_close
+        } else {
+            false
+        }
+    }
+
+    pub fn simulate_resize(&self, size: Vector2F, cx: &mut TestAppContext) {
+        let mut window = self.platform_window_mut(cx);
+        window.size = size;
+        let mut handlers = mem::take(&mut window.resize_handlers);
+        drop(window);
+        for handler in &mut handlers {
+            handler();
+        }
+        self.platform_window_mut(cx).resize_handlers = handlers;
+    }
+
+    pub fn is_edited(&self, cx: &mut TestAppContext) -> bool {
+        self.platform_window_mut(cx).edited
+    }
+
+    pub fn simulate_prompt_answer(&self, answer: usize, cx: &mut TestAppContext) {
+        use postage::prelude::Sink as _;
+
+        let mut done_tx = self
+            .platform_window_mut(cx)
+            .pending_prompts
+            .borrow_mut()
+            .pop_front()
+            .expect("prompt was not called");
+        done_tx.try_send(answer).ok();
+    }
+
+    fn platform_window_mut<'a>(
+        &self,
+        cx: &'a mut TestAppContext,
+    ) -> std::cell::RefMut<'a, platform::test::Window> {
+        std::cell::RefMut::map(cx.cx.borrow_mut(), |state| {
+            let window = state.windows.get_mut(&self).unwrap();
+            let test_window = window
+                .platform_window
+                .as_any_mut()
+                .downcast_mut::<platform::test::Window>()
+                .unwrap();
+            test_window
+        })
+    }
+}
+
 impl<T: View> ViewHandle<T> {
     pub fn next_notification(&self, cx: &TestAppContext) -> impl Future<Output = ()> {
         use postage::prelude::{Sink as _, Stream as _};

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

@@ -13,9 +13,10 @@ use crate::{
     },
     text_layout::TextLayoutCache,
     util::post_inc,
-    Action, AnyView, AnyViewHandle, AppContext, BorrowAppContext, BorrowWindowContext, Effect,
-    Element, Entity, Handle, LayoutContext, MouseRegion, MouseRegionId, SceneBuilder, Subscription,
-    View, ViewContext, ViewHandle, WindowHandle, WindowInvalidation,
+    Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
+    BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
+    MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
+    WindowInvalidation,
 };
 use anyhow::{anyhow, bail, Result};
 use collections::{HashMap, HashSet};
@@ -51,8 +52,8 @@ pub struct Window {
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
     last_mouse_moved_event: Option<Event>,
-    pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
-    pub(crate) clicked_region_ids: HashSet<MouseRegionId>,
+    pub(crate) hovered_region_ids: Vec<MouseRegionId>,
+    pub(crate) clicked_region_ids: Vec<MouseRegionId>,
     pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
     mouse_position: Vector2F,
     text_layout_cache: TextLayoutCache,
@@ -60,7 +61,7 @@ pub struct Window {
 
 impl Window {
     pub fn new<V, F>(
-        window_id: usize,
+        handle: AnyWindowHandle,
         platform_window: Box<dyn platform::Window>,
         cx: &mut AppContext,
         build_view: F,
@@ -92,7 +93,7 @@ impl Window {
             appearance,
         };
 
-        let mut window_context = WindowContext::mutable(cx, &mut window, window_id);
+        let mut window_context = WindowContext::mutable(cx, &mut window, handle);
         let root_view = window_context.add_view(|cx| build_view(cx));
         if let Some(invalidation) = window_context.window.invalidation.take() {
             window_context.invalidate(invalidation, appearance);
@@ -113,7 +114,7 @@ impl Window {
 pub struct WindowContext<'a> {
     pub(crate) app_context: Reference<'a, AppContext>,
     pub(crate) window: Reference<'a, Window>,
-    pub(crate) window_id: usize,
+    pub(crate) window_handle: AnyWindowHandle,
     pub(crate) removed: bool,
 }
 
@@ -144,46 +145,64 @@ impl BorrowAppContext for WindowContext<'_> {
 impl BorrowWindowContext for WindowContext<'_> {
     type Result<T> = T;
 
-    fn read_window_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
-        if self.window_id == window_id {
+    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, handle: AnyWindowHandle, f: F) -> T {
+        if self.window_handle == handle {
             f(self)
         } else {
             panic!("read_with called with id of window that does not belong to this context")
         }
     }
 
+    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::read_window(self, window, f)
+    }
+
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
         &mut self,
-        window_id: usize,
+        handle: AnyWindowHandle,
         f: F,
     ) -> T {
-        if self.window_id == window_id {
+        if self.window_handle == handle {
             f(self)
         } else {
             panic!("update called with id of window that does not belong to this context")
         }
     }
+
+    fn update_window_optional<T, F>(&mut self, handle: AnyWindowHandle, f: F) -> Option<T>
+    where
+        F: FnOnce(&mut WindowContext) -> Option<T>,
+    {
+        BorrowWindowContext::update_window(self, handle, f)
+    }
 }
 
 impl<'a> WindowContext<'a> {
     pub fn mutable(
         app_context: &'a mut AppContext,
         window: &'a mut Window,
-        window_id: usize,
+        handle: AnyWindowHandle,
     ) -> Self {
         Self {
             app_context: Reference::Mutable(app_context),
             window: Reference::Mutable(window),
-            window_id,
+            window_handle: handle,
             removed: false,
         }
     }
 
-    pub fn immutable(app_context: &'a AppContext, window: &'a Window, window_id: usize) -> Self {
+    pub fn immutable(
+        app_context: &'a AppContext,
+        window: &'a Window,
+        handle: AnyWindowHandle,
+    ) -> Self {
         Self {
             app_context: Reference::Immutable(app_context),
             window: Reference::Immutable(window),
-            window_id,
+            window_handle: handle,
             removed: false,
         }
     }
@@ -192,8 +211,8 @@ impl<'a> WindowContext<'a> {
         self.removed = true;
     }
 
-    pub fn window_id(&self) -> usize {
-        self.window_id
+    pub fn window(&self) -> AnyWindowHandle {
+        self.window_handle
     }
 
     pub fn app_context(&mut self) -> &mut AppContext {
@@ -216,10 +235,10 @@ impl<'a> WindowContext<'a> {
     where
         F: FnOnce(&mut dyn AnyView, &mut Self) -> T,
     {
-        let window_id = self.window_id;
-        let mut view = self.views.remove(&(window_id, view_id))?;
+        let handle = self.window_handle;
+        let mut view = self.views.remove(&(handle, view_id))?;
         let result = f(view.as_mut(), self);
-        self.views.insert((window_id, view_id), view);
+        self.views.insert((handle, view_id), view);
         Some(result)
     }
 
@@ -244,9 +263,9 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         self.app_context.defer(move |cx| {
-            cx.update_window(window_id, |cx| callback(cx));
+            cx.update_window(handle, |cx| callback(cx));
         })
     }
 
@@ -286,10 +305,10 @@ impl<'a> WindowContext<'a> {
         H: Handle<E>,
         F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
     {
-        let window_id = self.window_id;
+        let window_handle = self.window_handle;
         self.app_context
             .subscribe_internal(handle, move |emitter, event, cx| {
-                cx.update_window(window_id, |cx| callback(emitter, event, cx))
+                cx.update_window(window_handle, |cx| callback(emitter, event, cx))
                     .unwrap_or(false)
             })
     }
@@ -298,17 +317,17 @@ impl<'a> WindowContext<'a> {
     where
         F: 'static + FnMut(bool, &mut WindowContext) -> bool,
     {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         let subscription_id = post_inc(&mut self.next_subscription_id);
         self.pending_effects
             .push_back(Effect::WindowActivationObservation {
-                window_id,
+                window: handle,
                 subscription_id,
                 callback: Box::new(callback),
             });
         Subscription::WindowActivationObservation(
             self.window_activation_observations
-                .subscribe(window_id, subscription_id),
+                .subscribe(handle, subscription_id),
         )
     }
 
@@ -316,17 +335,17 @@ impl<'a> WindowContext<'a> {
     where
         F: 'static + FnMut(bool, &mut WindowContext) -> bool,
     {
-        let window_id = self.window_id;
+        let window = self.window_handle;
         let subscription_id = post_inc(&mut self.next_subscription_id);
         self.pending_effects
             .push_back(Effect::WindowFullscreenObservation {
-                window_id,
+                window,
                 subscription_id,
                 callback: Box::new(callback),
             });
         Subscription::WindowActivationObservation(
             self.window_activation_observations
-                .subscribe(window_id, subscription_id),
+                .subscribe(window, subscription_id),
         )
     }
 
@@ -334,17 +353,17 @@ impl<'a> WindowContext<'a> {
     where
         F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
     {
-        let window_id = self.window_id;
+        let window = self.window_handle;
         let subscription_id = post_inc(&mut self.next_subscription_id);
         self.pending_effects
             .push_back(Effect::WindowBoundsObservation {
-                window_id,
+                window,
                 subscription_id,
                 callback: Box::new(callback),
             });
         Subscription::WindowBoundsObservation(
             self.window_bounds_observations
-                .subscribe(window_id, subscription_id),
+                .subscribe(window, subscription_id),
         )
     }
 
@@ -353,13 +372,13 @@ impl<'a> WindowContext<'a> {
         F: 'static
             + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
     {
-        let window_id = self.window_id;
+        let window = self.window_handle;
         let subscription_id = post_inc(&mut self.next_subscription_id);
         self.keystroke_observations
-            .add_callback(window_id, subscription_id, Box::new(callback));
+            .add_callback(window, subscription_id, Box::new(callback));
         Subscription::KeystrokeObservation(
             self.keystroke_observations
-                .subscribe(window_id, subscription_id),
+                .subscribe(window, subscription_id),
         )
     }
 
@@ -367,11 +386,11 @@ impl<'a> WindowContext<'a> {
         &self,
         view_id: usize,
     ) -> Vec<(&'static str, Box<dyn Action>, SmallVec<[Binding; 1]>)> {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         let mut contexts = Vec::new();
         let mut handler_depths_by_action_id = HashMap::<TypeId, usize>::default();
         for (depth, view_id) in self.ancestors(view_id).enumerate() {
-            if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
+            if let Some(view_metadata) = self.views_metadata.get(&(handle, view_id)) {
                 contexts.push(view_metadata.keymap_context.clone());
                 if let Some(actions) = self.actions.get(&view_metadata.type_id) {
                     handler_depths_by_action_id
@@ -416,13 +435,13 @@ impl<'a> WindowContext<'a> {
     }
 
     pub(crate) fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         if let Some(focused_view_id) = self.focused_view_id() {
             let dispatch_path = self
                 .ancestors(focused_view_id)
                 .filter_map(|view_id| {
                     self.views_metadata
-                        .get(&(window_id, view_id))
+                        .get(&(handle, view_id))
                         .map(|view| (view_id, view.keymap_context.clone()))
                 })
                 .collect();
@@ -447,15 +466,10 @@ impl<'a> WindowContext<'a> {
                 }
             };
 
-            self.keystroke(
-                window_id,
-                keystroke.clone(),
-                handled_by,
-                match_result.clone(),
-            );
+            self.keystroke(handle, keystroke.clone(), handled_by, match_result.clone());
             keystroke_handled
         } else {
-            self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
+            self.keystroke(handle, keystroke.clone(), None, MatchResult::None);
             false
         }
     }
@@ -463,7 +477,7 @@ impl<'a> WindowContext<'a> {
     pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
         let mut mouse_events = SmallVec::<[_; 2]>::new();
         let mut notified_views: HashSet<usize> = Default::default();
-        let window_id = self.window_id;
+        let handle = self.window_handle;
 
         // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
         //    get mapped into the mouse-specific MouseEvent type.
@@ -664,6 +678,7 @@ impl<'a> WindowContext<'a> {
                     let mut highest_z_index = None;
                     let mouse_position = self.window.mouse_position.clone();
                     let window = &mut *self.window;
+                    let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
                     for (region, z_index) in window.mouse_regions.iter().rev() {
                         // Allow mouse regions to appear transparent to hovers
                         if !region.hoverable {
@@ -682,7 +697,11 @@ impl<'a> WindowContext<'a> {
                         // highest_z_index is set.
                         if contains_mouse && z_index == highest_z_index.unwrap() {
                             //Ensure that hover entrance events aren't sent twice
-                            if window.hovered_region_ids.insert(region.id()) {
+                            if let Err(ix) = window.hovered_region_ids.binary_search(&region.id()) {
+                                window.hovered_region_ids.insert(ix, region.id());
+                            }
+                            // window.hovered_region_ids.insert(region.id());
+                            if !prev_hovered_regions.contains(&region.id()) {
                                 valid_regions.push(region.clone());
                                 if region.notify_on_hover {
                                     notified_views.insert(region.id().view_id());
@@ -690,7 +709,7 @@ impl<'a> WindowContext<'a> {
                             }
                         } else {
                             // Ensure that hover exit events aren't sent twice
-                            if window.hovered_region_ids.remove(&region.id()) {
+                            if prev_hovered_regions.contains(&region.id()) {
                                 valid_regions.push(region.clone());
                                 if region.notify_on_hover {
                                     notified_views.insert(region.id().view_id());
@@ -827,19 +846,19 @@ impl<'a> WindowContext<'a> {
         }
 
         for view_id in notified_views {
-            self.notify_view(window_id, view_id);
+            self.notify_view(handle, view_id);
         }
 
         any_event_handled
     }
 
     pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         if let Some(focused_view_id) = self.window.focused_view_id {
             for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
-                if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
+                if let Some(mut view) = self.views.remove(&(handle, view_id)) {
                     let handled = view.key_down(event, self, view_id);
-                    self.views.insert((window_id, view_id), view);
+                    self.views.insert((handle, view_id), view);
                     if handled {
                         return true;
                     }
@@ -853,12 +872,12 @@ impl<'a> WindowContext<'a> {
     }
 
     pub(crate) fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         if let Some(focused_view_id) = self.window.focused_view_id {
             for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
-                if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
+                if let Some(mut view) = self.views.remove(&(handle, view_id)) {
                     let handled = view.key_up(event, self, view_id);
-                    self.views.insert((window_id, view_id), view);
+                    self.views.insert((handle, view_id), view);
                     if handled {
                         return true;
                     }
@@ -872,12 +891,12 @@ impl<'a> WindowContext<'a> {
     }
 
     pub(crate) fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         if let Some(focused_view_id) = self.window.focused_view_id {
             for view_id in self.ancestors(focused_view_id).collect::<Vec<_>>() {
-                if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
+                if let Some(mut view) = self.views.remove(&(handle, view_id)) {
                     let handled = view.modifiers_changed(event, self, view_id);
-                    self.views.insert((window_id, view_id), view);
+                    self.views.insert((handle, view_id), view);
                     if handled {
                         return true;
                     }
@@ -912,14 +931,14 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         let view_id = params.view_id;
         let mut view = self
             .views
-            .remove(&(window_id, view_id))
+            .remove(&(handle, view_id))
             .ok_or_else(|| anyhow!("view not found"))?;
         let element = view.render(self, view_id);
-        self.views.insert((window_id, view_id), view);
+        self.views.insert((handle, view_id), view);
         Ok(element)
     }
 
@@ -947,9 +966,9 @@ impl<'a> WindowContext<'a> {
                 } else if old_parent_id == new_parent_id {
                     current_view_id = *old_parent_id.unwrap();
                 } else {
-                    let window_id = self.window_id;
+                    let handle = self.window_handle;
                     for view_id_to_notify in view_ids_to_notify {
-                        self.notify_view(window_id, view_id_to_notify);
+                        self.notify_view(handle, view_id_to_notify);
                     }
                     break;
                 }
@@ -1117,7 +1136,7 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn focus(&mut self, view_id: Option<usize>) {
-        self.app_context.focus(self.window_id, view_id);
+        self.app_context.focus(self.window_handle, view_id);
     }
 
     pub fn window_bounds(&self) -> WindowBounds {
@@ -1157,17 +1176,6 @@ 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) -> WindowHandle<V>
-    where
-        V: View,
-        F: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        let root_view = self.add_view(|cx| build_root_view(cx));
-        self.window.focused_view_id = Some(root_view.id());
-        self.window.root_view = Some(root_view.into_any());
-        WindowHandle::new(self.window_id)
-    }
-
     pub fn add_view<T, F>(&mut self, build_view: F) -> ViewHandle<T>
     where
         T: View,
@@ -1181,26 +1189,26 @@ impl<'a> WindowContext<'a> {
         T: View,
         F: FnOnce(&mut ViewContext<T>) -> Option<T>,
     {
-        let window_id = self.window_id;
+        let handle = self.window_handle;
         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();
             view.update_keymap_context(&mut keymap_context, cx.app_context());
             self.views_metadata.insert(
-                (window_id, view_id),
+                (handle, view_id),
                 ViewMetadata {
                     type_id: TypeId::of::<T>(),
                     keymap_context,
                 },
             );
-            self.views.insert((window_id, view_id), Box::new(view));
+            self.views.insert((handle, view_id), Box::new(view));
             self.window
                 .invalidation
                 .get_or_insert_with(Default::default)
                 .updated
                 .insert(view_id);
-            Some(ViewHandle::new(window_id, view_id, &self.ref_counts))
+            Some(ViewHandle::new(handle, view_id, &self.ref_counts))
         } else {
             None
         };
@@ -1377,7 +1385,7 @@ pub struct ChildView {
 
 impl ChildView {
     pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
-        let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
+        let view_name = cx.view_ui_name(view.window, view.id()).unwrap();
         Self {
             view_id: view.id(),
             view_name,
@@ -1426,7 +1434,7 @@ impl<V: View> Element<V> for ChildView {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) {
         if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
             rendered_view

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

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

crates/gpui/src/elements.rs 🔗

@@ -33,8 +33,8 @@ use crate::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    json, Action, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle,
-    WindowContext,
+    json, Action, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    WeakViewHandle, WindowContext,
 };
 use anyhow::{anyhow, Result};
 use collections::HashMap;
@@ -65,7 +65,7 @@ pub trait Element<V: View>: 'static {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState;
 
     fn rect_for_text_range(
@@ -310,7 +310,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
                 mut layout,
             } => {
                 let bounds = RectF::new(origin, size);
-                let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
+                let paint = element.paint(
+                    scene,
+                    bounds,
+                    visible_bounds,
+                    &mut layout,
+                    view,
+                    &mut PaintContext::new(cx),
+                );
                 ElementState::PostPaint {
                     element,
                     constraint,
@@ -328,7 +335,14 @@ impl<V: View, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
                 ..
             } => {
                 let bounds = RectF::new(origin, bounds.size());
-                let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
+                let paint = element.paint(
+                    scene,
+                    bounds,
+                    visible_bounds,
+                    &mut layout,
+                    view,
+                    &mut PaintContext::new(cx),
+                );
                 ElementState::PostPaint {
                     element,
                     constraint,
@@ -525,7 +539,7 @@ impl<V: View> Element<V> for AnyElement<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         self.paint(scene, bounds.origin(), visible_bounds, view, cx);
     }

crates/gpui/src/elements/align.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use json::ToJson;
 
@@ -69,7 +70,7 @@ impl<V: View> Element<V> for Align<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let my_center = bounds.size() / 2.;
         let my_target = my_center + my_center * self.alignment;

crates/gpui/src/elements/canvas.rs 🔗

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
 use super::Element;
 use crate::{
     json::{self, json},
-    SceneBuilder, View, ViewContext,
+    PaintContext, SceneBuilder, View, ViewContext,
 };
 use json::ToJson;
 use pathfinder_geometry::{
@@ -56,7 +56,7 @@ where
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         self.0(scene, bounds, visible_bounds, view, cx)
     }

crates/gpui/src/elements/clipped.rs 🔗

@@ -4,7 +4,8 @@ use pathfinder_geometry::{rect::RectF, vector::Vector2F};
 use serde_json::json;
 
 use crate::{
-    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 
 pub struct Clipped<V: View> {
@@ -37,7 +38,7 @@ impl<V: View> Element<V> for Clipped<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         scene.paint_layer(Some(bounds), |scene| {
             self.child

crates/gpui/src/elements/constrained_box.rs 🔗

@@ -5,7 +5,8 @@ use serde_json::json;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 
 pub struct ConstrainedBox<V: View> {
@@ -156,7 +157,7 @@ impl<V: View> Element<V> for ConstrainedBox<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         scene.paint_layer(Some(visible_bounds), |scene| {
             self.child

crates/gpui/src/elements/container.rs 🔗

@@ -10,7 +10,8 @@ use crate::{
     json::ToJson,
     platform::CursorStyle,
     scene::{self, Border, CursorRegion, Quad},
-    AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -214,7 +215,7 @@ impl<V: View> Element<V> for Container<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let quad_bounds = RectF::from_points(
             bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),

crates/gpui/src/elements/empty.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    LayoutContext, SceneBuilder, View, ViewContext,
+    LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
 };
 use crate::{Element, SizeConstraint};
 
@@ -57,7 +57,7 @@ impl<V: View> Element<V> for Empty {
         _: RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut ViewContext<V>,
+        _: &mut PaintContext<V>,
     ) -> Self::PaintState {
     }
 

crates/gpui/src/elements/expanded.rs 🔗

@@ -2,7 +2,8 @@ use std::ops::Range;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use serde_json::json;
 
@@ -61,7 +62,7 @@ impl<V: View> Element<V> for Expanded<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         self.child
             .paint(scene, bounds.origin(), visible_bounds, view, cx);

crates/gpui/src/elements/flex.rs 🔗

@@ -2,8 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
 
 use crate::{
     json::{self, ToJson, Value},
-    AnyElement, Axis, Element, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint,
-    Vector2FExt, View, ViewContext,
+    AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
+    SizeConstraint, Vector2FExt, View, ViewContext,
 };
 use pathfinder_geometry::{
     rect::RectF,
@@ -258,7 +258,7 @@ impl<V: View> Element<V> for Flex<V> {
         visible_bounds: RectF,
         remaining_space: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
@@ -449,7 +449,7 @@ impl<V: View> Element<V> for FlexItem<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         self.child
             .paint(scene, bounds.origin(), visible_bounds, view, cx)

crates/gpui/src/elements/hook.rs 🔗

@@ -3,7 +3,8 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 
 pub struct Hook<V: View> {
@@ -52,7 +53,7 @@ impl<V: View> Element<V> for Hook<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) {
         self.child
             .paint(scene, bounds.origin(), visible_bounds, view, cx);

crates/gpui/src/elements/image.rs 🔗

@@ -5,8 +5,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    scene, Border, Element, ImageData, LayoutContext, SceneBuilder, SizeConstraint, View,
-    ViewContext,
+    scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+    View, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -97,7 +97,7 @@ impl<V: View> Element<V> for Image {
         _: RectF,
         layout: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut ViewContext<V>,
+        _: &mut PaintContext<V>,
     ) -> Self::PaintState {
         if let Some(data) = layout {
             scene.push_image(scene::Image {

crates/gpui/src/elements/keystroke_label.rs 🔗

@@ -66,7 +66,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
         visible_bounds: RectF,
         element: &mut AnyElement<V>,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) {
         element.paint(scene, bounds.origin(), visible_bounds, view, cx);
     }

crates/gpui/src/elements/label.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
     },
     json::{ToJson, Value},
     text_layout::{Line, RunStyle},
-    Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -163,7 +163,7 @@ impl<V: View> Element<V> for Label {
         visible_bounds: RectF,
         line: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
         line.paint(

crates/gpui/src/elements/list.rs 🔗

@@ -4,8 +4,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::json,
-    AnyElement, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
-    ViewContext,
+    AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
+    View, ViewContext,
 };
 use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
 use sum_tree::{Bias, SumTree};
@@ -255,7 +255,7 @@ impl<V: View> Element<V> for List<V> {
         visible_bounds: RectF,
         scroll_top: &mut ListOffset,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
         scene.push_layer(Some(visible_bounds));
@@ -647,7 +647,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{elements::Empty, geometry::vector::vec2f, Entity};
+    use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
     use rand::prelude::*;
     use std::env;
 
@@ -988,7 +988,7 @@ mod tests {
             _: RectF,
             _: &mut (),
             _: &mut V,
-            _: &mut ViewContext<V>,
+            _: &mut PaintContext<V>,
         ) {
             unimplemented!()
         }

crates/gpui/src/elements/mouse_event_handler.rs 🔗

@@ -10,8 +10,8 @@ use crate::{
         CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
         MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
     },
-    AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder,
-    SizeConstraint, View, ViewContext,
+    AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
+    SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde_json::json;
 use std::{marker::PhantomData, ops::Range};
@@ -256,7 +256,7 @@ impl<Tag, V: View> Element<V> for MouseEventHandler<Tag, V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         if self.above {
             self.child

crates/gpui/src/elements/overlay.rs 🔗

@@ -3,8 +3,8 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
-    AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
-    ViewContext,
+    AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
+    SizeConstraint, View, ViewContext,
 };
 use serde_json::json;
 
@@ -143,7 +143,7 @@ impl<V: View> Element<V> for Overlay<V> {
         _: RectF,
         size: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) {
         let (anchor_position, mut bounds) = match self.position_mode {
             OverlayPositionMode::Window => {

crates/gpui/src/elements/resizable.rs 🔗

@@ -7,8 +7,8 @@ use crate::{
     geometry::rect::RectF,
     platform::{CursorStyle, MouseButton},
     scene::MouseDrag,
-    AnyElement, Axis, Element, LayoutContext, MouseRegion, SceneBuilder, SizeConstraint, View,
-    ViewContext,
+    AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
+    SizeConstraint, View, ViewContext,
 };
 
 #[derive(Copy, Clone, Debug)]
@@ -125,7 +125,7 @@ impl<V: View> Element<V> for Resizable<V> {
         visible_bounds: pathfinder_geometry::rect::RectF,
         constraint: &mut SizeConstraint,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         scene.push_stacking_context(None, None);
 

crates/gpui/src/elements/stack.rs 🔗

@@ -3,7 +3,8 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::{self, json, ToJson},
-    AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
+    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 
 /// Element which renders it's children in a stack on top of each other.
@@ -57,7 +58,7 @@ impl<V: View> Element<V> for Stack<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         for child in &mut self.children {
             scene.paint_layer(None, |scene| {

crates/gpui/src/elements/svg.rs 🔗

@@ -1,5 +1,6 @@
 use super::constrain_size_preserving_aspect_ratio;
 use crate::json::ToJson;
+use crate::PaintContext;
 use crate::{
     color::Color,
     geometry::{
@@ -73,7 +74,7 @@ impl<V: View> Element<V> for Svg {
         _visible_bounds: RectF,
         svg: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut ViewContext<V>,
+        _: &mut PaintContext<V>,
     ) {
         if let Some(svg) = svg.clone() {
             scene.push_icon(scene::Icon {

crates/gpui/src/elements/text.rs 🔗

@@ -7,8 +7,8 @@ use crate::{
     },
     json::{ToJson, Value},
     text_layout::{Line, RunStyle, ShapedBoundary},
-    AppContext, Element, FontCache, LayoutContext, SceneBuilder, SizeConstraint, TextLayoutCache,
-    View, ViewContext,
+    AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+    TextLayoutCache, View, ViewContext,
 };
 use log::warn;
 use serde_json::json;
@@ -171,7 +171,7 @@ impl<V: View> Element<V> for Text {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let mut origin = bounds.origin();
         let empty = Vec::new();

crates/gpui/src/elements/tooltip.rs 🔗

@@ -6,8 +6,8 @@ use crate::{
     fonts::TextStyle,
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    Action, Axis, ElementStateHandle, LayoutContext, SceneBuilder, SizeConstraint, Task, View,
-    ViewContext,
+    Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+    Task, View, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -194,7 +194,7 @@ impl<V: View> Element<V> for Tooltip<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) {
         self.child
             .paint(scene, bounds.origin(), visible_bounds, view, cx);

crates/gpui/src/elements/uniform_list.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     },
     json::{self, json},
     platform::ScrollWheelEvent,
-    AnyElement, LayoutContext, MouseRegion, SceneBuilder, View, ViewContext,
+    AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, View, ViewContext,
 };
 use json::ToJson;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -278,7 +278,7 @@ impl<V: View> Element<V> for UniformList<V> {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
 

crates/gpui/src/fonts.rs 🔗

@@ -71,6 +71,32 @@ pub struct TextStyle {
     pub underline: Underline,
 }
 
+impl TextStyle {
+    pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
+        TextStyle {
+            color: refinement.color.unwrap_or(self.color),
+            font_family_name: refinement
+                .font_family_name
+                .unwrap_or_else(|| self.font_family_name.clone()),
+            font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
+            font_id: refinement.font_id.unwrap_or(self.font_id),
+            font_size: refinement.font_size.unwrap_or(self.font_size),
+            font_properties: refinement.font_properties.unwrap_or(self.font_properties),
+            underline: refinement.underline.unwrap_or(self.underline),
+        }
+    }
+}
+
+pub struct TextStyleRefinement {
+    pub color: Option<Color>,
+    pub font_family_name: Option<Arc<str>>,
+    pub font_family_id: Option<FamilyId>,
+    pub font_id: Option<FontId>,
+    pub font_size: Option<f32>,
+    pub font_properties: Option<Properties>,
+    pub underline: Option<Underline>,
+}
+
 #[derive(JsonSchema)]
 #[serde(remote = "Properties")]
 pub struct PropertiesDef {

crates/gpui/src/platform.rs 🔗

@@ -19,7 +19,7 @@ use crate::{
     },
     keymap_matcher::KeymapMatcher,
     text_layout::{LineLayout, RunStyle},
-    Action, ClipboardItem, Menu, Scene,
+    Action, AnyWindowHandle, ClipboardItem, Menu, Scene,
 };
 use anyhow::{anyhow, bail, Result};
 use async_task::Runnable;
@@ -58,13 +58,13 @@ pub trait Platform: Send + Sync {
 
     fn open_window(
         &self,
-        id: usize,
+        handle: AnyWindowHandle,
         options: WindowOptions,
         executor: Rc<executor::Foreground>,
     ) -> Box<dyn Window>;
-    fn main_window_id(&self) -> Option<usize>;
+    fn main_window(&self) -> Option<AnyWindowHandle>;
 
-    fn add_status_item(&self, id: usize) -> Box<dyn Window>;
+    fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn Window>;
 
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;

crates/gpui/src/platform/mac.rs 🔗

@@ -21,7 +21,7 @@ pub use fonts::FontSystem;
 use platform::{MacForegroundPlatform, MacPlatform};
 pub use renderer::Surface;
 use std::{ops::Range, rc::Rc, sync::Arc};
-use window::Window;
+use window::MacWindow;
 
 use crate::executor;
 

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -1,12 +1,12 @@
 use super::{
     event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
-    FontSystem, Window,
+    FontSystem, MacWindow,
 };
 use crate::{
     executor,
     keymap_matcher::KeymapMatcher,
     platform::{self, AppVersion, CursorStyle, Event},
-    Action, ClipboardItem, Menu, MenuItem,
+    Action, AnyWindowHandle, ClipboardItem, Menu, MenuItem,
 };
 use anyhow::{anyhow, Result};
 use block::ConcreteBlock;
@@ -590,18 +590,18 @@ impl platform::Platform for MacPlatform {
 
     fn open_window(
         &self,
-        id: usize,
+        handle: AnyWindowHandle,
         options: platform::WindowOptions,
         executor: Rc<executor::Foreground>,
     ) -> Box<dyn platform::Window> {
-        Box::new(Window::open(id, options, executor, self.fonts()))
+        Box::new(MacWindow::open(handle, options, executor, self.fonts()))
     }
 
-    fn main_window_id(&self) -> Option<usize> {
-        Window::main_window_id()
+    fn main_window(&self) -> Option<AnyWindowHandle> {
+        MacWindow::main_window()
     }
 
-    fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
+    fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
         Box::new(StatusItem::add(self.fonts()))
     }
 

crates/gpui/src/platform/mac/window.rs 🔗

@@ -13,6 +13,7 @@ use crate::{
         Event, InputHandler, KeyDownEvent, Modifiers, ModifiersChangedEvent, MouseButton,
         MouseButtonEvent, MouseMovedEvent, Scene, WindowBounds, WindowKind,
     },
+    AnyWindowHandle,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -282,7 +283,7 @@ struct InsertText {
 }
 
 struct WindowState {
-    id: usize,
+    handle: AnyWindowHandle,
     native_window: id,
     kind: WindowKind,
     event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
@@ -422,11 +423,11 @@ impl WindowState {
     }
 }
 
-pub struct Window(Rc<RefCell<WindowState>>);
+pub struct MacWindow(Rc<RefCell<WindowState>>);
 
-impl Window {
+impl MacWindow {
     pub fn open(
-        id: usize,
+        handle: AnyWindowHandle,
         options: platform::WindowOptions,
         executor: Rc<executor::Foreground>,
         fonts: Arc<dyn platform::FontSystem>,
@@ -504,7 +505,7 @@ impl Window {
             assert!(!native_view.is_null());
 
             let window = Self(Rc::new(RefCell::new(WindowState {
-                id,
+                handle,
                 native_window,
                 kind: options.kind,
                 event_callback: None,
@@ -621,13 +622,13 @@ impl Window {
         }
     }
 
-    pub fn main_window_id() -> Option<usize> {
+    pub fn main_window() -> Option<AnyWindowHandle> {
         unsafe {
             let app = NSApplication::sharedApplication(nil);
             let main_window: id = msg_send![app, mainWindow];
             if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
-                let id = get_window_state(&*main_window).borrow().id;
-                Some(id)
+                let handle = get_window_state(&*main_window).borrow().handle;
+                Some(handle)
             } else {
                 None
             }
@@ -635,7 +636,7 @@ impl Window {
     }
 }
 
-impl Drop for Window {
+impl Drop for MacWindow {
     fn drop(&mut self) {
         let this = self.0.borrow();
         let window = this.native_window;
@@ -649,7 +650,7 @@ impl Drop for Window {
     }
 }
 
-impl platform::Window for Window {
+impl platform::Window for MacWindow {
     fn bounds(&self) -> WindowBounds {
         self.0.as_ref().borrow().bounds()
     }
@@ -881,7 +882,7 @@ impl platform::Window for Window {
 
     fn is_topmost_for_position(&self, position: Vector2F) -> bool {
         let self_borrow = self.0.borrow();
-        let self_id = self_borrow.id;
+        let self_handle = self_borrow.handle;
 
         unsafe {
             let app = NSApplication::sharedApplication(nil);
@@ -898,8 +899,8 @@ impl platform::Window for Window {
             let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
             let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
             if is_panel == YES || is_window == YES {
-                let topmost_window_id = get_window_state(&*top_most_window).borrow().id;
-                topmost_window_id == self_id
+                let topmost_window = get_window_state(&*top_most_window).borrow().handle;
+                topmost_window == self_handle
             } else {
                 // Someone else's window is on top
                 false
@@ -1086,7 +1087,10 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                 button: MouseButton::Left,
                 modifiers: Modifiers { ctrl: true, .. },
                 ..
-            }) => return,
+            }) => {
+                window_state_borrow.synthetic_drag_counter += 1;
+                return;
+            }
 
             _ => None,
         };

crates/gpui/src/platform/test.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     keymap_matcher::KeymapMatcher,
-    Action, ClipboardItem, Menu,
+    Action, AnyWindowHandle, ClipboardItem, Menu,
 };
 use anyhow::{anyhow, Result};
 use collections::VecDeque;
@@ -102,7 +102,7 @@ pub struct Platform {
     fonts: Arc<dyn super::FontSystem>,
     current_clipboard_item: Mutex<Option<ClipboardItem>>,
     cursor: Mutex<CursorStyle>,
-    active_window_id: Arc<Mutex<Option<usize>>>,
+    active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
 }
 
 impl Platform {
@@ -112,7 +112,7 @@ impl Platform {
             fonts: Arc::new(super::current::FontSystem::new()),
             current_clipboard_item: Default::default(),
             cursor: Mutex::new(CursorStyle::Arrow),
-            active_window_id: Default::default(),
+            active_window: Default::default(),
         }
     }
 }
@@ -146,30 +146,30 @@ impl super::Platform for Platform {
 
     fn open_window(
         &self,
-        id: usize,
+        handle: AnyWindowHandle,
         options: super::WindowOptions,
         _executor: Rc<super::executor::Foreground>,
     ) -> Box<dyn super::Window> {
-        *self.active_window_id.lock() = Some(id);
+        *self.active_window.lock() = Some(handle);
         Box::new(Window::new(
-            id,
+            handle,
             match options.bounds {
                 WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
                 WindowBounds::Fixed(rect) => rect.size(),
             },
-            self.active_window_id.clone(),
+            self.active_window.clone(),
         ))
     }
 
-    fn main_window_id(&self) -> Option<usize> {
-        self.active_window_id.lock().clone()
+    fn main_window(&self) -> Option<AnyWindowHandle> {
+        self.active_window.lock().clone()
     }
 
-    fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
+    fn add_status_item(&self, handle: AnyWindowHandle) -> Box<dyn crate::platform::Window> {
         Box::new(Window::new(
-            id,
+            handle,
             vec2f(24., 24.),
-            self.active_window_id.clone(),
+            self.active_window.clone(),
         ))
     }
 
@@ -256,7 +256,7 @@ impl super::Screen for Screen {
 }
 
 pub struct Window {
-    id: usize,
+    handle: AnyWindowHandle,
     pub(crate) size: Vector2F,
     scale_factor: f32,
     current_scene: Option<crate::Scene>,
@@ -270,13 +270,17 @@ pub struct Window {
     pub(crate) title: Option<String>,
     pub(crate) edited: bool,
     pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
-    active_window_id: Arc<Mutex<Option<usize>>>,
+    active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
 }
 
 impl Window {
-    pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
+    pub fn new(
+        handle: AnyWindowHandle,
+        size: Vector2F,
+        active_window: Arc<Mutex<Option<AnyWindowHandle>>>,
+    ) -> Self {
         Self {
-            id,
+            handle,
             size,
             event_handlers: Default::default(),
             resize_handlers: Default::default(),
@@ -290,7 +294,7 @@ impl Window {
             title: None,
             edited: false,
             pending_prompts: Default::default(),
-            active_window_id,
+            active_window,
         }
     }
 
@@ -342,7 +346,7 @@ impl super::Window for Window {
     }
 
     fn activate(&self) {
-        *self.active_window_id.lock() = Some(self.id);
+        *self.active_window.lock() = Some(self.handle);
     }
 
     fn set_title(&mut self, title: &str) {

crates/gpui/src/scene/mouse_region.rs 🔗

@@ -177,7 +177,7 @@ impl MouseRegion {
     }
 }
 
-#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
+#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, PartialOrd, Ord)]
 pub struct MouseRegionId {
     view_id: usize,
     tag: TypeId,

crates/language/src/language.rs 🔗

@@ -45,7 +45,7 @@ use syntax_map::SyntaxSnapshot;
 use theme::{SyntaxTheme, Theme};
 use tree_sitter::{self, Query};
 use unicase::UniCase;
-use util::http::HttpClient;
+use util::{http::HttpClient, paths::PathExt};
 use util::{merge_json_value_into, post_inc, ResultExt, TryFutureExt as _, UnwrapFuture};
 
 #[cfg(any(test, feature = "test-support"))]
@@ -182,8 +182,8 @@ impl CachedLspAdapter {
         self.adapter.workspace_configuration(cx)
     }
 
-    pub async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
-        self.adapter.process_diagnostics(params).await
+    pub fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
+        self.adapter.process_diagnostics(params)
     }
 
     pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
@@ -262,7 +262,7 @@ pub trait LspAdapter: 'static + Send + Sync {
         container_dir: PathBuf,
     ) -> Option<LanguageServerBinary>;
 
-    async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
     async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
 
@@ -777,7 +777,7 @@ impl LanguageRegistry {
     ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
         let path = path.as_ref();
         let filename = path.file_name().and_then(|name| name.to_str());
-        let extension = path.extension().and_then(|name| name.to_str());
+        let extension = path.extension_or_hidden_file_name();
         let path_suffixes = [extension, filename];
         self.get_or_load_language(|config| {
             let path_matches = config
@@ -1487,12 +1487,6 @@ impl Language {
         None
     }
 
-    pub async fn process_diagnostics(&self, diagnostics: &mut lsp::PublishDiagnosticsParams) {
-        for adapter in &self.adapters {
-            adapter.process_diagnostics(diagnostics).await;
-        }
-    }
-
     pub async fn process_completion(self: &Arc<Self>, completion: &mut lsp::CompletionItem) {
         for adapter in &self.adapters {
             adapter.process_completion(completion).await;
@@ -1756,7 +1750,7 @@ impl LspAdapter for Arc<FakeLspAdapter> {
         unreachable!();
     }
 
-    async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
+    fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
 
     async fn disk_based_diagnostic_sources(&self) -> Vec<String> {
         self.disk_based_diagnostics_sources.clone()

crates/project/src/project.rs 🔗

@@ -2769,24 +2769,21 @@ impl Project {
         language_server
             .on_notification::<lsp::notification::PublishDiagnostics, _>({
                 let adapter = adapter.clone();
-                move |mut params, cx| {
+                move |mut params, mut cx| {
                     let this = this;
                     let adapter = adapter.clone();
-                    cx.spawn(|mut cx| async move {
-                        adapter.process_diagnostics(&mut params).await;
-                        if let Some(this) = this.upgrade(&cx) {
-                            this.update(&mut cx, |this, cx| {
-                                this.update_diagnostics(
-                                    server_id,
-                                    params,
-                                    &adapter.disk_based_diagnostic_sources,
-                                    cx,
-                                )
-                                .log_err();
-                            });
-                        }
-                    })
-                    .detach();
+                    adapter.process_diagnostics(&mut params);
+                    if let Some(this) = this.upgrade(&cx) {
+                        this.update(&mut cx, |this, cx| {
+                            this.update_diagnostics(
+                                server_id,
+                                params,
+                                &adapter.disk_based_diagnostic_sources,
+                                cx,
+                            )
+                            .log_err();
+                        });
+                    }
                 }
             })
             .detach();

crates/project/src/terminals.rs 🔗

@@ -1,5 +1,5 @@
 use crate::Project;
-use gpui::{ModelContext, ModelHandle, WeakModelHandle};
+use gpui::{AnyWindowHandle, ModelContext, ModelHandle, WeakModelHandle};
 use std::path::PathBuf;
 use terminal::{Terminal, TerminalBuilder, TerminalSettings};
 
@@ -11,7 +11,7 @@ impl Project {
     pub fn create_terminal(
         &mut self,
         working_directory: Option<PathBuf>,
-        window_id: usize,
+        window: AnyWindowHandle,
         cx: &mut ModelContext<Self>,
     ) -> anyhow::Result<ModelHandle<Terminal>> {
         if self.is_remote() {
@@ -27,7 +27,7 @@ impl Project {
                 settings.env.clone(),
                 Some(settings.blinking.clone()),
                 settings.alternate_scroll,
-                window_id,
+                window,
             )
             .map(|builder| {
                 let terminal_handle = cx.add_model(|cx| builder.subscribe(cx));

crates/project_panel/src/project_panel.rs 🔗

@@ -1415,7 +1415,7 @@ impl ProjectPanel {
 
             if cx
                 .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<ProjectEntryId>(cx.window_id())
+                .currently_dragged::<ProjectEntryId>(cx.window())
                 .is_some()
                 && dragged_entry_destination
                     .as_ref()
@@ -1459,7 +1459,7 @@ impl ProjectPanel {
         .on_up(MouseButton::Left, move |_, this, cx| {
             if let Some((_, dragged_entry)) = cx
                 .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<ProjectEntryId>(cx.window_id())
+                .currently_dragged::<ProjectEntryId>(cx.window())
             {
                 this.move_entry(
                     *dragged_entry,
@@ -1472,7 +1472,7 @@ impl ProjectPanel {
         .on_move(move |_, this, cx| {
             if cx
                 .global::<DragAndDrop<Workspace>>()
-                .currently_dragged::<ProjectEntryId>(cx.window_id())
+                .currently_dragged::<ProjectEntryId>(cx.window())
                 .is_some()
             {
                 this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
@@ -1702,7 +1702,7 @@ impl ClipboardEntry {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::{TestAppContext, ViewHandle};
+    use gpui::{AnyWindowHandle, TestAppContext, ViewHandle, WindowHandle};
     use pretty_assertions::assert_eq;
     use project::FakeFs;
     use serde_json::json;
@@ -1848,7 +1848,6 @@ mod tests {
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
@@ -1870,7 +1869,7 @@ mod tests {
         // Add a file with the root folder selected. The filename editor is placed
         // before the first file in the root folder.
         panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             let panel = panel.read(cx);
             assert!(panel.filename_editor.is_focused(cx));
         });
@@ -2201,7 +2200,6 @@ mod tests {
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
@@ -2223,7 +2221,7 @@ mod tests {
         // Add a file with the root folder selected. The filename editor is placed
         // before the first file in the root folder.
         panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             let panel = panel.read(cx);
             assert!(panel.filename_editor.is_focused(cx));
         });
@@ -2378,7 +2376,6 @@ mod tests {
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         toggle_expand_dir(&panel, "src/test", cx);
@@ -2395,9 +2392,9 @@ mod tests {
                 "          third.rs"
             ]
         );
-        ensure_single_file_is_opened(window_id, &workspace, "test/first.rs", cx);
+        ensure_single_file_is_opened(window, "test/first.rs", cx);
 
-        submit_deletion(window_id, &panel, cx);
+        submit_deletion(window.into(), &panel, cx);
         assert_eq!(
             visible_entries_as_strings(&panel, 0..10, cx),
             &[
@@ -2408,7 +2405,7 @@ mod tests {
             ],
             "Project panel should have no deleted file, no other file is selected in it"
         );
-        ensure_no_open_items_and_panes(window_id, &workspace, cx);
+        ensure_no_open_items_and_panes(window.into(), &workspace, cx);
 
         select_path(&panel, "src/test/second.rs", cx);
         panel.update(cx, |panel, cx| panel.open_file(&Open, cx));
@@ -2422,9 +2419,9 @@ mod tests {
                 "          third.rs"
             ]
         );
-        ensure_single_file_is_opened(window_id, &workspace, "test/second.rs", cx);
+        ensure_single_file_is_opened(window, "test/second.rs", cx);
 
-        cx.update_window(window_id, |cx| {
+        window.update(cx, |cx| {
             let active_items = workspace
                 .read(cx)
                 .panes()
@@ -2440,13 +2437,13 @@ mod tests {
                 .expect("Open item should be an editor");
             open_editor.update(cx, |editor, cx| editor.set_text("Another text!", cx));
         });
-        submit_deletion(window_id, &panel, cx);
+        submit_deletion(window.into(), &panel, cx);
         assert_eq!(
             visible_entries_as_strings(&panel, 0..10, cx),
             &["v src", "    v test", "          third.rs"],
             "Project panel should have no deleted file, with one last file remaining"
         );
-        ensure_no_open_items_and_panes(window_id, &workspace, cx);
+        ensure_no_open_items_and_panes(window.into(), &workspace, cx);
     }
 
     #[gpui::test]
@@ -2469,7 +2466,6 @@ mod tests {
         let project = Project::test(fs.clone(), ["/src".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "src/", cx);
@@ -2480,7 +2476,7 @@ mod tests {
             &["v src  <== selected", "    > test"]
         );
         panel.update(cx, |panel, cx| panel.new_directory(&NewDirectory, cx));
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             let panel = panel.read(cx);
             assert!(panel.filename_editor.is_focused(cx));
         });
@@ -2511,7 +2507,7 @@ mod tests {
             &["v src", "    > test  <== selected"]
         );
         panel.update(cx, |panel, cx| panel.new_file(&NewFile, cx));
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             let panel = panel.read(cx);
             assert!(panel.filename_editor.is_focused(cx));
         });
@@ -2561,7 +2557,7 @@ mod tests {
             ],
         );
         panel.update(cx, |panel, cx| panel.rename(&Rename, cx));
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             let panel = panel.read(cx);
             assert!(panel.filename_editor.is_focused(cx));
         });
@@ -2858,13 +2854,11 @@ mod tests {
     }
 
     fn ensure_single_file_is_opened(
-        window_id: usize,
-        workspace: &ViewHandle<Workspace>,
+        window: WindowHandle<Workspace>,
         expected_path: &str,
         cx: &mut TestAppContext,
     ) {
-        cx.read_window(window_id, |cx| {
-            let workspace = workspace.read(cx);
+        window.update_root(cx, |workspace, cx| {
             let worktrees = workspace.worktrees(cx).collect::<Vec<_>>();
             assert_eq!(worktrees.len(), 1);
             let worktree_id = WorktreeId::from_usize(worktrees[0].id());
@@ -2886,12 +2880,12 @@ mod tests {
     }
 
     fn submit_deletion(
-        window_id: usize,
+        window: AnyWindowHandle,
         panel: &ViewHandle<ProjectPanel>,
         cx: &mut TestAppContext,
     ) {
         assert!(
-            !cx.has_pending_prompt(window_id),
+            !window.has_pending_prompt(cx),
             "Should have no prompts before the deletion"
         );
         panel.update(cx, |panel, cx| {
@@ -2901,27 +2895,27 @@ mod tests {
                 .detach_and_log_err(cx);
         });
         assert!(
-            cx.has_pending_prompt(window_id),
+            window.has_pending_prompt(cx),
             "Should have a prompt after the deletion"
         );
-        cx.simulate_prompt_answer(window_id, 0);
+        window.simulate_prompt_answer(0, cx);
         assert!(
-            !cx.has_pending_prompt(window_id),
+            !window.has_pending_prompt(cx),
             "Should have no prompts after prompt was replied to"
         );
         cx.foreground().run_until_parked();
     }
 
     fn ensure_no_open_items_and_panes(
-        window_id: usize,
+        window: AnyWindowHandle,
         workspace: &ViewHandle<Workspace>,
         cx: &mut TestAppContext,
     ) {
         assert!(
-            !cx.has_pending_prompt(window_id),
+            !window.has_pending_prompt(cx),
             "Should have no prompts after deletion operation closes the file"
         );
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             let open_project_paths = workspace
                 .read(cx)
                 .panes()

crates/project_symbols/src/project_symbols.rs 🔗

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

crates/search/src/buffer_search.rs 🔗

@@ -850,12 +850,9 @@ mod tests {
             )
         });
         let window = cx.add_window(|_| EmptyView);
+        let editor = window.add_view(cx, |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.window_id(), |cx| {
+        let search_bar = window.add_view(cx, |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(cx);
@@ -1232,11 +1229,9 @@ mod tests {
         );
         let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
         let window = cx.add_window(|_| EmptyView);
-        let window_id = window.window_id();
-
-        let editor = cx.add_view(window_id, |cx| Editor::for_buffer(buffer.clone(), None, cx));
+        let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
 
-        let search_bar = cx.add_view(window_id, |cx| {
+        let search_bar = window.add_view(cx, |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(cx);
@@ -1252,12 +1247,13 @@ mod tests {
             search_bar.activate_current_match(cx);
         });
 
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             assert!(
                 !editor.is_focused(cx),
                 "Initially, the editor should not be focused"
             );
         });
+
         let initial_selections = editor.update(cx, |editor, cx| {
             let initial_selections = editor.selections.display_ranges(cx);
             assert_eq!(
@@ -1274,7 +1270,7 @@ mod tests {
             cx.focus(search_bar.query_editor.as_any());
             search_bar.select_all_matches(&SelectAllMatches, cx);
         });
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             assert!(
                 editor.is_focused(cx),
                 "Should focus editor after successful SelectAllMatches"
@@ -1298,7 +1294,7 @@ mod tests {
         search_bar.update(cx, |search_bar, cx| {
             search_bar.select_next_match(&SelectNextMatch, cx);
         });
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             assert!(
                 editor.is_focused(cx),
                 "Should still have editor focused after SelectNextMatch"
@@ -1327,7 +1323,7 @@ mod tests {
             cx.focus(search_bar.query_editor.as_any());
             search_bar.select_all_matches(&SelectAllMatches, cx);
         });
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             assert!(
                 editor.is_focused(cx),
                 "Should focus editor after successful SelectAllMatches"
@@ -1351,7 +1347,7 @@ mod tests {
         search_bar.update(cx, |search_bar, cx| {
             search_bar.select_prev_match(&SelectPrevMatch, cx);
         });
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             assert!(
                 editor.is_focused(cx),
                 "Should still have editor focused after SelectPrevMatch"
@@ -1387,7 +1383,7 @@ mod tests {
         search_bar.update(cx, |search_bar, cx| {
             search_bar.select_all_matches(&SelectAllMatches, cx);
         });
-        cx.read_window(window_id, |cx| {
+        window.read_with(cx, |cx| {
             assert!(
                 !editor.is_focused(cx),
                 "Should not switch focus to editor if SelectAllMatches does not find any matches"
@@ -1421,11 +1417,9 @@ mod tests {
         let buffer = cx.add_model(|cx| Buffer::new(0, buffer_text, cx));
         let window = cx.add_window(|_| EmptyView);
 
-        let editor = cx.add_view(window.window_id(), |cx| {
-            Editor::for_buffer(buffer.clone(), None, cx)
-        });
+        let editor = window.add_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx));
 
-        let search_bar = cx.add_view(window.window_id(), |cx| {
+        let search_bar = window.add_view(cx, |cx| {
             let mut search_bar = BufferSearchBar::new(cx);
             search_bar.set_active_pane_item(Some(&editor), cx);
             search_bar.show(cx);

crates/search/src/project_search.rs 🔗

@@ -1568,7 +1568,6 @@ pub mod tests {
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
 
         let active_item = cx.read(|cx| {
             workspace
@@ -1599,9 +1598,9 @@ pub mod tests {
         };
         let search_view_id = search_view.id();
 
-        cx.spawn(
-            |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
-        )
+        cx.spawn(|mut cx| async move {
+            window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+        })
         .detach();
         deterministic.run_until_parked();
         search_view.update(cx, |search_view, cx| {
@@ -1652,7 +1651,7 @@ pub mod tests {
             );
         });
         cx.spawn(
-            |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
+            |mut cx| async move { window.dispatch_action(search_view_id, &ToggleFocus, &mut cx) },
         )
         .detach();
         deterministic.run_until_parked();
@@ -1683,9 +1682,9 @@ pub mod tests {
                 "Search view with mismatching query should be focused after search results are available",
             );
         });
-        cx.spawn(
-            |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
-        )
+        cx.spawn(|mut cx| async move {
+            window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+        })
         .detach();
         deterministic.run_until_parked();
         search_view.update(cx, |search_view, cx| {
@@ -1713,9 +1712,9 @@ pub mod tests {
             );
         });
 
-        cx.spawn(
-            |mut cx| async move { cx.dispatch_action(window_id, search_view_id, &ToggleFocus) },
-        )
+        cx.spawn(|mut cx| async move {
+            window.dispatch_action(search_view_id, &ToggleFocus, &mut cx);
+        })
         .detach();
         deterministic.run_until_parked();
         search_view.update(cx, |search_view, cx| {
@@ -1874,7 +1873,6 @@ pub mod tests {
         let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         workspace.update(cx, |workspace, cx| {
             ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
         });
@@ -1889,7 +1887,7 @@ pub mod tests {
                 .expect("Search view expected to appear after new search event trigger")
         });
 
-        let search_bar = cx.add_view(window_id, |cx| {
+        let search_bar = window.add_view(cx, |cx| {
             let mut search_bar = ProjectSearchBar::new();
             search_bar.set_active_pane_item(Some(&search_view), cx);
             // search_bar.show(cx);

crates/terminal/src/terminal.rs 🔗

@@ -53,7 +53,7 @@ use gpui::{
     keymap_matcher::Keystroke,
     platform::{Modifiers, MouseButton, MouseMovedEvent, TouchPhase},
     scene::{MouseDown, MouseDrag, MouseScrollWheel, MouseUp},
-    AppContext, ClipboardItem, Entity, ModelContext, Task,
+    AnyWindowHandle, AppContext, ClipboardItem, Entity, ModelContext, Task,
 };
 
 use crate::mappings::{
@@ -404,7 +404,7 @@ impl TerminalBuilder {
         mut env: HashMap<String, String>,
         blink_settings: Option<TerminalBlink>,
         alternate_scroll: AlternateScroll,
-        window_id: usize,
+        window: AnyWindowHandle,
     ) -> Result<TerminalBuilder> {
         let pty_config = {
             let alac_shell = match shell.clone() {
@@ -462,7 +462,7 @@ impl TerminalBuilder {
         let pty = match tty::new(
             &pty_config,
             TerminalSize::default().into(),
-            window_id as u64,
+            window.id() as u64,
         ) {
             Ok(pty) => pty,
             Err(error) => {

crates/terminal_view/src/terminal_element.rs 🔗

@@ -10,8 +10,9 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     serde_json::json,
     text_layout::{Line, RunStyle},
-    AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion, Quad,
-    SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
+    AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion,
+    PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext,
+    WeakModelHandle,
 };
 use itertools::Itertools;
 use language::CursorShape;
@@ -734,7 +735,7 @@ impl Element<TerminalView> for TerminalElement {
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut TerminalView,
-        cx: &mut ViewContext<TerminalView>,
+        cx: &mut PaintContext<TerminalView>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -48,7 +48,7 @@ impl TerminalPanel {
     fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
         let weak_self = cx.weak_handle();
         let pane = cx.add_view(|cx| {
-            let window_id = cx.window_id();
+            let window = cx.window();
             let mut pane = Pane::new(
                 workspace.weak_handle(),
                 workspace.project().clone(),
@@ -60,7 +60,7 @@ impl TerminalPanel {
             pane.set_can_navigate(false, cx);
             pane.on_can_drop(move |drag_and_drop, cx| {
                 drag_and_drop
-                    .currently_dragged::<DraggedItem>(window_id)
+                    .currently_dragged::<DraggedItem>(window)
                     .map_or(false, |(_, item)| {
                         item.handle.act_as::<TerminalView>(cx).is_some()
                     })
@@ -255,10 +255,10 @@ impl TerminalPanel {
                     .clone();
                 let working_directory =
                     crate::get_working_directory(workspace, cx, working_directory_strategy);
-                let window_id = cx.window_id();
+                let window = cx.window();
                 if let Some(terminal) = workspace.project().update(cx, |project, cx| {
                     project
-                        .create_terminal(working_directory, window_id, cx)
+                        .create_terminal(working_directory, window, cx)
                         .log_err()
                 }) {
                     let terminal = Box::new(cx.add_view(|cx| {

crates/terminal_view/src/terminal_view.rs 🔗

@@ -112,11 +112,11 @@ impl TerminalView {
         let working_directory =
             get_working_directory(workspace, cx, strategy.working_directory.clone());
 
-        let window_id = cx.window_id();
+        let window = cx.window();
         let terminal = workspace
             .project()
             .update(cx, |project, cx| {
-                project.create_terminal(working_directory, window_id, cx)
+                project.create_terminal(working_directory, window, cx)
             })
             .notify_err(workspace, cx);
 
@@ -741,7 +741,7 @@ impl Item for TerminalView {
         item_id: workspace::ItemId,
         cx: &mut ViewContext<Pane>,
     ) -> Task<anyhow::Result<ViewHandle<Self>>> {
-        let window_id = cx.window_id();
+        let window = cx.window();
         cx.spawn(|pane, mut cx| async move {
             let cwd = TERMINAL_DB
                 .get_working_directory(item_id, workspace_id)
@@ -762,7 +762,7 @@ impl Item for TerminalView {
                 });
 
             let terminal = project.update(&mut cx, |project, cx| {
-                project.create_terminal(cwd, window_id, cx)
+                project.create_terminal(cwd, window, cx)
             })?;
             Ok(pane.update(&mut cx, |_, cx| {
                 cx.add_view(|cx| TerminalView::new(terminal, workspace, workspace_id, cx))

crates/theme/src/ui.rs 🔗

@@ -202,7 +202,6 @@ where
     F: FnOnce(&mut gpui::ViewContext<V>) -> D,
 {
     const TITLEBAR_HEIGHT: f32 = 28.;
-    // let active = cx.window_is_active(cx.window_id());
 
     Flex::column()
         .with_child(

crates/util/src/paths.rs 🔗

@@ -33,6 +33,7 @@ pub mod legacy {
 pub trait PathExt {
     fn compact(&self) -> PathBuf;
     fn icon_suffix(&self) -> Option<&str>;
+    fn extension_or_hidden_file_name(&self) -> Option<&str>;
 }
 
 impl<T: AsRef<Path>> PathExt for T {
@@ -60,6 +61,7 @@ impl<T: AsRef<Path>> PathExt for T {
         }
     }
 
+    /// Returns a suffix of the path that is used to determine which file icon to use
     fn icon_suffix(&self) -> Option<&str> {
         let file_name = self.as_ref().file_name()?.to_str()?;
 
@@ -69,8 +71,16 @@ impl<T: AsRef<Path>> PathExt for T {
 
         self.as_ref()
             .extension()
-            .map(|extension| extension.to_str())
-            .flatten()
+            .and_then(|extension| extension.to_str())
+    }
+
+    /// Returns a file's extension or, if the file is hidden, its name without the leading dot
+    fn extension_or_hidden_file_name(&self) -> Option<&str> {
+        if let Some(extension) = self.as_ref().extension() {
+            return extension.to_str();
+        }
+
+        self.as_ref().file_name()?.to_str()?.split('.').last()
     }
 }
 
@@ -294,7 +304,7 @@ mod tests {
     }
 
     #[test]
-    fn test_path_suffix() {
+    fn test_icon_suffix() {
         // No dots in name
         let path = Path::new("/a/b/c/file_name.rs");
         assert_eq!(path.icon_suffix(), Some("rs"));
@@ -315,4 +325,27 @@ mod tests {
         let path = Path::new("/a/b/c/.eslintrc.js");
         assert_eq!(path.icon_suffix(), Some("eslintrc.js"));
     }
+
+    #[test]
+    fn test_extension_or_hidden_file_name() {
+        // No dots in name
+        let path = Path::new("/a/b/c/file_name.rs");
+        assert_eq!(path.extension_or_hidden_file_name(), Some("rs"));
+
+        // Single dot in name
+        let path = Path::new("/a/b/c/file.name.rs");
+        assert_eq!(path.extension_or_hidden_file_name(), Some("rs"));
+
+        // Multiple dots in name
+        let path = Path::new("/a/b/c/long.file.name.rs");
+        assert_eq!(path.extension_or_hidden_file_name(), Some("rs"));
+
+        // Hidden file, no extension
+        let path = Path::new("/a/b/c/.gitignore");
+        assert_eq!(path.extension_or_hidden_file_name(), Some("gitignore"));
+
+        // Hidden file, with extension
+        let path = Path::new("/a/b/c/.eslintrc.js");
+        assert_eq!(path.extension_or_hidden_file_name(), Some("js"));
+    }
 }

crates/vim/src/editor_events.rs 🔗

@@ -10,7 +10,7 @@ pub fn init(cx: &mut AppContext) {
 
 fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
     if let Some(previously_active_editor) = Vim::read(cx).active_editor.clone() {
-        cx.update_window(previously_active_editor.window_id(), |cx| {
+        previously_active_editor.window().update(cx, |cx| {
             Vim::update(cx, |vim, cx| {
                 vim.update_active_editor(cx, |previously_active_editor, cx| {
                     vim.unhook_vim_settings(previously_active_editor, cx)
@@ -19,7 +19,7 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
         });
     }
 
-    cx.update_window(editor.window_id(), |cx| {
+    editor.window().update(cx, |cx| {
         Vim::update(cx, |vim, cx| {
             vim.set_active_editor(editor.clone(), cx);
         });
@@ -27,7 +27,7 @@ fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
 }
 
 fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
-    cx.update_window(editor.window_id(), |cx| {
+    editor.window().update(cx, |cx| {
         Vim::update(cx, |vim, cx| {
             if let Some(previous_editor) = vim.active_editor.clone() {
                 if previous_editor == editor.clone() {
@@ -41,7 +41,7 @@ fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
 }
 
 fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
-    cx.update_window(editor.window_id(), |cx| {
+    editor.window().update(cx, |cx| {
         cx.update_default_global(|vim: &mut Vim, _| {
             if let Some(previous_editor) = vim.active_editor.clone() {
                 if previous_editor == editor.clone() {

crates/vim/src/mode_indicator.rs 🔗

@@ -20,7 +20,7 @@ impl ModeIndicator {
             if let Some(mode_indicator) = handle.upgrade(cx) {
                 match event {
                     VimEvent::ModeChanged { mode } => {
-                        cx.update_window(mode_indicator.window_id(), |cx| {
+                        mode_indicator.window().update(cx, |cx| {
                             mode_indicator.update(cx, move |mode_indicator, cx| {
                                 mode_indicator.set_mode(mode, cx);
                             })

crates/vim/src/test/vim_test_context.rs 🔗

@@ -85,8 +85,8 @@ impl<'a> VimTestContext<'a> {
     }
 
     pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
-        let window_id = self.window_id;
-        self.update_window(window_id, |cx| {
+        let window = self.window;
+        window.update(self.cx.cx.cx, |cx| {
             Vim::update(cx, |vim, cx| {
                 vim.switch_mode(mode, false, cx);
             })

crates/workspace/src/dock.rs 🔗

@@ -213,7 +213,7 @@ impl Dock {
     pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option<usize> {
         self.panel_entries.iter().position(|entry| {
             let panel = entry.panel.as_any();
-            cx.view_ui_name(panel.window_id(), panel.id()) == Some(ui_name)
+            cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name)
         })
     }
 
@@ -542,16 +542,15 @@ impl View for PanelButtons {
                                         tooltip_action.as_ref().map(|action| action.boxed_clone());
                                     move |_, this, cx| {
                                         if let Some(tooltip_action) = &tooltip_action {
-                                            let window_id = cx.window_id();
+                                            let window = cx.window();
                                             let view_id = this.workspace.id();
                                             let tooltip_action = tooltip_action.boxed_clone();
                                             cx.spawn(|_, mut cx| async move {
-                                                cx.dispatch_action(
-                                                    window_id,
+                                                window.dispatch_action(
                                                     view_id,
                                                     &*tooltip_action,
-                                                )
-                                                .ok();
+                                                    &mut cx,
+                                                );
                                             })
                                             .detach();
                                         }

crates/workspace/src/item.rs 🔗

@@ -6,6 +6,7 @@ use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
 use anyhow::Result;
 use client::{proto, Client};
 use gpui::geometry::vector::Vector2F;
+use gpui::AnyWindowHandle;
 use gpui::{
     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
     ViewContext, ViewHandle, WeakViewHandle, WindowContext,
@@ -250,7 +251,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
     fn workspace_deactivated(&self, cx: &mut WindowContext);
     fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
     fn id(&self) -> usize;
-    fn window_id(&self) -> usize;
+    fn window(&self) -> AnyWindowHandle;
     fn as_any(&self) -> &AnyViewHandle;
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
@@ -280,7 +281,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
 
 pub trait WeakItemHandle {
     fn id(&self) -> usize;
-    fn window_id(&self) -> usize;
+    fn window(&self) -> AnyWindowHandle;
     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
 }
 
@@ -542,8 +543,8 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         self.id()
     }
 
-    fn window_id(&self) -> usize {
-        self.window_id()
+    fn window(&self) -> AnyWindowHandle {
+        AnyViewHandle::window(self)
     }
 
     fn as_any(&self) -> &AnyViewHandle {
@@ -649,8 +650,8 @@ impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
         self.id()
     }
 
-    fn window_id(&self) -> usize {
-        self.window_id()
+    fn window(&self) -> AnyWindowHandle {
+        self.window()
     }
 
     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {

crates/workspace/src/pane.rs 🔗

@@ -25,8 +25,8 @@ use gpui::{
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
     Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-    LayoutContext, ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle, WindowContext,
+    LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext,
+    ViewHandle, WeakViewHandle, WindowContext,
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
@@ -1900,7 +1900,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut ViewContext<V>,
+        cx: &mut PaintContext<V>,
     ) -> Self::PaintState {
         let background = theme::current(cx).editor.background;
 
@@ -1917,8 +1917,8 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
                 gpui::platform::MouseButton::Left,
                 move |_, _: &mut V, cx| {
-                    let window_id = cx.window_id();
-                    cx.app_context().focus(window_id, Some(child_view_id))
+                    let window = cx.window();
+                    cx.app_context().focus(window, Some(child_view_id))
                 },
             ),
         );

crates/workspace/src/pane/dragged_item_receiver.rs 🔗

@@ -28,11 +28,11 @@ where
     let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
     let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
         drag_and_drop
-            .currently_dragged::<DraggedItem>(cx.window_id())
+            .currently_dragged::<DraggedItem>(cx.window())
             .map(|(drag_position, _)| drag_position)
             .or_else(|| {
                 drag_and_drop
-                    .currently_dragged::<ProjectEntryId>(cx.window_id())
+                    .currently_dragged::<ProjectEntryId>(cx.window())
                     .map(|(drag_position, _)| drag_position)
             })
     } else {
@@ -91,10 +91,10 @@ where
                 let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
 
                 if drag_and_drop
-                    .currently_dragged::<DraggedItem>(cx.window_id())
+                    .currently_dragged::<DraggedItem>(cx.window())
                     .is_some()
                     || drag_and_drop
-                        .currently_dragged::<ProjectEntryId>(cx.window_id())
+                        .currently_dragged::<ProjectEntryId>(cx.window())
                         .is_some()
                 {
                     cx.notify();
@@ -122,11 +122,11 @@ pub fn handle_dropped_item<V: View>(
     }
     let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
     let action = if let Some((_, dragged_item)) =
-        drag_and_drop.currently_dragged::<DraggedItem>(cx.window_id())
+        drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
     {
         Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
     } else if let Some((_, project_entry)) =
-        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
+        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
     {
         Action::Open(*project_entry)
     } else {

crates/workspace/src/pane_group.rs 🔗

@@ -595,7 +595,7 @@ mod element {
         platform::{CursorStyle, MouseButton},
         scene::MouseDrag,
         AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
-        RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
+        PaintContext, RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
     };
 
     use crate::{
@@ -856,7 +856,7 @@ mod element {
             visible_bounds: RectF,
             remaining_space: &mut Self::LayoutState,
             view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
+            cx: &mut PaintContext<Workspace>,
         ) -> Self::PaintState {
             let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
             let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

crates/workspace/src/searchable.rs 🔗

@@ -235,7 +235,7 @@ impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
 
 impl PartialEq for Box<dyn SearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {
-        self.id() == other.id() && self.window_id() == other.window_id()
+        self.id() == other.id() && self.window() == other.window()
     }
 }
 
@@ -259,7 +259,7 @@ impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
 
 impl PartialEq for Box<dyn WeakSearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {
-        self.id() == other.id() && self.window_id() == other.window_id()
+        self.id() == other.id() && self.window() == other.window()
     }
 }
 
@@ -267,6 +267,6 @@ impl Eq for Box<dyn WeakSearchableItemHandle> {}
 
 impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        (self.id(), self.window_id()).hash(state)
+        (self.id(), self.window().id()).hash(state)
     }
 }

crates/workspace/src/status_bar.rs 🔗

@@ -8,8 +8,8 @@ use gpui::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription,
-    View, ViewContext, ViewHandle, WindowContext,
+    AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
+    Subscription, View, ViewContext, ViewHandle, WindowContext,
 };
 
 pub trait StatusItemView: View {
@@ -231,7 +231,7 @@ impl Element<StatusBar> for StatusBarElement {
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut StatusBar,
-        cx: &mut ViewContext<StatusBar>,
+        cx: &mut PaintContext<StatusBar>,
     ) -> Self::PaintState {
         let origin_y = bounds.upper_right().y();
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();

crates/workspace/src/workspace.rs 🔗

@@ -37,7 +37,7 @@ use gpui::{
     },
     AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
     ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle, WindowContext,
+    WeakViewHandle, WindowContext, WindowHandle,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 use itertools::Itertools;
@@ -753,7 +753,7 @@ impl Workspace {
     fn new_local(
         abs_paths: Vec<PathBuf>,
         app_state: Arc<AppState>,
-        requesting_window_id: Option<usize>,
+        requesting_window: Option<WindowHandle<Workspace>>,
         cx: &mut AppContext,
     ) -> Task<(
         WeakViewHandle<Workspace>,
@@ -797,55 +797,60 @@ impl Workspace {
                 DB.next_id().await.unwrap_or(0)
             };
 
-            let window = requesting_window_id.and_then(|window_id| {
-                cx.update(|cx| {
-                    cx.replace_root_view(window_id, |cx| {
-                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-                    })
-                })
-            });
-            let window = window.unwrap_or_else(|| {
-                let window_bounds_override = window_bounds_env_override(&cx);
-                let (bounds, display) = if let Some(bounds) = window_bounds_override {
-                    (Some(bounds), None)
-                } else {
-                    serialized_workspace
-                        .as_ref()
-                        .and_then(|serialized_workspace| {
-                            let display = serialized_workspace.display?;
-                            let mut bounds = serialized_workspace.bounds?;
-
-                            // Stored bounds are relative to the containing display.
-                            // So convert back to global coordinates if that screen still exists
-                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                if let Some(screen) = cx.platform().screen_by_id(display) {
-                                    let screen_bounds = screen.bounds();
-                                    window_bounds.set_origin_x(
-                                        window_bounds.origin_x() + screen_bounds.origin_x(),
-                                    );
-                                    window_bounds.set_origin_y(
-                                        window_bounds.origin_y() + screen_bounds.origin_y(),
-                                    );
-                                    bounds = WindowBounds::Fixed(window_bounds);
-                                } else {
-                                    // Screen no longer exists. Return none here.
-                                    return None;
+            let window = if let Some(window) = requesting_window {
+                window.replace_root(&mut cx, |cx| {
+                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+                });
+                window
+            } else {
+                {
+                    let window_bounds_override = window_bounds_env_override(&cx);
+                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
+                        (Some(bounds), None)
+                    } else {
+                        serialized_workspace
+                            .as_ref()
+                            .and_then(|serialized_workspace| {
+                                let display = serialized_workspace.display?;
+                                let mut bounds = serialized_workspace.bounds?;
+
+                                // Stored bounds are relative to the containing display.
+                                // So convert back to global coordinates if that screen still exists
+                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
+                                    if let Some(screen) = cx.platform().screen_by_id(display) {
+                                        let screen_bounds = screen.bounds();
+                                        window_bounds.set_origin_x(
+                                            window_bounds.origin_x() + screen_bounds.origin_x(),
+                                        );
+                                        window_bounds.set_origin_y(
+                                            window_bounds.origin_y() + screen_bounds.origin_y(),
+                                        );
+                                        bounds = WindowBounds::Fixed(window_bounds);
+                                    } else {
+                                        // Screen no longer exists. Return none here.
+                                        return None;
+                                    }
                                 }
-                            }
 
-                            Some((bounds, display))
-                        })
-                        .unzip()
-                };
-
-                // Use the serialized workspace to construct the new window
-                cx.add_window(
-                    (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
-                    |cx| {
-                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-                    },
-                )
-            });
+                                Some((bounds, display))
+                            })
+                            .unzip()
+                    };
+
+                    // Use the serialized workspace to construct the new window
+                    cx.add_window(
+                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+                        |cx| {
+                            Workspace::new(
+                                workspace_id,
+                                project_handle.clone(),
+                                app_state.clone(),
+                                cx,
+                            )
+                        },
+                    )
+                }
+            };
 
             // We haven't yielded the main thread since obtaining the window handle,
             // so the window exists.
@@ -1231,14 +1236,14 @@ impl Workspace {
 
     pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
         cx.spawn(|mut cx| async move {
-            let id = cx
-                .window_ids()
+            let window = cx
+                .windows()
                 .into_iter()
-                .find(|&id| cx.window_is_active(id));
-            if let Some(id) = id {
+                .find(|window| window.is_active(&cx).unwrap_or(false));
+            if let Some(window) = window {
                 //This can only get called when the window's project connection has been lost
                 //so we don't need to prompt the user for anything and instead just close the window
-                cx.remove_window(id);
+                window.remove(&mut cx);
             }
         })
         .detach();
@@ -1249,11 +1254,11 @@ impl Workspace {
         _: &CloseWindow,
         cx: &mut ViewContext<Self>,
     ) -> Option<Task<Result<()>>> {
-        let window_id = cx.window_id();
+        let window = cx.window();
         let prepare = self.prepare_to_close(false, cx);
         Some(cx.spawn(|_, mut cx| async move {
             if prepare.await? {
-                cx.remove_window(window_id);
+                window.remove(&mut cx);
             }
             Ok(())
         }))
@@ -1265,13 +1270,13 @@ impl Workspace {
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<bool>> {
         let active_call = self.active_call().cloned();
-        let window_id = cx.window_id();
+        let window = cx.window();
 
         cx.spawn(|this, mut cx| async move {
             let workspace_count = cx
-                .window_ids()
+                .windows()
                 .into_iter()
-                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+                .filter(|window| window.root_is::<Workspace>())
                 .count();
 
             if let Some(active_call) = active_call {
@@ -1279,11 +1284,11 @@ impl Workspace {
                     && workspace_count == 1
                     && active_call.read_with(&cx, |call, _| call.room().is_some())
                 {
-                    let answer = cx.prompt(
-                        window_id,
+                    let answer = window.prompt(
                         PromptLevel::Warning,
                         "Do you want to leave the current call?",
                         &["Close window and hang up", "Cancel"],
+                        &mut cx,
                     );
 
                     if let Some(mut answer) = answer {
@@ -1389,7 +1394,7 @@ impl Workspace {
         paths: Vec<PathBuf>,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
-        let window_id = cx.window_id();
+        let window = cx.window().downcast::<Self>();
         let is_remote = self.project.read(cx).is_remote();
         let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
         let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
@@ -1401,15 +1406,15 @@ impl Workspace {
         let app_state = self.app_state.clone();
 
         cx.spawn(|_, mut cx| async move {
-            let window_id_to_replace = if let Some(close_task) = close_task {
+            let window_to_replace = if let Some(close_task) = close_task {
                 if !close_task.await? {
                     return Ok(());
                 }
-                Some(window_id)
+                window
             } else {
                 None
             };
-            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
+            cx.update(|cx| open_paths(&paths, &app_state, window_to_replace, cx))
                 .await?;
             Ok(())
         })
@@ -3180,7 +3185,7 @@ impl Workspace {
             let left_visible = left_dock.is_open();
             let left_active_panel = left_dock.visible_panel().and_then(|panel| {
                 Some(
-                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
+                    cx.view_ui_name(panel.as_any().window(), panel.id())?
                         .to_string(),
                 )
             });
@@ -3193,7 +3198,7 @@ impl Workspace {
             let right_visible = right_dock.is_open();
             let right_active_panel = right_dock.visible_panel().and_then(|panel| {
                 Some(
-                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
+                    cx.view_ui_name(panel.as_any().window(), panel.id())?
                         .to_string(),
                 )
             });
@@ -3206,7 +3211,7 @@ impl Workspace {
             let bottom_visible = bottom_dock.is_open();
             let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
                 Some(
-                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
+                    cx.view_ui_name(panel.as_any().window(), panel.id())?
                         .to_string(),
                 )
             });
@@ -3841,9 +3846,9 @@ pub fn activate_workspace_for_project(
     cx: &mut AsyncAppContext,
     predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
 ) -> Option<WeakViewHandle<Workspace>> {
-    for window_id in cx.window_ids() {
-        let handle = cx
-            .update_window(window_id, |cx| {
+    for window in cx.windows() {
+        let handle = window
+            .update(cx, |cx| {
                 if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
                     let project = workspace_handle.read(cx).project.clone();
                     if project.update(cx, &predicate) {
@@ -3870,7 +3875,7 @@ pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
 pub fn open_paths(
     abs_paths: &[PathBuf],
     app_state: &Arc<AppState>,
-    requesting_window_id: Option<usize>,
+    requesting_window: Option<WindowHandle<Workspace>>,
     cx: &mut AppContext,
 ) -> Task<
     Result<(
@@ -3898,7 +3903,7 @@ pub fn open_paths(
         } else {
             Ok(cx
                 .update(|cx| {
-                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
+                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
                 })
                 .await)
         }
@@ -3959,18 +3964,23 @@ pub fn join_remote_project(
 ) -> Task<Result<()>> {
     cx.spawn(|mut cx| async move {
         let existing_workspace = cx
-            .window_ids()
+            .windows()
             .into_iter()
-            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
-            .find(|workspace| {
-                cx.read_window(workspace.window_id(), |cx| {
-                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
+            .find_map(|window| {
+                window.downcast::<Workspace>().and_then(|window| {
+                    window.read_root_with(&cx, |workspace, cx| {
+                        if workspace.project().read(cx).remote_id() == Some(project_id) {
+                            Some(cx.handle().downgrade())
+                        } else {
+                            None
+                        }
+                    })
                 })
-                .unwrap_or(false)
-            });
+            })
+            .flatten();
 
         let workspace = if let Some(existing_workspace) = existing_workspace {
-            existing_workspace.downgrade()
+            existing_workspace
         } else {
             let active_call = cx.read(ActiveCall::global);
             let room = active_call
@@ -4009,7 +4019,7 @@ pub fn join_remote_project(
             workspace.downgrade()
         };
 
-        cx.activate_window(workspace.window_id());
+        workspace.window().activate(&mut cx);
         cx.platform().activate(true);
 
         workspace.update(&mut cx, |workspace, cx| {
@@ -4048,29 +4058,22 @@ pub fn join_remote_project(
 pub fn restart(_: &Restart, cx: &mut AppContext) {
     let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
     cx.spawn(|mut cx| async move {
-        let mut workspaces = cx
-            .window_ids()
+        let mut workspace_windows = cx
+            .windows()
             .into_iter()
-            .filter_map(|window_id| {
-                Some(
-                    cx.root_view(window_id)?
-                        .clone()
-                        .downcast::<Workspace>()?
-                        .downgrade(),
-                )
-            })
+            .filter_map(|window| window.downcast::<Workspace>())
             .collect::<Vec<_>>();
 
         // If multiple windows have unsaved changes, and need a save prompt,
         // prompt in the active window before switching to a different window.
-        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
+        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
 
-        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
-            let answer = cx.prompt(
-                workspace.window_id(),
+        if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
+            let answer = window.prompt(
                 PromptLevel::Info,
                 "Are you sure you want to restart?",
                 &["Restart", "Cancel"],
+                &mut cx,
             );
 
             if let Some(mut answer) = answer {
@@ -4082,14 +4085,13 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
         }
 
         // If the user cancels any save prompt, then keep the app open.
-        for workspace in workspaces {
-            if !workspace
-                .update(&mut cx, |workspace, cx| {
-                    workspace.prepare_to_close(true, cx)
-                })?
-                .await?
-            {
-                return Ok(());
+        for window in workspace_windows {
+            if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
+                workspace.prepare_to_close(true, cx)
+            }) {
+                if !close.await? {
+                    return Ok(());
+                }
             }
         }
         cx.platform().restart();
@@ -4214,17 +4216,11 @@ mod tests {
                     .map(|e| e.id)
             );
         });
-        assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
-            Some("one.txt — root1")
-        );
+        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1"));
 
         // Add a second item to a non-empty pane
         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
-        assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
-            Some("two.txt — root1")
-        );
+        assert_eq!(window.current_title(cx).as_deref(), Some("two.txt — root1"));
         project.read_with(cx, |project, cx| {
             assert_eq!(
                 project.active_entry(),
@@ -4240,10 +4236,7 @@ mod tests {
         })
         .await
         .unwrap();
-        assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
-            Some("one.txt — root1")
-        );
+        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root1"));
         project.read_with(cx, |project, cx| {
             assert_eq!(
                 project.active_entry(),
@@ -4261,16 +4254,13 @@ mod tests {
             .await
             .unwrap();
         assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
+            window.current_title(cx).as_deref(),
             Some("one.txt — root1, root2")
         );
 
         // Remove a project folder
         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-        assert_eq!(
-            cx.current_window_title(window.window_id()).as_deref(),
-            Some("one.txt — root2")
-        );
+        assert_eq!(window.current_title(cx).as_deref(), Some("one.txt — root2"));
     }
 
     #[gpui::test]
@@ -4304,9 +4294,9 @@ mod tests {
         });
         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
         cx.foreground().run_until_parked();
-        cx.simulate_prompt_answer(window.window_id(), 2 /* cancel */);
+        window.simulate_prompt_answer(2, cx); // cancel
         cx.foreground().run_until_parked();
-        assert!(!cx.has_pending_prompt(window.window_id()));
+        assert!(!window.has_pending_prompt(cx));
         assert!(!task.await.unwrap());
     }
 
@@ -4365,10 +4355,10 @@ mod tests {
             assert_eq!(pane.items_len(), 4);
             assert_eq!(pane.active_item().unwrap().id(), item1.id());
         });
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(window.has_pending_prompt(cx));
 
         // Confirm saving item 1.
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        window.simulate_prompt_answer(0, cx);
         cx.foreground().run_until_parked();
 
         // Item 1 is saved. There's a prompt to save item 3.
@@ -4379,10 +4369,10 @@ mod tests {
             assert_eq!(pane.items_len(), 3);
             assert_eq!(pane.active_item().unwrap().id(), item3.id());
         });
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(window.has_pending_prompt(cx));
 
         // Cancel saving item 3.
-        cx.simulate_prompt_answer(window.window_id(), 1);
+        window.simulate_prompt_answer(1, cx);
         cx.foreground().run_until_parked();
 
         // Item 3 is reloaded. There's a prompt to save item 4.
@@ -4393,10 +4383,10 @@ mod tests {
             assert_eq!(pane.items_len(), 2);
             assert_eq!(pane.active_item().unwrap().id(), item4.id());
         });
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(window.has_pending_prompt(cx));
 
         // Confirm saving item 4.
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        window.simulate_prompt_answer(0, cx);
         cx.foreground().run_until_parked();
 
         // There's a prompt for a path for item 4.
@@ -4499,7 +4489,7 @@ mod tests {
                 &[ProjectEntryId::from_proto(0)]
             );
         });
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        window.simulate_prompt_answer(0, cx);
 
         cx.foreground().run_until_parked();
         left_pane.read_with(cx, |pane, cx| {
@@ -4508,7 +4498,7 @@ mod tests {
                 &[ProjectEntryId::from_proto(2)]
             );
         });
-        cx.simulate_prompt_answer(window.window_id(), 0);
+        window.simulate_prompt_answer(0, cx);
 
         cx.foreground().run_until_parked();
         close.await.unwrap();
@@ -4547,7 +4537,7 @@ mod tests {
         });
 
         // Deactivating the window saves the file.
-        cx.simulate_window_activation(None);
+        window.simulate_deactivation(cx);
         deterministic.run_until_parked();
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
 
@@ -4568,12 +4558,12 @@ mod tests {
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
 
         // Deactivating the window still saves the file.
-        cx.simulate_window_activation(Some(window.window_id()));
+        window.simulate_activation(cx);
         item.update(cx, |item, cx| {
             cx.focus_self();
             item.is_dirty = true;
         });
-        cx.simulate_window_activation(None);
+        window.simulate_deactivation(cx);
 
         deterministic.run_until_parked();
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
@@ -4610,7 +4600,7 @@ mod tests {
         pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
             .await
             .unwrap();
-        assert!(!cx.has_pending_prompt(window.window_id()));
+        assert!(!window.has_pending_prompt(cx));
         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.
@@ -4631,7 +4621,7 @@ mod tests {
         let _close_items =
             pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
         deterministic.run_until_parked();
-        assert!(cx.has_pending_prompt(window.window_id()));
+        assert!(window.has_pending_prompt(cx));
         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
     }
 

crates/zed/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.99.0"
+version = "0.100.0"
 publish = false
 
 [lib]

crates/zed/src/languages/bash/config.toml 🔗

@@ -1,5 +1,5 @@
 name = "Shell Script"
-path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin"]
+path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile"]
 line_comment = "# "
 first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
 brackets = [

crates/zed/src/languages/rust.rs 🔗

@@ -102,7 +102,7 @@ impl LspAdapter for RustLspAdapter {
         Some("rust-analyzer/flycheck".into())
     }
 
-    async fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
+    fn process_diagnostics(&self, params: &mut lsp::PublishDiagnosticsParams) {
         lazy_static! {
             static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
         }
@@ -310,7 +310,7 @@ mod tests {
                 },
             ],
         };
-        RustLspAdapter.process_diagnostics(&mut params).await;
+        RustLspAdapter.process_diagnostics(&mut params);
 
         assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
 

crates/zed/src/main.rs 🔗

@@ -16,7 +16,7 @@ use futures::{
     channel::{mpsc, oneshot},
     FutureExt, SinkExt, StreamExt,
 };
-use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task, ViewContext};
+use gpui::{Action, App, AppContext, AssetSource, AsyncAppContext, Task};
 use isahc::{config::Configurable, Request};
 use language::{LanguageRegistry, Point};
 use log::LevelFilter;
@@ -45,7 +45,6 @@ use std::{
     time::{Duration, SystemTime, UNIX_EPOCH},
 };
 use sum_tree::Bias;
-use terminal_view::{get_working_directory, TerminalSettings, TerminalView};
 use util::{
     channel::ReleaseChannel,
     http::{self, HttpClient},
@@ -58,7 +57,7 @@ use fs::RealFs;
 #[cfg(debug_assertions)]
 use staff_mode::StaffMode;
 use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
-use workspace::{item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace};
+use workspace::AppState;
 use zed::{
     assets::Assets,
     build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
@@ -660,6 +659,10 @@ fn load_embedded_fonts(app: &App) {
     let embedded_fonts = Mutex::new(Vec::new());
     smol::block_on(app.background().scoped(|scope| {
         for font_path in &font_paths {
+            if !font_path.ends_with(".ttf") {
+                continue;
+            }
+
             scope.spawn(async {
                 let font_path = &*font_path;
                 let font_bytes = Assets.load(font_path).unwrap().to_vec();
@@ -927,35 +930,6 @@ async fn handle_cli_connection(
     }
 }
 
-pub fn dock_default_item_factory(
-    workspace: &mut Workspace,
-    cx: &mut ViewContext<Workspace>,
-) -> Option<Box<dyn ItemHandle>> {
-    let strategy = settings::get::<TerminalSettings>(cx)
-        .working_directory
-        .clone();
-    let working_directory = get_working_directory(workspace, cx, strategy);
-
-    let window_id = cx.window_id();
-    let terminal = workspace
-        .project()
-        .update(cx, |project, cx| {
-            project.create_terminal(working_directory, window_id, cx)
-        })
-        .notify_err(workspace, cx)?;
-
-    let terminal_view = cx.add_view(|cx| {
-        TerminalView::new(
-            terminal,
-            workspace.weak_handle(),
-            workspace.database_id(),
-            cx,
-        )
-    });
-
-    Some(Box::new(terminal_view))
-}
-
 pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
     &[
         ("Go to file", &file_finder::Toggle),

crates/zed/src/zed.rs 🔗

@@ -165,13 +165,12 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
         move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
             let app_state = workspace.app_state().clone();
             let markdown = app_state.languages.language_for_name("JSON");
-            let window_id = cx.window_id();
+            let window = cx.window();
             cx.spawn(|workspace, mut cx| async move {
                 let markdown = markdown.await.log_err();
-                let content = to_string_pretty(
-                    &cx.debug_elements(window_id)
-                        .ok_or_else(|| anyhow!("could not debug elements for {window_id}"))?,
-                )
+                let content = to_string_pretty(&window.debug_elements(&cx).ok_or_else(|| {
+                    anyhow!("could not debug elements for window {}", window.id())
+                })?)
                 .unwrap();
                 workspace
                     .update(&mut cx, |workspace, cx| {
@@ -405,29 +404,22 @@ pub fn build_window_options(
 fn quit(_: &Quit, cx: &mut gpui::AppContext) {
     let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
     cx.spawn(|mut cx| async move {
-        let mut workspaces = cx
-            .window_ids()
+        let mut workspace_windows = cx
+            .windows()
             .into_iter()
-            .filter_map(|window_id| {
-                Some(
-                    cx.root_view(window_id)?
-                        .clone()
-                        .downcast::<Workspace>()?
-                        .downgrade(),
-                )
-            })
+            .filter_map(|window| window.downcast::<Workspace>())
             .collect::<Vec<_>>();
 
         // If multiple windows have unsaved changes, and need a save prompt,
         // prompt in the active window before switching to a different window.
-        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
+        workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
 
-        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
-            let answer = cx.prompt(
-                workspace.window_id(),
+        if let (true, Some(window)) = (should_confirm, workspace_windows.first().copied()) {
+            let answer = window.prompt(
                 PromptLevel::Info,
                 "Are you sure you want to quit?",
                 &["Quit", "Cancel"],
+                &mut cx,
             );
 
             if let Some(mut answer) = answer {
@@ -439,14 +431,13 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
         }
 
         // If the user cancels any save prompt, then keep the app open.
-        for workspace in workspaces {
-            if !workspace
-                .update(&mut cx, |workspace, cx| {
-                    workspace.prepare_to_close(true, cx)
-                })?
-                .await?
-            {
-                return Ok(());
+        for window in workspace_windows {
+            if let Some(close) = window.update_root(&mut cx, |workspace, cx| {
+                workspace.prepare_to_close(false, cx)
+            }) {
+                if close.await? {
+                    return Ok(());
+                }
             }
         }
         cx.platform().quit();
@@ -723,8 +714,8 @@ mod tests {
     use editor::{scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
     use fs::{FakeFs, Fs};
     use gpui::{
-        actions, elements::Empty, executor::Deterministic, Action, AnyElement, AppContext,
-        AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
+        actions, elements::Empty, executor::Deterministic, Action, AnyElement, AnyWindowHandle,
+        AppContext, AssetSource, Element, Entity, TestAppContext, View, ViewHandle,
     };
     use language::LanguageRegistry;
     use node_runtime::NodeRuntime;
@@ -781,17 +772,13 @@ mod tests {
         })
         .await
         .unwrap();
-        assert_eq!(cx.window_ids().len(), 1);
+        assert_eq!(cx.windows().len(), 1);
 
         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
             .await
             .unwrap();
-        assert_eq!(cx.window_ids().len(), 1);
-        let workspace_1 = cx
-            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
-            .unwrap()
-            .downcast::<Workspace>()
-            .unwrap();
+        assert_eq!(cx.windows().len(), 1);
+        let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
         workspace_1.update(cx, |workspace, cx| {
             assert_eq!(workspace.worktrees(cx).count(), 2);
             assert!(workspace.left_dock().read(cx).is_open());
@@ -808,27 +795,22 @@ mod tests {
         })
         .await
         .unwrap();
-        assert_eq!(cx.window_ids().len(), 2);
+        assert_eq!(cx.windows().len(), 2);
 
         // Replace existing windows
-        let window_id = cx.window_ids()[0];
+        let window = cx.windows()[0].downcast::<Workspace>().unwrap();
         cx.update(|cx| {
             open_paths(
                 &[PathBuf::from("/root/c"), PathBuf::from("/root/d")],
                 &app_state,
-                Some(window_id),
+                Some(window),
                 cx,
             )
         })
         .await
         .unwrap();
-        assert_eq!(cx.window_ids().len(), 2);
-        let workspace_1 = cx
-            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
-            .unwrap()
-            .clone()
-            .downcast::<Workspace>()
-            .unwrap();
+        assert_eq!(cx.windows().len(), 2);
+        let workspace_1 = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
         workspace_1.update(cx, |workspace, cx| {
             assert_eq!(
                 workspace
@@ -854,14 +836,11 @@ mod tests {
         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
             .await
             .unwrap();
-        assert_eq!(cx.window_ids().len(), 1);
+        assert_eq!(cx.windows().len(), 1);
 
         // When opening the workspace, the window is not in a edited state.
-        let workspace = cx
-            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
-            .unwrap()
-            .downcast::<Workspace>()
-            .unwrap();
+        let window = cx.windows()[0].downcast::<Workspace>().unwrap();
+        let workspace = window.root(cx);
         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
         let editor = workspace.read_with(cx, |workspace, cx| {
             workspace
@@ -870,19 +849,19 @@ mod tests {
                 .downcast::<Editor>()
                 .unwrap()
         });
-        assert!(!cx.is_window_edited(workspace.window_id()));
+        assert!(!window.is_edited(cx));
 
         // Editing a buffer marks the window as edited.
         editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
-        assert!(cx.is_window_edited(workspace.window_id()));
+        assert!(window.is_edited(cx));
 
         // Undoing the edit restores the window's edited state.
         editor.update(cx, |editor, cx| editor.undo(&Default::default(), cx));
-        assert!(!cx.is_window_edited(workspace.window_id()));
+        assert!(!window.is_edited(cx));
 
         // Redoing the edit marks the window as edited again.
         editor.update(cx, |editor, cx| editor.redo(&Default::default(), cx));
-        assert!(cx.is_window_edited(workspace.window_id()));
+        assert!(window.is_edited(cx));
 
         // Closing the item restores the window's edited state.
         let close = pane.update(cx, |pane, cx| {
@@ -890,9 +869,10 @@ mod tests {
             pane.close_active_item(&Default::default(), cx).unwrap()
         });
         executor.run_until_parked();
-        cx.simulate_prompt_answer(workspace.window_id(), 1);
+
+        window.simulate_prompt_answer(1, cx);
         close.await.unwrap();
-        assert!(!cx.is_window_edited(workspace.window_id()));
+        assert!(!window.is_edited(cx));
 
         // Opening the buffer again doesn't impact the window's edited state.
         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
@@ -905,22 +885,22 @@ mod tests {
                 .downcast::<Editor>()
                 .unwrap()
         });
-        assert!(!cx.is_window_edited(workspace.window_id()));
+        assert!(!window.is_edited(cx));
 
         // Editing the buffer marks the window as edited.
         editor.update(cx, |editor, cx| editor.insert("EDIT", cx));
-        assert!(cx.is_window_edited(workspace.window_id()));
+        assert!(window.is_edited(cx));
 
         // Ensure closing the window via the mouse gets preempted due to the
         // buffer having unsaved changes.
-        assert!(!cx.simulate_window_close(workspace.window_id()));
+        assert!(!window.simulate_close(cx));
         executor.run_until_parked();
-        assert_eq!(cx.window_ids().len(), 1);
+        assert_eq!(cx.windows().len(), 1);
 
         // The window is successfully closed after the user dismisses the prompt.
-        cx.simulate_prompt_answer(workspace.window_id(), 1);
+        window.simulate_prompt_answer(1, cx);
         executor.run_until_parked();
-        assert_eq!(cx.window_ids().len(), 0);
+        assert_eq!(cx.windows().len(), 0);
     }
 
     #[gpui::test]
@@ -933,12 +913,13 @@ mod tests {
         })
         .await;
 
-        let window_id = *cx.window_ids().first().unwrap();
-        let workspace = cx
-            .read_window(window_id, |cx| cx.root_view().clone())
+        let window = cx
+            .windows()
+            .first()
             .unwrap()
             .downcast::<Workspace>()
             .unwrap();
+        let workspace = window.root(cx);
 
         let editor = workspace.update(cx, |workspace, cx| {
             workspace
@@ -981,9 +962,8 @@ mod tests {
             .await;
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
-        let workspace = cx
-            .add_window(|cx| Workspace::test_new(project, cx))
-            .root(cx);
+        let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+        let workspace = window.root(cx);
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -1104,12 +1084,8 @@ mod tests {
         cx.update(|cx| open_paths(&[PathBuf::from("/dir1/")], &app_state, None, cx))
             .await
             .unwrap();
-        assert_eq!(cx.window_ids().len(), 1);
-        let workspace = cx
-            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
-            .unwrap()
-            .downcast::<Workspace>()
-            .unwrap();
+        assert_eq!(cx.windows().len(), 1);
+        let workspace = cx.windows()[0].downcast::<Workspace>().unwrap().root(cx);
 
         #[track_caller]
         fn assert_project_panel_selection(
@@ -1297,7 +1273,6 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
 
         // Open a file within an existing worktree.
         workspace
@@ -1323,7 +1298,7 @@ mod tests {
         cx.read(|cx| assert!(editor.is_dirty(cx)));
 
         let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx));
-        cx.simulate_prompt_answer(window_id, 0);
+        window.simulate_prompt_answer(0, cx);
         save_task.await.unwrap();
         editor.read_with(cx, |editor, cx| {
             assert!(!editor.is_dirty(cx));
@@ -1340,11 +1315,10 @@ mod tests {
         project.update(cx, |project, _| project.languages().add(rust_lang()));
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
         let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap());
 
         // Create a new untitled buffer
-        cx.dispatch_action(window_id, NewFile);
+        cx.dispatch_action(window.into(), NewFile);
         let editor = workspace.read_with(cx, |workspace, cx| {
             workspace
                 .active_item(cx)
@@ -1399,7 +1373,7 @@ mod tests {
 
         // Open the same newly-created file in another pane item. The new editor should reuse
         // the same buffer.
-        cx.dispatch_action(window_id, NewFile);
+        cx.dispatch_action(window.into(), NewFile);
         workspace
             .update(cx, |workspace, cx| {
                 workspace.split_and_clone(
@@ -1435,10 +1409,9 @@ mod tests {
         project.update(cx, |project, _| project.languages().add(rust_lang()));
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
 
         // Create a new untitled buffer
-        cx.dispatch_action(window_id, NewFile);
+        cx.dispatch_action(window.into(), NewFile);
         let editor = workspace.read_with(cx, |workspace, cx| {
             workspace
                 .active_item(cx)
@@ -1488,7 +1461,6 @@ mod tests {
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
         let workspace = window.root(cx);
-        let window_id = window.window_id();
 
         let entries = cx.read(|cx| workspace.file_project_paths(cx));
         let file1 = entries[0].clone();
@@ -1510,7 +1482,7 @@ mod tests {
             (editor.downgrade(), buffer)
         });
 
-        cx.dispatch_action(window_id, pane::SplitRight);
+        cx.dispatch_action(window.into(), pane::SplitRight);
         let editor_2 = cx.update(|cx| {
             let pane_2 = workspace.read(cx).active_pane().clone();
             assert_ne!(pane_1, pane_2);
@@ -1520,7 +1492,7 @@ mod tests {
 
             pane2_item.downcast::<Editor>().unwrap().downgrade()
         });
-        cx.dispatch_action(window_id, workspace::CloseActiveItem);
+        cx.dispatch_action(window.into(), workspace::CloseActiveItem);
 
         cx.foreground().run_until_parked();
         workspace.read_with(cx, |workspace, _| {
@@ -1528,9 +1500,9 @@ mod tests {
             assert_eq!(workspace.active_pane(), &pane_1);
         });
 
-        cx.dispatch_action(window_id, workspace::CloseActiveItem);
+        cx.dispatch_action(window.into(), workspace::CloseActiveItem);
         cx.foreground().run_until_parked();
-        cx.simulate_prompt_answer(window_id, 1);
+        window.simulate_prompt_answer(1, cx);
         cx.foreground().run_until_parked();
 
         workspace.read_with(cx, |workspace, cx| {
@@ -2086,11 +2058,10 @@ mod tests {
         cx.foreground().run_until_parked();
 
         let window = cx.add_window(|_| TestView);
-        let window_id = window.window_id();
 
         // Test loading the keymap base at all
         assert_key_bindings_for(
-            window_id,
+            window.into(),
             cx,
             vec![("backspace", &A), ("k", &ActivatePreviousPane)],
             line!(),
@@ -2117,7 +2088,7 @@ mod tests {
         cx.foreground().run_until_parked();
 
         assert_key_bindings_for(
-            window_id,
+            window.into(),
             cx,
             vec![("backspace", &B), ("k", &ActivatePreviousPane)],
             line!(),
@@ -2140,7 +2111,7 @@ mod tests {
         cx.foreground().run_until_parked();
 
         assert_key_bindings_for(
-            window_id,
+            window.into(),
             cx,
             vec![("backspace", &B), ("[", &ActivatePrevItem)],
             line!(),
@@ -2148,7 +2119,7 @@ mod tests {
 
         #[track_caller]
         fn assert_key_bindings_for<'a>(
-            window_id: usize,
+            window: AnyWindowHandle,
             cx: &TestAppContext,
             actions: Vec<(&'static str, &'a dyn Action)>,
             line: u32,
@@ -2156,7 +2127,7 @@ mod tests {
             for (key, action) in actions {
                 // assert that...
                 assert!(
-                    cx.available_actions(window_id, 0)
+                    cx.available_actions(window, 0)
                         .into_iter()
                         .any(|(_, bound_action, b)| {
                             // action names match...
@@ -2257,11 +2228,10 @@ mod tests {
         cx.foreground().run_until_parked();
 
         let window = cx.add_window(|_| TestView);
-        let window_id = window.window_id();
 
         // Test loading the keymap base at all
         assert_key_bindings_for(
-            window_id,
+            window.into(),
             cx,
             vec![("backspace", &A), ("k", &ActivatePreviousPane)],
             line!(),
@@ -2287,7 +2257,12 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        assert_key_bindings_for(window_id, cx, vec![("k", &ActivatePreviousPane)], line!());
+        assert_key_bindings_for(
+            window.into(),
+            cx,
+            vec![("k", &ActivatePreviousPane)],
+            line!(),
+        );
 
         // Test modifying the base, while retaining the users keymap
         fs.save(
@@ -2305,11 +2280,11 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        assert_key_bindings_for(window_id, cx, vec![("[", &ActivatePrevItem)], line!());
+        assert_key_bindings_for(window.into(), cx, vec![("[", &ActivatePrevItem)], line!());
 
         #[track_caller]
         fn assert_key_bindings_for<'a>(
-            window_id: usize,
+            window: AnyWindowHandle,
             cx: &TestAppContext,
             actions: Vec<(&'static str, &'a dyn Action)>,
             line: u32,
@@ -2317,7 +2292,7 @@ mod tests {
             for (key, action) in actions {
                 // assert that...
                 assert!(
-                    cx.available_actions(window_id, 0)
+                    cx.available_actions(window, 0)
                         .into_iter()
                         .any(|(_, bound_action, b)| {
                             // action names match...
@@ -2351,6 +2326,11 @@ mod tests {
                     .unwrap()
                     .to_vec()
                     .into(),
+                Assets
+                    .load("fonts/plex/IBMPlexSans-Regular.ttf")
+                    .unwrap()
+                    .to_vec()
+                    .into(),
             ])
             .unwrap();
         let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());

styles/src/common.ts 🔗

@@ -4,6 +4,7 @@ export * from "./theme/theme_config"
 export { chroma }
 
 export const font_families = {
+    ui_sans: "IBM Plex Sans",
     sans: "Zed Sans",
     mono: "Zed Mono",
 }

styles/src/theme/create_theme.ts 🔗

@@ -1,4 +1,4 @@
-import { Scale, Color } from "chroma-js"
+import chroma, { Scale, Color } from "chroma-js"
 import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax"
 export { Syntax, ThemeSyntax, SyntaxHighlightStyle }
 import {
@@ -32,6 +32,7 @@ export interface Theme {
 
     players: Players
     syntax?: Partial<ThemeSyntax>
+    color_family: ColorFamily
 }
 
 export interface Meta {
@@ -69,6 +70,15 @@ export interface Players {
     "7": Player
 }
 
+export type ColorFamily = Partial<{ [K in keyof RampSet]: ColorFamilyRange }>
+
+export interface ColorFamilyRange {
+    low: number
+    high: number
+    range: number
+    scaling_value: number
+}
+
 export interface Shadow {
     blur: number
     color: string
@@ -162,6 +172,8 @@ export function create_theme(theme: ThemeConfig): Theme {
         "7": player(ramps.yellow),
     }
 
+    const color_family = build_color_family(ramps)
+
     return {
         name,
         is_light,
@@ -177,6 +189,7 @@ export function create_theme(theme: ThemeConfig): Theme {
 
         players,
         syntax,
+        color_family,
     }
 }
 
@@ -187,6 +200,28 @@ function player(ramp: Scale): Player {
     }
 }
 
+function build_color_family(ramps: RampSet): ColorFamily {
+    const color_family: ColorFamily = {}
+
+    for (const ramp in ramps) {
+        const ramp_value = ramps[ramp as keyof RampSet]
+
+        const lightnessValues = [ramp_value(0).get('hsl.l') * 100, ramp_value(1).get('hsl.l') * 100]
+        const low = Math.min(...lightnessValues)
+        const high = Math.max(...lightnessValues)
+        const range = high - low
+
+        color_family[ramp as keyof RampSet] = {
+            low,
+            high,
+            range,
+            scaling_value: 100 / range,
+        }
+    }
+
+    return color_family
+}
+
 function lowest_layer(ramps: RampSet): Layer {
     return {
         base: build_style_set(ramps.neutral, 0.2, 1),