Merge pull request #2372 from zed-industries/window_context_2

Antonio Scandurra created

Give elements access to their parent views and simplify contexts

Change summary

Cargo.lock                                                   |    1 
crates/activity_indicator/src/activity_indicator.rs          |   15 
crates/auto_update/src/auto_update.rs                        |    2 
crates/auto_update/src/update_notification.rs                |   14 
crates/breadcrumbs/src/breadcrumbs.rs                        |   30 
crates/collab/src/tests/integration_tests.rs                 |   25 
crates/collab_ui/src/collab_titlebar_item.rs                 |  148 
crates/collab_ui/src/collab_ui.rs                            |    2 
crates/collab_ui/src/collaborator_list_popover.rs            |   43 
crates/collab_ui/src/contact_finder.rs                       |   99 
crates/collab_ui/src/contact_list.rs                         |   86 
crates/collab_ui/src/contact_notification.rs                 |    5 
crates/collab_ui/src/contacts_popover.rs                     |   27 
crates/collab_ui/src/face_pile.rs                            |   31 
crates/collab_ui/src/incoming_call_notification.rs           |   16 
crates/collab_ui/src/notifications.rs                        |   14 
crates/collab_ui/src/project_shared_notification.rs          |   22 
crates/collab_ui/src/sharing_status_indicator.rs             |   10 
crates/command_palette/src/command_palette.rs                |  168 
crates/context_menu/src/context_menu.rs                      |   50 
crates/copilot/src/copilot.rs                                |    8 
crates/copilot/src/sign_in.rs                                |   89 
crates/copilot_button/src/copilot_button.rs                  |   17 
crates/diagnostics/src/diagnostics.rs                        |   25 
crates/diagnostics/src/items.rs                              |   18 
crates/drag_and_drop/src/drag_and_drop.rs                    |   64 
crates/editor/src/display_map/block_map.rs                   |   26 
crates/editor/src/editor.rs                                  |  159 
crates/editor/src/editor_tests.rs                            |  516 +
crates/editor/src/element.rs                                 |  528 +-
crates/editor/src/hover_popover.rs                           |   28 
crates/editor/src/items.rs                                   |   48 
crates/editor/src/link_go_to_definition.rs                   |    2 
crates/editor/src/scroll.rs                                  |   10 
crates/editor/src/test/editor_test_context.rs                |    3 
crates/feedback/src/deploy_feedback_button.rs                |   10 
crates/feedback/src/feedback_editor.rs                       |   46 
crates/feedback/src/feedback_info_text.rs                    |    8 
crates/feedback/src/submit_feedback_button.rs                |   10 
crates/file_finder/src/file_finder.rs                        |  303 
crates/go_to_line/src/go_to_line.rs                          |   43 
crates/gpui/examples/text.rs                                 |   25 
crates/gpui/src/app.rs                                       |  730 --
crates/gpui/src/app/callback_collection.rs                   |   13 
crates/gpui/src/app/menu.rs                                  |   14 
crates/gpui/src/app/test_app_context.rs                      |  213 
crates/gpui/src/app/window.rs                                | 1493 ++++++
crates/gpui/src/app/window_input_handler.rs                  |   57 
crates/gpui/src/elements.rs                                  |  503 +
crates/gpui/src/elements/align.rs                            |   35 
crates/gpui/src/elements/canvas.rs                           |   35 
crates/gpui/src/elements/clipped.rs                          |   41 
crates/gpui/src/elements/constrained_box.rs                  |   57 
crates/gpui/src/elements/container.rs                        |   58 
crates/gpui/src/elements/empty.rs                            |   20 
crates/gpui/src/elements/expanded.rs                         |   36 
crates/gpui/src/elements/flex.rs                             |   92 
crates/gpui/src/elements/hook.rs                             |   39 
crates/gpui/src/elements/image.rs                            |   20 
crates/gpui/src/elements/keystroke_label.rs                  |   37 
crates/gpui/src/elements/label.rs                            |   34 
crates/gpui/src/elements/list.rs                             |  622 +-
crates/gpui/src/elements/mouse_event_handler.rs              |   91 
crates/gpui/src/elements/overlay.rs                          |   64 
crates/gpui/src/elements/resizable.rs                        |   74 
crates/gpui/src/elements/stack.rs                            |   49 
crates/gpui/src/elements/svg.rs                              |   20 
crates/gpui/src/elements/text.rs                             |   58 
crates/gpui/src/elements/tooltip.rs                          |  107 
crates/gpui/src/elements/uniform_list.rs                     |   89 
crates/gpui/src/gpui.rs                                      |    8 
crates/gpui/src/platform.rs                                  |    2 
crates/gpui/src/platform/mac/platform.rs                     |    2 
crates/gpui/src/platform/test.rs                             |   37 
crates/gpui/src/presenter.rs                                 | 1154 ----
crates/gpui/src/scene.rs                                     |   22 
crates/gpui/src/scene/mouse_region.rs                        |  292 
crates/gpui/src/test.rs                                      |    8 
crates/gpui/src/text_layout.rs                               |   22 
crates/gpui/src/views/select.rs                              |   54 
crates/gpui_macros/src/gpui_macros.rs                        |   57 
crates/journal/src/journal.rs                                |   58 
crates/language_selector/Cargo.toml                          |    1 
crates/language_selector/src/active_buffer_language.rs       |   10 
crates/language_selector/src/language_selector.rs            |  154 
crates/outline/src/outline.rs                                |  132 
crates/picker/src/picker.rs                                  |  228 
crates/project_panel/src/project_panel.rs                    |   69 
crates/project_symbols/src/project_symbols.rs                |  204 
crates/recent_projects/src/highlighted_workspace_location.rs |    4 
crates/recent_projects/src/recent_projects.rs                |  152 
crates/search/src/buffer_search.rs                           |   39 
crates/search/src/project_search.rs                          |   41 
crates/settings/src/settings_file.rs                         |   30 
crates/sum_tree/src/sum_tree.rs                              |    2 
crates/terminal_view/src/terminal_button.rs                  |   14 
crates/terminal_view/src/terminal_element.rs                 |  165 
crates/terminal_view/src/terminal_view.rs                    |   36 
crates/theme/src/ui.rs                                       |   72 
crates/theme_selector/src/theme_selector.rs                  |  175 
crates/theme_testbench/src/theme_testbench.rs                |   36 
crates/util/src/test/marked_text.rs                          |    5 
crates/vim/src/editor_events.rs                              |   86 
crates/vim/src/motion.rs                                     |    4 
crates/vim/src/normal.rs                                     |   49 
crates/vim/src/normal/change.rs                              |    6 
crates/vim/src/normal/delete.rs                              |    6 
crates/vim/src/normal/yank.rs                                |    6 
crates/vim/src/object.rs                                     |   21 
crates/vim/src/test/vim_test_context.rs                      |   19 
crates/vim/src/vim.rs                                        |  193 
crates/vim/src/visual.rs                                     |   10 
crates/vim/test_data/test_change_sentence_object.json        |   44 
crates/vim/test_data/test_delete_sentence_object.json        |   44 
crates/vim/test_data/test_f_and_t.json                       |  430 
crates/welcome/src/base_keymap_picker.rs                     |  123 
crates/welcome/src/welcome.rs                                |    8 
crates/workspace/src/dock.rs                                 |   34 
crates/workspace/src/dock/toggle_dock_button.rs              |   20 
crates/workspace/src/item.rs                                 |  179 
crates/workspace/src/notifications.rs                        |   16 
crates/workspace/src/pane.rs                                 |  306 
crates/workspace/src/pane/dragged_item_receiver.rs           |   31 
crates/workspace/src/pane_group.rs                           |   18 
crates/workspace/src/persistence/model.rs                    |   21 
crates/workspace/src/searchable.rs                           |   43 
crates/workspace/src/shared_screen.rs                        |   24 
crates/workspace/src/sidebar.rs                              |   22 
crates/workspace/src/status_bar.rs                           |   47 
crates/workspace/src/toolbar.rs                              |   26 
crates/workspace/src/workspace.rs                            |  409 
crates/zed/src/main.rs                                       |  132 
crates/zed/src/zed.rs                                        |  268 
133 files changed, 6,995 insertions(+), 6,430 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3371,6 +3371,7 @@ dependencies = [
  "project",
  "settings",
  "theme",
+ "util",
  "workspace",
 ]
 

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -2,10 +2,10 @@ use auto_update::{AutoUpdateStatus, AutoUpdater, DismissErrorMessage};
 use editor::Editor;
 use futures::StreamExt;
 use gpui::{
-    actions,
+    actions, anyhow,
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Action, AppContext, Entity, ModelHandle, RenderContext, View, ViewContext, ViewHandle,
+    Action, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
 };
 use language::{LanguageRegistry, LanguageServerBinaryStatus};
 use project::{LanguageServerProgress, Project};
@@ -73,11 +73,12 @@ impl ActivityIndicator {
                                 status: event,
                             });
                             cx.notify();
-                        });
+                        })?;
                     } else {
                         break;
                     }
                 }
+                anyhow::Ok(())
             })
             .detach();
             cx.observe(&project, |_, _, cx| cx.notify()).detach();
@@ -172,7 +173,7 @@ impl ActivityIndicator {
             .flatten()
     }
 
-    fn content_to_render(&mut self, cx: &mut RenderContext<Self>) -> Content {
+    fn content_to_render(&mut self, cx: &mut ViewContext<Self>) -> Content {
         // Show any language server has pending activity.
         let mut pending_work = self.pending_language_server_work(cx);
         if let Some(PendingWork {
@@ -314,14 +315,14 @@ impl View for ActivityIndicator {
         "ActivityIndicator"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let Content {
             icon,
             message,
             action,
         } = self.content_to_render(cx);
 
-        let mut element = MouseEventHandler::<Self>::new(0, cx, |state, cx| {
+        let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
             let theme = &cx
                 .global::<Settings>()
                 .theme
@@ -361,7 +362,7 @@ impl View for ActivityIndicator {
         if let Some(action) = action {
             element = element
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_any_action(action.boxed_clone())
                 });
         }

crates/auto_update/src/auto_update.rs 🔗

@@ -113,7 +113,7 @@ pub fn notify_of_any_new_update(
                         .read(cx)
                         .set_should_show_update_notification(false, cx)
                         .detach_and_log_err(cx);
-                });
+                })?;
             }
         }
         anyhow::Ok(())

crates/auto_update/src/update_notification.rs 🔗

@@ -2,7 +2,7 @@ use crate::ViewReleaseNotes;
 use gpui::{
     elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
     platform::{AppVersion, CursorStyle, MouseButton},
-    Element, Entity, View, ViewContext,
+    Drawable, Entity, View, ViewContext,
 };
 use menu::Cancel;
 use settings::Settings;
@@ -26,13 +26,13 @@ impl View for UpdateNotification {
         "UpdateNotification"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
         let theme = &theme.update_notification;
 
         let app_name = cx.global::<ReleaseChannel>().display_name();
 
-        MouseEventHandler::<ViewReleaseNotes>::new(0, cx, |state, cx| {
+        MouseEventHandler::<ViewReleaseNotes, _>::new(0, cx, |state, cx| {
             Flex::column()
                 .with_child(
                     Flex::row()
@@ -50,7 +50,7 @@ impl View for UpdateNotification {
                             .boxed(),
                         )
                         .with_child(
-                            MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
+                            MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
                                 let style = theme.dismiss_button.style_for(state, false);
                                 Svg::new("icons/x_mark_8.svg")
                                     .with_color(style.color)
@@ -65,7 +65,9 @@ impl View for UpdateNotification {
                                     .boxed()
                             })
                             .with_padding(Padding::uniform(5.))
-                            .on_click(MouseButton::Left, move |_, cx| cx.dispatch_action(Cancel))
+                            .on_click(MouseButton::Left, move |_, _, cx| {
+                                cx.dispatch_action(Cancel)
+                            })
                             .aligned()
                             .constrained()
                             .with_height(cx.font_cache().line_height(theme.message.text.font_size))
@@ -87,7 +89,7 @@ impl View for UpdateNotification {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(ViewReleaseNotes)
         })
         .boxed()

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{
-    elements::*, platform::MouseButton, AppContext, Entity, RenderContext, Subscription, View,
-    ViewContext, ViewHandle,
+    elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
+    ViewHandle,
 };
 use itertools::Itertools;
 use search::ProjectSearchView;
@@ -41,7 +41,7 @@ impl View for Breadcrumbs {
         "Breadcrumbs"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let active_item = match &self.active_item {
             Some(active_item) => active_item,
             None => return Empty::new().boxed(),
@@ -54,10 +54,22 @@ impl View for Breadcrumbs {
         let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {
             Some(breadcrumbs) => breadcrumbs,
             None => return Empty::new().boxed(),
-        };
+        }
+        .into_iter()
+        .map(|breadcrumb| {
+            let text = Text::new(
+                breadcrumb.text,
+                theme.workspace.breadcrumbs.default.text.clone(),
+            );
+            if let Some(highlights) = breadcrumb.highlights {
+                text.with_highlights(highlights).boxed()
+            } else {
+                text.boxed()
+            }
+        });
 
         let crumbs = Flex::row()
-            .with_children(Itertools::intersperse_with(breadcrumbs.into_iter(), || {
+            .with_children(Itertools::intersperse_with(breadcrumbs, || {
                 Label::new(" 〉 ", style.default.text.clone()).boxed()
             }))
             .constrained()
@@ -72,14 +84,14 @@ impl View for Breadcrumbs {
                 .boxed();
         }
 
-        MouseEventHandler::<Breadcrumbs>::new(0, cx, |state, _| {
+        MouseEventHandler::<Breadcrumbs, Breadcrumbs>::new(0, cx, |state, _| {
             let style = style.style_for(state, false);
             crumbs.with_style(style.container).boxed()
         })
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(outline::Toggle);
         })
-        .with_tooltip::<Breadcrumbs, _>(
+        .with_tooltip::<Breadcrumbs>(
             0,
             "Show symbol outline".to_owned(),
             Some(Box::new(outline::Toggle)),
@@ -136,7 +148,7 @@ impl ToolbarItemView for Breadcrumbs {
         }
     }
 
-    fn pane_focus_update(&mut self, pane_focused: bool, _: &mut gpui::AppContext) {
+    fn pane_focus_update(&mut self, pane_focused: bool, _: &mut ViewContext<Self>) {
         self.pane_focused = pane_focused;
     }
 }

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

@@ -1470,7 +1470,8 @@ async fn test_host_disconnect(
     deterministic.run_until_parked();
     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
 
-    let (_, workspace_b) = cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
+    let (window_id_b, workspace_b) =
+        cx_b.add_window(|cx| Workspace::test_new(project_b.clone(), cx));
     let editor_b = workspace_b
         .update(cx_b, |workspace, cx| {
             workspace.open_path((worktree_id, "b.txt"), None, true, cx)
@@ -1479,12 +1480,9 @@ async fn test_host_disconnect(
         .unwrap()
         .downcast::<Editor>()
         .unwrap();
-    cx_b.read(|cx| {
-        assert_eq!(
-            cx.focused_view_id(workspace_b.window_id()),
-            Some(editor_b.id())
-        );
-    });
+    assert!(cx_b
+        .read_window(window_id_b, |cx| editor_b.is_focused(cx))
+        .unwrap());
     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
     assert!(cx_b.is_window_edited(workspace_b.window_id()));
 
@@ -1498,8 +1496,8 @@ async fn test_host_disconnect(
     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
 
     // Ensure client B's edited state is reset and that the whole window is blurred.
-    cx_b.read(|cx| {
-        assert_eq!(cx.focused_view_id(workspace_b.window_id()), None);
+    cx_b.read_window(window_id_b, |cx| {
+        assert_eq!(cx.focused_view_id(), None);
     });
     assert!(!cx_b.is_window_edited(workspace_b.window_id()));
 
@@ -6228,7 +6226,8 @@ async fn test_basic_following(
         .update(cx_a, |workspace, cx| {
             workspace::Pane::go_back(workspace, None, cx)
         })
-        .await;
+        .await
+        .unwrap();
     deterministic.run_until_parked();
     workspace_b.read_with(cx_b, |workspace, cx| {
         assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
@@ -6238,7 +6237,8 @@ async fn test_basic_following(
         .update(cx_a, |workspace, cx| {
             workspace::Pane::go_back(workspace, None, cx)
         })
-        .await;
+        .await
+        .unwrap();
     deterministic.run_until_parked();
     workspace_b.read_with(cx_b, |workspace, cx| {
         assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
@@ -6248,7 +6248,8 @@ async fn test_basic_following(
         .update(cx_a, |workspace, cx| {
             workspace::Pane::go_forward(workspace, None, cx)
         })
-        .await;
+        .await
+        .unwrap();
     deterministic.run_until_parked();
     workspace_b.read_with(cx_b, |workspace, cx| {
         assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -16,7 +16,7 @@ use gpui::{
     impl_internal_actions,
     json::{self, ToJson},
     platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ImageData, ModelHandle, RenderContext, Subscription, View, ViewContext,
+    AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
     ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
@@ -68,7 +68,7 @@ impl View for CollabTitlebarItem {
         "CollabTitlebarItem"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let workspace = if let Some(workspace) = self.workspace.upgrade(cx) {
             workspace
         } else {
@@ -325,8 +325,8 @@ impl CollabTitlebarItem {
     fn render_toggle_contacts_button(
         &self,
         theme: &Theme,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let titlebar = &theme.workspace.titlebar;
 
         let badge = if self
@@ -352,7 +352,7 @@ impl CollabTitlebarItem {
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
+                MouseEventHandler::<ToggleContactsMenu, Self>::new(0, cx, |state, _| {
                     let style = titlebar
                         .toggle_contacts_button
                         .style_for(state, self.contacts_popover.is_some());
@@ -369,10 +369,10 @@ impl CollabTitlebarItem {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(ToggleContactsMenu);
                 })
-                .with_tooltip::<ToggleContactsMenu, _>(
+                .with_tooltip::<ToggleContactsMenu>(
                     0,
                     "Show contacts menu".into(),
                     Some(Box::new(ToggleContactsMenu)),
@@ -390,8 +390,8 @@ impl CollabTitlebarItem {
         &self,
         theme: &Theme,
         room: &ModelHandle<Room>,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let icon;
         let tooltip;
         if room.read(cx).is_screen_sharing() {
@@ -403,7 +403,7 @@ impl CollabTitlebarItem {
         }
 
         let titlebar = &theme.workspace.titlebar;
-        MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
+        MouseEventHandler::<ToggleScreenSharing, Self>::new(0, cx, |state, _| {
             let style = titlebar.call_control.style_for(state, false);
             Svg::new(icon)
                 .with_color(style.color)
@@ -418,10 +418,10 @@ impl CollabTitlebarItem {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleScreenSharing);
         })
-        .with_tooltip::<ToggleScreenSharing, _>(
+        .with_tooltip::<ToggleScreenSharing>(
             0,
             tooltip.into(),
             Some(Box::new(ToggleScreenSharing)),
@@ -436,8 +436,8 @@ impl CollabTitlebarItem {
         &self,
         workspace: &ViewHandle<Workspace>,
         theme: &Theme,
-        cx: &mut RenderContext<Self>,
-    ) -> Option<ElementBox> {
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Element<Self>> {
         let project = workspace.read(cx).project();
         if project.read(cx).is_remote() {
             return None;
@@ -457,7 +457,7 @@ impl CollabTitlebarItem {
         Some(
             Stack::new()
                 .with_child(
-                    MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
+                    MouseEventHandler::<ShareUnshare, Self>::new(0, cx, |state, _| {
                         //TODO: Ensure this button has consistant width for both text variations
                         let style = titlebar.share_button.style_for(state, false);
                         Label::new(label, style.text.clone())
@@ -466,14 +466,14 @@ impl CollabTitlebarItem {
                             .boxed()
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, move |_, cx| {
+                    .on_click(MouseButton::Left, move |_, _, cx| {
                         if is_shared {
                             cx.dispatch_action(UnshareProject);
                         } else {
                             cx.dispatch_action(ShareProject);
                         }
                     })
-                    .with_tooltip::<ShareUnshare, _>(
+                    .with_tooltip::<ShareUnshare>(
                         0,
                         tooltip.to_owned(),
                         None,
@@ -489,12 +489,12 @@ impl CollabTitlebarItem {
         )
     }
 
-    fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_user_menu_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
         let titlebar = &theme.workspace.titlebar;
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
+                MouseEventHandler::<ToggleUserMenu, Self>::new(0, cx, |state, _| {
                     let style = titlebar.call_control.style_for(state, false);
                     Svg::new("icons/ellipsis_14.svg")
                         .with_color(style.color)
@@ -509,10 +509,10 @@ impl CollabTitlebarItem {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(ToggleUserMenu);
                 })
-                .with_tooltip::<ToggleUserMenu, _>(
+                .with_tooltip::<ToggleUserMenu>(
                     0,
                     "Toggle user menu".to_owned(),
                     Some(Box::new(ToggleUserMenu)),
@@ -533,9 +533,9 @@ impl CollabTitlebarItem {
             .boxed()
     }
 
-    fn render_sign_in_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_sign_in_button(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
         let titlebar = &theme.workspace.titlebar;
-        MouseEventHandler::<SignIn>::new(0, cx, |state, _| {
+        MouseEventHandler::<SignIn, Self>::new(0, cx, |state, _| {
             let style = titlebar.sign_in_prompt.style_for(state, false);
             Label::new("Sign In", style.text.clone())
                 .contained()
@@ -543,7 +543,7 @@ impl CollabTitlebarItem {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(SignIn);
         })
         .boxed()
@@ -552,8 +552,8 @@ impl CollabTitlebarItem {
     fn render_contacts_popover_host<'a>(
         &'a self,
         _theme: &'a theme::Titlebar,
-        cx: &'a RenderContext<Self>,
-    ) -> Option<ElementBox> {
+        cx: &'a ViewContext<Self>,
+    ) -> Option<Element<Self>> {
         self.contacts_popover.as_ref().map(|popover| {
             Overlay::new(ChildView::new(popover, cx).boxed())
                 .with_fit_mode(OverlayFitMode::SwitchAnchor)
@@ -571,8 +571,8 @@ impl CollabTitlebarItem {
         workspace: &ViewHandle<Workspace>,
         theme: &Theme,
         room: &ModelHandle<Room>,
-        cx: &mut RenderContext<Self>,
-    ) -> Vec<ElementBox> {
+        cx: &mut ViewContext<Self>,
+    ) -> Vec<Element<Self>> {
         let mut participants = room
             .read(cx)
             .remote_participants()
@@ -613,8 +613,8 @@ impl CollabTitlebarItem {
         theme: &Theme,
         user: &Arc<User>,
         peer_id: PeerId,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let replica_id = workspace.read(cx).project().read(cx).replica_id();
         Container::new(self.render_face_pile(
             user,
@@ -637,8 +637,8 @@ impl CollabTitlebarItem {
         location: Option<ParticipantLocation>,
         workspace: &ViewHandle<Workspace>,
         theme: &Theme,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let project_id = workspace.read(cx).project().read(cx).remote_id();
         let room = ActiveCall::global(cx).read(cx).room();
         let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
@@ -749,41 +749,42 @@ impl CollabTitlebarItem {
 
         if let Some(location) = location {
             if let Some(replica_id) = replica_id {
-                content =
-                    MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
-                        content
-                    })
-                    .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, move |_, cx| {
-                        cx.dispatch_action(ToggleFollow(peer_id))
-                    })
-                    .with_tooltip::<ToggleFollow, _>(
-                        peer_id.as_u64() as usize,
-                        if is_being_followed {
-                            format!("Unfollow {}", user.github_login)
-                        } else {
-                            format!("Follow {}", user.github_login)
-                        },
-                        Some(Box::new(FollowNextCollaborator)),
-                        theme.tooltip.clone(),
-                        cx,
-                    )
-                    .boxed();
+                content = MouseEventHandler::<ToggleFollow, Self>::new(
+                    replica_id.into(),
+                    cx,
+                    move |_, _| content,
+                )
+                .with_cursor_style(CursorStyle::PointingHand)
+                .on_click(MouseButton::Left, move |_, _, cx| {
+                    cx.dispatch_action(ToggleFollow(peer_id))
+                })
+                .with_tooltip::<ToggleFollow>(
+                    peer_id.as_u64() as usize,
+                    if is_being_followed {
+                        format!("Unfollow {}", user.github_login)
+                    } else {
+                        format!("Follow {}", user.github_login)
+                    },
+                    Some(Box::new(FollowNextCollaborator)),
+                    theme.tooltip.clone(),
+                    cx,
+                )
+                .boxed();
             } else if let ParticipantLocation::SharedProject { project_id } = location {
                 let user_id = user.id;
-                content = MouseEventHandler::<JoinProject>::new(
+                content = MouseEventHandler::<JoinProject, Self>::new(
                     peer_id.as_u64() as usize,
                     cx,
                     move |_, _| content,
                 )
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(JoinProject {
                         project_id,
                         follow_user_id: user_id,
                     })
                 })
-                .with_tooltip::<JoinProject, _>(
+                .with_tooltip::<JoinProject>(
                     peer_id.as_u64() as usize,
                     format!("Follow {} into external project", user.github_login),
                     Some(Box::new(FollowNextCollaborator)),
@@ -800,7 +801,7 @@ impl CollabTitlebarItem {
         workspace: &ViewHandle<Workspace>,
         location: Option<ParticipantLocation>,
         mut style: AvatarStyle,
-        cx: &RenderContext<Self>,
+        cx: &ViewContext<Self>,
     ) -> AvatarStyle {
         if let Some(location) = location {
             if let ParticipantLocation::SharedProject { project_id } = location {
@@ -815,11 +816,11 @@ impl CollabTitlebarItem {
         style
     }
 
-    fn render_face(
+    fn render_face<V: View>(
         avatar: Arc<ImageData>,
         avatar_style: AvatarStyle,
         background_color: Color,
-    ) -> ElementBox {
+    ) -> Element<V> {
         Image::from_data(avatar)
             .with_style(avatar_style.image)
             .aligned()
@@ -836,8 +837,8 @@ impl CollabTitlebarItem {
     fn render_connection_status(
         &self,
         status: &client::Status,
-        cx: &mut RenderContext<Self>,
-    ) -> Option<ElementBox> {
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Element<Self>> {
         enum ConnectionStatusButton {}
 
         let theme = &cx.global::<Settings>().theme.clone();
@@ -863,7 +864,7 @@ impl CollabTitlebarItem {
                 .boxed(),
             ),
             client::Status::UpgradeRequired => Some(
-                MouseEventHandler::<ConnectionStatusButton>::new(0, cx, |_, _| {
+                MouseEventHandler::<ConnectionStatusButton, Self>::new(0, cx, |_, _| {
                     Label::new(
                         "Please update Zed to collaborate",
                         theme.workspace.titlebar.outdated_warning.text.clone(),
@@ -874,7 +875,7 @@ impl CollabTitlebarItem {
                     .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(auto_update::Check);
                 })
                 .boxed(),
@@ -894,7 +895,7 @@ impl AvatarRibbon {
     }
 }
 
-impl Element for AvatarRibbon {
+impl Drawable<CollabTitlebarItem> for AvatarRibbon {
     type LayoutState = ();
 
     type PaintState = ();
@@ -902,17 +903,20 @@ impl Element for AvatarRibbon {
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        _: &mut gpui::LayoutContext,
+        _: &mut CollabTitlebarItem,
+        _: &mut ViewContext<CollabTitlebarItem>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
 
     fn paint(
         &mut self,
-        bounds: gpui::geometry::rect::RectF,
-        _: gpui::geometry::rect::RectF,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        _: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut gpui::PaintContext,
+        _: &mut CollabTitlebarItem,
+        _: &mut ViewContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let mut path = PathBuilder::new();
         path.reset(bounds.lower_left());
@@ -923,7 +927,7 @@ impl Element for AvatarRibbon {
         path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
         path.curve_to(bounds.lower_right(), bounds.upper_right());
         path.line_to(bounds.lower_left());
-        cx.scene.push_path(path.build(self.color, None));
+        scene.push_path(path.build(self.color, None));
     }
 
     fn rect_for_text_range(
@@ -933,17 +937,19 @@ impl Element for AvatarRibbon {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &gpui::MeasurementContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> Option<RectF> {
         None
     }
 
     fn debug(
         &self,
-        bounds: gpui::geometry::rect::RectF,
+        bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &gpui::DebugContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> gpui::json::Value {
         json::json!({
             "type": "AvatarRibbon",

crates/collab_ui/src/collaborator_list_popover.rs 🔗

@@ -1,10 +1,7 @@
 use call::ActiveCall;
 use client::UserStore;
 use gpui::Action;
-use gpui::{
-    actions, elements::*, platform::MouseButton, Entity, ModelHandle, RenderContext, View,
-    ViewContext,
-};
+use gpui::{actions, elements::*, platform::MouseButton, Entity, ModelHandle, View, ViewContext};
 use settings::Settings;
 
 use crate::collab_titlebar_item::ToggleCollaboratorList;
@@ -21,7 +18,7 @@ enum Collaborator {
 actions!(collaborator_list_popover, [NoOp]);
 
 pub(crate) struct CollaboratorListPopover {
-    list_state: ListState,
+    list_state: ListState<Self>,
 }
 
 impl Entity for CollaboratorListPopover {
@@ -33,10 +30,10 @@ impl View for CollaboratorListPopover {
         "CollaboratorListPopover"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
 
-        MouseEventHandler::<Self>::new(0, cx, |_, _| {
+        MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
             List::new(self.list_state.clone())
                 .contained()
                 .with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key
@@ -45,7 +42,7 @@ impl View for CollaboratorListPopover {
                 .with_height(theme.contacts_popover.height)
                 .boxed()
         })
-        .on_down_out(MouseButton::Left, move |_, cx| {
+        .on_down_out(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleCollaboratorList);
         })
         .boxed()
@@ -83,7 +80,6 @@ impl CollaboratorListPopover {
                 collaborators.len(),
                 Orientation::Top,
                 0.,
-                cx,
                 move |_, index, cx| match &collaborators[index] {
                     Collaborator::SelfUser { username } => render_collaborator_list_entry(
                         index,
@@ -120,8 +116,8 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
     icon: Svg,
     icon_action: IA,
     icon_tooltip: String,
-    cx: &mut RenderContext<CollaboratorListPopover>,
-) -> ElementBox {
+    cx: &mut ViewContext<CollaboratorListPopover>,
+) -> Element<CollaboratorListPopover> {
     enum Username {}
     enum UsernameTooltip {}
     enum Icon {}
@@ -131,19 +127,20 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
     let username_theme = theme.contact_list.contact_username.text.clone();
     let tooltip_theme = theme.tooltip.clone();
 
-    let username = MouseEventHandler::<Username>::new(index, cx, |_, _| {
-        Label::new(username.to_owned(), username_theme.clone()).boxed()
-    })
-    .on_click(MouseButton::Left, move |_, cx| {
-        if let Some(username_action) = username_action.clone() {
-            cx.dispatch_action(username_action);
-        }
-    });
+    let username =
+        MouseEventHandler::<Username, CollaboratorListPopover>::new(index, cx, |_, _| {
+            Label::new(username.to_owned(), username_theme.clone()).boxed()
+        })
+        .on_click(MouseButton::Left, move |_, _, cx| {
+            if let Some(username_action) = username_action.clone() {
+                cx.dispatch_action(username_action);
+            }
+        });
 
     Flex::row()
         .with_child(if let Some(username_tooltip) = username_tooltip {
             username
-                .with_tooltip::<UsernameTooltip, _>(
+                .with_tooltip::<UsernameTooltip>(
                     index,
                     username_tooltip,
                     None,
@@ -155,11 +152,11 @@ fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
             username.boxed()
         })
         .with_child(
-            MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed())
-                .on_click(MouseButton::Left, move |_, cx| {
+            MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon.boxed())
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(icon_action.clone())
                 })
-                .with_tooltip::<IconTooltip, _>(index, icon_tooltip, None, tooltip_theme, cx)
+                .with_tooltip::<IconTooltip>(index, icon_tooltip, None, tooltip_theme, cx)
                 .boxed(),
         )
         .boxed()

crates/collab_ui/src/contact_finder.rs 🔗

@@ -1,49 +1,41 @@
 use client::{ContactRequestStatus, User, UserStore};
-use gpui::{
-    elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState, RenderContext, Task,
-    View, ViewContext, ViewHandle,
-};
-use picker::{Picker, PickerDelegate};
+use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
 use std::sync::Arc;
 use util::TryFutureExt;
 
 pub fn init(cx: &mut AppContext) {
-    Picker::<ContactFinder>::init(cx);
+    Picker::<ContactFinderDelegate>::init(cx);
 }
 
-pub struct ContactFinder {
-    picker: ViewHandle<Picker<Self>>,
-    potential_contacts: Arc<[Arc<User>]>,
-    user_store: ModelHandle<UserStore>,
-    selected_index: usize,
-}
+pub type ContactFinder = Picker<ContactFinderDelegate>;
 
-pub enum Event {
-    Dismissed,
+pub fn build_contact_finder(
+    user_store: ModelHandle<UserStore>,
+    cx: &mut ViewContext<ContactFinder>,
+) -> ContactFinder {
+    Picker::new(
+        ContactFinderDelegate {
+            user_store,
+            potential_contacts: Arc::from([]),
+            selected_index: 0,
+        },
+        cx,
+    )
 }
 
-impl Entity for ContactFinder {
-    type Event = Event;
+pub struct ContactFinderDelegate {
+    potential_contacts: Arc<[Arc<User>]>,
+    user_store: ModelHandle<UserStore>,
+    selected_index: usize,
 }
 
-impl View for ContactFinder {
-    fn ui_name() -> &'static str {
-        "ContactFinder"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
+impl PickerDelegate for ContactFinderDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Search collaborator by username...".into()
     }
-}
 
-impl PickerDelegate for ContactFinder {
     fn match_count(&self) -> usize {
         self.potential_contacts.len()
     }
@@ -52,22 +44,22 @@ impl PickerDelegate for ContactFinder {
         self.selected_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
         self.selected_index = ix;
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
         let search_users = self
             .user_store
             .update(cx, |store, cx| store.fuzzy_search_users(query, cx));
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(|picker, mut cx| async move {
             async {
                 let potential_contacts = search_users.await?;
-                this.update(&mut cx, |this, cx| {
-                    this.potential_contacts = potential_contacts.into();
+                picker.update(&mut cx, |picker, cx| {
+                    picker.delegate_mut().potential_contacts = potential_contacts.into();
                     cx.notify();
-                });
+                })?;
                 anyhow::Ok(())
             }
             .log_err()
@@ -75,7 +67,7 @@ impl PickerDelegate for ContactFinder {
         })
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
         if let Some(user) = self.potential_contacts.get(self.selected_index) {
             let user_store = self.user_store.read(cx);
             match user_store.contact_request_status(user) {
@@ -94,8 +86,8 @@ impl PickerDelegate for ContactFinder {
         }
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
+        cx.emit(PickerEvent::Dismiss);
     }
 
     fn render_match(
@@ -104,7 +96,7 @@ impl PickerDelegate for ContactFinder {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &gpui::AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let theme = &cx.global::<Settings>().theme;
         let user = &self.potential_contacts[ix];
         let request_status = self.user_store.read(cx).contact_request_status(user);
@@ -164,28 +156,3 @@ impl PickerDelegate for ContactFinder {
             .boxed()
     }
 }
-
-impl ContactFinder {
-    pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
-        let this = cx.weak_handle();
-        Self {
-            picker: cx.add_view(|cx| {
-                Picker::new("Search collaborator by username...", this, cx)
-                    .with_theme(|theme| theme.contact_finder.picker.clone())
-            }),
-            potential_contacts: Arc::from([]),
-            user_store,
-            selected_index: 0,
-        }
-    }
-
-    pub fn editor_text(&self, cx: &AppContext) -> String {
-        self.picker.read(cx).query(cx)
-    }
-
-    pub fn with_editor_text(self, editor_text: String, cx: &mut ViewContext<Self>) -> Self {
-        self.picker
-            .update(cx, |picker, cx| picker.set_query(editor_text, cx));
-        self
-    }
-}

crates/collab_ui/src/contact_list.rs 🔗

@@ -11,7 +11,7 @@ use gpui::{
     impl_actions, impl_internal_actions,
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, PromptLevel},
-    AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext, ViewHandle,
+    AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::Project;
@@ -159,7 +159,7 @@ pub enum Event {
 pub struct ContactList {
     entries: Vec<ContactEntry>,
     match_candidates: Vec<StringMatchCandidate>,
-    list_state: ListState,
+    list_state: ListState<Self>,
     project: ModelHandle<Project>,
     user_store: ModelHandle<UserStore>,
     filter_editor: ViewHandle<Editor>,
@@ -202,7 +202,7 @@ impl ContactList {
         })
         .detach();
 
-        let list_state = ListState::new(0, Orientation::Top, 1000., cx, move |this, ix, cx| {
+        let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
             let theme = cx.global::<Settings>().theme.clone();
             let is_selected = this.selection == Some(ix);
             let current_project_id = this.project.read(cx).remote_id();
@@ -748,7 +748,7 @@ impl ContactList {
         is_pending: bool,
         is_selected: bool,
         theme: &theme::ContactList,
-    ) -> ElementBox {
+    ) -> Element<Self> {
         Flex::row()
             .with_children(user.avatar.clone().map(|avatar| {
                 Image::from_data(avatar)
@@ -799,8 +799,8 @@ impl ContactList {
         is_last: bool,
         is_selected: bool,
         theme: &theme::ContactList,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let font_cache = cx.font_cache();
         let host_avatar_height = theme
             .contact_avatar
@@ -819,7 +819,7 @@ impl ContactList {
             worktree_root_names.join(", ")
         };
 
-        MouseEventHandler::<JoinProject>::new(project_id as usize, cx, |mouse_state, _| {
+        MouseEventHandler::<JoinProject, Self>::new(project_id as usize, cx, |mouse_state, _| {
             let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
             let row = theme.project_row.style_for(mouse_state, is_selected);
 
@@ -827,14 +827,14 @@ impl ContactList {
                 .with_child(
                     Stack::new()
                         .with_child(
-                            Canvas::new(move |bounds, _, cx| {
+                            Canvas::new(move |scene, bounds, _, _, _| {
                                 let start_x = bounds.min_x() + (bounds.width() / 2.)
                                     - (tree_branch.width / 2.);
                                 let end_x = bounds.max_x();
                                 let start_y = bounds.min_y();
                                 let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
 
-                                cx.scene.push_quad(gpui::Quad {
+                                scene.push_quad(gpui::Quad {
                                     bounds: RectF::from_points(
                                         vec2f(start_x, start_y),
                                         vec2f(
@@ -846,7 +846,7 @@ impl ContactList {
                                     border: gpui::Border::default(),
                                     corner_radius: 0.,
                                 });
-                                cx.scene.push_quad(gpui::Quad {
+                                scene.push_quad(gpui::Quad {
                                     bounds: RectF::from_points(
                                         vec2f(start_x, end_y),
                                         vec2f(end_x, end_y + tree_branch.width),
@@ -882,7 +882,7 @@ impl ContactList {
         } else {
             CursorStyle::Arrow
         })
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             if !is_current {
                 cx.dispatch_global_action(JoinProject {
                     project_id,
@@ -898,8 +898,8 @@ impl ContactList {
         is_last: bool,
         is_selected: bool,
         theme: &theme::ContactList,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let font_cache = cx.font_cache();
         let host_avatar_height = theme
             .contact_avatar
@@ -913,7 +913,7 @@ impl ContactList {
         let baseline_offset =
             row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
 
-        MouseEventHandler::<OpenSharedScreen>::new(
+        MouseEventHandler::<OpenSharedScreen, Self>::new(
             peer_id.as_u64() as usize,
             cx,
             |mouse_state, _| {
@@ -924,7 +924,7 @@ impl ContactList {
                     .with_child(
                         Stack::new()
                             .with_child(
-                                Canvas::new(move |bounds, _, cx| {
+                                Canvas::new(move |scene, bounds, _, _, _| {
                                     let start_x = bounds.min_x() + (bounds.width() / 2.)
                                         - (tree_branch.width / 2.);
                                     let end_x = bounds.max_x();
@@ -932,7 +932,7 @@ impl ContactList {
                                     let end_y =
                                         bounds.min_y() + baseline_offset - (cap_height / 2.);
 
-                                    cx.scene.push_quad(gpui::Quad {
+                                    scene.push_quad(gpui::Quad {
                                         bounds: RectF::from_points(
                                             vec2f(start_x, start_y),
                                             vec2f(
@@ -944,7 +944,7 @@ impl ContactList {
                                         border: gpui::Border::default(),
                                         corner_radius: 0.,
                                     });
-                                    cx.scene.push_quad(gpui::Quad {
+                                    scene.push_quad(gpui::Quad {
                                         bounds: RectF::from_points(
                                             vec2f(start_x, end_y),
                                             vec2f(end_x, end_y + tree_branch.width),
@@ -988,7 +988,7 @@ impl ContactList {
             },
         )
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(OpenSharedScreen { peer_id });
         })
         .boxed()
@@ -999,8 +999,8 @@ impl ContactList {
         theme: &theme::ContactList,
         is_selected: bool,
         is_collapsed: bool,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         enum Header {}
         enum LeaveCallContactList {}
 
@@ -1015,14 +1015,14 @@ impl ContactList {
         };
         let leave_call = if section == Section::ActiveCall {
             Some(
-                MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| {
+                MouseEventHandler::<LeaveCallContactList, Self>::new(0, cx, |state, _| {
                     let style = theme.leave_call.style_for(state, false);
                     Label::new("Leave Call", style.text.clone())
                         .contained()
                         .with_style(style.container)
                         .boxed()
                 })
-                .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(LeaveCall))
+                .on_click(MouseButton::Left, |_, _, cx| cx.dispatch_action(LeaveCall))
                 .aligned()
                 .boxed(),
             )
@@ -1031,7 +1031,7 @@ impl ContactList {
         };
 
         let icon_size = theme.section_icon_size;
-        MouseEventHandler::<Header>::new(section as usize, cx, |_, _| {
+        MouseEventHandler::<Header, Self>::new(section as usize, cx, |_, _| {
             Flex::row()
                 .with_child(
                     Svg::new(if is_collapsed {
@@ -1065,7 +1065,7 @@ impl ContactList {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleExpanded(section))
         })
         .boxed()
@@ -1077,15 +1077,15 @@ impl ContactList {
         project: &ModelHandle<Project>,
         theme: &theme::ContactList,
         is_selected: bool,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let online = contact.online;
         let busy = contact.busy || calling;
         let user_id = contact.user.id;
         let github_login = contact.user.github_login.clone();
         let initial_project = project.clone();
         let mut element =
-            MouseEventHandler::<Contact>::new(contact.user.id as usize, cx, |_, cx| {
+            MouseEventHandler::<Contact, Self>::new(contact.user.id as usize, cx, |_, cx| {
                 Flex::row()
                     .with_children(contact.user.avatar.clone().map(|avatar| {
                         let status_badge = if contact.online {
@@ -1128,7 +1128,7 @@ impl ContactList {
                         .boxed(),
                     )
                     .with_child(
-                        MouseEventHandler::<Cancel>::new(
+                        MouseEventHandler::<Cancel, Self>::new(
                             contact.user.id as usize,
                             cx,
                             |mouse_state, _| {
@@ -1142,7 +1142,7 @@ impl ContactList {
                         )
                         .with_padding(Padding::uniform(2.))
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_click(MouseButton::Left, move |_, cx| {
+                        .on_click(MouseButton::Left, move |_, _, cx| {
                             cx.dispatch_action(RemoveContact {
                                 user_id,
                                 github_login: github_login.clone(),
@@ -1172,7 +1172,7 @@ impl ContactList {
                     )
                     .boxed()
             })
-            .on_click(MouseButton::Left, move |_, cx| {
+            .on_click(MouseButton::Left, move |_, _, cx| {
                 if online && !busy {
                     cx.dispatch_action(Call {
                         recipient_user_id: user_id,
@@ -1194,8 +1194,8 @@ impl ContactList {
         theme: &theme::ContactList,
         is_incoming: bool,
         is_selected: bool,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         enum Decline {}
         enum Accept {}
         enum Cancel {}
@@ -1228,7 +1228,7 @@ impl ContactList {
 
         if is_incoming {
             row.add_children([
-                MouseEventHandler::<Decline>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Decline, Self>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1239,7 +1239,7 @@ impl ContactList {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(RespondToContactRequest {
                         user_id,
                         accept: false,
@@ -1248,7 +1248,7 @@ impl ContactList {
                 .contained()
                 .with_margin_right(button_spacing)
                 .boxed(),
-                MouseEventHandler::<Accept>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Accept, Self>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1260,7 +1260,7 @@ impl ContactList {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(RespondToContactRequest {
                         user_id,
                         accept: true,
@@ -1270,7 +1270,7 @@ impl ContactList {
             ]);
         } else {
             row.add_child(
-                MouseEventHandler::<Cancel>::new(user.id as usize, cx, |mouse_state, _| {
+                MouseEventHandler::<Cancel, Self>::new(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {
                         &theme.disabled_button
                     } else {
@@ -1283,7 +1283,7 @@ impl ContactList {
                 })
                 .with_padding(Padding::uniform(2.))
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(RemoveContact {
                         user_id,
                         github_login: github_login.clone(),
@@ -1331,7 +1331,7 @@ impl View for ContactList {
         cx
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         enum AddContact {}
         let theme = cx.global::<Settings>().theme.clone();
 
@@ -1346,7 +1346,7 @@ impl View for ContactList {
                             .boxed(),
                     )
                     .with_child(
-                        MouseEventHandler::<AddContact>::new(0, cx, |_, _| {
+                        MouseEventHandler::<AddContact, Self>::new(0, cx, |_, _| {
                             render_icon_button(
                                 &theme.contact_list.add_contact_button,
                                 "icons/user_plus_16.svg",
@@ -1354,10 +1354,10 @@ impl View for ContactList {
                             .boxed()
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_click(MouseButton::Left, |_, cx| {
+                        .on_click(MouseButton::Left, |_, _, cx| {
                             cx.dispatch_action(contacts_popover::ToggleContactFinder)
                         })
-                        .with_tooltip::<AddContact, _>(
+                        .with_tooltip::<AddContact>(
                             0,
                             "Search for new contact".into(),
                             None,
@@ -1387,7 +1387,7 @@ impl View for ContactList {
     }
 }
 
-fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element {
+fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Drawable<ContactList> {
     Svg::new(svg_path)
         .with_color(style.color)
         .constrained()

crates/collab_ui/src/contact_notification.rs 🔗

@@ -3,8 +3,7 @@ use std::sync::Arc;
 use crate::notifications::render_user_notification;
 use client::{ContactEventKind, User, UserStore};
 use gpui::{
-    elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, RenderContext, View,
-    ViewContext,
+    elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, View, ViewContext,
 };
 use workspace::notifications::Notification;
 
@@ -43,7 +42,7 @@ impl View for ContactNotification {
         "ContactNotification"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         match self.kind {
             ContactEventKind::Requested => render_user_notification(
                 self.user.clone(),

crates/collab_ui/src/contacts_popover.rs 🔗

@@ -1,9 +1,14 @@
-use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu};
+use crate::{
+    contact_finder::{build_contact_finder, ContactFinder},
+    contact_list::ContactList,
+    ToggleContactsMenu,
+};
 use client::UserStore;
 use gpui::{
-    actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, RenderContext,
-    View, ViewContext, ViewHandle,
+    actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View,
+    ViewContext, ViewHandle,
 };
+use picker::PickerEvent;
 use project::Project;
 use settings::Settings;
 
@@ -50,19 +55,19 @@ impl ContactsPopover {
     fn toggle_contact_finder(&mut self, _: &ToggleContactFinder, cx: &mut ViewContext<Self>) {
         match &self.child {
             Child::ContactList(list) => self.show_contact_finder(list.read(cx).editor_text(cx), cx),
-            Child::ContactFinder(finder) => {
-                self.show_contact_list(finder.read(cx).editor_text(cx), cx)
-            }
+            Child::ContactFinder(finder) => self.show_contact_list(finder.read(cx).query(cx), cx),
         }
     }
 
     fn show_contact_finder(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) {
         let child = cx.add_view(|cx| {
-            ContactFinder::new(self.user_store.clone(), cx).with_editor_text(editor_text, cx)
+            let finder = build_contact_finder(self.user_store.clone(), cx);
+            finder.set_query(editor_text, cx);
+            finder
         });
         cx.focus(&child);
         self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
-            crate::contact_finder::Event::Dismissed => cx.emit(Event::Dismissed),
+            PickerEvent::Dismiss => cx.emit(Event::Dismissed),
         }));
         self.child = Child::ContactFinder(child);
         cx.notify();
@@ -91,14 +96,14 @@ impl View for ContactsPopover {
         "ContactsPopover"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
         let child = match &self.child {
             Child::ContactList(child) => ChildView::new(child, cx),
             Child::ContactFinder(child) => ChildView::new(child, cx),
         };
 
-        MouseEventHandler::<ContactsPopover>::new(0, cx, |_, _| {
+        MouseEventHandler::<ContactsPopover, Self>::new(0, cx, |_, _| {
             Flex::column()
                 .with_child(child.flex(1., true).boxed())
                 .contained()
@@ -108,7 +113,7 @@ impl View for ContactsPopover {
                 .with_height(theme.contacts_popover.height)
                 .boxed()
         })
-        .on_down_out(MouseButton::Left, move |_, cx| {
+        .on_down_out(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_action(ToggleContactsMenu);
         })
         .boxed()

crates/collab_ui/src/face_pile.rs 🔗

@@ -7,12 +7,14 @@ use gpui::{
     },
     json::ToJson,
     serde_json::{self, json},
-    Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext,
+    Axis, Drawable, Element, SceneBuilder, ViewContext,
 };
 
+use crate::CollabTitlebarItem;
+
 pub(crate) struct FacePile {
     overlap: f32,
-    faces: Vec<ElementBox>,
+    faces: Vec<Element<CollabTitlebarItem>>,
 }
 
 impl FacePile {
@@ -24,20 +26,21 @@ impl FacePile {
     }
 }
 
-impl Element for FacePile {
+impl Drawable<CollabTitlebarItem> for FacePile {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        cx: &mut gpui::LayoutContext,
+        view: &mut CollabTitlebarItem,
+        cx: &mut ViewContext<CollabTitlebarItem>,
     ) -> (Vector2F, Self::LayoutState) {
         debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
 
         let mut width = 0.;
         for face in &mut self.faces {
-            width += face.layout(constraint, cx).x();
+            width += face.layout(constraint, view, cx).x();
         }
         width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
 
@@ -46,10 +49,12 @@ impl Element for FacePile {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _layout: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut CollabTitlebarItem,
+        cx: &mut ViewContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
@@ -59,8 +64,8 @@ impl Element for FacePile {
         for face in self.faces.iter_mut().rev() {
             let size = face.size();
             origin_x -= size.x();
-            cx.paint_layer(None, |cx| {
-                face.paint(vec2f(origin_x, origin_y), visible_bounds, cx);
+            scene.paint_layer(None, |scene| {
+                face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
             });
             origin_x += self.overlap;
         }
@@ -75,7 +80,8 @@ impl Element for FacePile {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> Option<RectF> {
         None
     }
@@ -85,7 +91,8 @@ impl Element for FacePile {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &CollabTitlebarItem,
+        _: &ViewContext<CollabTitlebarItem>,
     ) -> serde_json::Value {
         json!({
             "type": "FacePile",
@@ -94,8 +101,8 @@ impl Element for FacePile {
     }
 }
 
-impl Extend<ElementBox> for FacePile {
-    fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+impl Extend<Element<CollabTitlebarItem>> for FacePile {
+    fn extend<T: IntoIterator<Item = Element<CollabTitlebarItem>>>(&mut self, children: T) {
         self.faces.extend(children);
     }
 }

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f},
     impl_internal_actions,
     platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
-    AppContext, Entity, RenderContext, View, ViewContext,
+    AppContext, Element, Entity, View, ViewContext,
 };
 use settings::Settings;
 use util::ResultExt;
@@ -99,7 +99,7 @@ impl IncomingCallNotification {
         }
     }
 
-    fn render_caller(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_caller(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = &cx.global::<Settings>().theme.incoming_call_notification;
         let default_project = proto::ParticipantProject::default();
         let initial_project = self
@@ -165,13 +165,13 @@ impl IncomingCallNotification {
             .boxed()
     }
 
-    fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
         enum Accept {}
         enum Decline {}
 
         Flex::column()
             .with_child(
-                MouseEventHandler::<Accept>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     Label::new("Accept", theme.accept_button.text.clone())
                         .aligned()
@@ -180,14 +180,14 @@ impl IncomingCallNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(RespondToCall { accept: true });
                 })
                 .flex(1., true)
                 .boxed(),
             )
             .with_child(
-                MouseEventHandler::<Decline>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.incoming_call_notification;
                     Label::new("Decline", theme.decline_button.text.clone())
                         .aligned()
@@ -196,7 +196,7 @@ impl IncomingCallNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(RespondToCall { accept: false });
                 })
                 .flex(1., true)
@@ -222,7 +222,7 @@ impl View for IncomingCallNotification {
         "IncomingCallNotification"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let background = cx
             .global::<Settings>()
             .theme

crates/collab_ui/src/notifications.rs 🔗

@@ -2,7 +2,7 @@ use client::User;
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Action, Element, ElementBox, RenderContext, View,
+    Action, Drawable, Element, View, ViewContext,
 };
 use settings::Settings;
 use std::sync::Arc;
@@ -16,8 +16,8 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
     body: Option<&'static str>,
     dismiss_action: A,
     buttons: Vec<(&'static str, Box<dyn Action>)>,
-    cx: &mut RenderContext<V>,
-) -> ElementBox {
+    cx: &mut ViewContext<V>,
+) -> Element<V> {
     let theme = cx.global::<Settings>().theme.clone();
     let theme = &theme.contact_notification;
 
@@ -51,7 +51,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                     .boxed(),
                 )
                 .with_child(
-                    MouseEventHandler::<Dismiss>::new(user.id as usize, cx, |state, _| {
+                    MouseEventHandler::<Dismiss, V>::new(user.id as usize, cx, |state, _| {
                         let style = theme.dismiss_button.style_for(state, false);
                         Svg::new("icons/x_mark_8.svg")
                             .with_color(style.color)
@@ -67,7 +67,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
                     .with_padding(Padding::uniform(5.))
-                    .on_click(MouseButton::Left, move |_, cx| {
+                    .on_click(MouseButton::Left, move |_, _, cx| {
                         cx.dispatch_any_action(dismiss_action.boxed_clone())
                     })
                     .aligned()
@@ -96,7 +96,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                 Flex::row()
                     .with_children(buttons.into_iter().enumerate().map(
                         |(ix, (message, action))| {
-                            MouseEventHandler::<Button>::new(ix, cx, |state, _| {
+                            MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
                                 let button = theme.button.style_for(state, false);
                                 Label::new(message, button.text.clone())
                                     .contained()
@@ -104,7 +104,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
                                     .boxed()
                             })
                             .with_cursor_style(CursorStyle::PointingHand)
-                            .on_click(MouseButton::Left, move |_, cx| {
+                            .on_click(MouseButton::Left, move |_, _, cx| {
                                 cx.dispatch_any_action(action.boxed_clone())
                             })
                             .boxed()

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
-    AppContext, Entity, RenderContext, View, ViewContext,
+    AppContext, Entity, View, ViewContext,
 };
 use settings::Settings;
 use std::sync::Arc;
@@ -94,17 +94,15 @@ impl ProjectSharedNotification {
     }
 
     fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
-        let window_id = cx.window_id();
-        cx.remove_window(window_id);
+        cx.remove_window();
         cx.propagate_action();
     }
 
     fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
-        let window_id = cx.window_id();
-        cx.remove_window(window_id);
+        cx.remove_window();
     }
 
-    fn render_owner(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_owner(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = &cx.global::<Settings>().theme.project_shared_notification;
         Flex::row()
             .with_children(self.owner.avatar.clone().map(|avatar| {
@@ -164,7 +162,7 @@ impl ProjectSharedNotification {
             .boxed()
     }
 
-    fn render_buttons(&self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_buttons(&self, cx: &mut ViewContext<Self>) -> Element<Self> {
         enum Open {}
         enum Dismiss {}
 
@@ -173,7 +171,7 @@ impl ProjectSharedNotification {
 
         Flex::column()
             .with_child(
-                MouseEventHandler::<Open>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.project_shared_notification;
                     Label::new("Open", theme.open_button.text.clone())
                         .aligned()
@@ -182,7 +180,7 @@ impl ProjectSharedNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     cx.dispatch_action(JoinProject {
                         project_id,
                         follow_user_id: owner_user_id,
@@ -192,7 +190,7 @@ impl ProjectSharedNotification {
                 .boxed(),
             )
             .with_child(
-                MouseEventHandler::<Dismiss>::new(0, cx, |_, cx| {
+                MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme.project_shared_notification;
                     Label::new("Dismiss", theme.dismiss_button.text.clone())
                         .aligned()
@@ -201,7 +199,7 @@ impl ProjectSharedNotification {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(DismissProject);
                 })
                 .flex(1., true)
@@ -227,7 +225,7 @@ impl View for ProjectSharedNotification {
         "ProjectSharedNotification"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::Element<Self> {
         let background = cx
             .global::<Settings>()
             .theme

crates/collab_ui/src/sharing_status_indicator.rs 🔗

@@ -3,7 +3,7 @@ use gpui::{
     color::Color,
     elements::{MouseEventHandler, Svg},
     platform::{Appearance, MouseButton},
-    AppContext, Element, ElementBox, Entity, RenderContext, View,
+    AppContext, Drawable, Element, Entity, View, ViewContext,
 };
 use settings::Settings;
 
@@ -40,13 +40,13 @@ impl View for SharingStatusIndicator {
         "SharingStatusIndicator"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
-        let color = match cx.appearance {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
+        let color = match cx.window_appearance() {
             Appearance::Light | Appearance::VibrantLight => Color::black(),
             Appearance::Dark | Appearance::VibrantDark => Color::white(),
         };
 
-        MouseEventHandler::<Self>::new(0, cx, |_, _| {
+        MouseEventHandler::<Self, Self>::new(0, cx, |_, _| {
             Svg::new("icons/disable_screen_sharing_12.svg")
                 .with_color(color)
                 .constrained()
@@ -54,7 +54,7 @@ impl View for SharingStatusIndicator {
                 .aligned()
                 .boxed()
         })
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(ToggleScreenSharing);
         })
         .boxed()

crates/command_palette/src/command_palette.rs 🔗

@@ -1,26 +1,25 @@
 use collections::CommandPaletteFilter;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions,
-    elements::{ChildView, Flex, Label, ParentElement},
-    keymap_matcher::Keystroke,
-    Action, AnyViewHandle, AppContext, Element, Entity, MouseState, RenderContext, View,
-    ViewContext, ViewHandle,
+    actions, elements::*, keymap_matcher::Keystroke, Action, AppContext, Drawable, MouseState,
+    ViewContext,
 };
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
 use std::cmp;
+use util::ResultExt;
 use workspace::Workspace;
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(CommandPalette::toggle);
-    Picker::<CommandPalette>::init(cx);
+    cx.add_action(toggle_command_palette);
+    CommandPalette::init(cx);
 }
 
 actions!(command_palette, [Toggle]);
 
-pub struct CommandPalette {
-    picker: ViewHandle<Picker<Self>>,
+pub type CommandPalette = Picker<CommandPaletteDelegate>;
+
+pub struct CommandPaletteDelegate {
     actions: Vec<Command>,
     matches: Vec<StringMatch>,
     selected_ix: usize,
@@ -42,11 +41,21 @@ struct Command {
     keystrokes: Vec<Keystroke>,
 }
 
-impl CommandPalette {
-    pub fn new(focused_view_id: usize, cx: &mut ViewContext<Self>) -> Self {
-        let this = cx.weak_handle();
+fn toggle_command_palette(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+    let workspace = cx.handle();
+    let focused_view_id = cx.focused_view_id().unwrap_or_else(|| workspace.id());
+
+    cx.defer(move |workspace, cx| {
+        workspace.toggle_modal(cx, |_, cx| {
+            cx.add_view(|cx| Picker::new(CommandPaletteDelegate::new(focused_view_id, cx), cx))
+        });
+    });
+}
+
+impl CommandPaletteDelegate {
+    pub fn new(focused_view_id: usize, cx: &mut ViewContext<Picker<Self>>) -> Self {
         let actions = cx
-            .available_actions(cx.window_id(), focused_view_id)
+            .available_actions(focused_view_id)
             .filter_map(|(name, action, bindings)| {
                 if cx.has_global::<CommandPaletteFilter>() {
                     let filter = cx.global::<CommandPaletteFilter>();
@@ -67,79 +76,20 @@ impl CommandPalette {
             })
             .collect();
 
-        let picker = cx.add_view(|cx| Picker::new("Execute a command...", this, cx));
         Self {
-            picker,
             actions,
             matches: vec![],
             selected_ix: 0,
             focused_view_id,
         }
     }
-
-    fn toggle(_: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-        let workspace = cx.handle();
-        let window_id = cx.window_id();
-        let focused_view_id = cx
-            .focused_view_id(window_id)
-            .unwrap_or_else(|| workspace.id());
-
-        cx.as_mut().defer(move |cx| {
-            let this = cx.add_view(&workspace, |cx| Self::new(focused_view_id, cx));
-            workspace.update(cx, |workspace, cx| {
-                workspace.toggle_modal(cx, |_, cx| {
-                    cx.subscribe(&this, Self::on_event).detach();
-                    this
-                });
-            });
-        });
-    }
-
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<Self>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => workspace.dismiss_modal(cx),
-            Event::Confirmed {
-                window_id,
-                focused_view_id,
-                action,
-            } => {
-                let window_id = *window_id;
-                let focused_view_id = *focused_view_id;
-                let action = action.boxed_clone();
-                workspace.dismiss_modal(cx);
-                cx.as_mut()
-                    .defer(move |cx| cx.dispatch_any_action_at(window_id, focused_view_id, action))
-            }
-        }
-    }
-}
-
-impl Entity for CommandPalette {
-    type Event = Event;
 }
 
-impl View for CommandPalette {
-    fn ui_name() -> &'static str {
-        "CommandPalette"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for CommandPaletteDelegate {
+    fn placeholder_text(&self) -> std::sync::Arc<str> {
+        "Execute a command...".into()
     }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl PickerDelegate for CommandPalette {
     fn match_count(&self) -> usize {
         self.matches.len()
     }
@@ -148,14 +98,14 @@ impl PickerDelegate for CommandPalette {
         self.selected_ix
     }
 
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
         self.selected_ix = ix;
     }
 
     fn update_matches(
         &mut self,
         query: String,
-        cx: &mut gpui::ViewContext<Self>,
+        cx: &mut ViewContext<Picker<Self>>,
     ) -> gpui::Task<()> {
         let candidates = self
             .actions
@@ -167,7 +117,7 @@ impl PickerDelegate for CommandPalette {
                 char_bag: command.name.chars().collect(),
             })
             .collect::<Vec<_>>();
-        cx.spawn(move |this, mut cx| async move {
+        cx.spawn(move |picker, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -190,32 +140,34 @@ impl PickerDelegate for CommandPalette {
                 )
                 .await
             };
-            this.update(&mut cx, |this, _| {
-                this.matches = matches;
-                if this.matches.is_empty() {
-                    this.selected_ix = 0;
-                } else {
-                    this.selected_ix = cmp::min(this.selected_ix, this.matches.len() - 1);
-                }
-            });
+            picker
+                .update(&mut cx, |picker, _| {
+                    let delegate = picker.delegate_mut();
+                    delegate.matches = matches;
+                    if delegate.matches.is_empty() {
+                        delegate.selected_ix = 0;
+                    } else {
+                        delegate.selected_ix =
+                            cmp::min(delegate.selected_ix, delegate.matches.len() - 1);
+                    }
+                })
+                .log_err();
         })
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
-    }
+    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
         if !self.matches.is_empty() {
+            let window_id = cx.window_id();
+            let focused_view_id = self.focused_view_id;
             let action_ix = self.matches[self.selected_ix].candidate_id;
-            cx.emit(Event::Confirmed {
-                window_id: cx.window_id(),
-                focused_view_id: self.focused_view_id,
-                action: self.actions.remove(action_ix).action,
+            let action = self.actions.remove(action_ix).action;
+            cx.defer(move |_, cx| {
+                cx.dispatch_any_action_at(window_id, focused_view_id, action);
             });
-        } else {
-            cx.emit(Event::Dismissed);
         }
+        cx.emit(PickerEvent::Dismiss);
     }
 
     fn render_match(
@@ -224,7 +176,7 @@ impl PickerDelegate for CommandPalette {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &gpui::AppContext,
-    ) -> gpui::ElementBox {
+    ) -> Element<Picker<Self>> {
         let mat = &self.matches[ix];
         let command = &self.actions[mat.candidate_id];
         let settings = cx.global::<Settings>();
@@ -360,7 +312,7 @@ mod tests {
         });
 
         workspace.update(cx, |workspace, cx| {
-            CommandPalette::toggle(workspace, &Toggle, cx)
+            toggle_command_palette(workspace, &Toggle, cx);
         });
 
         let palette = workspace.read_with(cx, |workspace, _| {
@@ -369,13 +321,15 @@ mod tests {
 
         palette
             .update(cx, |palette, cx| {
-                palette.update_matches("bcksp".to_string(), cx)
+                palette
+                    .delegate_mut()
+                    .update_matches("bcksp".to_string(), cx)
             })
             .await;
 
         palette.update(cx, |palette, cx| {
-            assert_eq!(palette.matches[0].string, "editor: backspace");
-            palette.confirm(cx);
+            assert_eq!(palette.delegate().matches[0].string, "editor: backspace");
+            palette.confirm(&Default::default(), cx);
         });
 
         editor.read_with(cx, |editor, cx| {
@@ -390,7 +344,7 @@ mod tests {
         });
 
         workspace.update(cx, |workspace, cx| {
-            CommandPalette::toggle(workspace, &Toggle, cx);
+            toggle_command_palette(workspace, &Toggle, cx);
         });
 
         // Assert editor command not present
@@ -400,10 +354,14 @@ mod tests {
 
         palette
             .update(cx, |palette, cx| {
-                palette.update_matches("bcksp".to_string(), cx)
+                palette
+                    .delegate_mut()
+                    .update_matches("bcksp".to_string(), cx)
             })
             .await;
 
-        palette.update(cx, |palette, _| assert!(palette.matches.is_empty()));
+        palette.update(cx, |palette, _| {
+            assert!(palette.delegate().matches.is_empty())
+        });
     }
 }

crates/context_menu/src/context_menu.rs 🔗

@@ -4,15 +4,13 @@ use gpui::{
     impl_internal_actions,
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton},
-    Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, SizeConstraint,
-    Subscription, View, ViewContext,
+    Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
+    View, ViewContext,
 };
 use menu::*;
 use settings::Settings;
 use std::{any::TypeId, borrow::Cow, time::Duration};
 
-pub type StaticItem = Box<dyn Fn(&mut AppContext) -> ElementBox>;
-
 #[derive(Copy, Clone, PartialEq)]
 struct Clicked;
 
@@ -28,7 +26,10 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(ContextMenu::cancel);
 }
 
-type ContextMenuItemBuilder = Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> ElementBox>;
+pub type StaticItem = Box<dyn Fn(&mut AppContext) -> Element<ContextMenu>>;
+
+type ContextMenuItemBuilder =
+    Box<dyn Fn(&mut MouseState, &theme::ContextMenuItem) -> Element<ContextMenu>>;
 
 pub enum ContextMenuItemLabel {
     String(Cow<'static, str>),
@@ -141,7 +142,7 @@ impl View for ContextMenu {
         cx
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if !self.visible {
             return Empty::new().boxed();
         }
@@ -151,10 +152,10 @@ impl View for ContextMenu {
         let expanded_menu = self
             .render_menu(cx)
             .constrained()
-            .dynamically(move |constraint, cx| {
+            .dynamically(move |constraint, view, cx| {
                 SizeConstraint::strict_along(
                     Axis::Horizontal,
-                    collapsed_menu.layout(constraint, cx).x(),
+                    collapsed_menu.layout(constraint, view, cx).x(),
                 )
             })
             .boxed();
@@ -209,9 +210,9 @@ impl ContextMenu {
                 cx.notify();
                 cx.spawn(|this, mut cx| async move {
                     cx.background().timer(Duration::from_millis(50)).await;
-                    this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx));
+                    this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))
                 })
-                .detach();
+                .detach_and_log_err(cx);
             }
         }
     }
@@ -314,7 +315,7 @@ impl ContextMenu {
             self.visible = true;
             self.show_count += 1;
             if !cx.is_self_focused() {
-                self.previously_focused_view_id = cx.focused_view_id(cx.window_id());
+                self.previously_focused_view_id = cx.focused_view_id();
             }
             cx.focus_self();
         } else {
@@ -327,8 +328,10 @@ impl ContextMenu {
         self.position_mode = mode;
     }
 
-    fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
-        let window_id = cx.window_id();
+    fn render_menu_for_measurement(
+        &self,
+        cx: &mut ViewContext<Self>,
+    ) -> impl Drawable<ContextMenu> {
         let style = cx.global::<Settings>().theme.context_menu.clone();
         Flex::row()
             .with_child(
@@ -386,7 +389,6 @@ impl ContextMenu {
                                 };
 
                                 KeystrokeLabel::new(
-                                    window_id,
                                     view_id,
                                     action.boxed_clone(),
                                     style.keystroke.container,
@@ -414,14 +416,13 @@ impl ContextMenu {
             .with_style(style.container)
     }
 
-    fn render_menu(&self, cx: &mut RenderContext<Self>) -> impl Element {
+    fn render_menu(&self, cx: &mut ViewContext<Self>) -> impl Drawable<ContextMenu> {
         enum Menu {}
         enum MenuItem {}
 
         let style = cx.global::<Settings>().theme.context_menu.clone();
 
-        let window_id = cx.window_id();
-        MouseEventHandler::<Menu>::new(0, cx, |_, cx| {
+        MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
             Flex::column()
                 .with_children(self.items.iter().enumerate().map(|(ix, item)| {
                     match item {
@@ -435,7 +436,7 @@ impl ContextMenu {
                                 }
                             };
 
-                            MouseEventHandler::<MenuItem>::new(ix, cx, |state, _| {
+                            MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
                                 let style =
                                     style.item.style_for(state, Some(ix) == self.selected_index);
 
@@ -452,7 +453,6 @@ impl ContextMenu {
                                     })
                                     .with_child({
                                         KeystrokeLabel::new(
-                                            window_id,
                                             view_id,
                                             action.boxed_clone(),
                                             style.keystroke.container,
@@ -466,14 +466,14 @@ impl ContextMenu {
                                     .boxed()
                             })
                             .with_cursor_style(CursorStyle::PointingHand)
-                            .on_up(MouseButton::Left, |_, _| {}) // Capture these events
-                            .on_down(MouseButton::Left, |_, _| {}) // Capture these events
-                            .on_click(MouseButton::Left, move |_, cx| {
+                            .on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
+                            .on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
+                            .on_click(MouseButton::Left, move |_, _, cx| {
                                 cx.dispatch_action(Clicked);
                                 let window_id = cx.window_id();
                                 cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
                             })
-                            .on_drag(MouseButton::Left, |_, _| {})
+                            .on_drag(MouseButton::Left, |_, _, _| {})
                             .boxed()
                         }
 
@@ -491,7 +491,7 @@ impl ContextMenu {
                 .with_style(style.container)
                 .boxed()
         })
-        .on_down_out(MouseButton::Left, |_, cx| cx.dispatch_action(Cancel))
-        .on_down_out(MouseButton::Right, |_, cx| cx.dispatch_action(Cancel))
+        .on_down_out(MouseButton::Left, |_, _, cx| cx.dispatch_action(Cancel))
+        .on_down_out(MouseButton::Right, |_, _, cx| cx.dispatch_action(Cancel))
     }
 }

crates/copilot/src/copilot.rs 🔗

@@ -48,8 +48,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
 
     cx.observe(&copilot, |handle, cx| {
         let status = handle.read(cx).status();
-        cx.update_global::<collections::CommandPaletteFilter, _, _>(
-            move |filter, _cx| match status {
+        cx.update_default_global::<collections::CommandPaletteFilter, _, _>(move |filter, _cx| {
+            match status {
                 Status::Disabled => {
                     filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
                     filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
@@ -62,8 +62,8 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
                     filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
                     filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
                 }
-            },
-        );
+            }
+        });
     })
     .detach();
 

crates/copilot/src/sign_in.rs 🔗

@@ -4,7 +4,8 @@ use gpui::{
     geometry::rect::RectF,
     impl_internal_actions,
     platform::{WindowBounds, WindowKind, WindowOptions},
-    AppContext, ClipboardItem, Element, Entity, View, ViewContext, ViewHandle,
+    AnyViewHandle, AppContext, ClipboardItem, Drawable, Element, Entity, View, ViewContext,
+    ViewHandle,
 };
 use settings::Settings;
 use theme::ui::modal;
@@ -31,26 +32,32 @@ pub fn init(cx: &mut AppContext) {
             match &status {
                 crate::Status::SigningIn { prompt } => {
                     if let Some(code_verification_handle) = code_verification.as_mut() {
-                        if cx.has_window(code_verification_handle.window_id()) {
-                            code_verification_handle.update(cx, |code_verification_view, cx| {
-                                code_verification_view.set_status(status, cx)
+                        let window_id = code_verification_handle.window_id();
+                        if cx.has_window(window_id) {
+                            cx.update_window(window_id, |cx| {
+                                code_verification_handle.update(cx, |code_verification, cx| {
+                                    code_verification.set_status(status, cx)
+                                });
+                                cx.activate_window();
                             });
-                            cx.activate_window(code_verification_handle.window_id());
                         } else {
-                            create_copilot_auth_window(cx, &status, &mut code_verification);
+                            code_verification = Some(create_copilot_auth_window(cx, &status));
                         }
                     } else if let Some(_prompt) = prompt {
-                        create_copilot_auth_window(cx, &status, &mut code_verification);
+                        code_verification = Some(create_copilot_auth_window(cx, &status));
                     }
                 }
                 Status::Authorized | Status::Unauthorized => {
                     if let Some(code_verification) = code_verification.as_ref() {
-                        code_verification.update(cx, |code_verification, cx| {
-                            code_verification.set_status(status, cx)
-                        });
+                        let window_id = code_verification.window_id();
+                        cx.update_window(window_id, |cx| {
+                            code_verification.update(cx, |code_verification, cx| {
+                                code_verification.set_status(status, cx)
+                            });
 
-                        cx.platform().activate(true);
-                        cx.activate_window(code_verification.window_id());
+                            cx.platform().activate(true);
+                            cx.activate_window();
+                        });
                     }
                 }
                 _ => {
@@ -73,8 +80,7 @@ pub fn init(cx: &mut AppContext) {
 fn create_copilot_auth_window(
     cx: &mut AppContext,
     status: &Status,
-    code_verification: &mut Option<ViewHandle<CopilotCodeVerification>>,
-) {
+) -> ViewHandle<CopilotCodeVerification> {
     let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
     let window_options = WindowOptions {
         bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
@@ -88,7 +94,7 @@ fn create_copilot_auth_window(
     let (_, view) = cx.add_window(window_options, |_cx| {
         CopilotCodeVerification::new(status.clone())
     });
-    *code_verification = Some(view);
+    view
 }
 
 pub struct CopilotCodeVerification {
@@ -112,8 +118,8 @@ impl CopilotCodeVerification {
     fn render_device_code(
         data: &PromptUserDeviceFlow,
         style: &theme::Copilot,
-        cx: &mut gpui::RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let copied = cx
             .read_from_clipboard()
             .map(|item| item.text() == &data.user_code)
@@ -121,7 +127,7 @@ impl CopilotCodeVerification {
 
         let device_code_style = &style.auth.prompting.device_code;
 
-        MouseEventHandler::<Self>::new(0, cx, |state, _cx| {
+        MouseEventHandler::<Self, _>::new(0, cx, |state, _cx| {
             Flex::row()
                 .with_children([
                     Label::new(data.user_code.clone(), device_code_style.text.clone())
@@ -148,7 +154,7 @@ impl CopilotCodeVerification {
         })
         .on_click(gpui::platform::MouseButton::Left, {
             let user_code = data.user_code.clone();
-            move |_, cx| {
+            move |_, _, cx| {
                 cx.platform()
                     .write_to_clipboard(ClipboardItem::new(user_code.clone()));
                 cx.notify();
@@ -162,8 +168,10 @@ impl CopilotCodeVerification {
         connect_clicked: bool,
         data: &PromptUserDeviceFlow,
         style: &theme::Copilot,
-        cx: &mut gpui::RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
+        enum ConnectButton {}
+
         Flex::column()
             .with_children([
                 Flex::column()
@@ -205,7 +213,7 @@ impl CopilotCodeVerification {
                     .contained()
                     .with_style(style.auth.prompting.hint.container.clone())
                     .boxed(),
-                theme::ui::cta_button_with_click(
+                theme::ui::cta_button_with_click::<ConnectButton, _, _, _>(
                     if connect_clicked {
                         "Waiting for connection..."
                     } else {
@@ -216,7 +224,7 @@ impl CopilotCodeVerification {
                     cx,
                     {
                         let verification_uri = data.verification_uri.clone();
-                        move |_, cx| {
+                        move |_, _, cx| {
                             cx.platform().open_url(&verification_uri);
                             cx.dispatch_action(ClickedConnect)
                         }
@@ -227,10 +235,9 @@ impl CopilotCodeVerification {
             .align_children_center()
             .boxed()
     }
-    fn render_enabled_modal(
-        style: &theme::Copilot,
-        cx: &mut gpui::RenderContext<Self>,
-    ) -> ElementBox {
+    fn render_enabled_modal(style: &theme::Copilot, cx: &mut ViewContext<Self>) -> Element<Self> {
+        enum DoneButton {}
+
         let enabled_style = &style.auth.authorized;
         Flex::column()
             .with_children([
@@ -261,15 +268,12 @@ impl CopilotCodeVerification {
                     .contained()
                     .with_style(enabled_style.hint.container)
                     .boxed(),
-                theme::ui::cta_button_with_click(
+                theme::ui::cta_button_with_click::<DoneButton, _, _, _>(
                     "Done",
                     style.auth.content_width,
                     &style.auth.cta_button,
                     cx,
-                    |_, cx| {
-                        let window_id = cx.window_id();
-                        cx.remove_window(window_id)
-                    },
+                    |_, _, cx| cx.remove_window(),
                 )
                 .boxed(),
             ])
@@ -278,8 +282,8 @@ impl CopilotCodeVerification {
     }
     fn render_unauthorized_modal(
         style: &theme::Copilot,
-        cx: &mut gpui::RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let unauthorized_style = &style.auth.not_authorized;
 
         Flex::column()
@@ -322,14 +326,13 @@ impl CopilotCodeVerification {
                     .contained()
                     .with_style(unauthorized_style.warning.container)
                     .boxed(),
-                theme::ui::cta_button_with_click(
+                theme::ui::cta_button_with_click::<CopilotCodeVerification, _, _, _>(
                     "Subscribe on GitHub",
                     style.auth.content_width,
                     &style.auth.cta_button,
                     cx,
-                    |_, cx| {
-                        let window_id = cx.window_id();
-                        cx.remove_window(window_id);
+                    |_, _, cx| {
+                        cx.remove_window();
                         cx.platform().open_url(COPILOT_SIGN_UP_URL)
                     },
                 )
@@ -349,18 +352,20 @@ impl View for CopilotCodeVerification {
         "CopilotCodeVerification"
     }
 
-    fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
+    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         cx.notify()
     }
 
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
+    fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
         cx.notify()
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
+        enum ConnectModal {}
+
         let style = cx.global::<Settings>().theme.clone();
 
-        modal("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
+        modal::<ConnectModal, _, _, _>("Connect Copilot to Zed", &style.copilot.modal, cx, |cx| {
             Flex::column()
                 .with_children([
                     theme::ui::icon(&style.copilot.auth.header).boxed(),

crates/copilot_button/src/copilot_button.rs 🔗

@@ -6,8 +6,7 @@ use gpui::{
     elements::*,
     impl_internal_actions,
     platform::{CursorStyle, MouseButton},
-    AppContext, Element, ElementBox, Entity, MouseState, RenderContext, Subscription, View,
-    ViewContext, ViewHandle,
+    AppContext, Drawable, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle,
 };
 use settings::{settings_file::SettingsFile, Settings};
 use workspace::{
@@ -156,7 +155,7 @@ impl View for CopilotButton {
         "CopilotButton"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let settings = cx.global::<Settings>();
 
         if !settings.features.copilot {
@@ -176,7 +175,7 @@ impl View for CopilotButton {
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<Self>::new(0, cx, {
+                MouseEventHandler::<Self, _>::new(0, cx, {
                     let theme = theme.clone();
                     let status = status.clone();
                     move |state, _cx| {
@@ -218,7 +217,7 @@ impl View for CopilotButton {
                 .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, {
                     let status = status.clone();
-                    move |_, cx| match status {
+                    move |_, _, cx| match status {
                         Status::Authorized => cx.dispatch_action(DeployCopilotMenu),
                         Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action(
                             COPILOT_ERROR_TOAST_ID,
@@ -229,13 +228,7 @@ impl View for CopilotButton {
                         _ => cx.dispatch_action(DeployCopilotStartMenu),
                     }
                 })
-                .with_tooltip::<Self, _>(
-                    0,
-                    "GitHub Copilot".into(),
-                    None,
-                    theme.tooltip.clone(),
-                    cx,
-                )
+                .with_tooltip::<Self>(0, "GitHub Copilot".into(), None, theme.tooltip.clone(), cx)
                 .boxed(),
             )
             .with_child(

crates/diagnostics/src/diagnostics.rs 🔗

@@ -11,8 +11,7 @@ use editor::{
 };
 use gpui::{
     actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
-    AppContext, Entity, ModelHandle, RenderContext, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    AppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use language::{
     Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -90,7 +89,7 @@ impl View for ProjectDiagnosticsEditor {
         "ProjectDiagnosticsEditor"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if self.path_states.is_empty() {
             let theme = &cx.global::<Settings>().theme.project_diagnostics;
             Label::new("No problems in workspace", theme.empty_message.clone())
@@ -223,7 +222,7 @@ impl ProjectDiagnosticsEditor {
                         .await?;
                     this.update(&mut cx, |this, cx| {
                         this.populate_excerpts(path, language_server_id, buffer, cx)
-                    })
+                    })?;
                 }
                 Result::<_, anyhow::Error>::Ok(())
             }
@@ -531,12 +530,12 @@ impl ProjectDiagnosticsEditor {
 }
 
 impl Item for ProjectDiagnosticsEditor {
-    fn tab_content(
+    fn tab_content<T: View>(
         &self,
         _detail: Option<usize>,
         style: &theme::Tab,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<T> {
         render_summary(
             &self.summary,
             &style.label.text,
@@ -726,11 +725,11 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
     })
 }
 
-pub(crate) fn render_summary(
+pub(crate) fn render_summary<T: View>(
     summary: &DiagnosticSummary,
     text_style: &TextStyle,
     theme: &theme::ProjectDiagnostics,
-) -> ElementBox {
+) -> Element<T> {
     if summary.error_count == 0 && summary.warning_count == 0 {
         Label::new("No problems", text_style.clone()).boxed()
     } else {
@@ -804,7 +803,7 @@ mod tests {
         display_map::{BlockContext, TransformBlock},
         DisplayPoint,
     };
-    use gpui::TestAppContext;
+    use gpui::{TestAppContext, WindowContext};
     use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
     use project::FakeFs;
     use serde_json::json;
@@ -1479,10 +1478,8 @@ mod tests {
         });
     }
 
-    fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
-        let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
-        let mut cx = presenter.build_layout_context(Default::default(), false, cx);
-        cx.render(editor, |editor, cx| {
+    fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
+        editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
             snapshot
                 .blocks_in_range(0..snapshot.max_point().row())
@@ -1490,7 +1487,7 @@ mod tests {
                     let name = match block {
                         TransformBlock::Custom(block) => block
                             .render(&mut BlockContext {
-                                cx,
+                                view_context: cx,
                                 anchor_x: 0.,
                                 scroll_x: 0.,
                                 gutter_padding: 0.,

crates/diagnostics/src/items.rs 🔗

@@ -3,8 +3,8 @@ use editor::{Editor, GoToDiagnostic};
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    serde_json, AppContext, Entity, ModelHandle, RenderContext, Subscription, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    serde_json, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
+    WeakViewHandle,
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
@@ -85,14 +85,14 @@ impl View for DiagnosticIndicator {
         "DiagnosticIndicator"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         enum Summary {}
         enum Message {}
 
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let in_progress = !self.in_progress_checks.is_empty();
         let mut element = Flex::row().with_child(
-            MouseEventHandler::<Summary>::new(0, cx, |state, cx| {
+            MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
                 let style = cx
                     .global::<Settings>()
                     .theme
@@ -164,8 +164,10 @@ impl View for DiagnosticIndicator {
                     .boxed()
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Deploy))
-            .with_tooltip::<Summary, _>(
+            .on_click(MouseButton::Left, |_, _, cx| {
+                cx.dispatch_action(crate::Deploy)
+            })
+            .with_tooltip::<Summary>(
                 0,
                 "Project Diagnostics".to_string(),
                 Some(Box::new(crate::Deploy)),
@@ -190,7 +192,7 @@ impl View for DiagnosticIndicator {
         } else if let Some(diagnostic) = &self.current_diagnostic {
             let message_style = style.diagnostic_message.clone();
             element.add_child(
-                MouseEventHandler::<Message>::new(1, cx, |state, _| {
+                MouseEventHandler::<Message, _>::new(1, cx, |state, _| {
                     Label::new(
                         diagnostic.message.split('\n').next().unwrap().to_string(),
                         message_style.style_for(state, false).text.clone(),
@@ -201,7 +203,7 @@ impl View for DiagnosticIndicator {
                     .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(GoToDiagnostic)
                 })
                 .boxed(),

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},
-    AppContext, Element, ElementBox, EventContext, RenderContext, View, WeakViewHandle,
+    Drawable, Element, View, ViewContext, WeakViewHandle, WindowContext,
 };
 
 const DEAD_ZONE: f32 = 4.;
@@ -26,7 +26,7 @@ enum State<V: View> {
         region_offset: Vector2F,
         region: RectF,
         payload: Rc<dyn Any + 'static>,
-        render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
+        render: Rc<dyn Fn(Rc<dyn Any>, &mut ViewContext<V>) -> Element<V>>,
     },
     Canceled,
 }
@@ -111,7 +111,7 @@ impl<V: View> DragAndDrop<V> {
         })
     }
 
-    pub fn drag_started(event: MouseDown, cx: &mut EventContext) {
+    pub fn drag_started(event: MouseDown, cx: &mut WindowContext) {
         cx.update_global(|this: &mut Self, _| {
             this.currently_dragged = Some(State::Down {
                 region_offset: event.position - event.region.origin(),
@@ -123,8 +123,8 @@ impl<V: View> DragAndDrop<V> {
     pub fn dragging<T: Any>(
         event: MouseDrag,
         payload: Rc<T>,
-        cx: &mut EventContext,
-        render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
+        cx: &mut WindowContext,
+        render: Rc<impl 'static + Fn(&T, &mut ViewContext<V>) -> Element<V>>,
     ) {
         let window_id = cx.window_id();
         cx.update_global(|this: &mut Self, cx| {
@@ -178,7 +178,7 @@ impl<V: View> DragAndDrop<V> {
         });
     }
 
-    pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
+    pub fn render(cx: &mut ViewContext<V>) -> Option<Element<V>> {
         enum DraggedElementHandler {}
         cx.global::<Self>()
             .currently_dragged
@@ -202,20 +202,22 @@ impl<V: View> DragAndDrop<V> {
                         let position = position - region_offset;
                         Some(
                             Overlay::new(
-                                MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, cx| {
-                                    render(payload, cx)
-                                })
+                                MouseEventHandler::<DraggedElementHandler, V>::new(
+                                    0,
+                                    cx,
+                                    |_, cx| render(payload, cx),
+                                )
                                 .with_cursor_style(CursorStyle::Arrow)
-                                .on_up(MouseButton::Left, |_, cx| {
-                                    cx.defer(|cx| {
+                                .on_up(MouseButton::Left, |_, _, cx| {
+                                    cx.window_context().defer(|cx| {
                                         cx.update_global::<Self, _, _>(|this, cx| {
                                             this.finish_dragging(cx)
                                         });
                                     });
                                     cx.propagate_event();
                                 })
-                                .on_up_out(MouseButton::Left, |_, cx| {
-                                    cx.defer(|cx| {
+                                .on_up_out(MouseButton::Left, |_, _, cx| {
+                                    cx.window_context().defer(|cx| {
                                         cx.update_global::<Self, _, _>(|this, cx| {
                                             this.finish_dragging(cx)
                                         });
@@ -234,22 +236,22 @@ impl<V: View> DragAndDrop<V> {
                     }
 
                     State::Canceled => Some(
-                        MouseEventHandler::<DraggedElementHandler>::new(0, cx, |_, _| {
+                        MouseEventHandler::<DraggedElementHandler, V>::new(0, cx, |_, _| {
                             Empty::new()
                                 .constrained()
                                 .with_width(0.)
                                 .with_height(0.)
                                 .boxed()
                         })
-                        .on_up(MouseButton::Left, |_, cx| {
-                            cx.defer(|cx| {
+                        .on_up(MouseButton::Left, |_, _, cx| {
+                            cx.window_context().defer(|cx| {
                                 cx.update_global::<Self, _, _>(|this, _| {
                                     this.currently_dragged = None;
                                 });
                             });
                         })
-                        .on_up_out(MouseButton::Left, |_, cx| {
-                            cx.defer(|cx| {
+                        .on_up_out(MouseButton::Left, |_, _, cx| {
+                            cx.window_context().defer(|cx| {
                                 cx.update_global::<Self, _, _>(|this, _| {
                                     this.currently_dragged = None;
                                 });
@@ -261,7 +263,7 @@ impl<V: View> DragAndDrop<V> {
             })
     }
 
-    pub fn cancel_dragging<P: Any>(&mut self, cx: &mut AppContext) {
+    pub fn cancel_dragging<P: Any>(&mut self, cx: &mut WindowContext) {
         if let Some(State::Dragging {
             payload, window_id, ..
         }) = &self.currently_dragged
@@ -274,13 +276,13 @@ impl<V: View> DragAndDrop<V> {
         }
     }
 
-    fn finish_dragging(&mut self, cx: &mut AppContext) {
+    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);
         }
     }
 
-    fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut AppContext) {
+    fn notify_containers_for_window(&mut self, window_id: usize, cx: &mut WindowContext) {
         self.containers.retain(|container| {
             if let Some(container) = container.upgrade(cx) {
                 if container.window_id() == window_id {
@@ -294,35 +296,35 @@ impl<V: View> DragAndDrop<V> {
     }
 }
 
-pub trait Draggable {
-    fn as_draggable<V: View, P: Any>(
+pub trait Draggable<V: View> {
+    fn as_draggable<D: View, P: Any>(
         self,
         payload: P,
-        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
+        render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
     ) -> Self
     where
         Self: Sized;
 }
 
-impl<Tag> Draggable for MouseEventHandler<Tag> {
-    fn as_draggable<V: View, P: Any>(
+impl<Tag, V: View> Draggable<V> for MouseEventHandler<Tag, V> {
+    fn as_draggable<D: View, P: Any>(
         self,
         payload: P,
-        render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
+        render: impl 'static + Fn(&P, &mut ViewContext<D>) -> Element<D>,
     ) -> Self
     where
         Self: Sized,
     {
         let payload = Rc::new(payload);
         let render = Rc::new(render);
-        self.on_down(MouseButton::Left, move |e, cx| {
+        self.on_down(MouseButton::Left, move |e, _, cx| {
             cx.propagate_event();
-            DragAndDrop::<V>::drag_started(e, cx);
+            DragAndDrop::<D>::drag_started(e, cx);
         })
-        .on_drag(MouseButton::Left, move |e, cx| {
+        .on_drag(MouseButton::Left, move |e, _, cx| {
             let payload = payload.clone();
             let render = render.clone();
-            DragAndDrop::<V>::dragging(e, payload, cx, render)
+            DragAndDrop::<D>::dragging(e, payload, cx, render)
         })
     }
 }

crates/editor/src/display_map/block_map.rs 🔗

@@ -2,9 +2,9 @@ use super::{
     wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot},
     TextHighlights,
 };
-use crate::{Anchor, ExcerptId, ExcerptRange, ToPoint as _};
+use crate::{Anchor, Editor, ExcerptId, ExcerptRange, ToPoint as _};
 use collections::{Bound, HashMap, HashSet};
-use gpui::{fonts::HighlightStyle, ElementBox, RenderContext};
+use gpui::{fonts::HighlightStyle, Element, ViewContext};
 use language::{BufferSnapshot, Chunk, Patch, Point};
 use parking_lot::Mutex;
 use std::{
@@ -50,7 +50,7 @@ struct BlockRow(u32);
 #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
 struct WrapRow(u32);
 
-pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> ElementBox>;
+pub type RenderBlock = Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>;
 
 pub struct Block {
     id: BlockId,
@@ -69,7 +69,7 @@ where
     pub position: P,
     pub height: u8,
     pub style: BlockStyle,
-    pub render: Arc<dyn Fn(&mut BlockContext) -> ElementBox>,
+    pub render: Arc<dyn Fn(&mut BlockContext) -> Element<Editor>>,
     pub disposition: BlockDisposition,
 }
 
@@ -80,8 +80,8 @@ pub enum BlockStyle {
     Sticky,
 }
 
-pub struct BlockContext<'a, 'b> {
-    pub cx: &'b mut RenderContext<'a, crate::Editor>,
+pub struct BlockContext<'a, 'b, 'c, 'd> {
+    pub view_context: &'d mut ViewContext<'a, 'b, 'c, Editor>,
     pub anchor_x: f32,
     pub scroll_x: f32,
     pub gutter_width: f32,
@@ -932,22 +932,22 @@ impl BlockDisposition {
     }
 }
 
-impl<'a, 'b> Deref for BlockContext<'a, 'b> {
-    type Target = RenderContext<'a, crate::Editor>;
+impl<'a, 'b, 'c, 'd> Deref for BlockContext<'a, 'b, 'c, 'd> {
+    type Target = ViewContext<'a, 'b, 'c, Editor>;
 
     fn deref(&self) -> &Self::Target {
-        self.cx
+        self.view_context
     }
 }
 
-impl<'a, 'b> DerefMut for BlockContext<'a, 'b> {
+impl DerefMut for BlockContext<'_, '_, '_, '_> {
     fn deref_mut(&mut self) -> &mut Self::Target {
-        self.cx
+        self.view_context
     }
 }
 
 impl Block {
-    pub fn render(&self, cx: &mut BlockContext) -> ElementBox {
+    pub fn render(&self, cx: &mut BlockContext) -> Element<Editor> {
         self.render.lock()(cx)
     }
 
@@ -994,7 +994,7 @@ mod tests {
     use crate::display_map::suggestion_map::SuggestionMap;
     use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap};
     use crate::multi_buffer::MultiBuffer;
-    use gpui::{elements::Empty, Element};
+    use gpui::{elements::Empty, Drawable};
     use rand::prelude::*;
     use settings::Settings;
     use std::env;

crates/editor/src/editor.rs 🔗

@@ -41,8 +41,8 @@ use gpui::{
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton},
     serde_json::{self, json},
-    AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
-    ModelHandle, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Drawable, Element, Entity,
+    ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HideHover, HoverState};
@@ -724,8 +724,8 @@ impl ContextMenu {
         &self,
         cursor_position: DisplayPoint,
         style: EditorStyle,
-        cx: &mut RenderContext<Editor>,
-    ) -> (DisplayPoint, ElementBox) {
+        cx: &mut ViewContext<Editor>,
+    ) -> (DisplayPoint, Element<Editor>) {
         match self {
             ContextMenu::Completions(menu) => (cursor_position, menu.render(style, cx)),
             ContextMenu::CodeActions(menu) => menu.render(cursor_position, style, cx),
@@ -777,7 +777,7 @@ impl CompletionsMenu {
         !self.matches.is_empty()
     }
 
-    fn render(&self, style: EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
+    fn render(&self, style: EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
         enum CompletionTag {}
 
         let completions = self.completions.clone();
@@ -794,7 +794,7 @@ impl CompletionsMenu {
                     let completion = &completions[mat.candidate_id];
                     let item_ix = start_ix + ix;
                     items.push(
-                        MouseEventHandler::<CompletionTag>::new(
+                        MouseEventHandler::<CompletionTag, _>::new(
                             mat.candidate_id,
                             cx,
                             |state, _| {
@@ -823,7 +823,7 @@ impl CompletionsMenu {
                             },
                         )
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_down(MouseButton::Left, move |_, cx| {
+                        .on_down(MouseButton::Left, move |_, _, cx| {
                             cx.dispatch_action(ConfirmCompletion {
                                 item_ix: Some(item_ix),
                             });
@@ -953,8 +953,8 @@ impl CodeActionsMenu {
         &self,
         mut cursor_position: DisplayPoint,
         style: EditorStyle,
-        cx: &mut RenderContext<Editor>,
-    ) -> (DisplayPoint, ElementBox) {
+        cx: &mut ViewContext<Editor>,
+    ) -> (DisplayPoint, Element<Editor>) {
         enum ActionTag {}
 
         let container_style = style.autocomplete.container;
@@ -969,7 +969,7 @@ impl CodeActionsMenu {
                 for (ix, action) in actions[range].iter().enumerate() {
                     let item_ix = start_ix + ix;
                     items.push(
-                        MouseEventHandler::<ActionTag>::new(item_ix, cx, |state, _| {
+                        MouseEventHandler::<ActionTag, _>::new(item_ix, cx, |state, _| {
                             let item_style = if item_ix == selected_item {
                                 style.autocomplete.selected_item
                             } else if state.hovered() {
@@ -985,7 +985,7 @@ impl CodeActionsMenu {
                                 .boxed()
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_down(MouseButton::Left, move |_, cx| {
+                        .on_down(MouseButton::Left, move |_, _, cx| {
                             cx.dispatch_action(ConfirmCodeAction {
                                 item_ix: Some(item_ix),
                             });
@@ -1349,7 +1349,7 @@ impl Editor {
         self.buffer().read(cx).title(cx)
     }
 
-    pub fn snapshot(&mut self, cx: &mut AppContext) -> EditorSnapshot {
+    pub fn snapshot(&mut self, cx: &mut WindowContext) -> EditorSnapshot {
         EditorSnapshot {
             mode: self.mode,
             display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
@@ -2518,7 +2518,7 @@ impl Editor {
                             this.update_visible_copilot_suggestion(cx);
                         }
                     }
-                });
+                })?;
 
                 Ok::<_, anyhow::Error>(())
             }
@@ -2655,11 +2655,13 @@ impl Editor {
                 prev_task.await;
                 task = this
                     .upgrade(&cx)
-                    .and_then(|this| this.update(&mut cx, |this, _| this.code_actions_task.take()));
+                    .ok_or_else(|| anyhow!("editor dropped"))?
+                    .update(&mut cx, |this, _| this.code_actions_task.take())?;
             }
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, cx| {
+            this.upgrade(&cx)
+                .ok_or_else(|| anyhow!("editor dropped"))?
+                .update(&mut cx, |this, cx| {
                     if this.focused {
                         if let Some((buffer, actions)) = this.available_code_actions.clone() {
                             this.show_context_menu(
@@ -2674,8 +2676,8 @@ impl Editor {
                             );
                         }
                     }
-                })
-            }
+                })?;
+
             Ok::<_, anyhow::Error>(())
         })
         .detach_and_log_err(cx);
@@ -2787,7 +2789,7 @@ impl Editor {
                     cx,
                 );
             });
-        });
+        })?;
 
         Ok(())
     }
@@ -2818,6 +2820,7 @@ impl Editor {
                     });
                     cx.notify();
                 })
+                .log_err();
             }
         }));
         None
@@ -2907,7 +2910,8 @@ impl Editor {
                         cx,
                     );
                     cx.notify();
-                });
+                })
+                .log_err();
             }
         }));
         None
@@ -2953,20 +2957,21 @@ impl Editor {
                 .flatten()
                 .collect_vec();
 
-            this.upgrade(&cx)?.update(&mut cx, |this, cx| {
-                if !completions.is_empty() {
-                    this.copilot_state.cycled = false;
-                    this.copilot_state.pending_cycling_refresh = Task::ready(None);
-                    this.copilot_state.completions.clear();
-                    this.copilot_state.active_completion_index = 0;
-                    this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
-                    for completion in completions {
-                        this.copilot_state.push_completion(completion);
+            this.upgrade(&cx)?
+                .update(&mut cx, |this, cx| {
+                    if !completions.is_empty() {
+                        this.copilot_state.cycled = false;
+                        this.copilot_state.pending_cycling_refresh = Task::ready(None);
+                        this.copilot_state.completions.clear();
+                        this.copilot_state.active_completion_index = 0;
+                        this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
+                        for completion in completions {
+                            this.copilot_state.push_completion(completion);
+                        }
+                        this.update_visible_copilot_suggestion(cx);
                     }
-                    this.update_visible_copilot_suggestion(cx);
-                }
-            });
-
+                })
+                .log_err()?;
             Some(())
         });
 
@@ -2997,14 +3002,16 @@ impl Editor {
                     })
                     .await;
 
-                this.upgrade(&cx)?.update(&mut cx, |this, cx| {
-                    this.copilot_state.cycled = true;
-                    for completion in completions.log_err().into_iter().flatten() {
-                        this.copilot_state.push_completion(completion);
-                    }
-                    this.copilot_state.cycle_completions(direction);
-                    this.update_visible_copilot_suggestion(cx);
-                });
+                this.upgrade(&cx)?
+                    .update(&mut cx, |this, cx| {
+                        this.copilot_state.cycled = true;
+                        for completion in completions.log_err().into_iter().flatten() {
+                            this.copilot_state.push_completion(completion);
+                        }
+                        this.copilot_state.cycle_completions(direction);
+                        this.update_visible_copilot_suggestion(cx);
+                    })
+                    .log_err()?;
 
                 Some(())
             });
@@ -3123,19 +3130,19 @@ impl Editor {
         &self,
         style: &EditorStyle,
         active: bool,
-        cx: &mut RenderContext<Self>,
-    ) -> Option<ElementBox> {
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Element<Self>> {
         if self.available_code_actions.is_some() {
             enum CodeActions {}
             Some(
-                MouseEventHandler::<CodeActions>::new(0, cx, |state, _| {
+                MouseEventHandler::<CodeActions, _>::new(0, cx, |state, _| {
                     Svg::new("icons/bolt_8.svg")
                         .with_color(style.code_actions.indicator.style_for(state, active).color)
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
                 .with_padding(Padding::uniform(3.))
-                .on_down(MouseButton::Left, |_, cx| {
+                .on_down(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(ToggleCodeActions {
                         deployed_from_indicator: true,
                     });
@@ -3154,8 +3161,8 @@ impl Editor {
         gutter_hovered: bool,
         line_height: f32,
         gutter_margin: f32,
-        cx: &mut RenderContext<Self>,
-    ) -> Vec<Option<ElementBox>> {
+        cx: &mut ViewContext<Self>,
+    ) -> Vec<Option<Element<Self>>> {
         enum FoldIndicators {}
 
         let style = style.folds.clone();
@@ -3167,10 +3174,10 @@ impl Editor {
                 fold_data
                     .map(|(fold_status, buffer_row, active)| {
                         (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| {
-                            MouseEventHandler::<FoldIndicators>::new(
+                            MouseEventHandler::<FoldIndicators, _>::new(
                                 ix as usize,
                                 cx,
-                                |mouse_state, _| -> ElementBox {
+                                |mouse_state, _| -> Element<Editor> {
                                     Svg::new(match fold_status {
                                         FoldStatus::Folded => style.folded_icon.clone(),
                                         FoldStatus::Foldable => style.foldable_icon.clone(),
@@ -3197,7 +3204,7 @@ impl Editor {
                             .with_cursor_style(CursorStyle::PointingHand)
                             .with_padding(Padding::uniform(3.))
                             .on_click(MouseButton::Left, {
-                                move |_, cx| {
+                                move |_, _, cx| {
                                     cx.dispatch_any_action(match fold_status {
                                         FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }),
                                         FoldStatus::Foldable => Box::new(FoldAt { buffer_row }),
@@ -3222,8 +3229,8 @@ impl Editor {
         &self,
         cursor_position: DisplayPoint,
         style: EditorStyle,
-        cx: &mut RenderContext<Editor>,
-    ) -> Option<(DisplayPoint, ElementBox)> {
+        cx: &mut ViewContext<Editor>,
+    ) -> Option<(DisplayPoint, Element<Editor>)> {
         self.context_menu
             .as_ref()
             .map(|menu| menu.render(cursor_position, style, cx))
@@ -4106,7 +4113,7 @@ impl Editor {
 
     pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
         self.transact(cx, |this, cx| {
-            if let Some(item) = cx.as_mut().read_from_clipboard() {
+            if let Some(item) = cx.read_from_clipboard() {
                 let mut clipboard_text = Cow::Borrowed(item.text());
                 if let Some(mut clipboard_selections) = item.metadata::<Vec<ClipboardSelection>>() {
                     let old_selections = this.selections.all::<usize>(cx);
@@ -5610,7 +5617,7 @@ impl Editor {
             let definitions = definitions.await?;
             workspace.update(&mut cx, |workspace, cx| {
                 Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
-            });
+            })?;
 
             Ok::<(), anyhow::Error>(())
         })
@@ -5711,7 +5718,7 @@ impl Editor {
                     Self::open_locations_in_multibuffer(
                         workspace, locations, replica_id, title, cx,
                     );
-                });
+                })?;
 
                 Ok(())
             },
@@ -5899,7 +5906,7 @@ impl Editor {
                         editor: rename_editor,
                         block_id,
                     });
-                });
+                })?;
             }
 
             Ok(())
@@ -5945,7 +5952,7 @@ impl Editor {
 
             editor.update(&mut cx, |editor, cx| {
                 editor.refresh_document_highlights(cx);
-            });
+            })?;
             Ok(())
         }))
     }
@@ -5988,7 +5995,7 @@ impl Editor {
         self.pending_rename.as_ref()
     }
 
-    fn format(&mut self, _: &Format, cx: &mut ViewContext<'_, Self>) -> Option<Task<Result<()>>> {
+    fn format(&mut self, _: &Format, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
         let project = match &self.project {
             Some(project) => project.clone(),
             None => return None,
@@ -6001,7 +6008,7 @@ impl Editor {
         &mut self,
         project: ModelHandle<Project>,
         trigger: FormatTrigger,
-        cx: &mut ViewContext<'_, Self>,
+        cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
         let buffer = self.buffer().clone();
         let buffers = buffer.read(cx).all_buffers();
@@ -6747,9 +6754,16 @@ impl Editor {
         let position = action.position;
         let anchor = action.anchor;
         cx.spawn_weak(|_, mut cx| async move {
-            let editor = editor.await.log_err()?.downcast::<Editor>()?;
+            let editor = editor
+                .await?
+                .downcast::<Editor>()
+                .ok_or_else(|| anyhow!("opened item was not an editor"))?;
             editor.update(&mut cx, |editor, cx| {
-                let buffer = editor.buffer().read(cx).as_singleton()?;
+                let buffer = editor
+                    .buffer()
+                    .read(cx)
+                    .as_singleton()
+                    .ok_or_else(|| anyhow!("cannot jump in a multi-buffer"))?;
                 let buffer = buffer.read(cx);
                 let cursor = if buffer.can_resolve(&anchor) {
                     language::ToPoint::to_point(&anchor, buffer)
@@ -6763,11 +6777,11 @@ impl Editor {
                 });
                 editor.nav_history = nav_history;
 
-                Some(())
-            })?;
-            Some(())
+                anyhow::Ok(())
+            })??;
+            anyhow::Ok(())
         })
-        .detach()
+        .detach_and_log_err(cx);
     }
 
     fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
@@ -6993,7 +7007,7 @@ impl Entity for Editor {
 }
 
 impl View for Editor {
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let style = self.style(cx);
         let font_changed = self.display_map.update(cx, |map, cx| {
             map.set_fold_ellipses_color(style.folds.ellipses.text_color);
@@ -7001,19 +7015,14 @@ impl View for Editor {
         });
 
         if font_changed {
-            let handle = self.handle.clone();
-            cx.defer(move |cx| {
-                if let Some(editor) = handle.upgrade(cx) {
-                    editor.update(cx, |editor, cx| {
-                        hide_hover(editor, &HideHover, cx);
-                        hide_link_definition(editor, cx);
-                    })
-                }
+            cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
+                hide_hover(editor, &HideHover, cx);
+                hide_link_definition(editor, cx);
             });
         }
 
         Stack::new()
-            .with_child(EditorElement::new(self.handle.clone(), style.clone()).boxed())
+            .with_child(EditorElement::new(style.clone()).boxed())
             .with_child(ChildView::new(&self.mouse_context_menu, cx).boxed())
             .boxed()
     }

crates/editor/src/editor_tests.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     executor::Deterministic,
     geometry::{rect::RectF, vector::vec2f},
     platform::{WindowBounds, WindowOptions},
-    serde_json,
+    serde_json, TestAppContext,
 };
 use indoc::indoc;
 use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
@@ -23,13 +23,13 @@ use util::{
     test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
 };
 use workspace::{
-    item::{FollowableItem, ItemHandle},
+    item::{FollowableItem, Item, ItemHandle},
     NavigationEntry, Pane, ViewId,
 };
 
 #[gpui::test]
-fn test_edit_events(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_edit_events(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let buffer = cx.add_model(|cx| {
         let mut buffer = language::Buffer::new(0, "123456", cx);
         buffer.set_group_interval(Duration::from_secs(1));
@@ -37,7 +37,7 @@ fn test_edit_events(cx: &mut AppContext) {
     });
 
     let events = Rc::new(RefCell::new(Vec::new()));
-    let (_, editor1) = cx.add_window(Default::default(), {
+    let (_, editor1) = cx.add_window({
         let events = events.clone();
         |cx| {
             cx.subscribe(&cx.handle(), move |_, _, event, _| {
@@ -52,7 +52,7 @@ fn test_edit_events(cx: &mut AppContext) {
             Editor::for_buffer(buffer.clone(), None, cx)
         }
     });
-    let (_, editor2) = cx.add_window(Default::default(), {
+    let (_, editor2) = cx.add_window({
         let events = events.clone();
         |cx| {
             cx.subscribe(&cx.handle(), move |_, _, event, _| {
@@ -155,13 +155,13 @@ fn test_edit_events(cx: &mut AppContext) {
 }
 
 #[gpui::test]
-fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let mut now = Instant::now();
     let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
-    let group_interval = buffer.read(cx).transaction_group_interval();
+    let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+    let (_, editor) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 
     editor.update(cx, |editor, cx| {
         editor.start_transaction_at(now, cx);
@@ -225,8 +225,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut AppContext) {
 }
 
 #[gpui::test]
-fn test_ime_composition(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_ime_composition(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let buffer = cx.add_model(|cx| {
         let mut buffer = language::Buffer::new(0, "abcde", cx);
         // Ensure automatic grouping doesn't occur.
@@ -235,7 +235,7 @@ fn test_ime_composition(cx: &mut AppContext) {
     });
 
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
-    cx.add_window(Default::default(), |cx| {
+    cx.add_window(|cx| {
         let mut editor = build_editor(buffer.clone(), cx);
 
         // Start a new IME composition.
@@ -327,11 +327,13 @@ fn test_ime_composition(cx: &mut AppContext) {
 }
 
 #[gpui::test]
-fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_selection_with_mouse(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
 
-    let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
-    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
+        build_editor(buffer, cx)
+    });
     editor.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
     });
@@ -392,10 +394,12 @@ fn test_selection_with_mouse(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_canceling_pending_selection(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+        build_editor(buffer, cx)
+    });
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(2, 2), false, 1, cx);
@@ -424,7 +428,7 @@ fn test_canceling_pending_selection(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_clone(cx: &mut gpui::AppContext) {
+fn test_clone(cx: &mut TestAppContext) {
     let (text, selection_ranges) = marked_text_ranges(
         indoc! {"
             one
@@ -435,10 +439,12 @@ fn test_clone(cx: &mut gpui::AppContext) {
         "},
         true,
     );
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(&text, cx);
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
 
-    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    let (_, editor) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&text, cx);
+        build_editor(buffer, cx)
+    });
 
     editor.update(cx, |editor, cx| {
         editor.change_selections(None, cx, |s| s.select_ranges(selection_ranges.clone()));
@@ -470,8 +476,8 @@ fn test_clone(cx: &mut gpui::AppContext) {
         snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
     );
     assert_set_eq!(
-        cloned_editor.read(cx).selections.ranges::<Point>(cx),
-        editor.read(cx).selections.ranges(cx)
+        cloned_editor.read_with(cx, |editor, cx| editor.selections.ranges::<Point>(cx)),
+        editor.read_with(cx, |editor, cx| editor.selections.ranges(cx))
     );
     assert_set_eq!(
         cloned_editor.update(cx, |e, cx| e.selections.display_ranges(cx)),
@@ -480,19 +486,19 @@ fn test_clone(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_navigation_history(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_navigation_history(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     cx.set_global(DragAndDrop::<Workspace>::default());
     use workspace::item::Item;
-    let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(0, None, || &[], cx));
-    let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
+    let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx));
 
     cx.add_view(&pane, |cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
         let mut editor = build_editor(buffer.clone(), cx);
         let handle = cx.handle();
         editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
 
-        fn pop_history(editor: &mut Editor, cx: &mut AppContext) -> Option<NavigationEntry> {
+        fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
             editor.nav_history.as_mut().unwrap().pop_backward(cx)
         }
 
@@ -590,10 +596,12 @@ fn test_navigation_history(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_cancel(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_cancel(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
+        build_editor(buffer, cx)
+    });
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
@@ -630,30 +638,32 @@ fn test_cancel(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_fold_action(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(
-        &"
-            impl Foo {
-                // Hello!
+fn test_fold_action(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(
+            &"
+                impl Foo {
+                    // Hello!
 
-                fn a() {
-                    1
-                }
+                    fn a() {
+                        1
+                    }
 
-                fn b() {
-                    2
-                }
+                    fn b() {
+                        2
+                    }
 
-                fn c() {
-                    3
+                    fn c() {
+                        3
+                    }
                 }
-            }
-        "
-        .unindent(),
-        cx,
-    );
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+            "
+            .unindent(),
+            cx,
+        );
+        build_editor(buffer.clone(), cx)
+    });
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -712,15 +722,15 @@ fn test_fold_action(cx: &mut gpui::AppContext) {
         );
 
         view.unfold_lines(&UnfoldLines, cx);
-        assert_eq!(view.display_text(cx), buffer.read(cx).read(cx).text());
+        assert_eq!(view.display_text(cx), view.buffer.read(cx).read(cx).text());
     });
 }
 
 #[gpui::test]
-fn test_move_cursor(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_move_cursor(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
+    let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 
     buffer.update(cx, |buffer, cx| {
         buffer.edit(
@@ -732,7 +742,6 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
             cx,
         );
     });
-
     view.update(cx, |view, cx| {
         assert_eq!(
             view.selections.display_ranges(cx),
@@ -793,10 +802,12 @@ fn test_move_cursor(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγδε\n", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
     assert_eq!('ⓐ'.len_utf8(), 3);
     assert_eq!('α'.len_utf8(), 2);
@@ -895,10 +906,12 @@ fn test_move_cursor_multibyte(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
+        build_editor(buffer.clone(), cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
@@ -942,10 +955,12 @@ fn test_move_cursor_different_line_lengths(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("abc\n  def", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_beginning_end_of_line(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1102,10 +1117,12 @@ fn test_beginning_end_of_line(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1151,10 +1168,12 @@ fn test_prev_next_word_boundary(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
+        build_editor(buffer, cx)
+    });
 
     view.update(cx, |view, cx| {
         view.set_wrap_width(Some(140.), cx);
@@ -1330,10 +1349,12 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("one two three four", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("one two three four", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -1345,10 +1366,9 @@ fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
             ])
         });
         view.delete_to_previous_word_start(&DeleteToPreviousWordStart, cx);
+        assert_eq!(view.buffer.read(cx).read(cx).text(), "e two te four");
     });
 
-    assert_eq!(buffer.read(cx).read(cx).text(), "e two te four");
-
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -1359,16 +1379,17 @@ fn test_delete_to_word_boundary(cx: &mut gpui::AppContext) {
             ])
         });
         view.delete_to_next_word_end(&DeleteToNextWordEnd, cx);
+        assert_eq!(view.buffer.read(cx).read(cx).text(), "e t te our");
     });
-
-    assert_eq!(buffer.read(cx).read(cx).text(), "e t te our");
 }
 
 #[gpui::test]
-fn test_newline(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_newline(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
+        build_editor(buffer.clone(), cx)
+    });
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -1385,24 +1406,23 @@ fn test_newline(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(
-        "
-            a
-            b(
-                X
-            )
-            c(
-                X
-            )
-        "
-        .unindent()
-        .as_str(),
-        cx,
-    );
-
-    let (_, editor) = cx.add_window(Default::default(), |cx| {
+fn test_newline_with_old_selections(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, editor) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(
+            "
+                a
+                b(
+                    X
+                )
+                c(
+                    X
+                )
+            "
+            .unindent()
+            .as_str(),
+            cx,
+        );
         let mut editor = build_editor(buffer.clone(), cx);
         editor.change_selections(None, cx, |s| {
             s.select_ranges([
@@ -1413,28 +1433,27 @@ fn test_newline_with_old_selections(cx: &mut gpui::AppContext) {
         editor
     });
 
-    // Edit the buffer directly, deleting ranges surrounding the editor's selections
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit(
-            [
-                (Point::new(1, 2)..Point::new(3, 0), ""),
-                (Point::new(4, 2)..Point::new(6, 0), ""),
-            ],
-            None,
-            cx,
-        );
-        assert_eq!(
-            buffer.read(cx).text(),
-            "
-                a
-                b()
-                c()
-            "
-            .unindent()
-        );
-    });
-
     editor.update(cx, |editor, cx| {
+        // Edit the buffer directly, deleting ranges surrounding the editor's selections
+        editor.buffer.update(cx, |buffer, cx| {
+            buffer.edit(
+                [
+                    (Point::new(1, 2)..Point::new(3, 0), ""),
+                    (Point::new(4, 2)..Point::new(6, 0), ""),
+                ],
+                None,
+                cx,
+            );
+            assert_eq!(
+                buffer.read(cx).text(),
+                "
+                    a
+                    b()
+                    c()
+                "
+                .unindent()
+            );
+        });
         assert_eq!(
             editor.selections.ranges(cx),
             &[
@@ -1566,22 +1585,21 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_insert_with_old_selections(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
-    let (_, editor) = cx.add_window(Default::default(), |cx| {
+fn test_insert_with_old_selections(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, editor) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
         let mut editor = build_editor(buffer.clone(), cx);
         editor.change_selections(None, cx, |s| s.select_ranges([3..4, 11..12, 19..20]));
         editor
     });
 
-    // Edit the buffer directly, deleting ranges surrounding the editor's selections
-    buffer.update(cx, |buffer, cx| {
-        buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
-        assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
-    });
-
     editor.update(cx, |editor, cx| {
+        // Edit the buffer directly, deleting ranges surrounding the editor's selections
+        editor.buffer.update(cx, |buffer, cx| {
+            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
+            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
+        });
         assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 
         editor.insert("Z", cx);
@@ -1885,24 +1903,26 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
-    cx.set_global(
-        Settings::test(cx)
-            .with_language_defaults(
-                "TOML",
-                EditorSettings {
-                    tab_size: Some(2.try_into().unwrap()),
-                    ..Default::default()
-                },
-            )
-            .with_language_defaults(
-                "Rust",
-                EditorSettings {
-                    tab_size: Some(4.try_into().unwrap()),
-                    ..Default::default()
-                },
-            ),
-    );
+fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
+    cx.update(|cx| {
+        cx.set_global(
+            Settings::test(cx)
+                .with_language_defaults(
+                    "TOML",
+                    EditorSettings {
+                        tab_size: Some(2.try_into().unwrap()),
+                        ..Default::default()
+                    },
+                )
+                .with_language_defaults(
+                    "Rust",
+                    EditorSettings {
+                        tab_size: Some(4.try_into().unwrap()),
+                        ..Default::default()
+                    },
+                ),
+        );
+    });
     let toml_language = Arc::new(Language::new(
         LanguageConfig {
             name: "TOML".into(),
@@ -1944,7 +1964,7 @@ fn test_indent_outdent_with_excerpts(cx: &mut gpui::AppContext) {
         multibuffer
     });
 
-    cx.add_window(Default::default(), |cx| {
+    cx.add_window(|cx| {
         let mut editor = build_editor(multibuffer, cx);
 
         assert_eq!(
@@ -2071,10 +2091,12 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_delete_line(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_delete_line(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2094,9 +2116,11 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
         );
     });
 
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)])
@@ -2111,10 +2135,12 @@ fn test_delete_line(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_duplicate_line(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_duplicate_line(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2137,8 +2163,10 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
         );
     });
 
-    let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2159,10 +2187,12 @@ fn test_duplicate_line(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_move_line_up_down(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_move_line_up_down(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
@@ -2255,12 +2285,14 @@ fn test_move_line_up_down(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
-    let snapshot = buffer.read(cx).snapshot(cx);
-    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, editor) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
+        build_editor(buffer, cx)
+    });
     editor.update(cx, |editor, cx| {
+        let snapshot = editor.buffer.read(cx).snapshot(cx);
         editor.insert_blocks(
             [BlockProperties {
                 style: BlockStyle::Fixed,
@@ -2279,11 +2311,11 @@ fn test_move_line_up_down_with_blocks(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_transpose(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_transpose(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
 
     _ = cx
-        .add_window(Default::default(), |cx| {
+        .add_window(|cx| {
             let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
 
             editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
@@ -2304,7 +2336,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
         .1;
 
     _ = cx
-        .add_window(Default::default(), |cx| {
+        .add_window(|cx| {
             let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 
             editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
@@ -2330,7 +2362,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
         .1;
 
     _ = cx
-        .add_window(Default::default(), |cx| {
+        .add_window(|cx| {
             let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
 
             editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
@@ -2359,7 +2391,7 @@ fn test_transpose(cx: &mut gpui::AppContext) {
         .1;
 
     _ = cx
-        .add_window(Default::default(), |cx| {
+        .add_window(|cx| {
             let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), cx);
 
             editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
@@ -2573,10 +2605,12 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_select_all(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_select_all(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.select_all(&SelectAll, cx);
         assert_eq!(
@@ -2587,10 +2621,12 @@ fn test_select_all(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_select_line(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_select_line(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
             s.select_display_ranges([
@@ -2631,10 +2667,12 @@ fn test_select_line(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_split_selection_into_lines(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
+        build_editor(buffer, cx)
+    });
     view.update(cx, |view, cx| {
         view.fold_ranges(
             vec![
@@ -2699,10 +2737,12 @@ fn test_split_selection_into_lines(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_add_selection_above_below(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
-    let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, cx));
+fn test_add_selection_above_below(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, view) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
+        build_editor(buffer, cx)
+    });
 
     view.update(cx, |view, cx| {
         view.change_selections(None, cx, |s| {
@@ -4022,7 +4062,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
     assert!(cx.read(|cx| editor.is_dirty(cx)));
 
-    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
     fake_server
         .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
             assert_eq!(
@@ -4057,7 +4097,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
         futures::future::pending::<()>().await;
         unreachable!()
     });
-    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
     cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
     cx.foreground().start_waiting();
     save.await.unwrap();
@@ -4080,7 +4120,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
         })
     });
 
-    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
     fake_server
         .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
             assert_eq!(
@@ -4136,7 +4176,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
     editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
     assert!(cx.read(|cx| editor.is_dirty(cx)));
 
-    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
     fake_server
         .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
             assert_eq!(
@@ -4173,7 +4213,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
             unreachable!()
         },
     );
-    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
     cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
     cx.foreground().start_waiting();
     save.await.unwrap();
@@ -4196,7 +4236,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
         })
     });
 
-    let save = cx.update(|cx| editor.save(project.clone(), cx));
+    let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
     fake_server
         .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
             assert_eq!(
@@ -4977,8 +5017,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
     let multibuffer = cx.add_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0);
@@ -4996,12 +5036,11 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
             ],
             cx,
         );
+        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
         multibuffer
     });
 
-    assert_eq!(multibuffer.read(cx).read(cx).text(), "aaaa\nbbbb");
-
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
+    let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
     view.update(cx, |view, cx| {
         assert_eq!(view.text(cx), "aaaa\nbbbb");
         view.change_selections(None, cx, |s| {
@@ -5024,8 +5063,8 @@ fn test_editing_disjoint_excerpts(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let markers = vec![('[', ']').into(), ('(', ')').into()];
     let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
         indoc! {"
@@ -5049,7 +5088,7 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
         multibuffer
     });
 
-    let (_, view) = cx.add_window(Default::default(), |cx| build_editor(multibuffer, cx));
+    let (_, view) = cx.add_window(|cx| build_editor(multibuffer, cx));
     view.update(cx, |view, cx| {
         let (expected_text, selection_ranges) = marked_text_ranges(
             indoc! {"
@@ -5097,8 +5136,8 @@ fn test_editing_overlapping_excerpts(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_refresh_selections(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_refresh_selections(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
     let mut excerpt1_id = None;
     let multibuffer = cx.add_model(|cx| {
@@ -5120,13 +5159,11 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
             )
             .into_iter()
             .next();
+        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
         multibuffer
     });
-    assert_eq!(
-        multibuffer.read(cx).read(cx).text(),
-        "aaaa\nbbbb\nbbbb\ncccc"
-    );
-    let (_, editor) = cx.add_window(Default::default(), |cx| {
+
+    let (_, editor) = cx.add_window(|cx| {
         let mut editor = build_editor(multibuffer.clone(), cx);
         let snapshot = editor.snapshot(cx);
         editor.change_selections(None, cx, |s| {
@@ -5183,8 +5220,8 @@ fn test_refresh_selections(cx: &mut gpui::AppContext) {
 }
 
 #[gpui::test]
-fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
     let mut excerpt1_id = None;
     let multibuffer = cx.add_model(|cx| {
@@ -5206,13 +5243,11 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut gpui::AppContext)
             )
             .into_iter()
             .next();
+        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
         multibuffer
     });
-    assert_eq!(
-        multibuffer.read(cx).read(cx).text(),
-        "aaaa\nbbbb\nbbbb\ncccc"
-    );
-    let (_, editor) = cx.add_window(Default::default(), |cx| {
+
+    let (_, editor) = cx.add_window(|cx| {
         let mut editor = build_editor(multibuffer.clone(), cx);
         let snapshot = editor.snapshot(cx);
         editor.begin_selection(Point::new(1, 3).to_display_point(&snapshot), false, 1, cx);
@@ -5316,17 +5351,18 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 }
 
 #[gpui::test]
-fn test_highlighted_ranges(cx: &mut gpui::AppContext) {
-    let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
-
-    cx.set_global(Settings::test(cx));
-    let (_, editor) = cx.add_window(Default::default(), |cx| build_editor(buffer.clone(), cx));
+fn test_highlighted_ranges(cx: &mut TestAppContext) {
+    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    let (_, editor) = cx.add_window(|cx| {
+        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
+        build_editor(buffer.clone(), cx)
+    });
 
     editor.update(cx, |editor, cx| {
         struct Type1;
         struct Type2;
 
-        let buffer = buffer.read(cx).snapshot(cx);
+        let buffer = editor.buffer.read(cx).snapshot(cx);
 
         let anchor_range =
             |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);

crates/editor/src/element.rs 🔗

@@ -31,8 +31,8 @@ use gpui::{
     json::{self, ToJson},
     platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
     text_layout::{self, Line, RunStyle, TextLayoutCache},
-    AppContext, Axis, Border, CursorRegion, Element, ElementBox, EventContext, LayoutContext,
-    MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WeakViewHandle,
+    Axis, Border, CursorRegion, Drawable, Element, EventContext, MouseRegion, Quad, SceneBuilder,
+    SizeConstraint, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use json::json;
@@ -44,7 +44,7 @@ use std::{
     cmp::{self, Ordering},
     fmt::Write,
     iter,
-    ops::{DerefMut, Range},
+    ops::Range,
     sync::Arc,
 };
 use workspace::item::Item;
@@ -86,144 +86,128 @@ impl SelectionLayout {
 
 #[derive(Clone)]
 pub struct EditorElement {
-    view: WeakViewHandle<Editor>,
     style: Arc<EditorStyle>,
 }
 
 impl EditorElement {
-    pub fn new(view: WeakViewHandle<Editor>, style: EditorStyle) -> Self {
+    pub fn new(style: EditorStyle) -> Self {
         Self {
-            view,
             style: Arc::new(style),
         }
     }
 
-    fn view<'a>(&self, cx: &'a AppContext) -> &'a Editor {
-        self.view.upgrade(cx).unwrap().read(cx)
-    }
-
-    fn update_view<F, T>(&self, cx: &mut AppContext, f: F) -> T
-    where
-        F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
-    {
-        self.view.upgrade(cx).unwrap().update(cx, f)
-    }
-
-    fn snapshot(&self, cx: &mut AppContext) -> EditorSnapshot {
-        self.update_view(cx, |view, cx| view.snapshot(cx))
-    }
-
     fn attach_mouse_handlers(
-        view: &WeakViewHandle<Editor>,
+        scene: &mut SceneBuilder,
         position_map: &Arc<PositionMap>,
         has_popovers: bool,
         visible_bounds: RectF,
         text_bounds: RectF,
         gutter_bounds: RectF,
         bounds: RectF,
-        cx: &mut PaintContext,
+        cx: &mut ViewContext<Editor>,
     ) {
         enum EditorElementMouseHandlers {}
-        cx.scene.push_mouse_region(
-            MouseRegion::new::<EditorElementMouseHandlers>(view.id(), view.id(), visible_bounds)
-                .on_down(MouseButton::Left, {
-                    let position_map = position_map.clone();
-                    move |e, cx| {
-                        if !Self::mouse_down(
-                            e.platform_event,
-                            position_map.as_ref(),
-                            text_bounds,
-                            gutter_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event();
-                        }
-                    }
-                })
-                .on_down(MouseButton::Right, {
-                    let position_map = position_map.clone();
-                    move |e, cx| {
-                        if !Self::mouse_right_down(
-                            e.position,
-                            position_map.as_ref(),
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event();
-                        }
+        scene.push_mouse_region(
+            MouseRegion::new::<EditorElementMouseHandlers>(
+                cx.view_id(),
+                cx.view_id(),
+                visible_bounds,
+            )
+            .on_down(MouseButton::Left, {
+                let position_map = position_map.clone();
+                move |e, _, cx| {
+                    if !Self::mouse_down(
+                        e.platform_event,
+                        position_map.as_ref(),
+                        text_bounds,
+                        gutter_bounds,
+                        cx,
+                    ) {
+                        cx.propagate_event();
                     }
-                })
-                .on_up(MouseButton::Left, {
-                    let view = view.clone();
-                    let position_map = position_map.clone();
-                    move |e, cx| {
-                        if !Self::mouse_up(
-                            view.clone(),
-                            e.position,
-                            e.cmd,
-                            e.shift,
-                            position_map.as_ref(),
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
+                }
+            })
+            .on_down(MouseButton::Right, {
+                let position_map = position_map.clone();
+                move |event, _, cx| {
+                    if !Self::mouse_right_down(
+                        event.position,
+                        position_map.as_ref(),
+                        text_bounds,
+                        cx,
+                    ) {
+                        cx.propagate_event();
                     }
-                })
-                .on_drag(MouseButton::Left, {
-                    let view = view.clone();
-                    let position_map = position_map.clone();
-                    move |e, cx| {
-                        if !Self::mouse_dragged(
-                            view.clone(),
-                            e.platform_event,
-                            position_map.as_ref(),
-                            text_bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
+                }
+            })
+            .on_up(MouseButton::Left, {
+                let position_map = position_map.clone();
+                move |event, editor, cx| {
+                    if !Self::mouse_up(
+                        event.position,
+                        event.cmd,
+                        event.shift,
+                        position_map.as_ref(),
+                        text_bounds,
+                        editor,
+                        cx,
+                    ) {
+                        cx.propagate_event()
                     }
-                })
-                .on_move({
-                    let position_map = position_map.clone();
-                    move |e, cx| {
-                        if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
-                            cx.propagate_event()
-                        }
+                }
+            })
+            .on_drag(MouseButton::Left, {
+                let position_map = position_map.clone();
+                move |event, editor, cx| {
+                    if !Self::mouse_dragged(
+                        event.platform_event,
+                        position_map.as_ref(),
+                        text_bounds,
+                        editor,
+                        cx,
+                    ) {
+                        cx.propagate_event()
                     }
-                })
-                .on_move_out(move |_, cx| {
-                    if has_popovers {
-                        cx.dispatch_action(HideHover);
+                }
+            })
+            .on_move({
+                let position_map = position_map.clone();
+                move |e, _, cx| {
+                    if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
+                        cx.propagate_event()
                     }
-                })
-                .on_scroll({
-                    let position_map = position_map.clone();
-                    move |e, cx| {
-                        if !Self::scroll(
-                            e.position,
-                            *e.delta.raw(),
-                            e.delta.precise(),
-                            &position_map,
-                            bounds,
-                            cx,
-                        ) {
-                            cx.propagate_event()
-                        }
+                }
+            })
+            .on_move_out(move |_, _: &mut Editor, cx| {
+                if has_popovers {
+                    cx.dispatch_action(HideHover);
+                }
+            })
+            .on_scroll({
+                let position_map = position_map.clone();
+                move |e, _, cx| {
+                    if !Self::scroll(
+                        e.position,
+                        *e.delta.raw(),
+                        e.delta.precise(),
+                        &position_map,
+                        bounds,
+                        cx,
+                    ) {
+                        cx.propagate_event()
                     }
-                }),
+                }
+            }),
         );
 
         enum GutterHandlers {}
-        cx.scene.push_mouse_region(
-            MouseRegion::new::<GutterHandlers>(view.id(), view.id() + 1, gutter_bounds).on_hover(
-                |hover, cx| {
+        scene.push_mouse_region(
+            MouseRegion::new::<GutterHandlers>(cx.view_id(), cx.view_id() + 1, gutter_bounds)
+                .on_hover(|hover, _: &mut Editor, cx| {
                     cx.dispatch_action(GutterHover {
                         hovered: hover.started,
                     })
-                },
-            ),
+                }),
         )
     }
 
@@ -244,7 +228,7 @@ impl EditorElement {
         position_map: &PositionMap,
         text_bounds: RectF,
         gutter_bounds: RectF,
-        cx: &mut EventContext,
+        cx: &mut EventContext<Editor>,
     ) -> bool {
         if gutter_bounds.contains_point(position) {
             click_count = 3; // Simulate triple-click when clicking the gutter to select lines
@@ -279,7 +263,7 @@ impl EditorElement {
         position: Vector2F,
         position_map: &PositionMap,
         text_bounds: RectF,
-        cx: &mut EventContext,
+        cx: &mut EventContext<Editor>,
     ) -> bool {
         if !text_bounds.contains_point(position) {
             return false;
@@ -292,17 +276,16 @@ impl EditorElement {
     }
 
     fn mouse_up(
-        view: WeakViewHandle<Editor>,
         position: Vector2F,
         cmd: bool,
         shift: bool,
         position_map: &PositionMap,
         text_bounds: RectF,
-        cx: &mut EventContext,
+        editor: &mut Editor,
+        cx: &mut EventContext<Editor>,
     ) -> bool {
-        let view = view.upgrade(cx.app).unwrap().read(cx.app);
-        let end_selection = view.has_pending_selection();
-        let pending_nonempty_selections = view.has_pending_nonempty_selection();
+        let end_selection = editor.has_pending_selection();
+        let pending_nonempty_selections = editor.has_pending_nonempty_selection();
 
         if end_selection {
             cx.dispatch_action(Select(SelectPhase::End));
@@ -326,7 +309,6 @@ impl EditorElement {
     }
 
     fn mouse_dragged(
-        view: WeakViewHandle<Editor>,
         MouseMovedEvent {
             modifiers: Modifiers { cmd, shift, .. },
             position,
@@ -334,7 +316,8 @@ impl EditorElement {
         }: MouseMovedEvent,
         position_map: &PositionMap,
         text_bounds: RectF,
-        cx: &mut EventContext,
+        editor: &mut Editor,
+        cx: &mut EventContext<Editor>,
     ) -> bool {
         // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
         // Don't trigger hover popover if mouse is hovering over context menu
@@ -355,8 +338,7 @@ impl EditorElement {
             shift_held: shift,
         });
 
-        let view = view.upgrade(cx.app).unwrap().read(cx.app);
-        if view.has_pending_selection() {
+        if editor.has_pending_selection() {
             let mut scroll_delta = Vector2F::zero();
 
             let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0);
@@ -409,7 +391,7 @@ impl EditorElement {
         }: MouseMovedEvent,
         position_map: &PositionMap,
         text_bounds: RectF,
-        cx: &mut EventContext,
+        cx: &mut ViewContext<Editor>,
     ) -> bool {
         // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
         // Don't trigger hover popover if mouse is hovering over context menu
@@ -432,7 +414,7 @@ impl EditorElement {
         precise: bool,
         position_map: &PositionMap,
         bounds: RectF,
-        cx: &mut EventContext,
+        cx: &mut ViewContext<Editor>,
     ) -> bool {
         if !bounds.contains_point(position) {
             return false;
@@ -465,21 +447,21 @@ impl EditorElement {
 
     fn paint_background(
         &self,
+        scene: &mut SceneBuilder,
         gutter_bounds: RectF,
         text_bounds: RectF,
         layout: &LayoutState,
-        cx: &mut PaintContext,
     ) {
         let bounds = gutter_bounds.union_rect(text_bounds);
         let scroll_top =
             layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
-        cx.scene.push_quad(Quad {
+        scene.push_quad(Quad {
             bounds: gutter_bounds,
             background: Some(self.style.gutter_background),
             border: Border::new(0., Color::transparent_black()),
             corner_radius: 0.,
         });
-        cx.scene.push_quad(Quad {
+        scene.push_quad(Quad {
             bounds: text_bounds,
             background: Some(self.style.background),
             border: Border::new(0., Color::transparent_black()),
@@ -507,7 +489,7 @@ impl EditorElement {
                         bounds.width(),
                         layout.position_map.line_height * (end_row - start_row + 1) as f32,
                     );
-                    cx.scene.push_quad(Quad {
+                    scene.push_quad(Quad {
                         bounds: RectF::new(origin, size),
                         background: Some(self.style.active_line_background),
                         border: Border::default(),
@@ -527,7 +509,7 @@ impl EditorElement {
                     bounds.width(),
                     layout.position_map.line_height * highlighted_rows.len() as f32,
                 );
-                cx.scene.push_quad(Quad {
+                scene.push_quad(Quad {
                     bounds: RectF::new(origin, size),
                     background: Some(self.style.highlighted_line_background),
                     border: Border::default(),
@@ -539,10 +521,12 @@ impl EditorElement {
 
     fn paint_gutter(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut LayoutState,
-        cx: &mut PaintContext,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
     ) {
         let line_height = layout.position_map.line_height;
 
@@ -558,7 +542,7 @@ impl EditorElement {
         );
 
         if show_gutter {
-            Self::paint_diff_hunks(bounds, layout, cx);
+            Self::paint_diff_hunks(scene, bounds, layout, cx);
         }
 
         for (ix, line) in layout.line_number_layouts.iter().enumerate() {
@@ -569,7 +553,7 @@ impl EditorElement {
                         ix as f32 * line_height - (scroll_top % line_height),
                     );
 
-                line.paint(line_origin, visible_bounds, line_height, cx);
+                line.paint(scene, line_origin, visible_bounds, line_height, cx);
             }
         }
 
@@ -586,7 +570,7 @@ impl EditorElement {
 
                 let indicator_origin = bounds.origin() + position + centering_offset;
 
-                indicator.paint(indicator_origin, visible_bounds, cx);
+                indicator.paint(scene, indicator_origin, visible_bounds, editor, cx);
             }
         }
 
@@ -595,11 +579,22 @@ impl EditorElement {
             let mut y = *row as f32 * line_height - scroll_top;
             x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
             y += (line_height - indicator.size().y()) / 2.;
-            indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
+            indicator.paint(
+                scene,
+                bounds.origin() + vec2f(x, y),
+                visible_bounds,
+                editor,
+                cx,
+            );
         }
     }
 
-    fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
+    fn paint_diff_hunks(
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        layout: &mut LayoutState,
+        cx: &mut ViewContext<Editor>,
+    ) {
         let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
         let line_height = layout.position_map.line_height;
 
@@ -618,7 +613,7 @@ impl EditorElement {
                     let highlight_size = vec2f(width * 2., end_y - start_y);
                     let highlight_bounds = RectF::new(highlight_origin, highlight_size);
 
-                    cx.scene.push_quad(Quad {
+                    scene.push_quad(Quad {
                         bounds: highlight_bounds,
                         background: Some(diff_style.modified),
                         border: Border::new(0., Color::transparent_black()),
@@ -651,7 +646,7 @@ impl EditorElement {
                     let highlight_size = vec2f(width * 2., end_y - start_y);
                     let highlight_bounds = RectF::new(highlight_origin, highlight_size);
 
-                    cx.scene.push_quad(Quad {
+                    scene.push_quad(Quad {
                         bounds: highlight_bounds,
                         background: Some(diff_style.deleted),
                         border: Border::new(0., Color::transparent_black()),
@@ -673,7 +668,7 @@ impl EditorElement {
             let highlight_size = vec2f(width * 2., end_y - start_y);
             let highlight_bounds = RectF::new(highlight_origin, highlight_size);
 
-            cx.scene.push_quad(Quad {
+            scene.push_quad(Quad {
                 bounds: highlight_bounds,
                 background: Some(color),
                 border: Border::new(0., Color::transparent_black()),
@@ -684,14 +679,15 @@ impl EditorElement {
 
     fn paint_text(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut LayoutState,
-        cx: &mut PaintContext,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
     ) {
-        let view = self.view(cx.app);
         let style = &self.style;
-        let local_replica_id = view.replica_id(cx);
+        let local_replica_id = editor.replica_id(cx);
         let scroll_position = layout.position_map.snapshot.scroll_position();
         let start_row = layout.visible_display_row_range.start;
         let scroll_top = scroll_position.y() * layout.position_map.line_height;
@@ -700,11 +696,11 @@ impl EditorElement {
         let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
         let line_end_overshoot = 0.15 * layout.position_map.line_height;
 
-        cx.scene.push_layer(Some(bounds));
+        scene.push_layer(Some(bounds));
 
-        cx.scene.push_cursor_region(CursorRegion {
+        scene.push_cursor_region(CursorRegion {
             bounds,
-            style: if !view.link_go_to_definition_state.definitions.is_empty() {
+            style: if !editor.link_go_to_definition_state.definitions.is_empty() {
                 CursorStyle::PointingHand
             } else {
                 CursorStyle::IBeam
@@ -715,6 +711,7 @@ impl EditorElement {
             self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
         for (id, range, color) in layout.fold_ranges.iter() {
             self.paint_highlighted_range(
+                scene,
                 range.clone(),
                 *color,
                 fold_corner_radius,
@@ -724,7 +721,6 @@ impl EditorElement {
                 scroll_top,
                 scroll_left,
                 bounds,
-                cx,
             );
 
             for bound in range_to_bounds(
@@ -736,7 +732,7 @@ impl EditorElement {
                 line_end_overshoot,
                 &layout.position_map,
             ) {
-                cx.scene.push_cursor_region(CursorRegion {
+                scene.push_cursor_region(CursorRegion {
                     bounds: bound,
                     style: CursorStyle::PointingHand,
                 });
@@ -747,9 +743,9 @@ impl EditorElement {
                     .to_point(&layout.position_map.snapshot.display_snapshot)
                     .row;
 
-                cx.scene.push_mouse_region(
-                    MouseRegion::new::<FoldMarkers>(self.view.id(), *id as usize, bound)
-                        .on_click(MouseButton::Left, move |_, cx| {
+                scene.push_mouse_region(
+                    MouseRegion::new::<FoldMarkers>(cx.view_id(), *id as usize, bound)
+                        .on_click(MouseButton::Left, move |_, _: &mut Editor, cx| {
                             cx.dispatch_action(UnfoldAt { buffer_row })
                         })
                         .with_notify_on_hover(true)
@@ -760,6 +756,7 @@ impl EditorElement {
 
         for (range, color) in &layout.highlighted_ranges {
             self.paint_highlighted_range(
+                scene,
                 range.clone(),
                 *color,
                 0.,
@@ -769,7 +766,6 @@ impl EditorElement {
                 scroll_top,
                 scroll_left,
                 bounds,
-                cx,
             );
         }
 
@@ -781,6 +777,7 @@ impl EditorElement {
 
             for selection in selections {
                 self.paint_highlighted_range(
+                    scene,
                     selection.range.clone(),
                     selection_style.selection,
                     corner_radius,
@@ -790,10 +787,9 @@ impl EditorElement {
                     scroll_top,
                     scroll_left,
                     bounds,
-                    cx,
                 );
 
-                if view.show_local_cursors(cx) || *replica_id != local_replica_id {
+                if editor.show_local_cursors(cx) || *replica_id != local_replica_id {
                     let cursor_position = selection.head;
                     if layout
                         .visible_display_row_range
@@ -820,7 +816,7 @@ impl EditorElement {
                                         cursor_row_layout.font_for_index(cursor_column)?;
                                     let text = character.to_string();
 
-                                    Some(cx.text_layout_cache.layout_str(
+                                    Some(cx.text_layout_cache().layout_str(
                                         &text,
                                         cursor_row_layout.font_size(),
                                         &[(
@@ -858,6 +854,7 @@ impl EditorElement {
             for (ix, line) in layout.position_map.line_layouts.iter().enumerate() {
                 let row = start_row + ix as u32;
                 line.paint(
+                    scene,
                     content_origin
                         + vec2f(
                             -scroll_left,
@@ -870,14 +867,14 @@ impl EditorElement {
             }
         }
 
-        cx.scene.push_layer(Some(bounds));
-        for cursor in cursors {
-            cursor.paint(content_origin, cx);
-        }
-        cx.scene.pop_layer();
+        scene.paint_layer(Some(bounds), |scene| {
+            for cursor in cursors {
+                cursor.paint(scene, content_origin, cx);
+            }
+        });
 
         if let Some((position, context_menu)) = layout.context_menu.as_mut() {
-            cx.scene.push_stacking_context(None, None);
+            scene.push_stacking_context(None, None);
             let cursor_row_layout =
                 &layout.position_map.line_layouts[(position.row() - start_row) as usize];
             let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
@@ -888,8 +885,8 @@ impl EditorElement {
 
             // Snap the right edge of the list to the right edge of the window if
             // its horizontal bounds overflow.
-            if list_origin.x() + list_width > cx.window_size.x() {
-                list_origin.set_x((cx.window_size.x() - list_width).max(0.));
+            if list_origin.x() + list_width > cx.window_size().x() {
+                list_origin.set_x((cx.window_size().x() - list_width).max(0.));
             }
 
             if list_origin.y() + list_height > bounds.max_y() {
@@ -897,16 +894,18 @@ impl EditorElement {
             }
 
             context_menu.paint(
+                scene,
                 list_origin,
                 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+                editor,
                 cx,
             );
 
-            cx.scene.pop_stacking_context();
+            scene.pop_stacking_context();
         }
 
         if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
-            cx.scene.push_stacking_context(None, None);
+            scene.push_stacking_context(None, None);
 
             // This is safe because we check on layout whether the required row is available
             let hovered_row_layout =
@@ -937,8 +936,10 @@ impl EditorElement {
                     }
 
                     hover_popover.paint(
+                        scene,
                         popover_origin,
                         RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+                        editor,
                         cx,
                     );
 
@@ -957,8 +958,10 @@ impl EditorElement {
                     }
 
                     hover_popover.paint(
+                        scene,
                         popover_origin,
                         RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
+                        editor,
                         cx,
                     );
 
@@ -966,19 +969,24 @@ impl EditorElement {
                 }
             }
 
-            cx.scene.pop_stacking_context();
+            scene.pop_stacking_context();
         }
 
-        cx.scene.pop_layer();
+        scene.pop_layer();
     }
 
-    fn paint_scrollbar(&mut self, bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
+    fn paint_scrollbar(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        layout: &mut LayoutState,
+        cx: &mut ViewContext<Editor>,
+    ) {
         enum ScrollbarMouseHandlers {}
         if layout.mode != EditorMode::Full {
             return;
         }
 
-        let view = self.view.clone();
         let style = &self.style.theme.scrollbar;
 
         let top = bounds.min_y();
@@ -1008,13 +1016,13 @@ impl EditorElement {
         let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
 
         if layout.show_scrollbars {
-            cx.scene.push_quad(Quad {
+            scene.push_quad(Quad {
                 bounds: track_bounds,
                 border: style.track.border,
                 background: style.track.background_color,
                 ..Default::default()
             });
-            cx.scene.push_quad(Quad {
+            scene.push_quad(Quad {
                 bounds: thumb_bounds,
                 border: style.thumb.border,
                 background: style.thumb.background_color,
@@ -1022,63 +1030,42 @@ impl EditorElement {
             });
         }
 
-        cx.scene.push_cursor_region(CursorRegion {
+        scene.push_cursor_region(CursorRegion {
             bounds: track_bounds,
             style: CursorStyle::Arrow,
         });
-        cx.scene.push_mouse_region(
-            MouseRegion::new::<ScrollbarMouseHandlers>(view.id(), view.id(), track_bounds)
-                .on_move({
-                    let view = view.clone();
-                    move |_, cx| {
-                        if let Some(view) = view.upgrade(cx.deref_mut()) {
-                            view.update(cx.deref_mut(), |view, cx| {
-                                view.scroll_manager.show_scrollbar(cx);
-                            });
-                        }
-                    }
+        scene.push_mouse_region(
+            MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
+                .on_move(move |_, editor: &mut Editor, cx| {
+                    editor.scroll_manager.show_scrollbar(cx);
                 })
                 .on_down(MouseButton::Left, {
-                    let view = view.clone();
                     let row_range = row_range.clone();
-                    move |e, cx| {
-                        let y = e.position.y();
-                        if let Some(view) = view.upgrade(cx.deref_mut()) {
-                            view.update(cx.deref_mut(), |view, cx| {
-                                if y < thumb_top || thumb_bottom < y {
-                                    let center_row =
-                                        ((y - top) * max_row as f32 / height).round() as u32;
-                                    let top_row = center_row.saturating_sub(
-                                        (row_range.end - row_range.start) as u32 / 2,
-                                    );
-                                    let mut position = view.scroll_position(cx);
-                                    position.set_y(top_row as f32);
-                                    view.set_scroll_position(position, cx);
-                                } else {
-                                    view.scroll_manager.show_scrollbar(cx);
-                                }
-                            });
+                    move |event, editor: &mut Editor, cx| {
+                        let y = event.position.y();
+                        if y < thumb_top || thumb_bottom < y {
+                            let center_row = ((y - top) * max_row as f32 / height).round() as u32;
+                            let top_row = center_row
+                                .saturating_sub((row_range.end - row_range.start) as u32 / 2);
+                            let mut position = editor.scroll_position(cx);
+                            position.set_y(top_row as f32);
+                            editor.set_scroll_position(position, cx);
+                        } else {
+                            editor.scroll_manager.show_scrollbar(cx);
                         }
                     }
                 })
                 .on_drag(MouseButton::Left, {
-                    let view = view.clone();
-                    move |e, cx| {
-                        let y = e.prev_mouse_position.y();
-                        let new_y = e.position.y();
+                    move |event, editor: &mut Editor, cx| {
+                        let y = event.prev_mouse_position.y();
+                        let new_y = event.position.y();
                         if thumb_top < y && y < thumb_bottom {
-                            if let Some(view) = view.upgrade(cx.deref_mut()) {
-                                view.update(cx.deref_mut(), |view, cx| {
-                                    let mut position = view.scroll_position(cx);
-                                    position.set_y(
-                                        position.y() + (new_y - y) * (max_row as f32) / height,
-                                    );
-                                    if position.y() < 0.0 {
-                                        position.set_y(0.);
-                                    }
-                                    view.set_scroll_position(position, cx);
-                                });
+                            let mut position = editor.scroll_position(cx);
+                            position.set_y(position.y() + (new_y - y) * (max_row as f32) / height);
+                            if position.y() < 0.0 {
+                                position.set_y(0.);
                             }
+                            editor.set_scroll_position(position, cx);
                         }
                     }
                 }),
@@ -1088,6 +1075,7 @@ impl EditorElement {
     #[allow(clippy::too_many_arguments)]
     fn paint_highlighted_range(
         &self,
+        scene: &mut SceneBuilder,
         range: Range<DisplayPoint>,
         color: Color,
         corner_radius: f32,
@@ -1097,7 +1085,6 @@ impl EditorElement {
         scroll_top: f32,
         scroll_left: f32,
         bounds: RectF,
-        cx: &mut PaintContext,
     ) {
         let start_row = layout.visible_display_row_range.start;
         let end_row = layout.visible_display_row_range.end;
@@ -1141,16 +1128,18 @@ impl EditorElement {
                     .collect(),
             };
 
-            highlighted_range.paint(bounds, cx.scene);
+            highlighted_range.paint(bounds, scene);
         }
     }
 
     fn paint_blocks(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut LayoutState,
-        cx: &mut PaintContext,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
     ) {
         let scroll_position = layout.position_map.snapshot.scroll_position();
         let scroll_left = scroll_position.x() * layout.position_map.em_width;
@@ -1165,15 +1154,17 @@ impl EditorElement {
             if !matches!(block.style, BlockStyle::Sticky) {
                 origin += vec2f(-scroll_left, 0.);
             }
-            block.element.paint(origin, visible_bounds, cx);
+            block
+                .element
+                .paint(scene, origin, visible_bounds, editor, cx);
         }
     }
 
-    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &LayoutContext) -> f32 {
+    fn max_line_number_width(&self, snapshot: &EditorSnapshot, cx: &ViewContext<Editor>) -> f32 {
         let digit_count = (snapshot.max_buffer_row() as f32).log10().floor() as usize + 1;
         let style = &self.style;
 
-        cx.text_layout_cache
+        cx.text_layout_cache()
             .layout_str(
                 "1".repeat(digit_count).as_str(),
                 style.text.font_size,
@@ -1218,7 +1209,7 @@ impl EditorElement {
         active_rows: &BTreeMap<u32, bool>,
         is_singleton: bool,
         snapshot: &EditorSnapshot,
-        cx: &LayoutContext,
+        cx: &ViewContext<Editor>,
     ) -> (
         Vec<Option<text_layout::Line>>,
         Vec<Option<(FoldStatus, BufferRow, bool)>>,
@@ -1243,7 +1234,7 @@ impl EditorElement {
                 if include_line_numbers {
                     line_number.clear();
                     write!(&mut line_number, "{}", buffer_row + 1).unwrap();
-                    line_number_layouts.push(Some(cx.text_layout_cache.layout_str(
+                    line_number_layouts.push(Some(cx.text_layout_cache().layout_str(
                         &line_number,
                         style.text.font_size,
                         &[(
@@ -1278,7 +1269,7 @@ impl EditorElement {
         &mut self,
         rows: Range<u32>,
         snapshot: &EditorSnapshot,
-        cx: &LayoutContext,
+        cx: &ViewContext<Editor>,
     ) -> Vec<text_layout::Line> {
         if rows.start >= rows.end {
             return Vec::new();
@@ -1301,7 +1292,7 @@ impl EditorElement {
                 .take(rows.len());
             placeholder_lines
                 .map(|line| {
-                    cx.text_layout_cache.layout_str(
+                    cx.text_layout_cache().layout_str(
                         line,
                         placeholder_style.font_size,
                         &[(
@@ -1361,8 +1352,8 @@ impl EditorElement {
             layout_highlighted_chunks(
                 chunks,
                 &style.text,
-                cx.text_layout_cache,
-                cx.font_cache,
+                cx.text_layout_cache(),
+                cx.font_cache(),
                 MAX_LINE_LEN,
                 rows.len() as usize,
             )
@@ -1384,14 +1375,9 @@ impl EditorElement {
         style: &EditorStyle,
         line_layouts: &[text_layout::Line],
         include_root: bool,
-        cx: &mut LayoutContext,
+        editor: &mut Editor,
+        cx: &mut ViewContext<Editor>,
     ) -> (f32, Vec<BlockLayout>) {
-        let editor = if let Some(editor) = self.view.upgrade(cx) {
-            editor
-        } else {
-            return Default::default();
-        };
-
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let scroll_x = snapshot.scroll_anchor.offset.x();
         let (fixed_blocks, non_fixed_blocks) = snapshot
@@ -1412,20 +1398,18 @@ impl EditorElement {
                             line_layouts[(align_to.row() - rows.start) as usize]
                                 .x_for_index(align_to.column() as usize)
                         } else {
-                            layout_line(align_to.row(), snapshot, style, cx.text_layout_cache)
+                            layout_line(align_to.row(), snapshot, style, cx.text_layout_cache())
                                 .x_for_index(align_to.column() as usize)
                         };
 
-                    cx.render(&editor, |_, cx| {
-                        block.render(&mut BlockContext {
-                            cx,
-                            anchor_x,
-                            gutter_padding,
-                            line_height,
-                            scroll_x,
-                            gutter_width,
-                            em_width,
-                        })
+                    block.render(&mut BlockContext {
+                        view_context: cx,
+                        anchor_x,
+                        gutter_padding,
+                        line_height,
+                        scroll_x,
+                        gutter_width,
+                        em_width,
                     })
                 }
                 TransformBlock::ExcerptHeader {
@@ -1451,36 +1435,34 @@ impl EditorElement {
                         };
 
                         enum JumpIcon {}
-                        cx.render(&editor, |_, cx| {
-                            MouseEventHandler::<JumpIcon>::new(id.into(), cx, |state, _| {
-                                let style = style.jump_icon.style_for(state, false);
-                                Svg::new("icons/arrow_up_right_8.svg")
-                                    .with_color(style.color)
-                                    .constrained()
-                                    .with_width(style.icon_width)
-                                    .aligned()
-                                    .contained()
-                                    .with_style(style.container)
-                                    .constrained()
-                                    .with_width(style.button_width)
-                                    .with_height(style.button_width)
-                                    .boxed()
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .on_click(MouseButton::Left, move |_, cx| {
-                                cx.dispatch_action(jump_action.clone())
-                            })
-                            .with_tooltip::<JumpIcon, _>(
-                                id.into(),
-                                "Jump to Buffer".to_string(),
-                                Some(Box::new(crate::OpenExcerpts)),
-                                tooltip_style.clone(),
-                                cx,
-                            )
-                            .aligned()
-                            .flex_float()
-                            .boxed()
+                        MouseEventHandler::<JumpIcon, _>::new(id.into(), cx, |state, _| {
+                            let style = style.jump_icon.style_for(state, false);
+                            Svg::new("icons/arrow_up_right_8.svg")
+                                .with_color(style.color)
+                                .constrained()
+                                .with_width(style.icon_width)
+                                .aligned()
+                                .contained()
+                                .with_style(style.container)
+                                .constrained()
+                                .with_width(style.button_width)
+                                .with_height(style.button_width)
+                                .boxed()
+                        })
+                        .with_cursor_style(CursorStyle::PointingHand)
+                        .on_click(MouseButton::Left, move |_, _, cx| {
+                            cx.dispatch_action(jump_action.clone())
                         })
+                        .with_tooltip::<JumpIcon>(
+                            id.into(),
+                            "Jump to Buffer".to_string(),
+                            Some(Box::new(crate::OpenExcerpts)),
+                            tooltip_style.clone(),
+                            cx,
+                        )
+                        .aligned()
+                        .flex_float()
+                        .boxed()
                     });
 
                     if *starts_new_buffer {

crates/editor/src/hover_popover.rs 🔗

@@ -4,7 +4,7 @@ use gpui::{
     elements::{Flex, MouseEventHandler, Padding, Text},
     impl_internal_actions,
     platform::{CursorStyle, MouseButton},
-    AppContext, Axis, Element, ElementBox, ModelHandle, RenderContext, Task, ViewContext,
+    AppContext, Axis, Drawable, Element, ModelHandle, Task, ViewContext,
 };
 use language::{Bias, DiagnosticEntry, DiagnosticSeverity};
 use project::{HoverBlock, Project};
@@ -208,7 +208,7 @@ fn show_hover(
                             local_diagnostic,
                             primary_diagnostic,
                         });
-                });
+                })?;
             }
 
             // Construct new hover popover from hover request
@@ -254,7 +254,7 @@ fn show_hover(
 
                     this.hover_state.info_popover = hover_popover;
                     cx.notify();
-                });
+                })?;
             }
             Ok::<_, anyhow::Error>(())
         }
@@ -282,8 +282,8 @@ impl HoverState {
         snapshot: &EditorSnapshot,
         style: &EditorStyle,
         visible_rows: Range<u32>,
-        cx: &mut RenderContext<Editor>,
-    ) -> Option<(DisplayPoint, Vec<ElementBox>)> {
+        cx: &mut ViewContext<Editor>,
+    ) -> Option<(DisplayPoint, Vec<Element<Editor>>)> {
         // If there is a diagnostic, position the popovers based on that.
         // Otherwise use the start of the hover range
         let anchor = self
@@ -323,9 +323,9 @@ pub struct InfoPopover {
 }
 
 impl InfoPopover {
-    pub fn render(&self, style: &EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
-        MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| {
-            let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx);
+    pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
+        MouseEventHandler::<InfoPopover, _>::new(0, cx, |_, cx| {
+            let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock>(1, None, cx);
             flex.extend(self.contents.iter().map(|content| {
                 let languages = self.project.read(cx).languages();
                 if let Some(language) = content.language.clone().and_then(|language| {
@@ -360,7 +360,7 @@ impl InfoPopover {
                 .with_style(style.hover_popover.container)
                 .boxed()
         })
-        .on_move(|_, _| {}) // Consume move events so they don't reach regions underneath.
+        .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
         .with_cursor_style(CursorStyle::Arrow)
         .with_padding(Padding {
             bottom: HOVER_POPOVER_GAP,
@@ -378,7 +378,7 @@ pub struct DiagnosticPopover {
 }
 
 impl DiagnosticPopover {
-    pub fn render(&self, style: &EditorStyle, cx: &mut RenderContext<Editor>) -> ElementBox {
+    pub fn render(&self, style: &EditorStyle, cx: &mut ViewContext<Editor>) -> Element<Editor> {
         enum PrimaryDiagnostic {}
 
         let mut text_style = style.hover_popover.prose.clone();
@@ -394,7 +394,7 @@ impl DiagnosticPopover {
 
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
-        MouseEventHandler::<DiagnosticPopover>::new(0, cx, |_, _| {
+        MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
             Text::new(self.local_diagnostic.diagnostic.message.clone(), text_style)
                 .with_soft_wrap(true)
                 .contained()
@@ -406,12 +406,12 @@ impl DiagnosticPopover {
             bottom: HOVER_POPOVER_GAP,
             ..Default::default()
         })
-        .on_move(|_, _| {}) // Consume move events so they don't reach regions underneath.
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(GoToDiagnostic)
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<PrimaryDiagnostic, _>(
+        .with_tooltip::<PrimaryDiagnostic>(
             0,
             "Go To Diagnostic".to_string(),
             Some(Box::new(crate::GoToDiagnostic)),

crates/editor/src/items.rs 🔗

@@ -8,7 +8,7 @@ use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
     elements::*, geometry::vector::vec2f, AppContext, AsyncAppContext, Entity, ModelHandle,
-    RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use language::{
     proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
@@ -28,7 +28,7 @@ use std::{
 };
 use text::Selection;
 use util::{ResultExt, TryFutureExt};
-use workspace::item::FollowableItemHandle;
+use workspace::item::{BreadcrumbText, FollowableItemHandle};
 use workspace::{
     item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
     searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -80,7 +80,9 @@ impl FollowableItem for Editor {
                 })
             });
 
-            let editor = editor.unwrap_or_else(|| {
+            let editor = if let Some(editor) = editor {
+                editor
+            } else {
                 pane.update(&mut cx, |_, cx| {
                     let multibuffer = cx.add_model(|cx| {
                         let mut multibuffer;
@@ -121,8 +123,8 @@ impl FollowableItem for Editor {
                         editor.remote_id = Some(remote_id);
                         editor
                     })
-                })
-            });
+                })?
+            };
 
             update_editor_from_message(
                 editor.clone(),
@@ -367,7 +369,7 @@ async fn update_editor_from_message(
 
             multibuffer.remove_excerpts(removed_excerpt_ids, cx);
         });
-    });
+    })?;
 
     // Deserialize the editor state.
     let (selections, pending_selection, scroll_top_anchor) = this.update(cx, |editor, cx| {
@@ -384,7 +386,7 @@ async fn update_editor_from_message(
             .scroll_top_anchor
             .and_then(|anchor| deserialize_anchor(&buffer, anchor));
         anyhow::Ok((selections, pending_selection, scroll_top_anchor))
-    })?;
+    })??;
 
     // Wait until the buffer has received all of the operations referenced by
     // the editor's new state.
@@ -399,7 +401,7 @@ async fn update_editor_from_message(
                 cx,
             )
         })
-    })
+    })?
     .await?;
 
     // Update the editor's state.
@@ -416,7 +418,7 @@ async fn update_editor_from_message(
                 cx,
             );
         }
-    });
+    })?;
     Ok(())
 }
 
@@ -556,12 +558,12 @@ impl Item for Editor {
         }
     }
 
-    fn tab_content(
+    fn tab_content<T: View>(
         &self,
         detail: Option<usize>,
         style: &theme::Tab,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<T> {
         Flex::row()
             .with_child(
                 Label::new(self.title(cx).to_string(), style.label.clone())
@@ -641,7 +643,7 @@ impl Item for Editor {
         self.report_event("save editor", cx);
         let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
         let buffers = self.buffer().clone().read(cx).all_buffers();
-        cx.as_mut().spawn(|mut cx| async move {
+        cx.spawn(|_, mut cx| async move {
             format.await?;
 
             if buffers.len() == 1 {
@@ -705,7 +707,7 @@ impl Item for Editor {
             let transaction = reload_buffers.log_err().await;
             this.update(&mut cx, |editor, cx| {
                 editor.request_autoscroll(Autoscroll::fit(), cx)
-            });
+            })?;
             buffer.update(&mut cx, |buffer, _| {
                 if let Some(transaction) = transaction {
                     if !buffer.is_singleton() {
@@ -762,7 +764,7 @@ impl Item for Editor {
         ToolbarItemLocation::PrimaryLeft { flex: None }
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
         let cursor = self.selections.newest_anchor().head();
         let multibuffer = &self.buffer().read(cx);
         let (buffer_id, symbols) =
@@ -782,15 +784,13 @@ impl Item for Editor {
             .map(|path| path.to_string_lossy().to_string())
             .unwrap_or_else(|| "untitled".to_string());
 
-        let filename_label = Label::new(filename, theme.workspace.breadcrumbs.default.text.clone());
-        let mut breadcrumbs = vec![filename_label.boxed()];
-        breadcrumbs.extend(symbols.into_iter().map(|symbol| {
-            Text::new(
-                symbol.text,
-                theme.workspace.breadcrumbs.default.text.clone(),
-            )
-            .with_highlights(symbol.highlight_ranges)
-            .boxed()
+        let mut breadcrumbs = vec![BreadcrumbText {
+            text: filename,
+            highlights: None,
+        }];
+        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
+            text: symbol.text,
+            highlights: Some(symbol.highlight_ranges),
         }));
         Some(breadcrumbs)
     }
@@ -1113,7 +1113,7 @@ impl View for CursorPosition {
         "CursorPosition"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if let Some(position) = self.position {
             let theme = &cx.global::<Settings>().theme.workspace.status_bar;
             let mut text = format!("{},{}", position.row + 1, position.column + 1);

crates/editor/src/scroll.rs 🔗

@@ -248,10 +248,12 @@ impl ScrollManager {
             self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
                 cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
                 if let Some(editor) = editor.upgrade(&cx) {
-                    editor.update(&mut cx, |editor, cx| {
-                        editor.scroll_manager.show_scrollbars = false;
-                        cx.notify();
-                    });
+                    editor
+                        .update(&mut cx, |editor, cx| {
+                            editor.scroll_manager.show_scrollbars = false;
+                            cx.notify();
+                        })
+                        .log_err();
                 }
             }));
         } else {

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

@@ -34,11 +34,10 @@ impl<'a> EditorTestContext<'a> {
             crate::init(cx);
 
             let (window_id, editor) = cx.add_window(Default::default(), |cx| {
+                cx.focus_self();
                 build_editor(MultiBuffer::build_simple("", cx), cx)
             });
 
-            editor.update(cx, |_, cx| cx.focus_self());
-
             (window_id, editor)
         });
 

crates/feedback/src/deploy_feedback_button.rs 🔗

@@ -1,7 +1,7 @@
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Entity, RenderContext, View, ViewContext,
+    Entity, View, ViewContext,
 };
 use settings::Settings;
 use workspace::{item::ItemHandle, StatusItemView};
@@ -27,12 +27,12 @@ impl View for DeployFeedbackButton {
         "DeployFeedbackButton"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let active = self.active;
         let theme = cx.global::<Settings>().theme.clone();
         Stack::new()
             .with_child(
-                MouseEventHandler::<Self>::new(0, cx, |state, _| {
+                MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {
                     let style = &theme
                         .workspace
                         .status_bar
@@ -53,12 +53,12 @@ impl View for DeployFeedbackButton {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     if !active {
                         cx.dispatch_action(GiveFeedback)
                     }
                 })
-                .with_tooltip::<Self, _>(
+                .with_tooltip::<Self>(
                     0,
                     "Send Feedback".into(),
                     Some(Box::new(GiveFeedback)),

crates/feedback/src/feedback_editor.rs 🔗

@@ -13,8 +13,8 @@ use gpui::{
     actions,
     elements::{ChildView, Flex, Label, ParentElement, Svg},
     platform::PromptLevel,
-    serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext,
-    Task, View, ViewContext, ViewHandle,
+    serde_json, AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View,
+    ViewContext, ViewHandle,
 };
 use isahc::Request;
 use language::Buffer;
@@ -134,24 +134,21 @@ impl FeedbackEditor {
             if answer == Some(0) {
                 match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
                     Ok(_) => {
-                        cx.update(|cx| {
-                            this.update(cx, |_, cx| {
-                                cx.dispatch_action(workspace::CloseActiveItem);
-                            })
-                        });
+                        this.update(&mut cx, |_, cx| {
+                            cx.dispatch_action(workspace::CloseActiveItem);
+                        })
+                        .log_err();
                     }
                     Err(error) => {
                         log::error!("{}", error);
-
-                        cx.update(|cx| {
-                            this.update(cx, |_, cx| {
-                                cx.prompt(
-                                    PromptLevel::Critical,
-                                    FEEDBACK_SUBMISSION_ERROR_TEXT,
-                                    &["OK"],
-                                );
-                            })
-                        });
+                        this.update(&mut cx, |_, cx| {
+                            cx.prompt(
+                                PromptLevel::Critical,
+                                FEEDBACK_SUBMISSION_ERROR_TEXT,
+                                &["OK"],
+                            );
+                        })
+                        .log_err();
                     }
                 }
             }
@@ -221,10 +218,10 @@ impl FeedbackEditor {
                             .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
                         workspace.add_item(Box::new(feedback_editor), cx);
                     })
-                })
-                .await;
+                })?
+                .await
         })
-        .detach();
+        .detach_and_log_err(cx);
     }
 }
 
@@ -233,7 +230,7 @@ impl View for FeedbackEditor {
         "FeedbackEditor"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         ChildView::new(&self.editor, cx).boxed()
     }
 
@@ -253,7 +250,12 @@ impl Item for FeedbackEditor {
         Some("Send Feedback".into())
     }
 
-    fn tab_content(&self, _: Option<usize>, style: &theme::Tab, _: &AppContext) -> ElementBox {
+    fn tab_content<T: View>(
+        &self,
+        _: Option<usize>,
+        style: &theme::Tab,
+        _: &AppContext,
+    ) -> Element<T> {
         Flex::row()
             .with_child(
                 Svg::new("icons/feedback_16.svg")

crates/feedback/src/feedback_info_text.rs 🔗

@@ -1,7 +1,7 @@
 use gpui::{
     elements::{Flex, Label, MouseEventHandler, ParentElement, Text},
     platform::{CursorStyle, MouseButton},
-    Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
+    Drawable, Element, Entity, View, ViewContext, ViewHandle,
 };
 use settings::Settings;
 use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
@@ -29,7 +29,7 @@ impl View for FeedbackInfoText {
         "FeedbackInfoText"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
 
         Flex::row()
@@ -43,7 +43,7 @@ impl View for FeedbackInfoText {
                 .boxed(),
             )
             .with_child(
-                MouseEventHandler::<OpenZedCommunityRepo>::new(0, cx, |state, _| {
+                MouseEventHandler::<OpenZedCommunityRepo, Self>::new(0, cx, |state, _| {
                     let contained_text = if state.hovered() {
                         &theme.feedback.link_text_hover
                     } else {
@@ -58,7 +58,7 @@ impl View for FeedbackInfoText {
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(OpenZedCommunityRepo)
                 })
                 .boxed(),

crates/feedback/src/submit_feedback_button.rs 🔗

@@ -1,7 +1,7 @@
 use gpui::{
     elements::{Label, MouseEventHandler},
     platform::{CursorStyle, MouseButton},
-    Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
+    Drawable, Element, Entity, View, ViewContext, ViewHandle,
 };
 use settings::Settings;
 use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
@@ -29,10 +29,10 @@ impl View for SubmitFeedbackButton {
         "SubmitFeedbackButton"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
         enum SubmitFeedbackButton {}
-        MouseEventHandler::<SubmitFeedbackButton>::new(0, cx, |state, _| {
+        MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
             let style = theme.feedback.submit_button.style_for(state, false);
             Label::new("Submit as Markdown", style.text.clone())
                 .contained()
@@ -40,13 +40,13 @@ impl View for SubmitFeedbackButton {
                 .boxed()
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, cx| {
+        .on_click(MouseButton::Left, |_, _, cx| {
             cx.dispatch_action(SubmitFeedback)
         })
         .aligned()
         .contained()
         .with_margin_left(theme.feedback.button_margin)
-        .with_tooltip::<Self, _>(
+        .with_tooltip::<Self>(
             0,
             "cmd-s".into(),
             Some(Box::new(SubmitFeedback)),

crates/file_finder/src/file_finder.rs 🔗

@@ -1,7 +1,6 @@
 use fuzzy::PathMatch;
 use gpui::{
-    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
-    RenderContext, Task, View, ViewContext, ViewHandle,
+    actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
 };
 use picker::{Picker, PickerDelegate};
 use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
@@ -13,12 +12,14 @@ use std::{
         Arc,
     },
 };
-use util::post_inc;
+use util::{post_inc, ResultExt};
 use workspace::Workspace;
 
-pub struct FileFinder {
+pub type FileFinder = Picker<FileFinderDelegate>;
+
+pub struct FileFinderDelegate {
+    workspace: WeakViewHandle<Workspace>,
     project: ModelHandle<Project>,
-    picker: ViewHandle<Picker<Self>>,
     search_count: usize,
     latest_search_id: usize,
     latest_search_did_cancel: bool,
@@ -32,8 +33,26 @@ pub struct FileFinder {
 actions!(file_finder, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(FileFinder::toggle);
-    Picker::<FileFinder>::init(cx);
+    cx.add_action(toggle_file_finder);
+    FileFinder::init(cx);
+}
+
+fn toggle_file_finder(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+    workspace.toggle_modal(cx, |workspace, cx| {
+        let relative_to = workspace
+            .active_item(cx)
+            .and_then(|item| item.project_path(cx))
+            .map(|project_path| project_path.path.clone());
+        let project = workspace.project().clone();
+        let workspace = cx.handle().downgrade();
+        let finder = cx.add_view(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(workspace, project, relative_to, cx),
+                cx,
+            )
+        });
+        finder
+    });
 }
 
 pub enum Event {
@@ -41,27 +60,7 @@ pub enum Event {
     Dismissed,
 }
 
-impl Entity for FileFinder {
-    type Event = Event;
-}
-
-impl View for FileFinder {
-    fn ui_name() -> &'static str {
-        "FileFinder"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl FileFinder {
+impl FileFinderDelegate {
     fn labels_for_match(&self, path_match: &PathMatch) -> (String, Vec<usize>, String, Vec<usize>) {
         let path = &path_match.path;
         let path_string = path.to_string_lossy();
@@ -88,48 +87,19 @@ impl FileFinder {
         (file_name, file_name_positions, full_path, path_positions)
     }
 
-    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-        workspace.toggle_modal(cx, |workspace, cx| {
-            let project = workspace.project().clone();
-            let relative_to = workspace
-                .active_item(cx)
-                .and_then(|item| item.project_path(cx))
-                .map(|project_path| project_path.path.clone());
-            let finder = cx.add_view(|cx| Self::new(project, relative_to, cx));
-            cx.subscribe(&finder, Self::on_event).detach();
-            finder
-        });
-    }
-
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<FileFinder>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Selected(project_path) => {
-                workspace
-                    .open_path(project_path.clone(), None, true, cx)
-                    .detach_and_log_err(cx);
-                workspace.dismiss_modal(cx);
-            }
-            Event::Dismissed => {
-                workspace.dismiss_modal(cx);
-            }
-        }
-    }
-
     pub fn new(
+        workspace: WeakViewHandle<Workspace>,
         project: ModelHandle<Project>,
         relative_to: Option<Arc<Path>>,
-        cx: &mut ViewContext<Self>,
+        cx: &mut ViewContext<FileFinder>,
     ) -> Self {
-        let handle = cx.weak_handle();
-        cx.observe(&project, Self::project_updated).detach();
+        cx.observe(&project, |picker, _, cx| {
+            picker.update_matches(picker.query(cx), cx);
+        })
+        .detach();
         Self {
+            workspace,
             project,
-            picker: cx.add_view(|cx| Picker::new("Search project files...", handle, cx)),
             search_count: 0,
             latest_search_id: 0,
             latest_search_did_cancel: false,
@@ -141,12 +111,7 @@ impl FileFinder {
         }
     }
 
-    fn project_updated(&mut self, _: ModelHandle<Project>, cx: &mut ViewContext<Self>) {
-        self.spawn_search(self.picker.read(cx).query(cx), cx)
-            .detach();
-    }
-
-    fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+    fn spawn_search(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
         let relative_to = self.relative_to.clone();
         let worktrees = self
             .project
@@ -172,7 +137,7 @@ impl FileFinder {
         self.cancel_flag.store(true, atomic::Ordering::Relaxed);
         self.cancel_flag = Arc::new(AtomicBool::new(false));
         let cancel_flag = self.cancel_flag.clone();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(|picker, mut cx| async move {
             let matches = fuzzy::match_path_sets(
                 candidate_sets.as_slice(),
                 &query,
@@ -184,9 +149,13 @@ impl FileFinder {
             )
             .await;
             let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
-            this.update(&mut cx, |this, cx| {
-                this.set_matches(search_id, did_cancel, query, matches, cx)
-            });
+            picker
+                .update(&mut cx, |picker, cx| {
+                    picker
+                        .delegate_mut()
+                        .set_matches(search_id, did_cancel, query, matches, cx)
+                })
+                .log_err();
         })
     }
 
@@ -196,7 +165,7 @@ impl FileFinder {
         did_cancel: bool,
         query: String,
         matches: Vec<PathMatch>,
-        cx: &mut ViewContext<Self>,
+        cx: &mut ViewContext<FileFinder>,
     ) {
         if search_id >= self.latest_search_id {
             self.latest_search_id = search_id;
@@ -208,12 +177,15 @@ impl FileFinder {
             self.latest_search_query = query;
             self.latest_search_did_cancel = did_cancel;
             cx.notify();
-            self.picker.update(cx, |_, cx| cx.notify());
         }
     }
 }
 
-impl PickerDelegate for FileFinder {
+impl PickerDelegate for FileFinderDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Search project files...".into()
+    }
+
     fn match_count(&self) -> usize {
         self.matches.len()
     }
@@ -231,13 +203,13 @@ impl PickerDelegate for FileFinder {
         0
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<FileFinder>) {
         let mat = &self.matches[ix];
         self.selected = Some((mat.worktree_id, mat.path.clone()));
         cx.notify();
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<FileFinder>) -> Task<()> {
         if query.is_empty() {
             self.latest_search_id = post_inc(&mut self.search_count);
             self.matches.clear();
@@ -248,18 +220,25 @@ impl PickerDelegate for FileFinder {
         }
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<FileFinder>) {
         if let Some(m) = self.matches.get(self.selected_index()) {
-            cx.emit(Event::Selected(ProjectPath {
-                worktree_id: WorktreeId::from_usize(m.worktree_id),
-                path: m.path.clone(),
-            }));
+            if let Some(workspace) = self.workspace.upgrade(cx) {
+                let project_path = ProjectPath {
+                    worktree_id: WorktreeId::from_usize(m.worktree_id),
+                    path: m.path.clone(),
+                };
+
+                workspace.update(cx, |workspace, cx| {
+                    workspace
+                        .open_path(project_path.clone(), None, true, cx)
+                        .detach_and_log_err(cx);
+                    workspace.dismiss_modal(cx);
+                })
+            }
         }
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
-    }
+    fn dismissed(&mut self, _: &mut ViewContext<FileFinder>) {}
 
     fn render_match(
         &self,
@@ -267,7 +246,7 @@ impl PickerDelegate for FileFinder {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let path_match = &self.matches[ix];
         let settings = cx.global::<Settings>();
         let style = settings.theme.picker.item.style_for(mouse_state, selected);
@@ -335,11 +314,11 @@ mod tests {
         let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
         finder
             .update(cx, |finder, cx| {
-                finder.update_matches("bna".to_string(), cx)
+                finder.delegate_mut().update_matches("bna".to_string(), cx)
             })
             .await;
         finder.read_with(cx, |finder, _| {
-            assert_eq!(finder.matches.len(), 2);
+            assert_eq!(finder.delegate().matches.len(), 2);
         });
 
         let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
@@ -384,23 +363,33 @@ mod tests {
 
         let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) =
-            cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+        let (_, finder) = cx.add_window(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(
+                    workspace.downgrade(),
+                    workspace.read(cx).project().clone(),
+                    None,
+                    cx,
+                ),
+                cx,
+            )
+        });
 
         let query = "hi".to_string();
         finder
-            .update(cx, |f, cx| f.spawn_search(query.clone(), cx))
+            .update(cx, |f, cx| f.delegate_mut().spawn_search(query.clone(), cx))
             .await;
-        finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 5));
+        finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 5));
 
         finder.update(cx, |finder, cx| {
-            let matches = finder.matches.clone();
+            let delegate = finder.delegate_mut();
+            let matches = delegate.matches.clone();
 
             // Simulate a search being cancelled after the time limit,
             // returning only a subset of the matches that would have been found.
-            drop(finder.spawn_search(query.clone(), cx));
-            finder.set_matches(
-                finder.latest_search_id,
+            drop(delegate.spawn_search(query.clone(), cx));
+            delegate.set_matches(
+                delegate.latest_search_id,
                 true, // did-cancel
                 query.clone(),
                 vec![matches[1].clone(), matches[3].clone()],
@@ -408,16 +397,16 @@ mod tests {
             );
 
             // Simulate another cancellation.
-            drop(finder.spawn_search(query.clone(), cx));
-            finder.set_matches(
-                finder.latest_search_id,
+            drop(delegate.spawn_search(query.clone(), cx));
+            delegate.set_matches(
+                delegate.latest_search_id,
                 true, // did-cancel
                 query.clone(),
                 vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
                 cx,
             );
 
-            assert_eq!(finder.matches, matches[0..4])
+            assert_eq!(delegate.matches, matches[0..4])
         });
     }
 
@@ -458,12 +447,21 @@ mod tests {
         )
         .await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) =
-            cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+        let (_, finder) = cx.add_window(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(
+                    workspace.downgrade(),
+                    workspace.read(cx).project().clone(),
+                    None,
+                    cx,
+                ),
+                cx,
+            )
+        });
         finder
-            .update(cx, |f, cx| f.spawn_search("hi".into(), cx))
+            .update(cx, |f, cx| f.delegate_mut().spawn_search("hi".into(), cx))
             .await;
-        finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 7));
+        finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7));
     }
 
     #[gpui::test]
@@ -482,20 +480,30 @@ mod tests {
         )
         .await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) =
-            cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+        let (_, finder) = cx.add_window(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(
+                    workspace.downgrade(),
+                    workspace.read(cx).project().clone(),
+                    None,
+                    cx,
+                ),
+                cx,
+            )
+        });
 
         // Even though there is only one worktree, that worktree's filename
         // is included in the matching, because the worktree is a single file.
         finder
-            .update(cx, |f, cx| f.spawn_search("thf".into(), cx))
+            .update(cx, |f, cx| f.delegate_mut().spawn_search("thf".into(), cx))
             .await;
         cx.read(|cx| {
             let finder = finder.read(cx);
-            assert_eq!(finder.matches.len(), 1);
+            let delegate = finder.delegate();
+            assert_eq!(delegate.matches.len(), 1);
 
             let (file_name, file_name_positions, full_path, full_path_positions) =
-                finder.labels_for_match(&finder.matches[0]);
+                delegate.labels_for_match(&delegate.matches[0]);
             assert_eq!(file_name, "the-file");
             assert_eq!(file_name_positions, &[0, 1, 4]);
             assert_eq!(full_path, "the-file");
@@ -505,9 +513,9 @@ mod tests {
         // Since the worktree root is a file, searching for its name followed by a slash does
         // not match anything.
         finder
-            .update(cx, |f, cx| f.spawn_search("thf/".into(), cx))
+            .update(cx, |f, cx| f.delegate_mut().spawn_search("thf/".into(), cx))
             .await;
-        finder.read_with(cx, |f, _| assert_eq!(f.matches.len(), 0));
+        finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 0));
     }
 
     #[gpui::test]
@@ -535,22 +543,32 @@ mod tests {
         .await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 
-        let (_, finder) =
-            cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+        let (_, finder) = cx.add_window(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(
+                    workspace.downgrade(),
+                    workspace.read(cx).project().clone(),
+                    None,
+                    cx,
+                ),
+                cx,
+            )
+        });
 
         // Run a search that matches two files with the same relative path.
         finder
-            .update(cx, |f, cx| f.spawn_search("a.t".into(), cx))
+            .update(cx, |f, cx| f.delegate_mut().spawn_search("a.t".into(), cx))
             .await;
 
         // Can switch between different matches with the same relative path.
-        finder.update(cx, |f, cx| {
-            assert_eq!(f.matches.len(), 2);
-            assert_eq!(f.selected_index(), 0);
-            f.set_selected_index(1, cx);
-            assert_eq!(f.selected_index(), 1);
-            f.set_selected_index(0, cx);
-            assert_eq!(f.selected_index(), 0);
+        finder.update(cx, |finder, cx| {
+            let delegate = finder.delegate_mut();
+            assert_eq!(delegate.matches.len(), 2);
+            assert_eq!(delegate.selected_index(), 0);
+            delegate.set_selected_index(1, cx);
+            assert_eq!(delegate.selected_index(), 1);
+            delegate.set_selected_index(0, cx);
+            assert_eq!(delegate.selected_index(), 0);
         });
     }
 
@@ -581,16 +599,28 @@ mod tests {
         // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
         // so that one should be sorted earlier
         let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt")));
-        let (_, finder) =
-            cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx));
+        let (_, finder) = cx.add_window(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(
+                    workspace.downgrade(),
+                    workspace.read(cx).project().clone(),
+                    b_path,
+                    cx,
+                ),
+                cx,
+            )
+        });
 
         finder
-            .update(cx, |f, cx| f.spawn_search("a.txt".into(), cx))
+            .update(cx, |f, cx| {
+                f.delegate_mut().spawn_search("a.txt".into(), cx)
+            })
             .await;
 
         finder.read_with(cx, |f, _| {
-            assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
-            assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
+            let delegate = f.delegate();
+            assert_eq!(delegate.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
+            assert_eq!(delegate.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
         });
     }
 
@@ -613,14 +643,23 @@ mod tests {
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
-        let (_, finder) =
-            cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
+        let (_, finder) = cx.add_window(|cx| {
+            Picker::new(
+                FileFinderDelegate::new(
+                    workspace.downgrade(),
+                    workspace.read(cx).project().clone(),
+                    None,
+                    cx,
+                ),
+                cx,
+            )
+        });
         finder
-            .update(cx, |f, cx| f.spawn_search("dir".into(), cx))
+            .update(cx, |f, cx| f.delegate_mut().spawn_search("dir".into(), cx))
             .await;
         cx.read(|cx| {
             let finder = finder.read(cx);
-            assert_eq!(finder.matches.len(), 0);
+            assert_eq!(finder.delegate().matches.len(), 0);
         });
     }
 }

crates/go_to_line/src/go_to_line.rs 🔗

@@ -3,12 +3,12 @@ use std::sync::Arc;
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, DisplayPoint, Editor};
 use gpui::{
     actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Axis, Entity,
-    RenderContext, View, ViewContext, ViewHandle,
+    View, ViewContext, ViewHandle,
 };
 use menu::{Cancel, Confirm};
 use settings::Settings;
 use text::{Bias, Point};
-use workspace::Workspace;
+use workspace::{Modal, Workspace};
 
 actions!(go_to_line, [Toggle]);
 
@@ -65,11 +65,7 @@ impl GoToLine {
             .active_item(cx)
             .and_then(|active_item| active_item.downcast::<Editor>())
         {
-            workspace.toggle_modal(cx, |_, cx| {
-                let view = cx.add_view(|cx| GoToLine::new(editor, cx));
-                cx.subscribe(&view, Self::on_event).detach();
-                view
-            });
+            workspace.toggle_modal(cx, |_, cx| cx.add_view(|cx| GoToLine::new(editor, cx)));
         }
     }
 
@@ -91,17 +87,6 @@ impl GoToLine {
         cx.emit(Event::Dismissed);
     }
 
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<Self>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => workspace.dismiss_modal(cx),
-        }
-    }
-
     fn on_line_editor_event(
         &mut self,
         _: ViewHandle<Editor>,
@@ -142,12 +127,14 @@ impl Entity for GoToLine {
 
     fn release(&mut self, cx: &mut AppContext) {
         let scroll_position = self.prev_scroll_position.take();
-        self.active_editor.update(cx, |editor, cx| {
-            editor.highlight_rows(None);
-            if let Some(scroll_position) = scroll_position {
-                editor.set_scroll_position(scroll_position, cx);
-            }
-        })
+        cx.update_window(self.active_editor.window_id(), |cx| {
+            self.active_editor.update(cx, |editor, cx| {
+                editor.highlight_rows(None);
+                if let Some(scroll_position) = scroll_position {
+                    editor.set_scroll_position(scroll_position, cx);
+                }
+            })
+        });
     }
 }
 
@@ -156,7 +143,7 @@ impl View for GoToLine {
         "GoToLine"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = &cx.global::<Settings>().theme.picker;
 
         let label = format!(
@@ -192,3 +179,9 @@ impl View for GoToLine {
         cx.focus(&self.line_editor);
     }
 }
+
+impl Modal for GoToLine {
+    fn dismiss_on_event(event: &Self::Event) -> bool {
+        matches!(event, Event::Dismissed)
+    }
+}

crates/gpui/examples/text.rs 🔗

@@ -2,7 +2,7 @@ use gpui::{
     color::Color,
     fonts::{Properties, Weight},
     text_layout::RunStyle,
-    DebugContext, Element as _, MeasurementContext, Quad,
+    Drawable, Element, Quad, SceneBuilder, View, ViewContext,
 };
 use log::LevelFilter;
 use pathfinder_geometry::rect::RectF;
@@ -30,12 +30,12 @@ impl gpui::View for TextView {
         "View"
     }
 
-    fn render(&mut self, _: &mut gpui::RenderContext<Self>) -> gpui::ElementBox {
+    fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Element<TextView> {
         TextElement.boxed()
     }
 }
 
-impl gpui::Element for TextElement {
+impl<V: View> Drawable<V> for TextElement {
     type LayoutState = ();
 
     type PaintState = ();
@@ -43,17 +43,20 @@ impl gpui::Element for TextElement {
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        _: &mut gpui::LayoutContext,
+        _: &mut V,
+        _: &mut ViewContext<V>,
     ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut gpui::PaintContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let font_size = 12.;
         let family = cx
@@ -84,7 +87,7 @@ impl gpui::Element for TextElement {
         };
 
         let text = "Hello world!";
-        let line = cx.text_layout_cache.layout_str(
+        let line = cx.text_layout_cache().layout_str(
             text,
             font_size,
             &[
@@ -96,12 +99,12 @@ impl gpui::Element for TextElement {
             ],
         );
 
-        cx.scene.push_quad(Quad {
+        scene.push_quad(Quad {
             bounds,
             background: Some(Color::white()),
             ..Default::default()
         });
-        line.paint(bounds.origin(), visible_bounds, bounds.height(), cx);
+        line.paint(scene, bounds.origin(), visible_bounds, bounds.height(), cx);
     }
 
     fn rect_for_text_range(
@@ -111,7 +114,8 @@ impl gpui::Element for TextElement {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -121,7 +125,8 @@ impl gpui::Element for TextElement {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> gpui::json::Value {
         todo!()
     }

crates/gpui/src/app.rs 🔗

@@ -4,6 +4,7 @@ mod menu;
 pub(crate) mod ref_counts;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test_app_context;
+pub(crate) mod window;
 mod window_input_handler;
 
 use std::{
@@ -23,10 +24,9 @@ use std::{
 
 use anyhow::{anyhow, Context, Result};
 use parking_lot::Mutex;
-use pathfinder_geometry::vector::Vector2F;
 use postage::oneshot;
-use smallvec::SmallVec;
 use smol::prelude::*;
+use util::ResultExt;
 use uuid::Uuid;
 
 pub use action::*;
@@ -41,16 +41,16 @@ pub use test_app_context::{ContextHandle, TestAppContext};
 use window_input_handler::WindowInputHandler;
 
 use crate::{
-    elements::ElementBox,
+    elements::{AnyRootElement, Element, RootElement},
     executor::{self, Task},
     keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
     platform::{
-        self, Appearance, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
+        self, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
         PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions,
     },
-    presenter::Presenter,
     util::post_inc,
-    AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, TextLayoutCache,
+    window::{Window, WindowContext},
+    AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId,
 };
 
 use self::ref_counts::RefCounts;
@@ -69,7 +69,7 @@ pub trait Entity: 'static {
 
 pub trait View: Entity + Sized {
     fn ui_name() -> &'static str;
-    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox;
+    fn render(&mut self, cx: &mut ViewContext<'_, '_, '_, Self>) -> Element<Self>;
     fn focus_in(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
     fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {}
     fn key_down(&mut self, _: &KeyDownEvent, _: &mut ViewContext<Self>) -> bool {
@@ -173,11 +173,13 @@ pub trait ReadViewWith {
 }
 
 pub trait UpdateView {
+    type Output<S>;
+
     fn update_view<T, S>(
         &mut self,
         handle: &ViewHandle<T>,
         update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
-    ) -> S
+    ) -> Self::Output<S>
     where
         T: View;
 }
@@ -321,6 +323,17 @@ impl App {
         state.pending_notifications.clear();
         result
     }
+
+    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
+        &mut self,
+        window_id: usize,
+        callback: F,
+    ) -> Option<T> {
+        let mut state = self.0.borrow_mut();
+        let result = state.update_window(window_id, callback);
+        state.pending_notifications.clear();
+        result
+    }
 }
 
 impl AsyncAppContext {
@@ -341,6 +354,14 @@ impl AsyncAppContext {
         self.0.borrow_mut().update(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 add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
     where
         T: Entity,
@@ -366,17 +387,18 @@ impl AsyncAppContext {
     }
 
     pub fn activate_window(&mut self, window_id: usize) {
-        self.update(|cx| cx.activate_window(window_id))
+        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],
-    ) -> oneshot::Receiver<usize> {
-        self.update(|cx| cx.prompt(window_id, level, msg, answers))
+    ) -> Option<oneshot::Receiver<usize>> {
+        self.update_window(window_id, |cx| cx.prompt(level, msg, answers))
     }
 
     pub fn platform(&self) -> Arc<dyn Platform> {
@@ -442,15 +464,20 @@ impl ReadModelWith for AsyncAppContext {
 }
 
 impl UpdateView for AsyncAppContext {
+    type Output<S> = Result<S>;
+
     fn update_view<T, S>(
         &mut self,
         handle: &ViewHandle<T>,
         update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
-    ) -> S
+    ) -> Result<S>
     where
         T: View,
     {
-        self.0.borrow_mut().update_view(handle, update)
+        self.0
+            .borrow_mut()
+            .update_window(handle.window_id, |cx| cx.update_view(handle, update))
+            .ok_or_else(|| anyhow!("window was closed"))
     }
 }
 
@@ -469,21 +496,21 @@ impl ReadViewWith for AsyncAppContext {
     }
 }
 
-type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut AppContext, usize, usize);
+type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
 type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
 
 type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool>;
 type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
 type ObservationCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
 type GlobalObservationCallback = Box<dyn FnMut(&mut AppContext)>;
-type FocusObservationCallback = Box<dyn FnMut(bool, &mut AppContext) -> bool>;
+type FocusObservationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
 type ReleaseObservationCallback = Box<dyn FnMut(&dyn Any, &mut AppContext)>;
 type ActionObservationCallback = Box<dyn FnMut(TypeId, &mut AppContext)>;
-type WindowActivationCallback = Box<dyn FnMut(bool, &mut AppContext) -> bool>;
-type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut AppContext) -> bool>;
-type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut AppContext) -> bool>;
+type WindowActivationCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
+type WindowFullscreenCallback = Box<dyn FnMut(bool, &mut WindowContext) -> bool>;
+type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool>;
 type KeystrokeCallback =
-    Box<dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut AppContext) -> bool>;
+    Box<dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool>;
 type ActiveLabeledTasksCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
 type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
 type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> bool>;
@@ -497,12 +524,13 @@ pub struct AppContext {
     element_states: HashMap<ElementStateId, Box<dyn Any>>,
     background: Arc<executor::Background>,
     ref_counts: Arc<Mutex<RefCounts>>,
-    font_cache: Arc<FontCache>,
-    platform: Arc<dyn Platform>,
 
     weak_self: Option<rc::Weak<RefCell<Self>>>,
+    platform: Arc<dyn Platform>,
     foreground_platform: Rc<dyn platform::ForegroundPlatform>,
-    assets: Arc<AssetCache>,
+    pub asset_cache: Arc<AssetCache>,
+    font_system: Arc<dyn FontSystem>,
+    pub font_cache: Arc<FontCache>,
     action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
     capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
     // Entity Types -> { Action Types -> Action Handlers }
@@ -528,9 +556,6 @@ pub struct AppContext {
     keystroke_observations: CallbackCollection<usize, KeystrokeCallback>,
     active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
 
-    #[allow(clippy::type_complexity)]
-    presenters_and_platform_windows:
-        HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
     foreground: Rc<executor::Foreground>,
     pending_effects: VecDeque<Effect>,
     pending_notifications: HashSet<usize>,
@@ -561,12 +586,13 @@ impl AppContext {
             element_states: Default::default(),
             ref_counts: Arc::new(Mutex::new(ref_counts)),
             background,
-            font_cache,
-            platform,
 
             weak_self: None,
+            font_system: platform.fonts(),
+            platform,
             foreground_platform,
-            assets: Arc::new(AssetCache::new(asset_source)),
+            font_cache,
+            asset_cache: Arc::new(AssetCache::new(asset_source)),
             action_deserializers: Default::default(),
             capture_actions: Default::default(),
             actions: Default::default(),
@@ -588,7 +614,6 @@ impl AppContext {
             keystroke_observations: Default::default(),
             action_dispatch_observations: Default::default(),
             active_labeled_task_observations: Default::default(),
-            presenters_and_platform_windows: Default::default(),
             foreground,
             pending_effects: VecDeque::new(),
             pending_notifications: Default::default(),
@@ -601,6 +626,30 @@ impl AppContext {
         }
     }
 
+    pub fn background(&self) -> &Arc<executor::Background> {
+        &self.background
+    }
+
+    pub fn font_cache(&self) -> &Arc<FontCache> {
+        &self.font_cache
+    }
+
+    pub fn platform(&self) -> &Arc<dyn Platform> {
+        &self.platform
+    }
+
+    pub fn has_global<T: 'static>(&self) -> bool {
+        self.globals.contains_key(&TypeId::of::<T>())
+    }
+
+    pub fn global<T: 'static>(&self) -> &T {
+        if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
+            global.downcast_ref().unwrap()
+        } else {
+            panic!("no global has been added for {}", type_name::<T>());
+        }
+    }
+
     pub fn upgrade(&self) -> App {
         App(self.weak_self.as_ref().unwrap().upgrade().unwrap())
     }
@@ -635,9 +684,7 @@ impl AppContext {
     }
 
     pub fn remove_all_windows(&mut self) {
-        for (window_id, _) in self.windows.drain() {
-            self.presenters_and_platform_windows.remove(&window_id);
-        }
+        self.windows.clear();
         self.flush_effects();
     }
 
@@ -645,12 +692,6 @@ impl AppContext {
         &self.foreground
     }
 
-    pub fn debug_elements(&self, window_id: usize) -> Option<crate::json::Value> {
-        self.presenters_and_platform_windows
-            .get(&window_id)
-            .and_then(|(presenter, _)| presenter.borrow().debug_elements(self))
-    }
-
     pub fn deserialize_action(
         &self,
         name: &str,
@@ -692,11 +733,10 @@ impl AppContext {
         let handler = Box::new(
             move |view: &mut dyn AnyView,
                   action: &dyn Action,
-                  cx: &mut AppContext,
-                  window_id: usize,
+                  cx: &mut WindowContext,
                   view_id: usize| {
                 let action = action.as_any().downcast_ref().unwrap();
-                let mut cx = ViewContext::new(cx, window_id, view_id);
+                let mut cx = ViewContext::mutable(cx, view_id);
                 handler(
                     view.as_any_mut()
                         .downcast_mut()
@@ -764,50 +804,32 @@ impl AppContext {
         }
     }
 
-    pub fn is_topmost_window_for_position(&self, window_id: usize, position: Vector2F) -> bool {
-        self.presenters_and_platform_windows
-            .get(&window_id)
-            .map_or(false, |(_, window)| {
-                window.is_topmost_for_position(position)
-            })
-    }
-
     pub fn has_window(&self, window_id: usize) -> bool {
         self.window_ids()
             .find(|window| window == &window_id)
             .is_some()
     }
 
-    pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
-        self.windows.keys().copied()
-    }
-
-    pub fn activate_window(&self, window_id: usize) {
-        if let Some((_, window)) = self.presenters_and_platform_windows.get(&window_id) {
-            window.activate()
-        }
+    pub fn window_is_active(&self, window_id: usize) -> bool {
+        self.windows.get(&window_id).map_or(false, |w| w.is_active)
     }
 
-    pub fn window_is_active(&self, window_id: usize) -> bool {
-        self.windows
-            .get(&window_id)
-            .map_or(false, |window| window.is_active)
+    pub fn root_view(&self, window_id: usize) -> Option<&AnyViewHandle> {
+        self.windows.get(&window_id).map(|w| w.root_view())
     }
 
-    pub fn window_is_fullscreen(&self, window_id: usize) -> bool {
-        self.windows
-            .get(&window_id)
-            .map_or(false, |window| window.is_fullscreen)
+    pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
+        self.windows.keys().copied()
     }
 
-    pub fn window_bounds(&self, window_id: usize) -> Option<WindowBounds> {
-        let (_, window) = self.presenters_and_platform_windows.get(&window_id)?;
-        Some(window.bounds())
+    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 window_display_uuid(&self, window_id: usize) -> Option<Uuid> {
-        let (_, window) = self.presenters_and_platform_windows.get(&window_id)?;
-        window.screen().display_uuid()
+    pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option<TypeId> {
+        self.views
+            .get(&(window_id, view_id))
+            .map(|view| view.as_any().type_id())
     }
 
     pub fn active_labeled_tasks<'a>(
@@ -816,58 +838,6 @@ impl AppContext {
         self.active_labeled_tasks.values().cloned()
     }
 
-    pub fn render_view(&mut self, params: RenderParams) -> Result<ElementBox> {
-        let window_id = params.window_id;
-        let view_id = params.view_id;
-        let mut view = self
-            .views
-            .remove(&(window_id, view_id))
-            .ok_or_else(|| anyhow!("view not found"))?;
-        let element = view.render(params, self);
-        self.views.insert((window_id, view_id), view);
-        Ok(element)
-    }
-
-    pub fn render_views(
-        &mut self,
-        window_id: usize,
-        titlebar_height: f32,
-        appearance: Appearance,
-    ) -> HashMap<usize, ElementBox> {
-        self.start_frame();
-        #[allow(clippy::needless_collect)]
-        let view_ids = self
-            .views
-            .keys()
-            .filter_map(|(win_id, view_id)| {
-                if *win_id == window_id {
-                    Some(*view_id)
-                } else {
-                    None
-                }
-            })
-            .collect::<Vec<_>>();
-
-        view_ids
-            .into_iter()
-            .map(|view_id| {
-                (
-                    view_id,
-                    self.render_view(RenderParams {
-                        window_id,
-                        view_id,
-                        titlebar_height,
-                        hovered_region_ids: Default::default(),
-                        clicked_region_ids: None,
-                        refreshing: false,
-                        appearance,
-                    })
-                    .unwrap(),
-                )
-            })
-            .collect()
-    }
-
     pub(crate) fn start_frame(&mut self) {
         self.frame_count += 1;
     }
@@ -879,35 +849,39 @@ impl AppContext {
         result
     }
 
-    fn show_character_palette(&self, window_id: usize) {
-        let (_, window) = &self.presenters_and_platform_windows[&window_id];
-        window.show_character_palette();
-    }
-
-    pub fn minimize_window(&self, window_id: usize) {
-        let (_, window) = &self.presenters_and_platform_windows[&window_id];
-        window.minimize();
-    }
-
-    pub fn zoom_window(&self, window_id: usize) {
-        let (_, window) = &self.presenters_and_platform_windows[&window_id];
-        window.zoom();
+    pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
+        &self,
+        window_id: usize,
+        callback: F,
+    ) -> Option<T> {
+        let window = self.windows.get(&window_id)?;
+        let window_context = WindowContext::immutable(self, &window, window_id);
+        Some(callback(&window_context))
     }
 
-    pub fn toggle_window_full_screen(&self, window_id: usize) {
-        let (_, window) = &self.presenters_and_platform_windows[&window_id];
-        window.toggle_full_screen();
+    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 prompt(
-        &self,
-        window_id: usize,
-        level: PromptLevel,
-        msg: &str,
-        answers: &[&str],
-    ) -> oneshot::Receiver<usize> {
-        let (_, window) = &self.presenters_and_platform_windows[&window_id];
-        window.prompt(level, msg, answers)
+    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))
     }
 
     pub fn prompt_for_paths(
@@ -978,7 +952,7 @@ impl AppContext {
         })
     }
 
-    pub fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
+    fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
     where
         E: Entity,
         E::Event: 'static,
@@ -1028,7 +1002,7 @@ impl AppContext {
 
     fn observe_focus<F, V>(&mut self, handle: &ViewHandle<V>, mut callback: F) -> Subscription
     where
-        F: 'static + FnMut(ViewHandle<V>, bool, &mut AppContext) -> bool,
+        F: 'static + FnMut(ViewHandle<V>, bool, &mut WindowContext) -> bool,
         V: View,
     {
         let subscription_id = post_inc(&mut self.next_subscription_id);
@@ -1111,72 +1085,7 @@ impl AppContext {
         )
     }
 
-    fn observe_window_activation<F>(&mut self, window_id: usize, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(bool, &mut AppContext) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.pending_effects
-            .push_back(Effect::WindowActivationObservation {
-                window_id,
-                subscription_id,
-                callback: Box::new(callback),
-            });
-        Subscription::WindowActivationObservation(
-            self.window_activation_observations
-                .subscribe(window_id, subscription_id),
-        )
-    }
-
-    fn observe_fullscreen<F>(&mut self, window_id: usize, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(bool, &mut AppContext) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.pending_effects
-            .push_back(Effect::WindowFullscreenObservation {
-                window_id,
-                subscription_id,
-                callback: Box::new(callback),
-            });
-        Subscription::WindowActivationObservation(
-            self.window_activation_observations
-                .subscribe(window_id, subscription_id),
-        )
-    }
-
-    fn observe_window_bounds<F>(&mut self, window_id: usize, callback: F) -> Subscription
-    where
-        F: 'static + FnMut(WindowBounds, Uuid, &mut AppContext) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.pending_effects
-            .push_back(Effect::WindowBoundsObservation {
-                window_id,
-                subscription_id,
-                callback: Box::new(callback),
-            });
-        Subscription::WindowBoundsObservation(
-            self.window_bounds_observations
-                .subscribe(window_id, subscription_id),
-        )
-    }
-
-    pub fn observe_keystrokes<F>(&mut self, window_id: usize, callback: F) -> Subscription
-    where
-        F: 'static
-            + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut AppContext) -> bool,
-    {
-        let subscription_id = post_inc(&mut self.next_subscription_id);
-        self.keystroke_observations
-            .add_callback(window_id, subscription_id, Box::new(callback));
-        Subscription::KeystrokeObservation(
-            self.keystroke_observations
-                .subscribe(window_id, subscription_id),
-        )
-    }
-
-    pub fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
+    fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
     where
         F: 'static + FnMut(&mut AppContext) -> bool,
     {
@@ -1203,172 +1112,54 @@ impl AppContext {
         })
     }
 
-    pub(crate) fn notify_model(&mut self, model_id: usize) {
+    fn notify_model(&mut self, model_id: usize) {
         if self.pending_notifications.insert(model_id) {
             self.pending_effects
                 .push_back(Effect::ModelNotification { model_id });
         }
     }
 
-    pub(crate) fn notify_view(&mut self, window_id: usize, view_id: usize) {
+    fn notify_view(&mut self, window_id: usize, view_id: usize) {
         if self.pending_notifications.insert(view_id) {
             self.pending_effects
                 .push_back(Effect::ViewNotification { window_id, view_id });
         }
     }
 
-    pub(crate) fn notify_global(&mut self, type_id: TypeId) {
+    fn notify_global(&mut self, type_id: TypeId) {
         if self.pending_global_notifications.insert(type_id) {
             self.pending_effects
                 .push_back(Effect::GlobalNotification { type_id });
         }
     }
 
-    pub(crate) fn name_for_view(&self, window_id: usize, view_id: usize) -> Option<&str> {
-        self.views
-            .get(&(window_id, view_id))
-            .map(|view| view.ui_name())
-    }
-
     pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = &'static str> + 'a {
         self.action_deserializers.keys().copied()
     }
 
-    /// Return keystrokes that would dispatch the given action on the given view.
-    pub(crate) fn keystrokes_for_action(
-        &mut self,
-        window_id: usize,
-        view_id: usize,
-        action: &dyn Action,
-    ) -> Option<SmallVec<[Keystroke; 2]>> {
-        let mut contexts = Vec::new();
-        let mut handler_depth = None;
-        for (i, view_id) in self.ancestors(window_id, view_id).enumerate() {
-            if let Some(view) = self.views.get(&(window_id, view_id)) {
-                if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
-                    if actions.contains_key(&action.as_any().type_id()) {
-                        handler_depth = Some(i);
-                    }
-                }
-                contexts.push(view.keymap_context(self));
-            }
-        }
-
-        if self.global_actions.contains_key(&action.as_any().type_id()) {
-            handler_depth = Some(contexts.len())
-        }
-
-        self.keystroke_matcher
-            .bindings_for_action_type(action.as_any().type_id())
-            .find_map(|b| {
-                handler_depth
-                    .map(|highest_handler| {
-                        if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
-                            Some(b.keystrokes().into())
-                        } else {
-                            None
-                        }
-                    })
-                    .flatten()
-            })
-    }
-
-    pub fn available_actions(
-        &self,
-        window_id: usize,
-        view_id: usize,
-    ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
-        let mut contexts = Vec::new();
-        let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
-        for (depth, view_id) in self.ancestors(window_id, view_id).enumerate() {
-            if let Some(view) = self.views.get(&(window_id, view_id)) {
-                contexts.push(view.keymap_context(self));
-                let view_type = view.as_any().type_id();
-                if let Some(actions) = self.actions.get(&view_type) {
-                    handler_depths_by_action_type.extend(
-                        actions
-                            .keys()
-                            .copied()
-                            .map(|action_type| (action_type, depth)),
-                    );
-                }
-            }
-        }
-
-        handler_depths_by_action_type.extend(
-            self.global_actions
-                .keys()
-                .copied()
-                .map(|action_type| (action_type, contexts.len())),
-        );
-
-        self.action_deserializers
-            .iter()
-            .filter_map(move |(name, (type_id, deserialize))| {
-                if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
-                    Some((
-                        *name,
-                        deserialize("{}").ok()?,
-                        self.keystroke_matcher
-                            .bindings_for_action_type(*type_id)
-                            .filter(|b| {
-                                (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
-                            })
-                            .collect(),
-                    ))
-                } else {
-                    None
-                }
-            })
-    }
-
     pub fn is_action_available(&self, action: &dyn Action) -> bool {
+        let mut available_in_window = false;
         let action_type = action.as_any().type_id();
         if let Some(window_id) = self.platform.main_window_id() {
-            if let Some(focused_view_id) = self.focused_view_id(window_id) {
-                for view_id in self.ancestors(window_id, focused_view_id) {
-                    if let Some(view) = self.views.get(&(window_id, view_id)) {
-                        let view_type = view.as_any().type_id();
-                        if let Some(actions) = self.actions.get(&view_type) {
-                            if actions.contains_key(&action_type) {
-                                return true;
+            available_in_window = self
+                .read_window(window_id, |cx| {
+                    if let Some(focused_view_id) = cx.focused_view_id() {
+                        for view_id in cx.ancestors(focused_view_id) {
+                            if let Some(view) = cx.views.get(&(window_id, view_id)) {
+                                let view_type = view.as_any().type_id();
+                                if let Some(actions) = cx.actions.get(&view_type) {
+                                    if actions.contains_key(&action_type) {
+                                        return true;
+                                    }
+                                }
                             }
                         }
                     }
-                }
-            }
-        }
-        self.global_actions.contains_key(&action_type)
-    }
-
-    // Traverses the parent tree. Walks down the tree toward the passed
-    // view calling visit with true. Then walks back up the tree calling visit with false.
-    // If `visit` returns false this function will immediately return.
-    // Returns a bool indicating if the traversal was completed early.
-    fn visit_dispatch_path(
-        &mut self,
-        window_id: usize,
-        view_id: usize,
-        mut visit: impl FnMut(usize, bool, &mut AppContext) -> bool,
-    ) -> bool {
-        // List of view ids from the leaf to the root of the window
-        let path = self.ancestors(window_id, view_id).collect::<Vec<_>>();
-
-        // Walk down from the root to the leaf calling visit with capture_phase = true
-        for view_id in path.iter().rev() {
-            if !visit(*view_id, true, self) {
-                return false;
-            }
-        }
-
-        // Walk up from the leaf to the root calling visit with capture_phase = false
-        for view_id in path.iter() {
-            if !visit(*view_id, false, self) {
-                return false;
-            }
+                    false
+                })
+                .unwrap_or(false);
         }
-
-        true
+        available_in_window || self.global_actions.contains_key(&action_type)
     }
 
     fn actions_mut(
@@ -1406,121 +1197,6 @@ impl AppContext {
         self.keystroke_matcher.clear_bindings();
     }
 
-    pub fn dispatch_key_down(&mut self, window_id: usize, event: &KeyDownEvent) -> bool {
-        if let Some(focused_view_id) = self.focused_view_id(window_id) {
-            for view_id in self
-                .ancestors(window_id, focused_view_id)
-                .collect::<Vec<_>>()
-            {
-                if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
-                    let handled = view.key_down(event, self, window_id, view_id);
-                    self.views.insert((window_id, view_id), view);
-                    if handled {
-                        return true;
-                    }
-                } else {
-                    log::error!("view {} does not exist", view_id)
-                }
-            }
-        }
-
-        false
-    }
-
-    pub fn dispatch_key_up(&mut self, window_id: usize, event: &KeyUpEvent) -> bool {
-        if let Some(focused_view_id) = self.focused_view_id(window_id) {
-            for view_id in self
-                .ancestors(window_id, focused_view_id)
-                .collect::<Vec<_>>()
-            {
-                if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
-                    let handled = view.key_up(event, self, window_id, view_id);
-                    self.views.insert((window_id, view_id), view);
-                    if handled {
-                        return true;
-                    }
-                } else {
-                    log::error!("view {} does not exist", view_id)
-                }
-            }
-        }
-
-        false
-    }
-
-    pub fn dispatch_modifiers_changed(
-        &mut self,
-        window_id: usize,
-        event: &ModifiersChangedEvent,
-    ) -> bool {
-        if let Some(focused_view_id) = self.focused_view_id(window_id) {
-            for view_id in self
-                .ancestors(window_id, focused_view_id)
-                .collect::<Vec<_>>()
-            {
-                if let Some(mut view) = self.views.remove(&(window_id, view_id)) {
-                    let handled = view.modifiers_changed(event, self, window_id, view_id);
-                    self.views.insert((window_id, view_id), view);
-                    if handled {
-                        return true;
-                    }
-                } else {
-                    log::error!("view {} does not exist", view_id)
-                }
-            }
-        }
-
-        false
-    }
-
-    pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: &Keystroke) -> bool {
-        if let Some(focused_view_id) = self.focused_view_id(window_id) {
-            let dispatch_path = self
-                .ancestors(window_id, focused_view_id)
-                .filter_map(|view_id| {
-                    self.views
-                        .get(&(window_id, view_id))
-                        .map(|view| (view_id, view.keymap_context(self)))
-                })
-                .collect();
-
-            let match_result = self
-                .keystroke_matcher
-                .push_keystroke(keystroke.clone(), dispatch_path);
-            let mut handled_by = None;
-
-            let keystroke_handled = match &match_result {
-                MatchResult::None => false,
-                MatchResult::Pending => true,
-                MatchResult::Matches(matches) => {
-                    for (view_id, action) in matches {
-                        if self.handle_dispatch_action_from_effect(
-                            window_id,
-                            Some(*view_id),
-                            action.as_ref(),
-                        ) {
-                            self.keystroke_matcher.clear_pending();
-                            handled_by = Some(action.boxed_clone());
-                            break;
-                        }
-                    }
-                    handled_by.is_some()
-                }
-            };
-
-            self.keystroke(
-                window_id,
-                keystroke.clone(),
-                handled_by,
-                match_result.clone(),
-            );
-            keystroke_handled
-        } else {
-            self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
-            false
-        }
-    }
-
     pub fn default_global<T: 'static + Default>(&mut self) -> &T {
         let type_id = TypeId::of::<T>();
         self.update(|this| {
@@ -1545,37 +1221,55 @@ impl AppContext {
         T: 'static + Default,
         F: FnOnce(&mut T, &mut AppContext) -> U,
     {
-        self.update(|this| {
-            let type_id = TypeId::of::<T>();
-            let mut state = this
-                .globals
-                .remove(&type_id)
-                .unwrap_or_else(|| Box::new(T::default()));
-            let result = update(state.downcast_mut().unwrap(), this);
-            this.globals.insert(type_id, state);
-            this.notify_global(type_id);
-            result
+        self.update(|mut this| {
+            Self::update_default_global_internal(&mut this, |global, cx| update(global, cx))
         })
     }
 
+    fn update_default_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
+    where
+        C: DerefMut<Target = AppContext>,
+        T: 'static + Default,
+        F: FnOnce(&mut T, &mut C) -> U,
+    {
+        let type_id = TypeId::of::<T>();
+        let mut state = this
+            .globals
+            .remove(&type_id)
+            .unwrap_or_else(|| Box::new(T::default()));
+        let result = update(state.downcast_mut().unwrap(), this);
+        this.globals.insert(type_id, state);
+        this.notify_global(type_id);
+        result
+    }
+
     pub fn update_global<T, F, U>(&mut self, update: F) -> U
     where
         T: 'static,
         F: FnOnce(&mut T, &mut AppContext) -> U,
     {
-        self.update(|this| {
-            let type_id = TypeId::of::<T>();
-            if let Some(mut state) = this.globals.remove(&type_id) {
-                let result = update(state.downcast_mut().unwrap(), this);
-                this.globals.insert(type_id, state);
-                this.notify_global(type_id);
-                result
-            } else {
-                panic!("No global added for {}", std::any::type_name::<T>());
-            }
+        self.update(|mut this| {
+            Self::update_global_internal(&mut this, |global, cx| update(global, cx))
         })
     }
 
+    fn update_global_internal<C, T, F, U>(this: &mut C, update: F) -> U
+    where
+        C: DerefMut<Target = AppContext>,
+        T: 'static,
+        F: FnOnce(&mut T, &mut C) -> U,
+    {
+        let type_id = TypeId::of::<T>();
+        if let Some(mut state) = this.globals.remove(&type_id) {
+            let result = update(state.downcast_mut().unwrap(), this);
+            this.globals.insert(type_id, state);
+            this.notify_global(type_id);
+            result
+        } else {
+            panic!("No global added for {}", std::any::type_name::<T>());
+        }
+    }
+
     pub fn clear_globals(&mut self) {
         self.globals.clear();
     }
@@ -1595,66 +1289,42 @@ impl AppContext {
         })
     }
 
-    pub fn add_window<T, F>(
+    pub fn add_window<V, F>(
         &mut self,
         window_options: WindowOptions,
         build_root_view: F,
-    ) -> (usize, ViewHandle<T>)
+    ) -> (usize, ViewHandle<V>)
     where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
+        V: View,
+        F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
             let window_id = post_inc(&mut this.next_window_id);
-            let root_view = this
-                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
-                .unwrap();
-            this.windows.insert(
-                window_id,
-                Window {
-                    root_view: root_view.clone().into_any(),
-                    focused_view_id: Some(root_view.id()),
-                    is_active: false,
-                    invalidation: None,
-                    is_fullscreen: false,
-                },
-            );
-            root_view.update(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
-
-            let window =
+            let platform_window =
                 this.platform
                     .open_window(window_id, window_options, this.foreground.clone());
-            this.register_platform_window(window_id, window);
-
+            let window = this.build_window(window_id, platform_window, build_root_view);
+            let root_view = window.root_view().clone().downcast::<V>().unwrap();
+            this.windows.insert(window_id, window);
             (window_id, root_view)
         })
     }
 
-    pub fn add_status_bar_item<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
+    pub fn add_status_bar_item<V, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<V>)
     where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
+        V: View,
+        F: FnOnce(&mut ViewContext<V>) -> V,
     {
         self.update(|this| {
             let window_id = post_inc(&mut this.next_window_id);
-            let root_view = this
-                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
-                .unwrap();
-            let focused_view_id = root_view.id();
-            this.windows.insert(
-                window_id,
-                Window {
-                    root_view: root_view.clone().into_any(),
-                    focused_view_id: Some(focused_view_id),
-                    is_active: false,
-                    invalidation: None,
-                    is_fullscreen: false,
-                },
-            );
-            root_view.update(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
+            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();
 
-            let status_item = this.platform.add_status_item();
-            this.register_platform_window(window_id, status_item);
+            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)
         })

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

@@ -1,4 +1,3 @@
-use crate::AppContext;
 use collections::{BTreeMap, HashMap, HashSet};
 use parking_lot::Mutex;
 use std::sync::Arc;
@@ -93,12 +92,10 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
         drop(callbacks);
     }
 
-    pub fn emit<C: FnMut(&mut F, &mut AppContext) -> bool>(
-        &mut self,
-        key: K,
-        cx: &mut AppContext,
-        mut call_callback: C,
-    ) {
+    pub fn emit<C>(&mut self, key: K, mut call_callback: C)
+    where
+        C: FnMut(&mut F) -> bool,
+    {
         let callbacks = self.internal.lock().callbacks.remove(&key);
         if let Some(callbacks) = callbacks {
             for (subscription_id, mut callback) in callbacks {
@@ -110,7 +107,7 @@ impl<K: Clone + Hash + Eq + Copy, F> CallbackCollection<K, F> {
                 }
 
                 drop(this);
-                let alive = call_callback(&mut callback, cx);
+                let alive = call_callback(&mut callback);
 
                 // If this callback's subscription was dropped while invoking the callback
                 // itself, or if the callback returns false, then just drop the callback.

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

@@ -78,8 +78,18 @@ pub(crate) fn setup_menu_handlers(foreground_platform: &dyn ForegroundPlatform,
         move |action| {
             let mut cx = cx.borrow_mut();
             if let Some(main_window_id) = cx.platform.main_window_id() {
-                if let Some(view_id) = cx.focused_view_id(main_window_id) {
-                    cx.handle_dispatch_action_from_effect(main_window_id, Some(view_id), action);
+                let dispatched = cx
+                    .update_window(main_window_id, |cx| {
+                        if let Some(view_id) = cx.focused_view_id() {
+                            cx.handle_dispatch_action_from_effect(Some(view_id), action);
+                            true
+                        } else {
+                            false
+                        }
+                    })
+                    .unwrap_or(false);
+
+                if dispatched {
                     return;
                 }
             }

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

@@ -1,6 +1,6 @@
 use std::{
+    any::Any,
     cell::RefCell,
-    marker::PhantomData,
     mem,
     path::PathBuf,
     rc::Rc,
@@ -21,10 +21,10 @@ use crate::{
     geometry::vector::Vector2F,
     keymap_matcher::Keystroke,
     platform,
-    platform::{Appearance, Event, InputHandler, KeyDownEvent, Platform},
+    platform::{Event, InputHandler, KeyDownEvent, Platform},
     Action, AnyViewHandle, AppContext, Entity, FontCache, Handle, ModelContext, ModelHandle,
-    ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext,
-    ViewHandle, WeakHandle,
+    ReadModelWith, ReadViewWith, Subscription, Task, UpdateModel, UpdateView, View, ViewContext,
+    ViewHandle, WeakHandle, WindowContext,
 };
 use collections::BTreeMap;
 
@@ -75,7 +75,7 @@ impl TestAppContext {
 
     pub fn dispatch_action<A: Action>(&self, window_id: usize, action: A) {
         let mut cx = self.cx.borrow_mut();
-        if let Some(view_id) = cx.focused_view_id(window_id) {
+        if let Some(view_id) = cx.windows.get(&window_id).and_then(|w| w.focused_view_id) {
             cx.handle_dispatch_action_from_effect(window_id, Some(view_id), &action);
         }
     }
@@ -85,31 +85,27 @@ impl TestAppContext {
     }
 
     pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
-        let handled = self.cx.borrow_mut().update(|cx| {
-            let presenter = cx
-                .presenters_and_platform_windows
-                .get(&window_id)
-                .unwrap()
-                .0
-                .clone();
-
-            if cx.dispatch_keystroke(window_id, &keystroke) {
-                return true;
-            }
+        let handled = self
+            .cx
+            .borrow_mut()
+            .update_window(window_id, |cx| {
+                if cx.dispatch_keystroke(&keystroke) {
+                    return true;
+                }
 
-            if presenter.borrow_mut().dispatch_event(
-                Event::KeyDown(KeyDownEvent {
-                    keystroke: keystroke.clone(),
-                    is_held,
-                }),
-                false,
-                cx,
-            ) {
-                return true;
-            }
+                if cx.dispatch_event(
+                    Event::KeyDown(KeyDownEvent {
+                        keystroke: keystroke.clone(),
+                        is_held,
+                    }),
+                    false,
+                ) {
+                    return true;
+                }
 
-            false
-        });
+                false
+            })
+            .unwrap_or(false);
 
         if !handled && !keystroke.cmd && !keystroke.ctrl {
             WindowInputHandler {
@@ -120,6 +116,22 @@ impl TestAppContext {
         }
     }
 
+    pub fn read_window<T, F: FnOnce(&WindowContext) -> T>(
+        &self,
+        window_id: usize,
+        callback: F,
+    ) -> Option<T> {
+        self.cx.borrow().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.cx.borrow_mut().update_window(window_id, callback)
+    }
+
     pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
     where
         T: Entity,
@@ -149,12 +161,28 @@ impl TestAppContext {
         self.cx.borrow_mut().add_view(parent_handle, build_view)
     }
 
-    pub fn window_ids(&self) -> Vec<usize> {
-        self.cx.borrow().window_ids().collect()
+    pub fn observe_global<E, F>(&mut self, callback: F) -> Subscription
+    where
+        E: Any,
+        F: 'static + FnMut(&mut AppContext),
+    {
+        self.cx.borrow_mut().observe_global::<E, F>(callback)
+    }
+
+    pub fn set_global<T: 'static>(&mut self, state: T) {
+        self.cx.borrow_mut().set_global(state);
     }
 
-    pub fn root_view(&self, window_id: usize) -> Option<AnyViewHandle> {
-        self.cx.borrow().root_view(window_id)
+    pub fn subscribe_global<E, F>(&mut self, callback: F) -> Subscription
+    where
+        E: Any,
+        F: 'static + FnMut(&E, &mut AppContext),
+    {
+        self.cx.borrow_mut().subscribe_global(callback)
+    }
+
+    pub fn window_ids(&self) -> Vec<usize> {
+        self.cx.borrow().window_ids().collect()
     }
 
     pub fn read<T, F: FnOnce(&AppContext) -> T>(&self, callback: F) -> T {
@@ -172,27 +200,6 @@ impl TestAppContext {
         result
     }
 
-    pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
-    where
-        F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
-        V: View,
-    {
-        handle.update(&mut *self.cx.borrow_mut(), |view, cx| {
-            let mut render_cx = RenderContext {
-                app: cx,
-                window_id: handle.window_id(),
-                view_id: handle.id(),
-                view_type: PhantomData,
-                titlebar_height: 0.,
-                hovered_region_ids: Default::default(),
-                clicked_region_ids: None,
-                refreshing: false,
-                appearance: Appearance::Light,
-            };
-            f(view, &mut render_cx)
-        })
-    }
-
     pub fn to_async(&self) -> AsyncAppContext {
         AsyncAppContext(self.cx.clone())
     }
@@ -245,7 +252,7 @@ impl TestAppContext {
         use postage::prelude::Sink as _;
 
         let mut done_tx = self
-            .window_mut(window_id)
+            .platform_window_mut(window_id)
             .pending_prompts
             .borrow_mut()
             .pop_front()
@@ -254,20 +261,23 @@ impl TestAppContext {
     }
 
     pub fn has_pending_prompt(&self, window_id: usize) -> bool {
-        let window = self.window_mut(window_id);
+        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.window_mut(window_id).title.clone()
+        self.platform_window_mut(window_id).title.clone()
     }
 
     pub fn simulate_window_close(&self, window_id: usize) -> bool {
-        let handler = self.window_mut(window_id).should_close_handler.take();
+        let handler = self
+            .platform_window_mut(window_id)
+            .should_close_handler
+            .take();
         if let Some(mut handler) = handler {
             let should_close = handler();
-            self.window_mut(window_id).should_close_handler = Some(handler);
+            self.platform_window_mut(window_id).should_close_handler = Some(handler);
             should_close
         } else {
             false
@@ -275,47 +285,37 @@ impl TestAppContext {
     }
 
     pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
-        let mut window = self.window_mut(window_id);
+        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.window_mut(window_id).resize_handlers = handlers;
+        self.platform_window_mut(window_id).resize_handlers = handlers;
     }
 
     pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
-        let mut handlers = BTreeMap::new();
-        {
-            let mut cx = self.cx.borrow_mut();
-            for (window_id, (_, window)) in &mut cx.presenters_and_platform_windows {
-                let window = window
-                    .as_any_mut()
-                    .downcast_mut::<platform::test::Window>()
-                    .unwrap();
-                handlers.insert(
-                    *window_id,
-                    mem::take(&mut window.active_status_change_handlers),
-                );
+        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)
             }
-        };
-        let mut handlers = handlers.into_iter().collect::<Vec<_>>();
-        handlers.sort_unstable_by_key(|(window_id, _)| Some(*window_id) == to_activate);
 
-        for (window_id, mut window_handlers) in handlers {
-            for window_handler in &mut window_handlers {
-                window_handler(Some(window_id) == to_activate);
+            if let Some(to_activate) = to_activate {
+                cx.window_changed_active_status(to_activate, true)
             }
-
-            self.window_mut(window_id)
-                .active_status_change_handlers
-                .extend(window_handlers);
-        }
+        });
     }
 
     pub fn is_window_edited(&self, window_id: usize) -> bool {
-        self.window_mut(window_id).edited
+        self.platform_window_mut(window_id).edited
     }
 
     pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
@@ -338,13 +338,11 @@ impl TestAppContext {
         self.assert_dropped(weak);
     }
 
-    fn window_mut(&self, window_id: usize) -> std::cell::RefMut<platform::test::Window> {
+    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
-                .presenters_and_platform_windows
-                .get_mut(&window_id)
-                .unwrap();
+            let window = state.windows.get_mut(&window_id).unwrap();
             let test_window = window
+                .platform_window
                 .as_any_mut()
                 .downcast_mut::<platform::test::Window>()
                 .unwrap();
@@ -406,6 +404,8 @@ impl ReadModelWith for TestAppContext {
 }
 
 impl UpdateView for TestAppContext {
+    type Output<S> = S;
+
     fn update_view<T, S>(
         &mut self,
         handle: &ViewHandle<T>,
@@ -414,7 +414,10 @@ impl UpdateView for TestAppContext {
     where
         T: View,
     {
-        self.cx.borrow_mut().update_view(handle, update)
+        self.cx
+            .borrow_mut()
+            .update_window(handle.window_id, |cx| cx.update_view(handle, update))
+            .unwrap()
     }
 }
 
@@ -579,22 +582,20 @@ impl<T: View> ViewHandle<T> {
         let timeout_duration = cx.condition_duration();
 
         let mut cx = cx.cx.borrow_mut();
-        let subscriptions = self.update(&mut *cx, |_, cx| {
-            (
-                cx.observe(self, {
-                    let mut tx = tx.clone();
-                    move |_, _, _| {
-                        tx.blocking_send(()).ok();
-                    }
-                }),
-                cx.subscribe(self, {
-                    let mut tx = tx.clone();
-                    move |_, _, _, _| {
-                        tx.blocking_send(()).ok();
-                    }
-                }),
-            )
-        });
+        let subscriptions = (
+            cx.observe(self, {
+                let mut tx = tx.clone();
+                move |_, _| {
+                    tx.blocking_send(()).ok();
+                }
+            }),
+            cx.subscribe(self, {
+                let mut tx = tx.clone();
+                move |_, _, _| {
+                    tx.blocking_send(()).ok();
+                }
+            }),
+        );
 
         let cx = cx.weak_self.as_ref().unwrap().upgrade().unwrap();
         let handle = self.downgrade();

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

@@ -0,0 +1,1493 @@
+use crate::{
+    elements::AnyRootElement,
+    geometry::rect::RectF,
+    json::{self, ToJson},
+    keymap_matcher::{Binding, Keystroke, MatchResult},
+    platform::{
+        self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
+        MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
+    },
+    scene::{
+        CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
+        MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
+    },
+    text_layout::TextLayoutCache,
+    util::post_inc,
+    Action, AnyModelHandle, AnyView, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle,
+    AppContext, Drawable, Effect, Entity, Handle, ModelContext, ModelHandle, MouseRegion,
+    MouseRegionId, ParentId, ReadModel, ReadView, SceneBuilder, Subscription, UpdateModel,
+    UpdateView, UpgradeModelHandle, UpgradeViewHandle, View, ViewContext, ViewHandle,
+    WeakModelHandle, WeakViewHandle, WindowInvalidation,
+};
+use anyhow::{anyhow, bail, Result};
+use collections::{HashMap, HashSet};
+use pathfinder_geometry::vector::{vec2f, Vector2F};
+use postage::oneshot;
+use serde_json::json;
+use smallvec::SmallVec;
+use sqlez::{
+    bindable::{Bind, Column, StaticColumnCount},
+    statement::Statement,
+};
+use std::{
+    any::TypeId,
+    ops::{Deref, DerefMut, Range},
+};
+use util::ResultExt;
+use uuid::Uuid;
+
+use super::Reference;
+
+pub struct Window {
+    pub(crate) root_view: Option<AnyViewHandle>,
+    pub(crate) focused_view_id: Option<usize>,
+    pub(crate) is_active: bool,
+    pub(crate) is_fullscreen: bool,
+    pub(crate) invalidation: Option<WindowInvalidation>,
+    pub(crate) platform_window: Box<dyn platform::Window>,
+    pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
+    titlebar_height: f32,
+    appearance: Appearance,
+    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) clicked_button: Option<MouseButton>,
+    mouse_position: Vector2F,
+    text_layout_cache: TextLayoutCache,
+}
+
+impl Window {
+    pub fn new<V, F>(
+        window_id: usize,
+        platform_window: Box<dyn platform::Window>,
+        cx: &mut AppContext,
+        build_view: F,
+    ) -> Self
+    where
+        F: FnOnce(&mut ViewContext<V>) -> V,
+        V: View,
+    {
+        let titlebar_height = platform_window.titlebar_height();
+        let appearance = platform_window.appearance();
+        let mut window = Self {
+            root_view: None,
+            focused_view_id: None,
+            is_active: false,
+            invalidation: None,
+            is_fullscreen: false,
+            platform_window,
+            rendered_views: Default::default(),
+            cursor_regions: Default::default(),
+            mouse_regions: Default::default(),
+            text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
+            last_mouse_moved_event: None,
+            hovered_region_ids: Default::default(),
+            clicked_region_ids: Default::default(),
+            clicked_button: None,
+            mouse_position: vec2f(0., 0.),
+            titlebar_height,
+            appearance,
+        };
+
+        let mut window_context = WindowContext::mutable(cx, &mut window, window_id);
+        let root_view = window_context
+            .build_and_insert_view(ParentId::Root, |cx| Some(build_view(cx)))
+            .unwrap();
+        if let Some(mut invalidation) = window_context.window.invalidation.take() {
+            window_context.invalidate(&mut invalidation, appearance);
+        }
+        window.focused_view_id = Some(root_view.id());
+        window.root_view = Some(root_view.into_any());
+        window
+    }
+
+    pub fn root_view(&self) -> &AnyViewHandle {
+        &self
+            .root_view
+            .as_ref()
+            .expect("root_view called during window construction")
+    }
+}
+
+pub struct WindowContext<'a: 'b, 'b> {
+    pub(crate) app_context: Reference<'a, AppContext>,
+    pub(crate) window: Reference<'b, Window>,
+    pub(crate) window_id: usize,
+    pub(crate) refreshing: bool,
+    pub(crate) removed: bool,
+}
+
+impl Deref for WindowContext<'_, '_> {
+    type Target = AppContext;
+
+    fn deref(&self) -> &Self::Target {
+        &self.app_context
+    }
+}
+
+impl DerefMut for WindowContext<'_, '_> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.app_context
+    }
+}
+
+impl ReadModel for WindowContext<'_, '_> {
+    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
+        self.app_context.read_model(handle)
+    }
+}
+
+impl UpdateModel for WindowContext<'_, '_> {
+    fn update_model<T: Entity, R>(
+        &mut self,
+        handle: &ModelHandle<T>,
+        update: &mut dyn FnMut(&mut T, &mut ModelContext<T>) -> R,
+    ) -> R {
+        self.app_context.update_model(handle, update)
+    }
+}
+
+impl ReadView for WindowContext<'_, '_> {
+    fn read_view<W: View>(&self, handle: &crate::ViewHandle<W>) -> &W {
+        self.app_context.read_view(handle)
+    }
+}
+
+impl UpdateView for WindowContext<'_, '_> {
+    type Output<S> = S;
+
+    fn update_view<T, S>(
+        &mut self,
+        handle: &ViewHandle<T>,
+        update: &mut dyn FnMut(&mut T, &mut ViewContext<T>) -> S,
+    ) -> S
+    where
+        T: View,
+    {
+        self.update_any_view(handle.view_id, |view, cx| {
+            let mut cx = ViewContext::mutable(cx, handle.view_id);
+            update(
+                view.as_any_mut()
+                    .downcast_mut()
+                    .expect("downcast is type safe"),
+                &mut cx,
+            )
+        })
+        .expect("view is already on the stack")
+    }
+}
+
+impl UpgradeModelHandle for WindowContext<'_, '_> {
+    fn upgrade_model_handle<T: Entity>(
+        &self,
+        handle: &WeakModelHandle<T>,
+    ) -> Option<ModelHandle<T>> {
+        self.app_context.upgrade_model_handle(handle)
+    }
+
+    fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
+        self.app_context.model_handle_is_upgradable(handle)
+    }
+
+    fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
+        self.app_context.upgrade_any_model_handle(handle)
+    }
+}
+
+impl UpgradeViewHandle for WindowContext<'_, '_> {
+    fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
+        self.app_context.upgrade_view_handle(handle)
+    }
+
+    fn upgrade_any_view_handle(&self, handle: &AnyWeakViewHandle) -> Option<AnyViewHandle> {
+        self.app_context.upgrade_any_view_handle(handle)
+    }
+}
+
+impl<'a: 'b, 'b> WindowContext<'a, 'b> {
+    pub fn mutable(
+        app_context: &'a mut AppContext,
+        window: &'b mut Window,
+        window_id: usize,
+    ) -> Self {
+        Self {
+            app_context: Reference::Mutable(app_context),
+            window: Reference::Mutable(window),
+            window_id,
+            refreshing: false,
+            removed: false,
+        }
+    }
+
+    pub fn immutable(app_context: &'a AppContext, window: &'b Window, window_id: usize) -> Self {
+        Self {
+            app_context: Reference::Immutable(app_context),
+            window: Reference::Immutable(window),
+            window_id,
+            refreshing: false,
+            removed: false,
+        }
+    }
+
+    pub fn remove_window(&mut self) {
+        self.removed = true;
+    }
+
+    pub fn window_id(&self) -> usize {
+        self.window_id
+    }
+
+    pub fn app_context(&mut self) -> &mut AppContext {
+        &mut self.app_context
+    }
+
+    pub fn root_view(&self) -> &AnyViewHandle {
+        self.window.root_view()
+    }
+
+    pub fn window_size(&self) -> Vector2F {
+        self.window.platform_window.content_size()
+    }
+
+    pub fn text_layout_cache(&self) -> &TextLayoutCache {
+        &self.window.text_layout_cache
+    }
+
+    pub(crate) fn update_any_view<F, T>(&mut self, view_id: usize, f: F) -> Option<T>
+    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 result = f(view.as_mut(), self);
+        self.views.insert((window_id, view_id), view);
+        Some(result)
+    }
+
+    pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut WindowContext)) {
+        let window_id = self.window_id;
+        self.app_context.defer(move |cx| {
+            cx.update_window(window_id, |cx| callback(cx));
+        })
+    }
+
+    pub fn update_global<T, F, U>(&mut self, update: F) -> U
+    where
+        T: 'static,
+        F: FnOnce(&mut T, &mut Self) -> U,
+    {
+        AppContext::update_global_internal(self, |global, cx| update(global, cx))
+    }
+
+    pub fn update_default_global<T, F, U>(&mut self, update: F) -> U
+    where
+        T: 'static + Default,
+        F: FnOnce(&mut T, &mut Self) -> U,
+    {
+        AppContext::update_default_global_internal(self, |global, cx| update(global, cx))
+    }
+
+    pub fn subscribe<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
+    where
+        E: Entity,
+        E::Event: 'static,
+        H: Handle<E>,
+        F: 'static + FnMut(H, &E::Event, &mut WindowContext),
+    {
+        self.subscribe_internal(handle, move |emitter, event, cx| {
+            callback(emitter, event, cx);
+            true
+        })
+    }
+
+    pub fn subscribe_internal<E, H, F>(&mut self, handle: &H, mut callback: F) -> Subscription
+    where
+        E: Entity,
+        E::Event: 'static,
+        H: Handle<E>,
+        F: 'static + FnMut(H, &E::Event, &mut WindowContext) -> bool,
+    {
+        let window_id = self.window_id;
+        self.app_context
+            .subscribe_internal(handle, move |emitter, event, cx| {
+                cx.update_window(window_id, |cx| callback(emitter, event, cx))
+                    .unwrap_or(false)
+            })
+    }
+
+    pub(crate) fn observe_window_activation<F>(&mut self, callback: F) -> Subscription
+    where
+        F: 'static + FnMut(bool, &mut WindowContext) -> bool,
+    {
+        let window_id = self.window_id;
+        let subscription_id = post_inc(&mut self.next_subscription_id);
+        self.pending_effects
+            .push_back(Effect::WindowActivationObservation {
+                window_id,
+                subscription_id,
+                callback: Box::new(callback),
+            });
+        Subscription::WindowActivationObservation(
+            self.window_activation_observations
+                .subscribe(window_id, subscription_id),
+        )
+    }
+
+    pub(crate) fn observe_fullscreen<F>(&mut self, callback: F) -> Subscription
+    where
+        F: 'static + FnMut(bool, &mut WindowContext) -> bool,
+    {
+        let window_id = self.window_id;
+        let subscription_id = post_inc(&mut self.next_subscription_id);
+        self.pending_effects
+            .push_back(Effect::WindowFullscreenObservation {
+                window_id,
+                subscription_id,
+                callback: Box::new(callback),
+            });
+        Subscription::WindowActivationObservation(
+            self.window_activation_observations
+                .subscribe(window_id, subscription_id),
+        )
+    }
+
+    pub(crate) fn observe_window_bounds<F>(&mut self, callback: F) -> Subscription
+    where
+        F: 'static + FnMut(WindowBounds, Uuid, &mut WindowContext) -> bool,
+    {
+        let window_id = self.window_id;
+        let subscription_id = post_inc(&mut self.next_subscription_id);
+        self.pending_effects
+            .push_back(Effect::WindowBoundsObservation {
+                window_id,
+                subscription_id,
+                callback: Box::new(callback),
+            });
+        Subscription::WindowBoundsObservation(
+            self.window_bounds_observations
+                .subscribe(window_id, subscription_id),
+        )
+    }
+
+    pub fn observe_keystrokes<F>(&mut self, callback: F) -> Subscription
+    where
+        F: 'static
+            + FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut WindowContext) -> bool,
+    {
+        let window_id = self.window_id;
+        let subscription_id = post_inc(&mut self.next_subscription_id);
+        self.keystroke_observations
+            .add_callback(window_id, subscription_id, Box::new(callback));
+        Subscription::KeystrokeObservation(
+            self.keystroke_observations
+                .subscribe(window_id, subscription_id),
+        )
+    }
+
+    /// Return keystrokes that would dispatch the given action on the given view.
+    pub(crate) fn keystrokes_for_action(
+        &mut self,
+        view_id: usize,
+        action: &dyn Action,
+    ) -> Option<SmallVec<[Keystroke; 2]>> {
+        let window_id = self.window_id;
+        let mut contexts = Vec::new();
+        let mut handler_depth = None;
+        for (i, view_id) in self.ancestors(view_id).enumerate() {
+            if let Some(view) = self.views.get(&(window_id, view_id)) {
+                if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
+                    if actions.contains_key(&action.as_any().type_id()) {
+                        handler_depth = Some(i);
+                    }
+                }
+                contexts.push(view.keymap_context(self));
+            }
+        }
+
+        if self.global_actions.contains_key(&action.as_any().type_id()) {
+            handler_depth = Some(contexts.len())
+        }
+
+        self.keystroke_matcher
+            .bindings_for_action_type(action.as_any().type_id())
+            .find_map(|b| {
+                handler_depth
+                    .map(|highest_handler| {
+                        if (0..=highest_handler).any(|depth| b.match_context(&contexts[depth..])) {
+                            Some(b.keystrokes().into())
+                        } else {
+                            None
+                        }
+                    })
+                    .flatten()
+            })
+    }
+
+    pub fn available_actions(
+        &self,
+        view_id: usize,
+    ) -> impl Iterator<Item = (&'static str, Box<dyn Action>, SmallVec<[&Binding; 1]>)> {
+        let window_id = self.window_id;
+        let mut contexts = Vec::new();
+        let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
+        for (depth, view_id) in self.ancestors(view_id).enumerate() {
+            if let Some(view) = self.views.get(&(window_id, view_id)) {
+                contexts.push(view.keymap_context(self));
+                let view_type = view.as_any().type_id();
+                if let Some(actions) = self.actions.get(&view_type) {
+                    handler_depths_by_action_type.extend(
+                        actions
+                            .keys()
+                            .copied()
+                            .map(|action_type| (action_type, depth)),
+                    );
+                }
+            }
+        }
+
+        handler_depths_by_action_type.extend(
+            self.global_actions
+                .keys()
+                .copied()
+                .map(|action_type| (action_type, contexts.len())),
+        );
+
+        self.action_deserializers
+            .iter()
+            .filter_map(move |(name, (type_id, deserialize))| {
+                if let Some(action_depth) = handler_depths_by_action_type.get(type_id).copied() {
+                    Some((
+                        *name,
+                        deserialize("{}").ok()?,
+                        self.keystroke_matcher
+                            .bindings_for_action_type(*type_id)
+                            .filter(|b| {
+                                (0..=action_depth).any(|depth| b.match_context(&contexts[depth..]))
+                            })
+                            .collect(),
+                    ))
+                } else {
+                    None
+                }
+            })
+    }
+
+    pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
+        let window_id = self.window_id;
+        if let Some(focused_view_id) = self.focused_view_id() {
+            let dispatch_path = self
+                .ancestors(focused_view_id)
+                .filter_map(|view_id| {
+                    self.views
+                        .get(&(window_id, view_id))
+                        .map(|view| (view_id, view.keymap_context(self)))
+                })
+                .collect();
+
+            let match_result = self
+                .keystroke_matcher
+                .push_keystroke(keystroke.clone(), dispatch_path);
+            let mut handled_by = None;
+
+            let keystroke_handled = match &match_result {
+                MatchResult::None => false,
+                MatchResult::Pending => true,
+                MatchResult::Matches(matches) => {
+                    for (view_id, action) in matches {
+                        if self.handle_dispatch_action_from_effect(Some(*view_id), action.as_ref())
+                        {
+                            self.keystroke_matcher.clear_pending();
+                            handled_by = Some(action.boxed_clone());
+                            break;
+                        }
+                    }
+                    handled_by.is_some()
+                }
+            };
+
+            self.keystroke(
+                window_id,
+                keystroke.clone(),
+                handled_by,
+                match_result.clone(),
+            );
+            keystroke_handled
+        } else {
+            self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
+            false
+        }
+    }
+
+    pub 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;
+
+        // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
+        //    get mapped into the mouse-specific MouseEvent type.
+        //  -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
+        //  -> Also updates mouse-related state
+        match &event {
+            Event::KeyDown(e) => return self.dispatch_key_down(e),
+
+            Event::KeyUp(e) => return self.dispatch_key_up(e),
+
+            Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(e),
+
+            Event::MouseDown(e) => {
+                // Click events are weird because they can be fired after a drag event.
+                // MDN says that browsers handle this by starting from 'the most
+                // specific ancestor element that contained both [positions]'
+                // So we need to store the overlapping regions on mouse down.
+
+                // If there is already clicked_button stored, don't replace it.
+                if self.window.clicked_button.is_none() {
+                    self.window.clicked_region_ids = self
+                        .window
+                        .mouse_regions
+                        .iter()
+                        .filter_map(|(region, _)| {
+                            if region.bounds.contains_point(e.position) {
+                                Some(region.id())
+                            } else {
+                                None
+                            }
+                        })
+                        .collect();
+
+                    self.window.clicked_button = Some(e.button);
+                }
+
+                mouse_events.push(MouseEvent::Down(MouseDown {
+                    region: Default::default(),
+                    platform_event: e.clone(),
+                }));
+                mouse_events.push(MouseEvent::DownOut(MouseDownOut {
+                    region: Default::default(),
+                    platform_event: e.clone(),
+                }));
+            }
+
+            Event::MouseUp(e) => {
+                // NOTE: The order of event pushes is important! MouseUp events MUST be fired
+                // before click events, and so the MouseUp events need to be pushed before
+                // MouseClick events.
+                mouse_events.push(MouseEvent::Up(MouseUp {
+                    region: Default::default(),
+                    platform_event: e.clone(),
+                }));
+                mouse_events.push(MouseEvent::UpOut(MouseUpOut {
+                    region: Default::default(),
+                    platform_event: e.clone(),
+                }));
+                mouse_events.push(MouseEvent::Click(MouseClick {
+                    region: Default::default(),
+                    platform_event: e.clone(),
+                }));
+            }
+
+            Event::MouseMoved(
+                e @ MouseMovedEvent {
+                    position,
+                    pressed_button,
+                    ..
+                },
+            ) => {
+                let mut style_to_assign = CursorStyle::Arrow;
+                for region in self.window.cursor_regions.iter().rev() {
+                    if region.bounds.contains_point(*position) {
+                        style_to_assign = region.style;
+                        break;
+                    }
+                }
+
+                if self
+                    .window
+                    .platform_window
+                    .is_topmost_for_position(*position)
+                {
+                    self.platform().set_cursor_style(style_to_assign);
+                }
+
+                if !event_reused {
+                    if pressed_button.is_some() {
+                        mouse_events.push(MouseEvent::Drag(MouseDrag {
+                            region: Default::default(),
+                            prev_mouse_position: self.window.mouse_position,
+                            platform_event: e.clone(),
+                        }));
+                    } else if let Some(clicked_button) = self.window.clicked_button {
+                        // Mouse up event happened outside the current window. Simulate mouse up button event
+                        let button_event = e.to_button_event(clicked_button);
+                        mouse_events.push(MouseEvent::Up(MouseUp {
+                            region: Default::default(),
+                            platform_event: button_event.clone(),
+                        }));
+                        mouse_events.push(MouseEvent::UpOut(MouseUpOut {
+                            region: Default::default(),
+                            platform_event: button_event.clone(),
+                        }));
+                        mouse_events.push(MouseEvent::Click(MouseClick {
+                            region: Default::default(),
+                            platform_event: button_event.clone(),
+                        }));
+                    }
+
+                    mouse_events.push(MouseEvent::Move(MouseMove {
+                        region: Default::default(),
+                        platform_event: e.clone(),
+                    }));
+                }
+
+                mouse_events.push(MouseEvent::Hover(MouseHover {
+                    region: Default::default(),
+                    platform_event: e.clone(),
+                    started: false,
+                }));
+                mouse_events.push(MouseEvent::MoveOut(MouseMoveOut {
+                    region: Default::default(),
+                }));
+
+                self.window.last_mouse_moved_event = Some(event.clone());
+            }
+
+            Event::MouseExited(event) => {
+                // When the platform sends a MouseExited event, synthesize
+                // a MouseMoved event whose position is outside the window's
+                // bounds so that hover and cursor state can be updated.
+                return self.dispatch_event(
+                    Event::MouseMoved(MouseMovedEvent {
+                        position: event.position,
+                        pressed_button: event.pressed_button,
+                        modifiers: event.modifiers,
+                    }),
+                    event_reused,
+                );
+            }
+
+            Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
+                region: Default::default(),
+                platform_event: e.clone(),
+            })),
+        }
+
+        if let Some(position) = event.position() {
+            self.window.mouse_position = position;
+        }
+
+        // 2. Dispatch mouse events on regions
+        let mut any_event_handled = false;
+        for mut mouse_event in mouse_events {
+            let mut valid_regions = Vec::new();
+
+            // GPUI elements are arranged by z_index but sibling elements can register overlapping
+            // mouse regions. As such, hover events are only fired on overlapping elements which
+            // are at the same z-index as the topmost element which overlaps with the mouse.
+            match &mouse_event {
+                MouseEvent::Hover(_) => {
+                    let mut highest_z_index = None;
+                    let mouse_position = self.window.mouse_position.clone();
+                    let window = &mut *self.window;
+                    for (region, z_index) in window.mouse_regions.iter().rev() {
+                        // Allow mouse regions to appear transparent to hovers
+                        if !region.hoverable {
+                            continue;
+                        }
+
+                        let contains_mouse = region.bounds.contains_point(mouse_position);
+
+                        if contains_mouse && highest_z_index.is_none() {
+                            highest_z_index = Some(z_index);
+                        }
+
+                        // This unwrap relies on short circuiting boolean expressions
+                        // The right side of the && is only executed when contains_mouse
+                        // is true, and we know above that when contains_mouse is true
+                        // 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()) {
+                                valid_regions.push(region.clone());
+                                if region.notify_on_hover {
+                                    notified_views.insert(region.id().view_id());
+                                }
+                            }
+                        } else {
+                            // Ensure that hover exit events aren't sent twice
+                            if window.hovered_region_ids.remove(&region.id()) {
+                                valid_regions.push(region.clone());
+                                if region.notify_on_hover {
+                                    notified_views.insert(region.id().view_id());
+                                }
+                            }
+                        }
+                    }
+                }
+
+                MouseEvent::Down(_) | MouseEvent::Up(_) => {
+                    for (region, _) in self.window.mouse_regions.iter().rev() {
+                        if region.bounds.contains_point(self.window.mouse_position) {
+                            valid_regions.push(region.clone());
+                            if region.notify_on_click {
+                                notified_views.insert(region.id().view_id());
+                            }
+                        }
+                    }
+                }
+
+                MouseEvent::Click(e) => {
+                    // Only raise click events if the released button is the same as the one stored
+                    if self
+                        .window
+                        .clicked_button
+                        .map(|clicked_button| clicked_button == e.button)
+                        .unwrap_or(false)
+                    {
+                        // Clear clicked regions and clicked button
+                        let clicked_region_ids = std::mem::replace(
+                            &mut self.window.clicked_region_ids,
+                            Default::default(),
+                        );
+                        self.window.clicked_button = None;
+
+                        // Find regions which still overlap with the mouse since the last MouseDown happened
+                        for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+                            if clicked_region_ids.contains(&mouse_region.id()) {
+                                if mouse_region
+                                    .bounds
+                                    .contains_point(self.window.mouse_position)
+                                {
+                                    valid_regions.push(mouse_region.clone());
+                                }
+                            }
+                        }
+                    }
+                }
+
+                MouseEvent::Drag(_) => {
+                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+                        if self.window.clicked_region_ids.contains(&mouse_region.id()) {
+                            valid_regions.push(mouse_region.clone());
+                        }
+                    }
+                }
+
+                MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => {
+                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+                        // NOT contains
+                        if !mouse_region
+                            .bounds
+                            .contains_point(self.window.mouse_position)
+                        {
+                            valid_regions.push(mouse_region.clone());
+                        }
+                    }
+                }
+
+                _ => {
+                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+                        // Contains
+                        if mouse_region
+                            .bounds
+                            .contains_point(self.window.mouse_position)
+                        {
+                            valid_regions.push(mouse_region.clone());
+                        }
+                    }
+                }
+            }
+
+            //3. Fire region events
+            let hovered_region_ids = self.window.hovered_region_ids.clone();
+            for valid_region in valid_regions.into_iter() {
+                let mut handled = false;
+                mouse_event.set_region(valid_region.bounds);
+                if let MouseEvent::Hover(e) = &mut mouse_event {
+                    e.started = hovered_region_ids.contains(&valid_region.id())
+                }
+                // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
+                // not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
+                // This behavior can be overridden by adding a Down handler
+                if let MouseEvent::Down(e) = &mouse_event {
+                    let has_click = valid_region
+                        .handlers
+                        .contains(MouseEvent::click_disc(), Some(e.button));
+                    let has_drag = valid_region
+                        .handlers
+                        .contains(MouseEvent::drag_disc(), Some(e.button));
+                    let has_down = valid_region
+                        .handlers
+                        .contains(MouseEvent::down_disc(), Some(e.button));
+                    if !has_down && (has_click || has_drag) {
+                        handled = true;
+                    }
+                }
+
+                // `event_consumed` should only be true if there are any handlers for this event.
+                let mut event_consumed = handled;
+                if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
+                    for callback in callbacks {
+                        handled = true;
+                        let view_id = valid_region.id().view_id();
+                        self.update_any_view(view_id, |view, cx| {
+                            handled = callback(mouse_event.clone(), view.as_any_mut(), cx, view_id);
+                        });
+                        event_consumed |= handled;
+                        any_event_handled |= handled;
+                    }
+                }
+
+                any_event_handled |= handled;
+
+                // For bubbling events, if the event was handled, don't continue dispatching.
+                // This only makes sense for local events which return false from is_capturable.
+                if event_consumed && mouse_event.is_capturable() {
+                    break;
+                }
+            }
+        }
+
+        for view_id in notified_views {
+            self.notify_view(window_id, view_id);
+        }
+
+        any_event_handled
+    }
+
+    pub fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
+        let window_id = self.window_id;
+        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)) {
+                    let handled = view.key_down(event, self, view_id);
+                    self.views.insert((window_id, view_id), view);
+                    if handled {
+                        return true;
+                    }
+                } else {
+                    log::error!("view {} does not exist", view_id)
+                }
+            }
+        }
+
+        false
+    }
+
+    pub fn dispatch_key_up(&mut self, event: &KeyUpEvent) -> bool {
+        let window_id = self.window_id;
+        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)) {
+                    let handled = view.key_up(event, self, view_id);
+                    self.views.insert((window_id, view_id), view);
+                    if handled {
+                        return true;
+                    }
+                } else {
+                    log::error!("view {} does not exist", view_id)
+                }
+            }
+        }
+
+        false
+    }
+
+    pub fn dispatch_modifiers_changed(&mut self, event: &ModifiersChangedEvent) -> bool {
+        let window_id = self.window_id;
+        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)) {
+                    let handled = view.modifiers_changed(event, self, view_id);
+                    self.views.insert((window_id, view_id), view);
+                    if handled {
+                        return true;
+                    }
+                } else {
+                    log::error!("view {} does not exist", view_id)
+                }
+            }
+        }
+
+        false
+    }
+
+    pub fn invalidate(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) {
+        self.start_frame();
+        self.window.appearance = appearance;
+        for view_id in &invalidation.removed {
+            invalidation.updated.remove(view_id);
+            self.window.rendered_views.remove(view_id);
+        }
+        for view_id in &invalidation.updated {
+            let titlebar_height = self.window.titlebar_height;
+            let hovered_region_ids = self.window.hovered_region_ids.clone();
+            let clicked_region_ids = self
+                .window
+                .clicked_button
+                .map(|button| (self.window.clicked_region_ids.clone(), button));
+
+            let element = self
+                .render_view(RenderParams {
+                    view_id: *view_id,
+                    titlebar_height,
+                    hovered_region_ids,
+                    clicked_region_ids,
+                    refreshing: false,
+                    appearance,
+                })
+                .unwrap();
+            self.window.rendered_views.insert(*view_id, element);
+        }
+    }
+
+    pub fn render_view(&mut self, params: RenderParams) -> Result<Box<dyn AnyRootElement>> {
+        let window_id = self.window_id;
+        let view_id = params.view_id;
+        let mut view = self
+            .views
+            .remove(&(window_id, view_id))
+            .ok_or_else(|| anyhow!("view not found"))?;
+        let element = view.render(self, view_id);
+        self.views.insert((window_id, view_id), view);
+        Ok(element)
+    }
+
+    pub fn build_scene(&mut self) -> Result<Scene> {
+        let window_size = self.window.platform_window.content_size();
+        let scale_factor = self.window.platform_window.scale_factor();
+
+        let root_view_id = self.window.root_view().id();
+        let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
+        rendered_root.layout(SizeConstraint::strict(window_size), self)?;
+
+        let mut scene_builder = SceneBuilder::new(scale_factor);
+        rendered_root.paint(
+            &mut scene_builder,
+            Vector2F::zero(),
+            RectF::from_points(Vector2F::zero(), window_size),
+            self,
+        )?;
+        self.window
+            .rendered_views
+            .insert(root_view_id, rendered_root);
+
+        self.window.text_layout_cache.finish_frame();
+        let scene = scene_builder.build();
+        self.window.cursor_regions = scene.cursor_regions();
+        self.window.mouse_regions = scene.mouse_regions();
+
+        if self.window_is_active() {
+            if let Some(event) = self.window.last_mouse_moved_event.clone() {
+                self.dispatch_event(event, true);
+            }
+        }
+
+        Ok(scene)
+    }
+
+    pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
+        let root_view_id = self.window.root_view().id();
+        self.window
+            .rendered_views
+            .get(&root_view_id)?
+            .rect_for_text_range(range_utf16, self)
+            .log_err()
+            .flatten()
+    }
+
+    pub fn debug_elements(&self) -> Option<json::Value> {
+        let view = self.window.root_view();
+        Some(json!({
+            "root_view": view.debug_json(self),
+            "root_element": self.window.rendered_views.get(&view.id())
+                .and_then(|root_element| {
+                    root_element.debug(self).log_err()
+                })
+        }))
+    }
+
+    pub fn set_window_title(&mut self, title: &str) {
+        self.window.platform_window.set_title(title);
+    }
+
+    pub fn set_window_edited(&mut self, edited: bool) {
+        self.window.platform_window.set_edited(edited);
+    }
+
+    pub fn is_topmost_window_for_position(&self, position: Vector2F) -> bool {
+        self.window
+            .platform_window
+            .is_topmost_for_position(position)
+    }
+
+    pub fn activate_window(&self) {
+        self.window.platform_window.activate();
+    }
+
+    pub fn window_is_active(&self) -> bool {
+        self.window.is_active
+    }
+
+    pub fn window_is_fullscreen(&self) -> bool {
+        self.window.is_fullscreen
+    }
+
+    pub(crate) fn handle_dispatch_action_from_effect(
+        &mut self,
+        view_id: Option<usize>,
+        action: &dyn Action,
+    ) -> bool {
+        if let Some(view_id) = view_id {
+            self.halt_action_dispatch = false;
+            self.visit_dispatch_path(view_id, |view_id, capture_phase, cx| {
+                cx.update_any_view(view_id, |view, cx| {
+                    let type_id = view.as_any().type_id();
+                    if let Some((name, mut handlers)) = cx
+                        .actions_mut(capture_phase)
+                        .get_mut(&type_id)
+                        .and_then(|h| h.remove_entry(&action.id()))
+                    {
+                        for handler in handlers.iter_mut().rev() {
+                            cx.halt_action_dispatch = true;
+                            handler(view, action, cx, view_id);
+                            if cx.halt_action_dispatch {
+                                break;
+                            }
+                        }
+                        cx.actions_mut(capture_phase)
+                            .get_mut(&type_id)
+                            .unwrap()
+                            .insert(name, handlers);
+                    }
+                });
+
+                !cx.halt_action_dispatch
+            });
+        }
+
+        if !self.halt_action_dispatch {
+            self.halt_action_dispatch = self.dispatch_global_action_any(action);
+        }
+
+        self.pending_effects
+            .push_back(Effect::ActionDispatchNotification {
+                action_id: action.id(),
+            });
+        self.halt_action_dispatch
+    }
+
+    /// Returns an iterator over all of the view ids from the passed view up to the root of the window
+    /// Includes the passed view itself
+    pub(crate) fn ancestors(&self, mut view_id: usize) -> impl Iterator<Item = usize> + '_ {
+        std::iter::once(view_id)
+            .into_iter()
+            .chain(std::iter::from_fn(move || {
+                if let Some(ParentId::View(parent_id)) =
+                    self.parents.get(&(self.window_id, view_id))
+                {
+                    view_id = *parent_id;
+                    Some(view_id)
+                } else {
+                    None
+                }
+            }))
+    }
+
+    /// Returns the id of the parent of the given view, or none if the given
+    /// view is the root.
+    pub(crate) fn parent(&self, view_id: usize) -> Option<usize> {
+        if let Some(ParentId::View(view_id)) = self.parents.get(&(self.window_id, view_id)) {
+            Some(*view_id)
+        } else {
+            None
+        }
+    }
+
+    // Traverses the parent tree. Walks down the tree toward the passed
+    // view calling visit with true. Then walks back up the tree calling visit with false.
+    // If `visit` returns false this function will immediately return.
+    fn visit_dispatch_path(
+        &mut self,
+        view_id: usize,
+        mut visit: impl FnMut(usize, bool, &mut WindowContext) -> bool,
+    ) {
+        // List of view ids from the leaf to the root of the window
+        let path = self.ancestors(view_id).collect::<Vec<_>>();
+
+        // Walk down from the root to the leaf calling visit with capture_phase = true
+        for view_id in path.iter().rev() {
+            if !visit(*view_id, true, self) {
+                return;
+            }
+        }
+
+        // Walk up from the leaf to the root calling visit with capture_phase = false
+        for view_id in path.iter() {
+            if !visit(*view_id, false, self) {
+                return;
+            }
+        }
+    }
+
+    pub fn focused_view_id(&self) -> Option<usize> {
+        self.window.focused_view_id
+    }
+
+    pub fn is_child_focused(&self, view: &AnyViewHandle) -> bool {
+        if let Some(focused_view_id) = self.focused_view_id() {
+            self.ancestors(focused_view_id)
+                .skip(1) // Skip self id
+                .any(|parent| parent == view.view_id)
+        } else {
+            false
+        }
+    }
+
+    pub fn window_bounds(&self) -> WindowBounds {
+        self.window.platform_window.bounds()
+    }
+
+    pub fn window_appearance(&self) -> Appearance {
+        self.window.appearance
+    }
+
+    pub fn window_display_uuid(&self) -> Option<Uuid> {
+        self.window.platform_window.screen().display_uuid()
+    }
+
+    pub fn show_character_palette(&self) {
+        self.window.platform_window.show_character_palette();
+    }
+
+    pub fn minimize_window(&self) {
+        self.window.platform_window.minimize();
+    }
+
+    pub fn zoom_window(&self) {
+        self.window.platform_window.zoom();
+    }
+
+    pub fn toggle_full_screen(&self) {
+        self.window.platform_window.toggle_full_screen();
+    }
+
+    pub fn prompt(
+        &self,
+        level: PromptLevel,
+        msg: &str,
+        answers: &[&str],
+    ) -> oneshot::Receiver<usize> {
+        self.window.platform_window.prompt(level, msg, answers)
+    }
+
+    pub fn replace_root_view<V, F>(&mut self, build_root_view: F) -> ViewHandle<V>
+    where
+        V: View,
+        F: FnOnce(&mut ViewContext<V>) -> V,
+    {
+        let root_view = self
+            .build_and_insert_view(ParentId::Root, |cx| Some(build_root_view(cx)))
+            .unwrap();
+        self.window.root_view = Some(root_view.clone().into_any());
+        self.window.focused_view_id = Some(root_view.id());
+        root_view
+    }
+
+    pub(crate) fn build_and_insert_view<T, F>(
+        &mut self,
+        parent_id: ParentId,
+        build_view: F,
+    ) -> Option<ViewHandle<T>>
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> Option<T>,
+    {
+        let window_id = self.window_id;
+        let view_id = post_inc(&mut self.next_entity_id);
+        // Make sure we can tell child views about their parentu
+        self.parents.insert((window_id, view_id), parent_id);
+        let mut cx = ViewContext::mutable(self, view_id);
+        let handle = if let Some(view) = build_view(&mut cx) {
+            self.views.insert((window_id, 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))
+        } else {
+            self.parents.remove(&(window_id, view_id));
+            None
+        };
+        handle
+    }
+}
+
+pub struct RenderParams {
+    pub view_id: usize,
+    pub titlebar_height: f32,
+    pub hovered_region_ids: HashSet<MouseRegionId>,
+    pub clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
+    pub refreshing: bool,
+    pub appearance: Appearance,
+}
+
+#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
+pub enum Axis {
+    #[default]
+    Horizontal,
+    Vertical,
+}
+
+impl Axis {
+    pub fn invert(self) -> Self {
+        match self {
+            Self::Horizontal => Self::Vertical,
+            Self::Vertical => Self::Horizontal,
+        }
+    }
+
+    pub fn component(&self, point: Vector2F) -> f32 {
+        match self {
+            Self::Horizontal => point.x(),
+            Self::Vertical => point.y(),
+        }
+    }
+}
+
+impl ToJson for Axis {
+    fn to_json(&self) -> serde_json::Value {
+        match self {
+            Axis::Horizontal => json!("horizontal"),
+            Axis::Vertical => json!("vertical"),
+        }
+    }
+}
+
+impl StaticColumnCount for Axis {}
+impl Bind for Axis {
+    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
+        match self {
+            Axis::Horizontal => "Horizontal",
+            Axis::Vertical => "Vertical",
+        }
+        .bind(statement, start_index)
+    }
+}
+
+impl Column for Axis {
+    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
+        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+            Ok((
+                match axis_text.as_str() {
+                    "Horizontal" => Axis::Horizontal,
+                    "Vertical" => Axis::Vertical,
+                    _ => bail!("Stored serialized item kind is incorrect"),
+                },
+                next_index,
+            ))
+        })
+    }
+}
+
+pub trait Vector2FExt {
+    fn along(self, axis: Axis) -> f32;
+}
+
+impl Vector2FExt for Vector2F {
+    fn along(self, axis: Axis) -> f32 {
+        match axis {
+            Axis::Horizontal => self.x(),
+            Axis::Vertical => self.y(),
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct SizeConstraint {
+    pub min: Vector2F,
+    pub max: Vector2F,
+}
+
+impl SizeConstraint {
+    pub fn new(min: Vector2F, max: Vector2F) -> Self {
+        Self { min, max }
+    }
+
+    pub fn strict(size: Vector2F) -> Self {
+        Self {
+            min: size,
+            max: size,
+        }
+    }
+
+    pub fn strict_along(axis: Axis, max: f32) -> Self {
+        match axis {
+            Axis::Horizontal => Self {
+                min: vec2f(max, 0.0),
+                max: vec2f(max, f32::INFINITY),
+            },
+            Axis::Vertical => Self {
+                min: vec2f(0.0, max),
+                max: vec2f(f32::INFINITY, max),
+            },
+        }
+    }
+
+    pub fn max_along(&self, axis: Axis) -> f32 {
+        match axis {
+            Axis::Horizontal => self.max.x(),
+            Axis::Vertical => self.max.y(),
+        }
+    }
+
+    pub fn min_along(&self, axis: Axis) -> f32 {
+        match axis {
+            Axis::Horizontal => self.min.x(),
+            Axis::Vertical => self.min.y(),
+        }
+    }
+
+    pub fn constrain(&self, size: Vector2F) -> Vector2F {
+        vec2f(
+            size.x().min(self.max.x()).max(self.min.x()),
+            size.y().min(self.max.y()).max(self.min.y()),
+        )
+    }
+}
+
+impl Default for SizeConstraint {
+    fn default() -> Self {
+        SizeConstraint {
+            min: Vector2F::zero(),
+            max: Vector2F::splat(f32::INFINITY),
+        }
+    }
+}
+
+impl ToJson for SizeConstraint {
+    fn to_json(&self) -> serde_json::Value {
+        json!({
+            "min": self.min.to_json(),
+            "max": self.max.to_json(),
+        })
+    }
+}
+
+pub struct ChildView {
+    view_id: usize,
+    view_name: &'static str,
+}
+
+impl ChildView {
+    pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
+        let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
+        Self {
+            view_id: view.id(),
+            view_name,
+        }
+    }
+}
+
+impl<V: View> Drawable<V> for ChildView {
+    type LayoutState = ();
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> (Vector2F, Self::LayoutState) {
+        if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
+            let size = rendered_view
+                .layout(constraint, cx)
+                .log_err()
+                .unwrap_or(Vector2F::zero());
+            cx.window.rendered_views.insert(self.view_id, rendered_view);
+            (size, ())
+        } else {
+            log::error!(
+                "layout called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
+                self.view_id,
+                self.view_name
+            );
+            (Vector2F::zero(), ())
+        }
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        _: &mut Self::LayoutState,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
+    ) {
+        if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
+            rendered_view
+                .paint(scene, bounds.origin(), visible_bounds, cx)
+                .log_err();
+            cx.window.rendered_views.insert(self.view_id, rendered_view);
+        } else {
+            log::error!(
+                "paint called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
+                self.view_id,
+                self.view_name
+            );
+        }
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        _: RectF,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &V,
+        cx: &ViewContext<V>,
+    ) -> Option<RectF> {
+        if let Some(rendered_view) = cx.window.rendered_views.get(&self.view_id) {
+            rendered_view
+                .rect_for_text_range(range_utf16, &cx.window_context)
+                .log_err()
+                .flatten()
+        } else {
+            log::error!(
+                "rect_for_text_range called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
+                self.view_id,
+                self.view_name
+            );
+            None
+        }
+    }
+
+    fn debug(
+        &self,
+        bounds: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &V,
+        cx: &ViewContext<V>,
+    ) -> serde_json::Value {
+        json!({
+            "type": "ChildView",
+            "view_id": self.view_id,
+            "bounds": bounds.to_json(),
+            "view": if let Some(view) = cx.views.get(&(cx.window_id, self.view_id))  {
+                view.debug_json(cx)
+            } else {
+                json!(null)
+            },
+            "child": if let Some(element) = cx.window.rendered_views.get(&self.view_id) {
+                element.debug(&cx.window_context).log_err().unwrap_or_else(|| json!(null))
+            } else {
+                json!(null)
+            }
+        })
+    }
+}

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

@@ -2,7 +2,7 @@ use std::{cell::RefCell, ops::Range, rc::Rc};
 
 use pathfinder_geometry::rect::RectF;
 
-use crate::{platform::InputHandler, AnyView, AppContext};
+use crate::{platform::InputHandler, window::WindowContext, AnyView, AppContext};
 
 pub struct WindowInputHandler {
     pub app: Rc<RefCell<AppContext>>,
@@ -12,7 +12,7 @@ pub struct WindowInputHandler {
 impl WindowInputHandler {
     fn read_focused_view<T, F>(&self, f: F) -> Option<T>
     where
-        F: FnOnce(&dyn AnyView, &AppContext) -> T,
+        F: FnOnce(&dyn AnyView, &WindowContext) -> T,
     {
         // Input-related application hooks are sometimes called by the OS during
         // a call to a window-manipulation API, like prompting the user for file
@@ -20,26 +20,26 @@ impl WindowInputHandler {
         // InputHandler methods need to fail gracefully.
         //
         // See https://github.com/zed-industries/community/issues/444
-        let app = self.app.try_borrow().ok()?;
-
-        let view_id = app.focused_view_id(self.window_id)?;
-        let view = app.views.get(&(self.window_id, view_id))?;
-        let result = f(view.as_ref(), &app);
-        Some(result)
+        let mut app = self.app.try_borrow_mut().ok()?;
+        app.update_window(self.window_id, |cx| {
+            let view_id = cx.window.focused_view_id?;
+            let view = cx.views.get(&(self.window_id, view_id))?;
+            let result = f(view.as_ref(), &cx);
+            Some(result)
+        })
+        .flatten()
     }
 
     fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
     where
-        F: FnOnce(usize, usize, &mut dyn AnyView, &mut AppContext) -> T,
+        F: FnOnce(&mut dyn AnyView, &mut WindowContext, usize) -> T,
     {
         let mut app = self.app.try_borrow_mut().ok()?;
-        app.update(|app| {
-            let view_id = app.focused_view_id(self.window_id)?;
-            let mut view = app.views.remove(&(self.window_id, view_id))?;
-            let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
-            app.views.insert((self.window_id, view_id), view);
-            Some(result)
+        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()
     }
 }
 
@@ -55,8 +55,8 @@ impl InputHandler for WindowInputHandler {
     }
 
     fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
-        self.update_focused_view(|window_id, view_id, view, cx| {
-            view.replace_text_in_range(range, text, cx, window_id, view_id);
+        self.update_focused_view(|view, cx, view_id| {
+            view.replace_text_in_range(range, text, cx, view_id);
         });
     }
 
@@ -66,8 +66,8 @@ impl InputHandler for WindowInputHandler {
     }
 
     fn unmark_text(&mut self) {
-        self.update_focused_view(|window_id, view_id, view, cx| {
-            view.unmark_text(cx, window_id, view_id);
+        self.update_focused_view(|view, cx, view_id| {
+            view.unmark_text(cx, view_id);
         });
     }
 
@@ -77,22 +77,15 @@ impl InputHandler for WindowInputHandler {
         new_text: &str,
         new_selected_range: Option<Range<usize>>,
     ) {
-        self.update_focused_view(|window_id, view_id, view, cx| {
-            view.replace_and_mark_text_in_range(
-                range,
-                new_text,
-                new_selected_range,
-                cx,
-                window_id,
-                view_id,
-            );
+        self.update_focused_view(|view, cx, view_id| {
+            view.replace_and_mark_text_in_range(range, new_text, new_selected_range, cx, view_id);
         });
     }
 
     fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
-        let app = self.app.borrow();
-        let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
-        let presenter = presenter.borrow();
-        presenter.rect_for_text_range(range_utf16, &app)
+        self.app
+            .borrow_mut()
+            .update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
+            .flatten()
     }
 }

crates/gpui/src/elements.rs 🔗

@@ -25,59 +25,47 @@ pub use self::{
     keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*,
     stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
 };
+pub use crate::window::ChildView;
+
 use self::{clipped::Clipped, expanded::Expanded};
-pub use crate::presenter::ChildView;
 use crate::{
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    json,
-    presenter::MeasurementContext,
-    Action, DebugContext, EventContext, LayoutContext, PaintContext, RenderContext, SizeConstraint,
-    View,
+    json, Action, SceneBuilder, SizeConstraint, View, ViewContext, WeakViewHandle, WindowContext,
 };
+use anyhow::{anyhow, Result};
 use core::panic;
 use json::ToJson;
 use std::{
     any::Any,
     borrow::Cow,
-    cell::RefCell,
+    marker::PhantomData,
     mem,
     ops::{Deref, DerefMut, Range},
-    rc::Rc,
 };
+use util::ResultExt;
 
-trait AnyElement {
-    fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F;
-    fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext);
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        cx: &MeasurementContext,
-    ) -> Option<RectF>;
-    fn debug(&self, cx: &DebugContext) -> serde_json::Value;
-
-    fn size(&self) -> Vector2F;
-    fn metadata(&self) -> Option<&dyn Any>;
-}
-
-pub trait Element {
+pub trait Drawable<V: View> {
     type LayoutState;
     type PaintState;
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState);
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState;
 
     fn rect_for_text_range(
@@ -87,7 +75,8 @@ pub trait Element {
         visible_bounds: RectF,
         layout: &Self::LayoutState,
         paint: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF>;
 
     fn metadata(&self) -> Option<&dyn Any> {
@@ -99,104 +88,117 @@ pub trait Element {
         bounds: RectF,
         layout: &Self::LayoutState,
         paint: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value;
 
-    fn boxed(self) -> ElementBox
+    fn boxed(self) -> Element<V>
     where
         Self: 'static + Sized,
     {
-        ElementBox(ElementRc {
+        Element {
+            drawable: Box::new(Lifecycle::Init { element: self }),
+            view_type: PhantomData,
             name: None,
-            element: Rc::new(RefCell::new(Lifecycle::Init { element: self })),
-        })
+        }
+    }
+
+    fn into_root(self, cx: &ViewContext<V>) -> RootElement<V>
+    where
+        Self: 'static + Sized,
+    {
+        RootElement {
+            element: self.boxed(),
+            view: cx.handle().downgrade(),
+        }
     }
 
-    fn named(self, name: impl Into<Cow<'static, str>>) -> ElementBox
+    fn named(self, name: impl Into<Cow<'static, str>>) -> Element<V>
     where
         Self: 'static + Sized,
     {
-        ElementBox(ElementRc {
+        Element {
+            drawable: Box::new(Lifecycle::Init { element: self }),
+            view_type: PhantomData,
             name: Some(name.into()),
-            element: Rc::new(RefCell::new(Lifecycle::Init { element: self })),
-        })
+        }
     }
 
-    fn constrained(self) -> ConstrainedBox
+    fn constrained(self) -> ConstrainedBox<V>
     where
         Self: 'static + Sized,
     {
         ConstrainedBox::new(self.boxed())
     }
 
-    fn aligned(self) -> Align
+    fn aligned(self) -> Align<V>
     where
         Self: 'static + Sized,
     {
         Align::new(self.boxed())
     }
 
-    fn clipped(self) -> Clipped
+    fn clipped(self) -> Clipped<V>
     where
         Self: 'static + Sized,
     {
         Clipped::new(self.boxed())
     }
 
-    fn contained(self) -> Container
+    fn contained(self) -> Container<V>
     where
         Self: 'static + Sized,
     {
         Container::new(self.boxed())
     }
 
-    fn expanded(self) -> Expanded
+    fn expanded(self) -> Expanded<V>
     where
         Self: 'static + Sized,
     {
         Expanded::new(self.boxed())
     }
 
-    fn flex(self, flex: f32, expanded: bool) -> FlexItem
+    fn flex(self, flex: f32, expanded: bool) -> FlexItem<V>
     where
         Self: 'static + Sized,
     {
         FlexItem::new(self.boxed()).flex(flex, expanded)
     }
 
-    fn flex_float(self) -> FlexItem
+    fn flex_float(self) -> FlexItem<V>
     where
         Self: 'static + Sized,
     {
         FlexItem::new(self.boxed()).float()
     }
 
-    fn with_tooltip<Tag: 'static, T: View>(
+    fn with_tooltip<Tag: 'static>(
         self,
         id: usize,
         text: String,
         action: Option<Box<dyn Action>>,
         style: TooltipStyle,
-        cx: &mut RenderContext<T>,
-    ) -> Tooltip
+        cx: &mut ViewContext<V>,
+    ) -> Tooltip<V>
     where
         Self: 'static + Sized,
     {
-        Tooltip::new::<Tag, T>(id, text, action, style, self.boxed(), cx)
+        Tooltip::new::<Tag, V>(id, text, action, style, self.boxed(), cx)
     }
 
-    fn with_resize_handle<Tag: 'static, T: View>(
+    fn with_resize_handle<Tag: 'static>(
         self,
         element_id: usize,
         side: Side,
         handle_size: f32,
         initial_size: f32,
-        cx: &mut RenderContext<T>,
-    ) -> Resizable
+        cx: &mut ViewContext<V>,
+    ) -> Resizable<V>
     where
         Self: 'static + Sized,
     {
-        Resizable::new::<Tag, T>(
+        Resizable::new::<Tag, V>(
             self.boxed(),
             element_id,
             side,
@@ -207,44 +209,72 @@ pub trait Element {
     }
 }
 
-pub enum Lifecycle<T: Element> {
+trait AnyDrawable<V: View> {
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Vector2F;
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        origin: Vector2F,
+        visible_bounds: RectF,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    );
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> Option<RectF>;
+
+    fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value;
+
+    fn size(&self) -> Vector2F;
+
+    fn metadata(&self) -> Option<&dyn Any>;
+}
+
+enum Lifecycle<V: View, E: Drawable<V>> {
     Empty,
     Init {
-        element: T,
+        element: E,
     },
     PostLayout {
-        element: T,
+        element: E,
         constraint: SizeConstraint,
         size: Vector2F,
-        layout: T::LayoutState,
+        layout: E::LayoutState,
     },
     PostPaint {
-        element: T,
+        element: E,
         constraint: SizeConstraint,
         bounds: RectF,
         visible_bounds: RectF,
-        layout: T::LayoutState,
-        paint: T::PaintState,
+        layout: E::LayoutState,
+        paint: E::PaintState,
     },
 }
 
-pub struct ElementBox(ElementRc);
-
-#[derive(Clone)]
-pub struct ElementRc {
-    name: Option<Cow<'static, str>>,
-    element: Rc<RefCell<dyn AnyElement>>,
-}
-
-impl<T: Element> AnyElement for Lifecycle<T> {
-    fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
+impl<V: View, E: Drawable<V>> AnyDrawable<V> for Lifecycle<V, E> {
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Vector2F {
         let result;
         *self = match mem::take(self) {
             Lifecycle::Empty => unreachable!(),
             Lifecycle::Init { mut element }
             | Lifecycle::PostLayout { mut element, .. }
             | Lifecycle::PostPaint { mut element, .. } => {
-                let (size, layout) = element.layout(constraint, cx);
+                let (size, layout) = element.layout(constraint, view, cx);
                 debug_assert!(size.x().is_finite());
                 debug_assert!(size.y().is_finite());
 
@@ -260,7 +290,14 @@ impl<T: Element> AnyElement for Lifecycle<T> {
         result
     }
 
-    fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        origin: Vector2F,
+        visible_bounds: RectF,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) {
         *self = match mem::take(self) {
             Lifecycle::PostLayout {
                 mut element,
@@ -269,7 +306,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
                 mut layout,
             } => {
                 let bounds = RectF::new(origin, size);
-                let paint = element.paint(bounds, visible_bounds, &mut layout, cx);
+                let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
                 Lifecycle::PostPaint {
                     element,
                     constraint,
@@ -287,7 +324,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
                 ..
             } => {
                 let bounds = RectF::new(origin, bounds.size());
-                let paint = element.paint(bounds, visible_bounds, &mut layout, cx);
+                let paint = element.paint(scene, bounds, visible_bounds, &mut layout, view, cx);
                 Lifecycle::PostPaint {
                     element,
                     constraint,
@@ -307,7 +344,8 @@ impl<T: Element> AnyElement for Lifecycle<T> {
     fn rect_for_text_range(
         &self,
         range_utf16: Range<usize>,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
         if let Lifecycle::PostPaint {
             element,
@@ -318,7 +356,15 @@ impl<T: Element> AnyElement for Lifecycle<T> {
             ..
         } = self
         {
-            element.rect_for_text_range(range_utf16, *bounds, *visible_bounds, layout, paint, cx)
+            element.rect_for_text_range(
+                range_utf16,
+                *bounds,
+                *visible_bounds,
+                layout,
+                paint,
+                view,
+                cx,
+            )
         } else {
             None
         }
@@ -341,7 +387,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
         }
     }
 
-    fn debug(&self, cx: &DebugContext) -> serde_json::Value {
+    fn debug(&self, view: &V, cx: &ViewContext<V>) -> serde_json::Value {
         match self {
             Lifecycle::PostPaint {
                 element,
@@ -351,7 +397,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
                 layout,
                 paint,
             } => {
-                let mut value = element.debug(*bounds, layout, paint, cx);
+                let mut value = element.debug(*bounds, layout, paint, view, cx);
                 if let json::Value::Object(map) = &mut value {
                     let mut new_map: crate::json::Map<String, serde_json::Value> =
                         Default::default();
@@ -373,72 +419,64 @@ impl<T: Element> AnyElement for Lifecycle<T> {
     }
 }
 
-impl<T: Element> Default for Lifecycle<T> {
+impl<V: View, E: Drawable<V>> Default for Lifecycle<V, E> {
     fn default() -> Self {
         Self::Empty
     }
 }
 
-impl ElementBox {
-    pub fn name(&self) -> Option<&str> {
-        self.0.name.as_deref()
-    }
-
-    pub fn metadata<T: 'static>(&self) -> Option<&T> {
-        let element = unsafe { &*self.0.element.as_ptr() };
-        element.metadata().and_then(|m| m.downcast_ref())
-    }
-}
-
-impl Clone for ElementBox {
-    fn clone(&self) -> Self {
-        ElementBox(self.0.clone())
-    }
-}
-
-impl From<ElementBox> for ElementRc {
-    fn from(val: ElementBox) -> Self {
-        val.0
-    }
+pub struct Element<V: View> {
+    drawable: Box<dyn AnyDrawable<V>>,
+    view_type: PhantomData<V>,
+    name: Option<Cow<'static, str>>,
 }
 
-impl Deref for ElementBox {
-    type Target = ElementRc;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
+impl<V: View> Element<V> {
+    pub fn name(&self) -> Option<&str> {
+        self.name.as_deref()
     }
-}
 
-impl DerefMut for ElementBox {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
+    pub fn metadata<T: 'static>(&self) -> Option<&T> {
+        self.drawable
+            .metadata()
+            .and_then(|data| data.downcast_ref::<T>())
     }
-}
 
-impl ElementRc {
-    pub fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F {
-        self.element.borrow_mut().layout(constraint, cx)
+    pub fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Vector2F {
+        self.drawable.layout(constraint, view, cx)
     }
 
-    pub fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext) {
-        self.element.borrow_mut().paint(origin, visible_bounds, cx);
+    pub fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        origin: Vector2F,
+        visible_bounds: RectF,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) {
+        self.drawable.paint(scene, origin, visible_bounds, view, cx);
     }
 
     pub fn rect_for_text_range(
         &self,
         range_utf16: Range<usize>,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.element.borrow().rect_for_text_range(range_utf16, cx)
+        self.drawable.rect_for_text_range(range_utf16, view, cx)
     }
 
     pub fn size(&self) -> Vector2F {
-        self.element.borrow().size()
+        self.drawable.size()
     }
 
-    pub fn debug(&self, cx: &DebugContext) -> json::Value {
-        let mut value = self.element.borrow().debug(cx);
+    pub fn debug(&self, view: &V, cx: &ViewContext<V>) -> json::Value {
+        let mut value = self.drawable.debug(view, cx);
 
         if let Some(name) = &self.name {
             if let json::Value::Object(map) = &mut value {
@@ -457,31 +495,248 @@ impl ElementRc {
         T: 'static,
         F: FnOnce(Option<&T>) -> R,
     {
-        let element = self.element.borrow();
-        f(element.metadata().and_then(|m| m.downcast_ref()))
+        f(self.drawable.metadata().and_then(|m| m.downcast_ref()))
+    }
+}
+
+pub struct RootElement<V: View> {
+    element: Element<V>,
+    view: WeakViewHandle<V>,
+}
+
+impl<V: View> RootElement<V> {
+    pub fn new(element: Element<V>, view: WeakViewHandle<V>) -> Self {
+        Self { element, view }
+    }
+}
+
+pub trait Component<V: View> {
+    fn render(&self, view: &mut V, cx: &mut ViewContext<V>) -> Element<V>;
+}
+
+pub struct ComponentHost<V: View, C: Component<V>> {
+    component: C,
+    view_type: PhantomData<V>,
+}
+
+impl<V: View, C: Component<V>> Deref for ComponentHost<V, C> {
+    type Target = C;
+
+    fn deref(&self) -> &Self::Target {
+        &self.component
+    }
+}
+
+impl<V: View, C: Component<V>> DerefMut for ComponentHost<V, C> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.component
+    }
+}
+
+impl<V: View, C: Component<V>> Drawable<V> for ComponentHost<V, C> {
+    type LayoutState = Element<V>;
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> (Vector2F, Element<V>) {
+        let mut element = self.component.render(view, cx);
+        let size = element.layout(constraint, view, cx);
+        (size, element)
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        element: &mut Element<V>,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) {
+        element.paint(scene, bounds.origin(), visible_bounds, view, cx);
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        _: RectF,
+        _: RectF,
+        element: &Element<V>,
+        _: &(),
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> Option<RectF> {
+        element.rect_for_text_range(range_utf16, view, cx)
+    }
+
+    fn debug(
+        &self,
+        _: RectF,
+        element: &Element<V>,
+        _: &(),
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> serde_json::Value {
+        element.debug(view, cx)
+    }
+}
+
+pub trait AnyRootElement {
+    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        origin: Vector2F,
+        visible_bounds: RectF,
+        cx: &mut WindowContext,
+    ) -> Result<()>;
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        cx: &WindowContext,
+    ) -> Result<Option<RectF>>;
+    fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value>;
+    fn name(&self) -> Option<&str>;
+}
+
+impl<V: View> AnyRootElement for RootElement<V> {
+    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
+        let view = self
+            .view
+            .upgrade(cx)
+            .ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
+        view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        origin: Vector2F,
+        visible_bounds: RectF,
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let view = self
+            .view
+            .upgrade(cx)
+            .ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
+
+        view.update(cx, |view, cx| {
+            self.element.paint(scene, origin, visible_bounds, view, cx);
+            Ok(())
+        })
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        cx: &WindowContext,
+    ) -> Result<Option<RectF>> {
+        let view = self.view.upgrade(cx).ok_or_else(|| {
+            anyhow!("rect_for_text_range called on a root element for a dropped view")
+        })?;
+        let view = view.read(cx);
+        let view_context = ViewContext::immutable(cx, self.view.id());
+        Ok(self
+            .element
+            .rect_for_text_range(range_utf16, view, &view_context))
+    }
+
+    fn debug(&self, cx: &WindowContext) -> Result<serde_json::Value> {
+        let view = self
+            .view
+            .upgrade(cx)
+            .ok_or_else(|| anyhow!("debug called on a root element for a dropped view"))?;
+        let view = view.read(cx);
+        let view_context = ViewContext::immutable(cx, self.view.id());
+        Ok(self.element.debug(view, &view_context))
+    }
+
+    fn name(&self) -> Option<&str> {
+        self.element.name()
+    }
+}
+
+impl<V: View, R: View> Drawable<V> for RootElement<R> {
+    type LayoutState = ();
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        _view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> (Vector2F, ()) {
+        let size = AnyRootElement::layout(self, constraint, cx)
+            .log_err()
+            .unwrap_or_else(|| Vector2F::zero());
+        (size, ())
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        _layout: &mut Self::LayoutState,
+        _view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) {
+        AnyRootElement::paint(self, scene, bounds.origin(), visible_bounds, cx).log_err();
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        _bounds: RectF,
+        _visible_bounds: RectF,
+        _layout: &Self::LayoutState,
+        _paint: &Self::PaintState,
+        _view: &V,
+        cx: &ViewContext<V>,
+    ) -> Option<RectF> {
+        AnyRootElement::rect_for_text_range(self, range_utf16, cx)
+            .log_err()
+            .flatten()
+    }
+
+    fn debug(
+        &self,
+        _bounds: RectF,
+        _layout: &Self::LayoutState,
+        _paint: &Self::PaintState,
+        _view: &V,
+        cx: &ViewContext<V>,
+    ) -> serde_json::Value {
+        AnyRootElement::debug(self, cx)
+            .log_err()
+            .unwrap_or_default()
     }
 }
 
-pub trait ParentElement<'a>: Extend<ElementBox> + Sized {
-    fn add_children(&mut self, children: impl IntoIterator<Item = ElementBox>) {
+pub trait ParentElement<'a, V: View>: Extend<Element<V>> + Sized {
+    fn add_children(&mut self, children: impl IntoIterator<Item = Element<V>>) {
         self.extend(children);
     }
 
-    fn add_child(&mut self, child: ElementBox) {
+    fn add_child(&mut self, child: Element<V>) {
         self.add_children(Some(child));
     }
 
-    fn with_children(mut self, children: impl IntoIterator<Item = ElementBox>) -> Self {
+    fn with_children(mut self, children: impl IntoIterator<Item = Element<V>>) -> Self {
         self.add_children(children);
         self
     }
 
-    fn with_child(self, child: ElementBox) -> Self {
+    fn with_child(self, child: Element<V>) -> Self {
         self.with_children(Some(child))
     }
 }
 
-impl<'a, T> ParentElement<'a> for T where T: Extend<ElementBox> {}
+impl<'a, V: View, T> ParentElement<'a, V> for T where T: Extend<Element<V>> {}
 
 pub fn constrain_size_preserving_aspect_ratio(max_size: Vector2F, size: Vector2F) -> Vector2F {
     if max_size.x().is_infinite() && max_size.y().is_infinite() {

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

@@ -1,20 +1,18 @@
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json,
-    presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use json::ToJson;
 
 use serde_json::json;
 
-pub struct Align {
-    child: ElementBox,
+pub struct Align<V: View> {
+    child: Element<V>,
     alignment: Vector2F,
 }
 
-impl Align {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> Align<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self {
             child,
             alignment: Vector2F::zero(),
@@ -42,18 +40,19 @@ impl Align {
     }
 }
 
-impl Element for Align {
+impl<V: View> Drawable<V> for Align<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         mut constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         constraint.min = Vector2F::zero();
-        let child_size = self.child.layout(constraint, cx);
+        let child_size = self.child.layout(constraint, view, cx);
         if size.x().is_infinite() {
             size.set_x(child_size.x());
         }
@@ -65,10 +64,12 @@ impl Element for Align {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let my_center = bounds.size() / 2.;
         let my_target = my_center + my_center * self.alignment;
@@ -77,8 +78,10 @@ impl Element for Align {
         let child_target = child_center + child_center * self.alignment;
 
         self.child.paint(
+            scene,
             bounds.origin() - (child_target - my_target),
             visible_bounds,
+            view,
             cx,
         );
     }
@@ -90,9 +93,10 @@ impl Element for Align {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -100,13 +104,14 @@ impl Element for Align {
         bounds: pathfinder_geometry::rect::RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
         json!({
             "type": "Align",
             "bounds": bounds.to_json(),
             "alignment": self.alignment.to_json(),
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -1,8 +1,9 @@
-use super::Element;
+use std::marker::PhantomData;
+
+use super::Drawable;
 use crate::{
     json::{self, json},
-    presenter::MeasurementContext,
-    DebugContext, PaintContext,
+    SceneBuilder, View, ViewContext,
 };
 use json::ToJson;
 use pathfinder_geometry::{
@@ -10,20 +11,21 @@ use pathfinder_geometry::{
     vector::{vec2f, Vector2F},
 };
 
-pub struct Canvas<F>(F);
+pub struct Canvas<V, F>(F, PhantomData<V>);
 
-impl<F> Canvas<F>
+impl<V, F> Canvas<V, F>
 where
-    F: FnMut(RectF, RectF, &mut PaintContext),
+    V: View,
+    F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
 {
     pub fn new(f: F) -> Self {
-        Self(f)
+        Self(f, PhantomData)
     }
 }
 
-impl<F> Element for Canvas<F>
+impl<V: View, F> Drawable<V> for Canvas<V, F>
 where
-    F: FnMut(RectF, RectF, &mut PaintContext),
+    F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
 {
     type LayoutState = ();
     type PaintState = ();
@@ -31,7 +33,8 @@ where
     fn layout(
         &mut self,
         constraint: crate::SizeConstraint,
-        _: &mut crate::LayoutContext,
+        _: &mut V,
+        _: &mut crate::ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() {
             constraint.max.x()
@@ -48,12 +51,14 @@ where
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.0(bounds, visible_bounds, cx)
+        self.0(scene, bounds, visible_bounds, view, cx)
     }
 
     fn rect_for_text_range(
@@ -63,7 +68,8 @@ where
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -73,7 +79,8 @@ where
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> json::Value {
         json!({"type": "Canvas", "bounds": bounds.to_json()})
     }

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

@@ -3,43 +3,44 @@ use std::ops::Range;
 use pathfinder_geometry::{rect::RectF, vector::Vector2F};
 use serde_json::json;
 
-use crate::{
-    json, DebugContext, Element, ElementBox, LayoutContext, MeasurementContext, PaintContext,
-    SizeConstraint,
-};
+use crate::{json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext};
 
-pub struct Clipped {
-    child: ElementBox,
+pub struct Clipped<V: View> {
+    child: Element<V>,
 }
 
-impl Clipped {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> Clipped<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self { child }
     }
 }
 
-impl Element for Clipped {
+impl<V: View> Drawable<V> for Clipped<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, cx), ())
+        (self.child.layout(constraint, view, cx), ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        cx.scene.push_layer(Some(bounds));
-        self.child.paint(bounds.origin(), visible_bounds, cx);
-        cx.scene.pop_layer();
+        scene.paint_layer(Some(bounds), |scene| {
+            self.child
+                .paint(scene, bounds.origin(), visible_bounds, view, cx)
+        })
     }
 
     fn rect_for_text_range(
@@ -49,9 +50,10 @@ impl Element for Clipped {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -59,11 +61,12 @@ impl Element for Clipped {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
         json!({
             "type": "Clipped",
-            "child": self.child.debug(cx)
+            "child": self.child.debug(view, cx)
         })
     }
 }

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

@@ -5,22 +5,20 @@ use serde_json::json;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json,
-    presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
-pub struct ConstrainedBox {
-    child: ElementBox,
-    constraint: Constraint,
+pub struct ConstrainedBox<V: View> {
+    child: Element<V>,
+    constraint: Constraint<V>,
 }
 
-pub enum Constraint {
+pub enum Constraint<V: View> {
     Static(SizeConstraint),
-    Dynamic(Box<dyn FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint>),
+    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
 }
 
-impl ToJson for Constraint {
+impl<V: View> ToJson for Constraint<V> {
     fn to_json(&self) -> serde_json::Value {
         match self {
             Constraint::Static(constraint) => constraint.to_json(),
@@ -29,8 +27,8 @@ impl ToJson for Constraint {
     }
 }
 
-impl ConstrainedBox {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> ConstrainedBox<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self {
             child,
             constraint: Constraint::Static(Default::default()),
@@ -39,7 +37,7 @@ impl ConstrainedBox {
 
     pub fn dynamically(
         mut self,
-        constraint: impl 'static + FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint,
+        constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
     ) -> Self {
         self.constraint = Constraint::Dynamic(Box::new(constraint));
         self
@@ -120,41 +118,48 @@ impl ConstrainedBox {
     fn constraint(
         &mut self,
         input_constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> SizeConstraint {
         match &mut self.constraint {
             Constraint::Static(constraint) => *constraint,
-            Constraint::Dynamic(compute_constraint) => compute_constraint(input_constraint, cx),
+            Constraint::Dynamic(compute_constraint) => {
+                compute_constraint(input_constraint, view, cx)
+            }
         }
     }
 }
 
-impl Element for ConstrainedBox {
+impl<V: View> Drawable<V> for ConstrainedBox<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         mut parent_constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        let constraint = self.constraint(parent_constraint, cx);
+        let constraint = self.constraint(parent_constraint, view, cx);
         parent_constraint.min = parent_constraint.min.max(constraint.min);
         parent_constraint.max = parent_constraint.max.min(constraint.max);
         parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
-        let size = self.child.layout(parent_constraint, cx);
+        let size = self.child.layout(parent_constraint, view, cx);
         (size, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        cx.paint_layer(Some(visible_bounds), |cx| {
-            self.child.paint(bounds.origin(), visible_bounds, cx);
+        scene.paint_layer(Some(visible_bounds), |scene| {
+            self.child
+                .paint(scene, bounds.origin(), visible_bounds, view, cx);
         })
     }
 
@@ -165,9 +170,10 @@ impl Element for ConstrainedBox {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -175,8 +181,9 @@ impl Element for ConstrainedBox {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
-        json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(cx)})
+        json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)})
     }
 }

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

@@ -9,9 +9,8 @@ use crate::{
     },
     json::ToJson,
     platform::CursorStyle,
-    presenter::MeasurementContext,
     scene::{self, Border, CursorRegion, Quad},
-    Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde::Deserialize;
 use serde_json::json;
@@ -36,13 +35,13 @@ pub struct ContainerStyle {
     pub cursor: Option<CursorStyle>,
 }
 
-pub struct Container {
-    child: ElementBox,
+pub struct Container<V: View> {
+    child: Element<V>,
     style: ContainerStyle,
 }
 
-impl Container {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> Container<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self {
             child,
             style: Default::default(),
@@ -185,14 +184,15 @@ impl Container {
     }
 }
 
-impl Element for Container {
+impl<V: View> Drawable<V> for Container<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size_buffer = self.margin_size() + self.padding_size();
         if !self.style.border.overlay {
@@ -202,16 +202,18 @@ impl Element for Container {
             min: (constraint.min - size_buffer).max(Vector2F::zero()),
             max: (constraint.max - size_buffer).max(Vector2F::zero()),
         };
-        let child_size = self.child.layout(child_constraint, cx);
+        let child_size = self.child.layout(child_constraint, view, cx);
         (child_size + size_buffer, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let quad_bounds = RectF::from_points(
             bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@@ -219,7 +221,7 @@ impl Element for Container {
         );
 
         if let Some(shadow) = self.style.shadow.as_ref() {
-            cx.scene.push_shadow(scene::Shadow {
+            scene.push_shadow(scene::Shadow {
                 bounds: quad_bounds + shadow.offset,
                 corner_radius: self.style.corner_radius,
                 sigma: shadow.blur,
@@ -229,7 +231,7 @@ impl Element for Container {
 
         if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
             if let Some(style) = self.style.cursor {
-                cx.scene.push_cursor_region(CursorRegion {
+                scene.push_cursor_region(CursorRegion {
                     bounds: hit_bounds,
                     style,
                 });
@@ -240,25 +242,26 @@ impl Element for Container {
             quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
 
         if self.style.border.overlay {
-            cx.scene.push_quad(Quad {
+            scene.push_quad(Quad {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: Default::default(),
                 corner_radius: self.style.corner_radius,
             });
 
-            self.child.paint(child_origin, visible_bounds, cx);
+            self.child
+                .paint(scene, child_origin, visible_bounds, view, cx);
 
-            cx.scene.push_layer(None);
-            cx.scene.push_quad(Quad {
+            scene.push_layer(None);
+            scene.push_quad(Quad {
                 bounds: quad_bounds,
                 background: self.style.overlay_color,
                 border: self.style.border,
                 corner_radius: self.style.corner_radius,
             });
-            cx.scene.pop_layer();
+            scene.pop_layer();
         } else {
-            cx.scene.push_quad(Quad {
+            scene.push_quad(Quad {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: self.style.border,
@@ -270,17 +273,18 @@ impl Element for Container {
                     self.style.border.left_width(),
                     self.style.border.top_width(),
                 );
-            self.child.paint(child_origin, visible_bounds, cx);
+            self.child
+                .paint(scene, child_origin, visible_bounds, view, cx);
 
             if self.style.overlay_color.is_some() {
-                cx.scene.push_layer(None);
-                cx.scene.push_quad(Quad {
+                scene.push_layer(None);
+                scene.push_quad(Quad {
                     bounds: quad_bounds,
                     background: self.style.overlay_color,
                     border: Default::default(),
                     corner_radius: 0.,
                 });
-                cx.scene.pop_layer();
+                scene.pop_layer();
             }
         }
     }
@@ -292,9 +296,10 @@ impl Element for Container {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -302,13 +307,14 @@ impl Element for Container {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &crate::DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "Container",
             "bounds": bounds.to_json(),
             "details": self.style.to_json(),
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -6,10 +6,9 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    presenter::MeasurementContext,
-    DebugContext,
+    SceneBuilder, View, ViewContext,
 };
-use crate::{Element, LayoutContext, PaintContext, SizeConstraint};
+use crate::{Drawable, SizeConstraint};
 
 #[derive(Default)]
 pub struct Empty {
@@ -27,14 +26,15 @@ impl Empty {
     }
 }
 
-impl Element for Empty {
+impl<V: View> Drawable<V> for Empty {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        _: &mut LayoutContext,
+        _: &mut V,
+        _: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() && !self.collapsed {
             constraint.max.x()
@@ -52,10 +52,12 @@ impl Element for Empty {
 
     fn paint(
         &mut self,
+        _: &mut SceneBuilder,
         _: RectF,
         _: RectF,
         _: &mut Self::LayoutState,
-        _: &mut PaintContext,
+        _: &mut V,
+        _: &mut ViewContext<V>,
     ) -> Self::PaintState {
     }
 
@@ -66,7 +68,8 @@ impl Element for Empty {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -76,7 +79,8 @@ impl Element for Empty {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "Empty",

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

@@ -2,20 +2,18 @@ use std::ops::Range;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json,
-    presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    json, Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde_json::json;
 
-pub struct Expanded {
-    child: ElementBox,
+pub struct Expanded<V: View> {
+    child: Element<V>,
     full_width: bool,
     full_height: bool,
 }
 
-impl Expanded {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> Expanded<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self {
             child,
             full_width: true,
@@ -36,14 +34,15 @@ impl Expanded {
     }
 }
 
-impl Element for Expanded {
+impl<V: View> Drawable<V> for Expanded<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         mut constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if self.full_width {
             constraint.min.set_x(constraint.max.x());
@@ -51,18 +50,21 @@ impl Element for Expanded {
         if self.full_height {
             constraint.min.set_y(constraint.max.y());
         }
-        let size = self.child.layout(constraint, cx);
+        let size = self.child.layout(constraint, view, cx);
         (size, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.child.paint(bounds.origin(), visible_bounds, cx);
+        self.child
+            .paint(scene, bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -72,9 +74,10 @@ impl Element for Expanded {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -82,13 +85,14 @@ impl Element for Expanded {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
         json!({
             "type": "Expanded",
             "full_width": self.full_width,
             "full_height": self.full_height,
-            "child": self.child.debug(cx)
+            "child": self.child.debug(view, cx)
         })
     }
 }

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

@@ -2,9 +2,8 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
 
 use crate::{
     json::{self, ToJson, Value},
-    presenter::MeasurementContext,
-    Axis, DebugContext, Element, ElementBox, ElementStateHandle, LayoutContext, PaintContext,
-    RenderContext, SizeConstraint, Vector2FExt, View,
+    Axis, Drawable, Element, ElementStateHandle, SceneBuilder, SizeConstraint, Vector2FExt, View,
+    ViewContext,
 };
 use pathfinder_geometry::{
     rect::RectF,
@@ -18,14 +17,14 @@ struct ScrollState {
     scroll_position: Cell<f32>,
 }
 
-pub struct Flex {
+pub struct Flex<V: View> {
     axis: Axis,
-    children: Vec<ElementBox>,
+    children: Vec<Element<V>>,
     scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
     child_alignment: f32,
 }
 
-impl Flex {
+impl<V: View> Flex<V> {
     pub fn new(axis: Axis) -> Self {
         Self {
             axis,
@@ -52,15 +51,14 @@ impl Flex {
         self
     }
 
-    pub fn scrollable<Tag, V>(
+    pub fn scrollable<Tag>(
         mut self,
         element_id: usize,
         scroll_to: Option<usize>,
-        cx: &mut RenderContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self
     where
         Tag: 'static,
-        V: View,
     {
         let scroll_state = cx.default_element_state::<Tag, Rc<ScrollState>>(element_id);
         scroll_state.read(cx).scroll_to.set(scroll_to);
@@ -75,7 +73,8 @@ impl Flex {
         remaining_space: &mut f32,
         remaining_flex: &mut f32,
         cross_axis_max: &mut f32,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
         let cross_axis = self.axis.invert();
         for child in &mut self.children {
@@ -102,7 +101,7 @@ impl Flex {
                             vec2f(constraint.max.x(), child_max),
                         ),
                     };
-                    let child_size = child.layout(child_constraint, cx);
+                    let child_size = child.layout(child_constraint, view, cx);
                     *remaining_space -= child_size.along(self.axis);
                     *remaining_flex -= flex;
                     *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
@@ -112,20 +111,21 @@ impl Flex {
     }
 }
 
-impl Extend<ElementBox> for Flex {
-    fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+impl<V: View> Extend<Element<V>> for Flex<V> {
+    fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
         self.children.extend(children);
     }
 }
 
-impl Element for Flex {
+impl<V: View> Drawable<V> for Flex<V> {
     type LayoutState = f32;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut total_flex = None;
         let mut fixed_space = 0.0;
@@ -150,7 +150,7 @@ impl Element for Flex {
                         vec2f(constraint.max.x(), INFINITY),
                     ),
                 };
-                let size = child.layout(child_constraint, cx);
+                let size = child.layout(child_constraint, view, cx);
                 fixed_space += size.along(self.axis);
                 cross_axis_max = cross_axis_max.max(size.along(cross_axis));
             }
@@ -168,6 +168,7 @@ impl Element for Flex {
                 &mut remaining_space,
                 &mut remaining_flex,
                 &mut cross_axis_max,
+                view,
                 cx,
             );
             self.layout_flex_children(
@@ -176,6 +177,7 @@ impl Element for Flex {
                 &mut remaining_space,
                 &mut remaining_flex,
                 &mut cross_axis_max,
+                view,
                 cx,
             );
 
@@ -247,26 +249,28 @@ impl Element for Flex {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         remaining_space: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
         let mut remaining_space = *remaining_space;
         let overflowing = remaining_space < 0.;
         if overflowing {
-            cx.scene.push_layer(Some(visible_bounds));
+            scene.push_layer(Some(visible_bounds));
         }
 
         if let Some(scroll_state) = &self.scroll_state {
-            cx.scene.push_mouse_region(
+            scene.push_mouse_region(
                 crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
                     .on_scroll({
                         let scroll_state = scroll_state.0.read(cx).clone();
                         let axis = self.axis;
-                        move |e, cx| {
+                        move |e, _: &mut V, cx| {
                             if remaining_space < 0. {
                                 let scroll_delta = e.delta.raw();
 
@@ -294,7 +298,7 @@ impl Element for Flex {
                             }
                         }
                     })
-                    .on_move(|_, _| { /* Capture move events */ }),
+                    .on_move(|_, _: &mut V, _| { /* Capture move events */ }),
             )
         }
 
@@ -343,7 +347,7 @@ impl Element for Flex {
                 aligned_child_origin
             };
 
-            child.paint(aligned_child_origin, visible_bounds, cx);
+            child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
 
             match self.axis {
                 Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
@@ -352,7 +356,7 @@ impl Element for Flex {
         }
 
         if overflowing {
-            cx.scene.pop_layer();
+            scene.pop_layer();
         }
     }
 
@@ -363,11 +367,12 @@ impl Element for Flex {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
         self.children
             .iter()
-            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
+            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
     }
 
     fn debug(
@@ -375,13 +380,14 @@ impl Element for Flex {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
         json!({
             "type": "Flex",
             "bounds": bounds.to_json(),
             "axis": self.axis.to_json(),
-            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
+            "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
         })
     }
 }
@@ -391,13 +397,13 @@ struct FlexParentData {
     float: bool,
 }
 
-pub struct FlexItem {
+pub struct FlexItem<V: View> {
     metadata: FlexParentData,
-    child: ElementBox,
+    child: Element<V>,
 }
 
-impl FlexItem {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> FlexItem<V> {
+    pub fn new(child: Element<V>) -> Self {
         FlexItem {
             metadata: FlexParentData {
                 flex: None,
@@ -418,27 +424,31 @@ impl FlexItem {
     }
 }
 
-impl Element for FlexItem {
+impl<V: View> Drawable<V> for FlexItem<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, cx);
+        let size = self.child.layout(constraint, view, cx);
         (size, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.child.paint(bounds.origin(), visible_bounds, cx)
+        self.child
+            .paint(scene, bounds.origin(), visible_bounds, view, cx)
     }
 
     fn rect_for_text_range(
@@ -448,9 +458,10 @@ impl Element for FlexItem {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn metadata(&self) -> Option<&dyn Any> {
@@ -462,12 +473,13 @@ impl Element for FlexItem {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Value {
         json!({
             "type": "Flexible",
             "flex": self.metadata.flex,
-            "child": self.child.debug(cx)
+            "child": self.child.debug(view, cx)
         })
     }
 }

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

@@ -3,17 +3,16 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
-pub struct Hook {
-    child: ElementBox,
-    after_layout: Option<Box<dyn FnMut(Vector2F, &mut LayoutContext)>>,
+pub struct Hook<V: View> {
+    child: Element<V>,
+    after_layout: Option<Box<dyn FnMut(Vector2F, &mut ViewContext<V>)>>,
 }
 
-impl Hook {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> Hook<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self {
             child,
             after_layout: None,
@@ -22,23 +21,24 @@ impl Hook {
 
     pub fn on_after_layout(
         mut self,
-        f: impl 'static + FnMut(Vector2F, &mut LayoutContext),
+        f: impl 'static + FnMut(Vector2F, &mut ViewContext<V>),
     ) -> Self {
         self.after_layout = Some(Box::new(f));
         self
     }
 }
 
-impl Element for Hook {
+impl<V: View> Drawable<V> for Hook<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, cx);
+        let size = self.child.layout(constraint, view, cx);
         if let Some(handler) = self.after_layout.as_mut() {
             handler(size, cx);
         }
@@ -47,12 +47,15 @@ impl Element for Hook {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
-        self.child.paint(bounds.origin(), visible_bounds, cx);
+        self.child
+            .paint(scene, bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -62,9 +65,10 @@ impl Element for Hook {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -72,11 +76,12 @@ impl Element for Hook {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "Hooks",
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -5,8 +5,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    presenter::MeasurementContext,
-    scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
+    scene, Border, Drawable, ImageData, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde::Deserialize;
 use std::{ops::Range, sync::Arc};
@@ -56,14 +55,15 @@ impl Image {
     }
 }
 
-impl Element for Image {
+impl<V: View> Drawable<V> for Image {
     type LayoutState = Option<Arc<ImageData>>;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let data = match &self.source {
             ImageSource::Path(path) => match cx.asset_cache.png(path) {
@@ -90,13 +90,15 @@ impl Element for Image {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         _: RectF,
         layout: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        _: &mut V,
+        _: &mut ViewContext<V>,
     ) -> Self::PaintState {
         if let Some(data) = layout {
-            cx.scene.push_image(scene::Image {
+            scene.push_image(scene::Image {
                 bounds,
                 border: self.style.border,
                 corner_radius: self.style.corner_radius,
@@ -113,7 +115,8 @@ impl Element for Image {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -123,7 +126,8 @@ impl Element for Image {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "Image",

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

@@ -2,7 +2,7 @@ use crate::{
     elements::*,
     fonts::TextStyle,
     geometry::{rect::RectF, vector::Vector2F},
-    Action, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    Action, Element, SizeConstraint,
 };
 use serde_json::json;
 
@@ -12,20 +12,17 @@ pub struct KeystrokeLabel {
     action: Box<dyn Action>,
     container_style: ContainerStyle,
     text_style: TextStyle,
-    window_id: usize,
     view_id: usize,
 }
 
 impl KeystrokeLabel {
     pub fn new(
-        window_id: usize,
         view_id: usize,
         action: Box<dyn Action>,
         container_style: ContainerStyle,
         text_style: TextStyle,
     ) -> Self {
         Self {
-            window_id,
             view_id,
             action,
             container_style,
@@ -34,18 +31,18 @@ impl KeystrokeLabel {
     }
 }
 
-impl Element for KeystrokeLabel {
-    type LayoutState = ElementBox;
+impl<V: View> Drawable<V> for KeystrokeLabel {
+    type LayoutState = Element<V>;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
-    ) -> (Vector2F, ElementBox) {
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> (Vector2F, Element<V>) {
         let mut element = if let Some(keystrokes) =
-            cx.app
-                .keystrokes_for_action(self.window_id, self.view_id, self.action.as_ref())
+            cx.keystrokes_for_action(self.view_id, self.action.as_ref())
         {
             Flex::row()
                 .with_children(keystrokes.iter().map(|keystroke| {
@@ -59,18 +56,20 @@ impl Element for KeystrokeLabel {
             Empty::new().collapsed().boxed()
         };
 
-        let size = element.layout(constraint, cx);
+        let size = element.layout(constraint, view, cx);
         (size, element)
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
-        element: &mut ElementBox,
-        cx: &mut PaintContext,
+        element: &mut Element<V>,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
-        element.paint(bounds.origin(), visible_bounds, cx);
+        element.paint(scene, bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -80,7 +79,8 @@ impl Element for KeystrokeLabel {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -88,14 +88,15 @@ impl Element for KeystrokeLabel {
     fn debug(
         &self,
         _: RectF,
-        element: &ElementBox,
+        element: &Element<V>,
         _: &(),
-        cx: &crate::DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "KeystrokeLabel",
             "action": self.action.name(),
-            "child": element.debug(cx)
+            "child": element.debug(view, cx)
         })
     }
 }

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

@@ -7,9 +7,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{ToJson, Value},
-    presenter::MeasurementContext,
     text_layout::{Line, RunStyle},
-    DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
+    Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde::Deserialize;
 use serde_json::json;
@@ -128,19 +127,22 @@ impl Label {
     }
 }
 
-impl Element for Label {
+impl<V: View> Drawable<V> for Label {
     type LayoutState = Line;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let runs = self.compute_runs();
-        let line =
-            cx.text_layout_cache
-                .layout_str(&self.text, self.style.text.font_size, runs.as_slice());
+        let line = cx.text_layout_cache().layout_str(
+            &self.text,
+            self.style.text.font_size,
+            runs.as_slice(),
+        );
 
         let size = vec2f(
             line.width()
@@ -155,12 +157,20 @@ impl Element for Label {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         line: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
+        line.paint(
+            scene,
+            bounds.origin(),
+            visible_bounds,
+            bounds.size().y(),
+            cx,
+        )
     }
 
     fn rect_for_text_range(
@@ -170,7 +180,8 @@ impl Element for Label {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -180,7 +191,8 @@ impl Element for Label {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Value {
         json!({
             "type": "Label",

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

@@ -4,19 +4,16 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::json,
-    presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, ElementRc, EventContext, LayoutContext, MouseRegion,
-    PaintContext, RenderContext, SizeConstraint, View, ViewContext,
+    Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
 };
-use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
+use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
 use sum_tree::{Bias, SumTree};
 
-pub struct List {
-    state: ListState,
+pub struct List<V: View> {
+    state: ListState<V>,
 }
 
-#[derive(Clone)]
-pub struct ListState(Rc<RefCell<StateInner>>);
+pub struct ListState<V: View>(Rc<RefCell<StateInner<V>>>);
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub enum Orientation {
@@ -24,16 +21,16 @@ pub enum Orientation {
     Bottom,
 }
 
-struct StateInner {
+struct StateInner<V: View> {
     last_layout_width: Option<f32>,
-    render_item: Box<dyn FnMut(usize, &mut LayoutContext) -> Option<ElementBox>>,
+    render_item: Box<dyn FnMut(&mut V, usize, &mut ViewContext<V>) -> Element<V>>,
     rendered_range: Range<usize>,
-    items: SumTree<ListItem>,
+    items: SumTree<ListItem<V>>,
     logical_scroll_top: Option<ListOffset>,
     orientation: Orientation,
     overdraw: f32,
     #[allow(clippy::type_complexity)]
-    scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut EventContext)>>,
+    scroll_handler: Option<Box<dyn FnMut(Range<usize>, &mut V, &mut ViewContext<V>)>>,
 }
 
 #[derive(Clone, Copy, Debug, Default, PartialEq)]
@@ -42,14 +39,23 @@ pub struct ListOffset {
     pub offset_in_item: f32,
 }
 
-#[derive(Clone)]
-enum ListItem {
+enum ListItem<V: View> {
     Unrendered,
-    Rendered(ElementRc),
+    Rendered(Rc<RefCell<Element<V>>>),
     Removed(f32),
 }
 
-impl std::fmt::Debug for ListItem {
+impl<V: View> Clone for ListItem<V> {
+    fn clone(&self) -> Self {
+        match self {
+            Self::Unrendered => Self::Unrendered,
+            Self::Rendered(element) => Self::Rendered(element.clone()),
+            Self::Removed(height) => Self::Removed(*height),
+        }
+    }
+}
+
+impl<V: View> Debug for ListItem<V> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
             Self::Unrendered => write!(f, "Unrendered"),
@@ -79,20 +85,21 @@ struct UnrenderedCount(usize);
 #[derive(Clone, Debug, Default)]
 struct Height(f32);
 
-impl List {
-    pub fn new(state: ListState) -> Self {
+impl<V: View> List<V> {
+    pub fn new(state: ListState<V>) -> Self {
         Self { state }
     }
 }
 
-impl Element for List {
+impl<V: View> Drawable<V> for List<V> {
     type LayoutState = ListOffset;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let state = &mut *self.state.0.borrow_mut();
         let size = constraint.max;
@@ -134,9 +141,10 @@ impl Element for List {
                 scroll_top.item_ix + ix,
                 existing_element,
                 item_constraint,
+                view,
                 cx,
             ) {
-                rendered_height += element.size().y();
+                rendered_height += element.borrow().size().y();
                 rendered_items.push_back(ListItem::Rendered(element));
             }
         }
@@ -151,9 +159,9 @@ impl Element for List {
                 cursor.prev(&());
                 if cursor.item().is_some() {
                     if let Some(element) =
-                        state.render_item(cursor.start().0, None, item_constraint, cx)
+                        state.render_item(cursor.start().0, None, item_constraint, view, cx)
                     {
-                        rendered_height += element.size().y();
+                        rendered_height += element.borrow().size().y();
                         rendered_items.push_front(ListItem::Rendered(element));
                     }
                 } else {
@@ -187,9 +195,9 @@ impl Element for List {
             cursor.prev(&());
             if let Some(item) = cursor.item() {
                 if let Some(element) =
-                    state.render_item(cursor.start().0, Some(item), item_constraint, cx)
+                    state.render_item(cursor.start().0, Some(item), item_constraint, view, cx)
                 {
-                    leading_overdraw += element.size().y();
+                    leading_overdraw += element.borrow().size().y();
                     rendered_items.push_front(ListItem::Rendered(element));
                 }
             } else {
@@ -241,25 +249,27 @@ impl Element for List {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         scroll_top: &mut ListOffset,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
-        cx.scene.push_layer(Some(visible_bounds));
-
-        cx.scene.push_mouse_region(
-            MouseRegion::new::<Self>(cx.current_view_id(), 0, bounds).on_scroll({
+        scene.push_layer(Some(visible_bounds));
+        scene.push_mouse_region(
+            MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
                 let state = self.state.clone();
                 let height = bounds.height();
                 let scroll_top = scroll_top.clone();
-                move |e, cx| {
+                move |e, view, cx| {
                     state.0.borrow_mut().scroll(
                         &scroll_top,
                         height,
                         *e.platform_event.delta.raw(),
                         e.platform_event.delta.precise(),
+                        view,
                         cx,
                     )
                 }
@@ -267,11 +277,13 @@ impl Element for List {
         );
 
         let state = &mut *self.state.0.borrow_mut();
-        for (mut element, origin) in state.visible_elements(bounds, scroll_top) {
-            element.paint(origin, visible_bounds, cx);
+        for (element, origin) in state.visible_elements(bounds, scroll_top) {
+            element
+                .borrow_mut()
+                .paint(scene, origin, visible_bounds, view, cx);
         }
 
-        cx.scene.pop_layer();
+        scene.pop_layer();
     }
 
     fn rect_for_text_range(
@@ -281,7 +293,8 @@ impl Element for List {
         _: RectF,
         scroll_top: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
         let state = self.state.0.borrow();
         let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
@@ -293,11 +306,15 @@ impl Element for List {
             }
 
             if let ListItem::Rendered(element) = item {
-                if let Some(rect) = element.rect_for_text_range(range_utf16.clone(), cx) {
+                if let Some(rect) =
+                    element
+                        .borrow()
+                        .rect_for_text_range(range_utf16.clone(), view, cx)
+                {
                     return Some(rect);
                 }
 
-                item_origin.set_y(item_origin.y() + element.size().y());
+                item_origin.set_y(item_origin.y() + element.borrow().size().y());
                 cursor.next(&());
             } else {
                 unreachable!();
@@ -312,12 +329,13 @@ impl Element for List {
         bounds: RectF,
         scroll_top: &Self::LayoutState,
         _: &(),
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         let state = self.state.0.borrow_mut();
         let visible_elements = state
             .visible_elements(bounds, scroll_top)
-            .map(|e| e.0.debug(cx))
+            .map(|e| e.0.borrow().debug(view, cx))
             .collect::<Vec<_>>();
         let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len());
         json!({
@@ -328,27 +346,22 @@ impl Element for List {
     }
 }
 
-impl ListState {
-    pub fn new<F, V>(
+impl<V: View> ListState<V> {
+    pub fn new<F>(
         element_count: usize,
         orientation: Orientation,
         overdraw: f32,
-        cx: &mut ViewContext<V>,
-        mut render_item: F,
+        render_item: F,
     ) -> Self
     where
         V: View,
-        F: 'static + FnMut(&mut V, usize, &mut RenderContext<V>) -> ElementBox,
+        F: 'static + FnMut(&mut V, usize, &mut ViewContext<V>) -> Element<V>,
     {
         let mut items = SumTree::new();
         items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
-        let handle = cx.weak_handle();
         Self(Rc::new(RefCell::new(StateInner {
             last_layout_width: None,
-            render_item: Box::new(move |ix, cx| {
-                let handle = handle.upgrade(cx)?;
-                Some(cx.render(&handle, |view, cx| render_item(view, ix, cx)))
-            }),
+            render_item: Box::new(render_item),
             rendered_range: 0..0,
             items,
             logical_scroll_top: None,
@@ -406,7 +419,7 @@ impl ListState {
 
     pub fn set_scroll_handler(
         &mut self,
-        handler: impl FnMut(Range<usize>, &mut EventContext) + 'static,
+        handler: impl FnMut(Range<usize>, &mut V, &mut ViewContext<V>) + 'static,
     ) {
         self.0.borrow_mut().scroll_handler = Some(Box::new(handler))
     }
@@ -426,20 +439,27 @@ impl ListState {
     }
 }
 
-impl StateInner {
+impl<V: View> Clone for ListState<V> {
+    fn clone(&self) -> Self {
+        Self(self.0.clone())
+    }
+}
+
+impl<V: View> StateInner<V> {
     fn render_item(
         &mut self,
         ix: usize,
-        existing_element: Option<&ListItem>,
+        existing_element: Option<&ListItem<V>>,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
-    ) -> Option<ElementRc> {
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Option<Rc<RefCell<Element<V>>>> {
         if let Some(ListItem::Rendered(element)) = existing_element {
             Some(element.clone())
         } else {
-            let mut element = (self.render_item)(ix, cx)?;
-            element.layout(constraint, cx);
-            Some(element.into())
+            let mut element = (self.render_item)(view, ix, cx);
+            element.layout(constraint, view, cx);
+            Some(Rc::new(RefCell::new(element)))
         }
     }
 
@@ -455,7 +475,7 @@ impl StateInner {
         &'a self,
         bounds: RectF,
         scroll_top: &ListOffset,
-    ) -> impl Iterator<Item = (ElementRc, Vector2F)> + 'a {
+    ) -> impl Iterator<Item = (Rc<RefCell<Element<V>>>, Vector2F)> + 'a {
         let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
         let mut cursor = self.items.cursor::<Count>();
         cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
@@ -467,7 +487,7 @@ impl StateInner {
 
                 if let ListItem::Rendered(element) = item {
                     let result = (element.clone(), item_origin);
-                    item_origin.set_y(item_origin.y() + element.size().y());
+                    item_origin.set_y(item_origin.y() + element.borrow().size().y());
                     cursor.next(&());
                     return Some(result);
                 }
@@ -485,7 +505,8 @@ impl StateInner {
         height: f32,
         mut delta: Vector2F,
         precise: bool,
-        cx: &mut EventContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
         if !precise {
             delta *= 20.;
@@ -511,7 +532,7 @@ impl StateInner {
 
         if self.scroll_handler.is_some() {
             let visible_range = self.visible_range(height, scroll_top);
-            self.scroll_handler.as_mut().unwrap()(visible_range, cx);
+            self.scroll_handler.as_mut().unwrap()(visible_range, view, cx);
         }
 
         cx.notify();
@@ -538,17 +559,17 @@ impl StateInner {
     }
 }
 
-impl ListItem {
+impl<V: View> ListItem<V> {
     fn remove(&self) -> Self {
         match self {
             ListItem::Unrendered => ListItem::Unrendered,
-            ListItem::Rendered(element) => ListItem::Removed(element.size().y()),
+            ListItem::Rendered(element) => ListItem::Removed(element.borrow().size().y()),
             ListItem::Removed(height) => ListItem::Removed(*height),
         }
     }
 }
 
-impl sum_tree::Item for ListItem {
+impl<V: View> sum_tree::Item for ListItem<V> {
     type Summary = ListItemSummary;
 
     fn summary(&self) -> Self::Summary {
@@ -563,7 +584,7 @@ impl sum_tree::Item for ListItem {
                 count: 1,
                 rendered_count: 1,
                 unrendered_count: 0,
-                height: element.size().y(),
+                height: element.borrow().size().y(),
             },
             ListItem::Removed(height) => ListItemSummary {
                 count: 1,
@@ -631,264 +652,261 @@ mod tests {
 
     #[crate::test(self)]
     fn test_layout(cx: &mut crate::AppContext) {
-        let mut presenter = cx.build_presenter(0, 0., Default::default());
-        let (_, view) = cx.add_window(Default::default(), |_| TestView);
-        let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
-
-        let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
-
-        let state = view.update(cx, |_, cx| {
-            ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, {
+        cx.add_window(Default::default(), |cx| {
+            let mut view = TestView;
+            let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
+            let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)]));
+            let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, {
                 let elements = elements.clone();
                 move |_, ix, _| {
                     let (id, height) = elements.borrow()[ix];
                     TestElement::new(id, height).boxed()
                 }
-            })
-        });
+            });
 
-        let mut list = List::new(state.clone());
-        let (size, _) = list.layout(
-            constraint,
-            &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
-        );
-        assert_eq!(size, vec2f(100., 40.));
-        assert_eq!(
-            state.0.borrow().items.summary().clone(),
-            ListItemSummary {
-                count: 3,
-                rendered_count: 3,
-                unrendered_count: 0,
-                height: 150.
-            }
-        );
+            let mut list = List::new(state.clone());
+            let (size, _) = list.layout(constraint, &mut view, cx);
+            assert_eq!(size, vec2f(100., 40.));
+            assert_eq!(
+                state.0.borrow().items.summary().clone(),
+                ListItemSummary {
+                    count: 3,
+                    rendered_count: 3,
+                    unrendered_count: 0,
+                    height: 150.
+                }
+            );
 
-        state.0.borrow_mut().scroll(
-            &ListOffset {
-                item_ix: 0,
-                offset_in_item: 0.,
-            },
-            40.,
-            vec2f(0., -54.),
-            true,
-            &mut presenter.build_event_context(&mut Default::default(), cx),
-        );
-        let (_, logical_scroll_top) = list.layout(
-            constraint,
-            &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
-        );
-        assert_eq!(
-            logical_scroll_top,
-            ListOffset {
-                item_ix: 2,
-                offset_in_item: 4.
-            }
-        );
-        assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
-
-        elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
-        elements.borrow_mut().push((5, 60.));
-        state.splice(1..2, 2);
-        state.splice(4..4, 1);
-        assert_eq!(
-            state.0.borrow().items.summary().clone(),
-            ListItemSummary {
-                count: 5,
-                rendered_count: 2,
-                unrendered_count: 3,
-                height: 120.
-            }
-        );
+            state.0.borrow_mut().scroll(
+                &ListOffset {
+                    item_ix: 0,
+                    offset_in_item: 0.,
+                },
+                40.,
+                vec2f(0., -54.),
+                true,
+                &mut view,
+                cx,
+            );
 
-        let (size, logical_scroll_top) = list.layout(
-            constraint,
-            &mut presenter.build_layout_context(vec2f(100., 40.), false, cx),
-        );
-        assert_eq!(size, vec2f(100., 40.));
-        assert_eq!(
-            state.0.borrow().items.summary().clone(),
-            ListItemSummary {
-                count: 5,
-                rendered_count: 5,
-                unrendered_count: 0,
-                height: 270.
-            }
-        );
-        assert_eq!(
-            logical_scroll_top,
-            ListOffset {
-                item_ix: 3,
-                offset_in_item: 4.
-            }
-        );
-        assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
+            let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+            assert_eq!(
+                logical_scroll_top,
+                ListOffset {
+                    item_ix: 2,
+                    offset_in_item: 4.
+                }
+            );
+            assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.);
+
+            elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]);
+            elements.borrow_mut().push((5, 60.));
+            state.splice(1..2, 2);
+            state.splice(4..4, 1);
+            assert_eq!(
+                state.0.borrow().items.summary().clone(),
+                ListItemSummary {
+                    count: 5,
+                    rendered_count: 2,
+                    unrendered_count: 3,
+                    height: 120.
+                }
+            );
+
+            let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
+            assert_eq!(size, vec2f(100., 40.));
+            assert_eq!(
+                state.0.borrow().items.summary().clone(),
+                ListItemSummary {
+                    count: 5,
+                    rendered_count: 5,
+                    unrendered_count: 0,
+                    height: 270.
+                }
+            );
+            assert_eq!(
+                logical_scroll_top,
+                ListOffset {
+                    item_ix: 3,
+                    offset_in_item: 4.
+                }
+            );
+            assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.);
+
+            view
+        });
     }
 
-    #[crate::test(self, iterations = 10, seed = 0)]
+    #[crate::test(self, iterations = 10)]
     fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) {
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
 
-        let (_, view) = cx.add_window(Default::default(), |_| TestView);
-        let mut presenter = cx.build_presenter(0, 0., Default::default());
-        let mut next_id = 0;
-        let elements = Rc::new(RefCell::new(
-            (0..rng.gen_range(0..=20))
-                .map(|_| {
-                    let id = next_id;
-                    next_id += 1;
-                    (id, rng.gen_range(0..=200) as f32 / 2.0)
-                })
-                .collect::<Vec<_>>(),
-        ));
-        let orientation = *[Orientation::Top, Orientation::Bottom]
-            .choose(&mut rng)
-            .unwrap();
-        let overdraw = rng.gen_range(1..=100) as f32;
-
-        let state = view.update(cx, |_, cx| {
-            ListState::new(elements.borrow().len(), orientation, overdraw, cx, {
+        cx.add_window(Default::default(), |cx| {
+            let mut view = TestView;
+
+            let mut next_id = 0;
+            let elements = Rc::new(RefCell::new(
+                (0..rng.gen_range(0..=20))
+                    .map(|_| {
+                        let id = next_id;
+                        next_id += 1;
+                        (id, rng.gen_range(0..=200) as f32 / 2.0)
+                    })
+                    .collect::<Vec<_>>(),
+            ));
+            let orientation = *[Orientation::Top, Orientation::Bottom]
+                .choose(&mut rng)
+                .unwrap();
+            let overdraw = rng.gen_range(1..=100) as f32;
+
+            let state = ListState::new(elements.borrow().len(), orientation, overdraw, {
                 let elements = elements.clone();
                 move |_, ix, _| {
                     let (id, height) = elements.borrow()[ix];
                     TestElement::new(id, height).boxed()
                 }
-            })
-        });
+            });
 
-        let mut width = rng.gen_range(0..=2000) as f32 / 2.;
-        let mut height = rng.gen_range(0..=2000) as f32 / 2.;
-        log::info!("orientation: {:?}", orientation);
-        log::info!("overdraw: {}", overdraw);
-        log::info!("elements: {:?}", elements.borrow());
-        log::info!("size: ({:?}, {:?})", width, height);
-        log::info!("==================");
-
-        let mut last_logical_scroll_top = None;
-        for _ in 0..operations {
-            match rng.gen_range(0..=100) {
-                0..=29 if last_logical_scroll_top.is_some() => {
-                    let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
-                    log::info!(
-                        "Scrolling by {:?}, previous scroll top: {:?}",
-                        delta,
-                        last_logical_scroll_top.unwrap()
-                    );
-                    state.0.borrow_mut().scroll(
-                        last_logical_scroll_top.as_ref().unwrap(),
-                        height,
-                        delta,
-                        true,
-                        &mut presenter.build_event_context(&mut Default::default(), cx),
-                    );
-                }
-                30..=34 => {
-                    width = rng.gen_range(0..=2000) as f32 / 2.;
-                    log::info!("changing width: {:?}", width);
-                }
-                35..=54 => {
-                    height = rng.gen_range(0..=1000) as f32 / 2.;
-                    log::info!("changing height: {:?}", height);
+            let mut width = rng.gen_range(0..=2000) as f32 / 2.;
+            let mut height = rng.gen_range(0..=2000) as f32 / 2.;
+            log::info!("orientation: {:?}", orientation);
+            log::info!("overdraw: {}", overdraw);
+            log::info!("elements: {:?}", elements.borrow());
+            log::info!("size: ({:?}, {:?})", width, height);
+            log::info!("==================");
+
+            let mut last_logical_scroll_top = None;
+            for _ in 0..operations {
+                match rng.gen_range(0..=100) {
+                    0..=29 if last_logical_scroll_top.is_some() => {
+                        let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw));
+                        log::info!(
+                            "Scrolling by {:?}, previous scroll top: {:?}",
+                            delta,
+                            last_logical_scroll_top.unwrap()
+                        );
+                        state.0.borrow_mut().scroll(
+                            last_logical_scroll_top.as_ref().unwrap(),
+                            height,
+                            delta,
+                            true,
+                            &mut view,
+                            cx,
+                        );
+                    }
+                    30..=34 => {
+                        width = rng.gen_range(0..=2000) as f32 / 2.;
+                        log::info!("changing width: {:?}", width);
+                    }
+                    35..=54 => {
+                        height = rng.gen_range(0..=1000) as f32 / 2.;
+                        log::info!("changing height: {:?}", height);
+                    }
+                    _ => {
+                        let mut elements = elements.borrow_mut();
+                        let end_ix = rng.gen_range(0..=elements.len());
+                        let start_ix = rng.gen_range(0..=end_ix);
+                        let new_elements = (0..rng.gen_range(0..10))
+                            .map(|_| {
+                                let id = next_id;
+                                next_id += 1;
+                                (id, rng.gen_range(0..=200) as f32 / 2.)
+                            })
+                            .collect::<Vec<_>>();
+                        log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
+                        state.splice(start_ix..end_ix, new_elements.len());
+                        elements.splice(start_ix..end_ix, new_elements);
+                        for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
+                            if let ListItem::Rendered(element) = item {
+                                let (expected_id, _) = elements[ix];
+                                element.borrow().with_metadata(|metadata: Option<&usize>| {
+                                    assert_eq!(*metadata.unwrap(), expected_id);
+                                });
+                            }
+                        }
+                    }
                 }
-                _ => {
-                    let mut elements = elements.borrow_mut();
-                    let end_ix = rng.gen_range(0..=elements.len());
-                    let start_ix = rng.gen_range(0..=end_ix);
-                    let new_elements = (0..rng.gen_range(0..10))
-                        .map(|_| {
-                            let id = next_id;
-                            next_id += 1;
-                            (id, rng.gen_range(0..=200) as f32 / 2.)
-                        })
-                        .collect::<Vec<_>>();
-                    log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements);
-                    state.splice(start_ix..end_ix, new_elements.len());
-                    elements.splice(start_ix..end_ix, new_elements);
-                    for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() {
-                        if let ListItem::Rendered(element) = item {
-                            let (expected_id, _) = elements[ix];
+
+                let mut list = List::new(state.clone());
+                let window_size = vec2f(width, height);
+                let (size, logical_scroll_top) = list.layout(
+                    SizeConstraint::new(vec2f(0., 0.), window_size),
+                    &mut view,
+                    cx,
+                );
+                assert_eq!(size, window_size);
+                last_logical_scroll_top = Some(logical_scroll_top);
+
+                let state = state.0.borrow();
+                log::info!("items {:?}", state.items.items(&()));
+
+                let scroll_top = state.scroll_top(&logical_scroll_top);
+                let rendered_top = (scroll_top - overdraw).max(0.);
+                let rendered_bottom = scroll_top + height + overdraw;
+                let mut item_top = 0.;
+
+                log::info!(
+                    "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
+                    rendered_top,
+                    rendered_bottom,
+                    scroll_top,
+                );
+
+                let mut first_rendered_element_top = None;
+                let mut last_rendered_element_bottom = None;
+                assert_eq!(state.items.summary().count, elements.borrow().len());
+                for (ix, item) in state.items.cursor::<()>().enumerate() {
+                    match item {
+                        ListItem::Unrendered => {
+                            let item_bottom = item_top;
+                            assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
+                            item_top = item_bottom;
+                        }
+                        ListItem::Removed(height) => {
+                            let (id, expected_height) = elements.borrow()[ix];
+                            assert_eq!(
+                                *height, expected_height,
+                                "element {} height didn't match",
+                                id
+                            );
+                            let item_bottom = item_top + height;
+                            assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
+                            item_top = item_bottom;
+                        }
+                        ListItem::Rendered(element) => {
+                            let (expected_id, expected_height) = elements.borrow()[ix];
+                            let element = element.borrow();
                             element.with_metadata(|metadata: Option<&usize>| {
                                 assert_eq!(*metadata.unwrap(), expected_id);
                             });
+                            assert_eq!(element.size().y(), expected_height);
+                            let item_bottom = item_top + element.size().y();
+                            first_rendered_element_top.get_or_insert(item_top);
+                            last_rendered_element_bottom = Some(item_bottom);
+                            assert!(item_bottom > rendered_top || item_top < rendered_bottom);
+                            item_top = item_bottom;
                         }
                     }
                 }
-            }
-
-            let mut list = List::new(state.clone());
-            let window_size = vec2f(width, height);
-            let (size, logical_scroll_top) = list.layout(
-                SizeConstraint::new(vec2f(0., 0.), window_size),
-                &mut presenter.build_layout_context(window_size, false, cx),
-            );
-            assert_eq!(size, window_size);
-            last_logical_scroll_top = Some(logical_scroll_top);
-
-            let state = state.0.borrow();
-            log::info!("items {:?}", state.items.items(&()));
-
-            let scroll_top = state.scroll_top(&logical_scroll_top);
-            let rendered_top = (scroll_top - overdraw).max(0.);
-            let rendered_bottom = scroll_top + height + overdraw;
-            let mut item_top = 0.;
-
-            log::info!(
-                "rendered top {:?}, rendered bottom {:?}, scroll top {:?}",
-                rendered_top,
-                rendered_bottom,
-                scroll_top,
-            );
 
-            let mut first_rendered_element_top = None;
-            let mut last_rendered_element_bottom = None;
-            assert_eq!(state.items.summary().count, elements.borrow().len());
-            for (ix, item) in state.items.cursor::<()>().enumerate() {
-                match item {
-                    ListItem::Unrendered => {
-                        let item_bottom = item_top;
-                        assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
-                        item_top = item_bottom;
-                    }
-                    ListItem::Removed(height) => {
-                        let (id, expected_height) = elements.borrow()[ix];
-                        assert_eq!(
-                            *height, expected_height,
-                            "element {} height didn't match",
-                            id
-                        );
-                        let item_bottom = item_top + height;
-                        assert!(item_bottom <= rendered_top || item_top >= rendered_bottom);
-                        item_top = item_bottom;
+                match orientation {
+                    Orientation::Top => {
+                        if let Some(first_rendered_element_top) = first_rendered_element_top {
+                            assert!(first_rendered_element_top <= scroll_top);
+                        }
                     }
-                    ListItem::Rendered(element) => {
-                        let (expected_id, expected_height) = elements.borrow()[ix];
-                        element.with_metadata(|metadata: Option<&usize>| {
-                            assert_eq!(*metadata.unwrap(), expected_id);
-                        });
-                        assert_eq!(element.size().y(), expected_height);
-                        let item_bottom = item_top + element.size().y();
-                        first_rendered_element_top.get_or_insert(item_top);
-                        last_rendered_element_bottom = Some(item_bottom);
-                        assert!(item_bottom > rendered_top || item_top < rendered_bottom);
-                        item_top = item_bottom;
+                    Orientation::Bottom => {
+                        if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
+                            assert!(last_rendered_element_bottom >= scroll_top + height);
+                        }
                     }
                 }
             }
 
-            match orientation {
-                Orientation::Top => {
-                    if let Some(first_rendered_element_top) = first_rendered_element_top {
-                        assert!(first_rendered_element_top <= scroll_top);
-                    }
-                }
-                Orientation::Bottom => {
-                    if let Some(last_rendered_element_bottom) = last_rendered_element_bottom {
-                        assert!(last_rendered_element_bottom >= scroll_top + height);
-                    }
-                }
-            }
-        }
+            view
+        });
     }
 
     struct TestView;
@@ -902,7 +920,7 @@ mod tests {
             "TestView"
         }
 
-        fn render(&mut self, _: &mut RenderContext<'_, Self>) -> ElementBox {
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
             Empty::new().boxed()
         }
     }
@@ -921,15 +939,28 @@ mod tests {
         }
     }
 
-    impl Element for TestElement {
+    impl<V: View> Drawable<V> for TestElement {
         type LayoutState = ();
         type PaintState = ();
 
-        fn layout(&mut self, _: SizeConstraint, _: &mut LayoutContext) -> (Vector2F, ()) {
+        fn layout(
+            &mut self,
+            _: SizeConstraint,
+            _: &mut V,
+            _: &mut ViewContext<V>,
+        ) -> (Vector2F, ()) {
             (self.size, ())
         }
 
-        fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut PaintContext) {
+        fn paint(
+            &mut self,
+            _: &mut SceneBuilder,
+            _: RectF,
+            _: RectF,
+            _: &mut (),
+            _: &mut V,
+            _: &mut ViewContext<V>,
+        ) {
             todo!()
         }
 
@@ -940,12 +971,13 @@ mod tests {
             _: RectF,
             _: &Self::LayoutState,
             _: &Self::PaintState,
-            _: &MeasurementContext,
+            _: &V,
+            _: &ViewContext<V>,
         ) -> Option<RectF> {
             todo!()
         }
 
-        fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
+        fn debug(&self, _: RectF, _: &(), _: &(), _: &V, _: &ViewContext<V>) -> serde_json::Value {
             self.id.into()
         }
 

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

@@ -10,14 +10,14 @@ use crate::{
         CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover,
         MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
     },
-    DebugContext, Element, ElementBox, EventContext, LayoutContext, MeasurementContext,
-    MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
+    Drawable, Element, EventContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View,
+    ViewContext,
 };
 use serde_json::json;
 use std::{marker::PhantomData, ops::Range};
 
-pub struct MouseEventHandler<Tag: 'static> {
-    child: ElementBox,
+pub struct MouseEventHandler<Tag: 'static, V: View> {
+    child: Element<V>,
     region_id: usize,
     cursor_style: Option<CursorStyle>,
     handlers: HandlerSet,
@@ -31,11 +31,11 @@ pub struct MouseEventHandler<Tag: 'static> {
 
 /// Element which provides a render_child callback with a MouseState and paints a mouse
 /// region under (or above) it for easy mouse event handling.
-impl<Tag> MouseEventHandler<Tag> {
-    pub fn new<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
+impl<Tag, V: View> MouseEventHandler<Tag, V> {
+    pub fn new<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
     where
         V: View,
-        F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
+        F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> Element<V>,
     {
         let mut mouse_state = cx.mouse_state::<Tag>(region_id);
         let child = render_child(&mut mouse_state, cx);
@@ -58,10 +58,10 @@ impl<Tag> MouseEventHandler<Tag> {
     /// Modifies the MouseEventHandler to render the MouseRegion above the child element. Useful
     /// for drag and drop handling and similar events which should be captured before the child
     /// gets the opportunity
-    pub fn above<V, F>(region_id: usize, cx: &mut RenderContext<V>, render_child: F) -> Self
+    pub fn above<F>(region_id: usize, cx: &mut ViewContext<V>, render_child: F) -> Self
     where
         V: View,
-        F: FnOnce(&mut MouseState, &mut RenderContext<V>) -> ElementBox,
+        F: FnOnce(&mut MouseState, &mut ViewContext<V>) -> Element<V>,
     {
         let mut handler = Self::new(region_id, cx, render_child);
         handler.above = true;
@@ -78,14 +78,17 @@ impl<Tag> MouseEventHandler<Tag> {
         self
     }
 
-    pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
+    pub fn on_move(
+        mut self,
+        handler: impl Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
+    ) -> Self {
         self.handlers = self.handlers.on_move(handler);
         self
     }
 
     pub fn on_move_out(
         mut self,
-        handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
+        handler: impl Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_move_out(handler);
         self
@@ -94,7 +97,7 @@ impl<Tag> MouseEventHandler<Tag> {
     pub fn on_down(
         mut self,
         button: MouseButton,
-        handler: impl Fn(MouseDown, &mut EventContext) + 'static,
+        handler: impl Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_down(button, handler);
         self
@@ -103,7 +106,7 @@ impl<Tag> MouseEventHandler<Tag> {
     pub fn on_up(
         mut self,
         button: MouseButton,
-        handler: impl Fn(MouseUp, &mut EventContext) + 'static,
+        handler: impl Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_up(button, handler);
         self
@@ -112,7 +115,7 @@ impl<Tag> MouseEventHandler<Tag> {
     pub fn on_click(
         mut self,
         button: MouseButton,
-        handler: impl Fn(MouseClick, &mut EventContext) + 'static,
+        handler: impl Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_click(button, handler);
         self
@@ -121,7 +124,7 @@ impl<Tag> MouseEventHandler<Tag> {
     pub fn on_down_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
+        handler: impl Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_down_out(button, handler);
         self
@@ -130,7 +133,7 @@ impl<Tag> MouseEventHandler<Tag> {
     pub fn on_up_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
+        handler: impl Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_up_out(button, handler);
         self
@@ -139,20 +142,23 @@ impl<Tag> MouseEventHandler<Tag> {
     pub fn on_drag(
         mut self,
         button: MouseButton,
-        handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
+        handler: impl Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_drag(button, handler);
         self
     }
 
-    pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
+    pub fn on_hover(
+        mut self,
+        handler: impl Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
+    ) -> Self {
         self.handlers = self.handlers.on_hover(handler);
         self
     }
 
     pub fn on_scroll(
         mut self,
-        handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
+        handler: impl Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
     ) -> Self {
         self.handlers = self.handlers.on_scroll(handler);
         self
@@ -176,19 +182,25 @@ impl<Tag> MouseEventHandler<Tag> {
         .round_out()
     }
 
-    fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut PaintContext) {
+    fn paint_regions(
+        &self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        cx: &mut ViewContext<V>,
+    ) {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
         let hit_bounds = self.hit_bounds(visible_bounds);
 
         if let Some(style) = self.cursor_style {
-            cx.scene.push_cursor_region(CursorRegion {
+            scene.push_cursor_region(CursorRegion {
                 bounds: hit_bounds,
                 style,
             });
         }
-        cx.scene.push_mouse_region(
+        scene.push_mouse_region(
             MouseRegion::from_handlers::<Tag>(
-                cx.current_view_id(),
+                cx.view_id(),
                 self.region_id,
                 hit_bounds,
                 self.handlers.clone(),
@@ -200,34 +212,39 @@ impl<Tag> MouseEventHandler<Tag> {
     }
 }
 
-impl<Tag> Element for MouseEventHandler<Tag> {
+impl<Tag, V: View> Drawable<V> for MouseEventHandler<Tag, V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, cx), ())
+        (self.child.layout(constraint, view, cx), ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         if self.above {
-            self.child.paint(bounds.origin(), visible_bounds, cx);
+            self.child
+                .paint(scene, bounds.origin(), visible_bounds, view, cx);
 
-            cx.paint_layer(None, |cx| {
-                self.paint_regions(bounds, visible_bounds, cx);
+            scene.paint_layer(None, |scene| {
+                self.paint_regions(scene, bounds, visible_bounds, cx);
             });
         } else {
-            self.paint_regions(bounds, visible_bounds, cx);
-            self.child.paint(bounds.origin(), visible_bounds, cx);
+            self.paint_regions(scene, bounds, visible_bounds, cx);
+            self.child
+                .paint(scene, bounds.origin(), visible_bounds, view, cx);
         }
     }
 
@@ -238,9 +255,10 @@ impl<Tag> Element for MouseEventHandler<Tag> {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -248,11 +266,12 @@ impl<Tag> Element for MouseEventHandler<Tag> {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "MouseEventHandler",
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -3,14 +3,12 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
-    presenter::MeasurementContext,
-    Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext,
-    SizeConstraint,
+    Axis, Drawable, Element, MouseRegion, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 use serde_json::json;
 
-pub struct Overlay {
-    child: ElementBox,
+pub struct Overlay<V: View> {
+    child: Element<V>,
     anchor_position: Option<Vector2F>,
     anchor_corner: AnchorCorner,
     fit_mode: OverlayFitMode,
@@ -74,8 +72,8 @@ impl AnchorCorner {
     }
 }
 
-impl Overlay {
-    pub fn new(child: ElementBox) -> Self {
+impl<V: View> Overlay<V> {
+    pub fn new(child: Element<V>) -> Self {
         Self {
             child,
             anchor_position: None,
@@ -118,30 +116,33 @@ impl Overlay {
     }
 }
 
-impl Element for Overlay {
+impl<V: View> Drawable<V> for Overlay<V> {
     type LayoutState = Vector2F;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let constraint = if self.anchor_position.is_some() {
-            SizeConstraint::new(Vector2F::zero(), cx.window_size)
+            SizeConstraint::new(Vector2F::zero(), cx.window_size())
         } else {
             constraint
         };
-        let size = self.child.layout(constraint, cx);
+        let size = self.child.layout(constraint, view, cx);
         (Vector2F::zero(), size)
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         _: RectF,
         size: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
         let (anchor_position, mut bounds) = match self.position_mode {
             OverlayPositionMode::Window => {
@@ -162,9 +163,9 @@ impl Element for Overlay {
             OverlayFitMode::SnapToWindow => {
                 // Snap the horizontal edges of the overlay to the horizontal edges of the window if
                 // its horizontal bounds overflow
-                if bounds.max_x() > cx.window_size.x() {
+                if bounds.max_x() > cx.window_size().x() {
                     let mut lower_right = bounds.lower_right();
-                    lower_right.set_x(cx.window_size.x());
+                    lower_right.set_x(cx.window_size().x());
                     bounds = RectF::from_points(lower_right - *size, lower_right);
                 } else if bounds.min_x() < 0. {
                     let mut upper_left = bounds.origin();
@@ -174,9 +175,9 @@ impl Element for Overlay {
 
                 // Snap the vertical edges of the overlay to the vertical edges of the window if
                 // its vertical bounds overflow.
-                if bounds.max_y() > cx.window_size.y() {
+                if bounds.max_y() > cx.window_size().y() {
                     let mut lower_right = bounds.lower_right();
-                    lower_right.set_y(cx.window_size.y());
+                    lower_right.set_y(cx.window_size().y());
                     bounds = RectF::from_points(lower_right - *size, lower_right);
                 } else if bounds.min_y() < 0. {
                     let mut upper_left = bounds.origin();
@@ -187,11 +188,11 @@ impl Element for Overlay {
             OverlayFitMode::SwitchAnchor => {
                 let mut anchor_corner = self.anchor_corner;
 
-                if bounds.max_x() > cx.window_size.x() {
+                if bounds.max_x() > cx.window_size().x() {
                     anchor_corner = anchor_corner.switch_axis(Axis::Horizontal);
                 }
 
-                if bounds.max_y() > cx.window_size.y() {
+                if bounds.max_y() > cx.window_size().y() {
                     anchor_corner = anchor_corner.switch_axis(Axis::Vertical);
                 }
 
@@ -211,21 +212,22 @@ impl Element for Overlay {
             OverlayFitMode::None => {}
         }
 
-        cx.paint_stacking_context(None, self.z_index, |cx| {
+        scene.paint_stacking_context(None, self.z_index, |scene| {
             if self.hoverable {
                 enum OverlayHoverCapture {}
                 // Block hovers in lower stacking contexts
-                cx.scene
-                    .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
-                        cx.current_view_id(),
-                        cx.current_view_id(),
-                        bounds,
-                    ));
+                scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
+                    cx.view_id(),
+                    cx.view_id(),
+                    bounds,
+                ));
             }
 
             self.child.paint(
+                scene,
                 bounds.origin(),
-                RectF::new(Vector2F::zero(), cx.window_size),
+                RectF::new(Vector2F::zero(), cx.window_size()),
+                view,
                 cx,
             );
         });
@@ -238,9 +240,10 @@ impl Element for Overlay {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -248,12 +251,13 @@ impl Element for Overlay {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "Overlay",
             "abs_position": self.anchor_position.to_json(),
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -7,7 +7,7 @@ use crate::{
     geometry::rect::RectF,
     platform::{CursorStyle, MouseButton},
     scene::MouseDrag,
-    Axis, Element, ElementBox, ElementStateHandle, MouseRegion, RenderContext, View,
+    Axis, Drawable, Element, ElementStateHandle, MouseRegion, SceneBuilder, View, ViewContext,
 };
 
 use super::{ConstrainedBox, Hook};
@@ -75,22 +75,22 @@ struct ResizeHandleState {
     custom_dimension: Cell<f32>,
 }
 
-pub struct Resizable {
+pub struct Resizable<V: View> {
     side: Side,
     handle_size: f32,
-    child: ElementBox,
+    child: Element<V>,
     state: Rc<ResizeHandleState>,
     _state_handle: ElementStateHandle<Rc<ResizeHandleState>>,
 }
 
-impl Resizable {
+impl<V: View> Resizable<V> {
     pub fn new<Tag: 'static, T: View>(
-        child: ElementBox,
+        child: Element<V>,
         element_id: usize,
         side: Side,
         handle_size: f32,
         initial_size: f32,
-        cx: &mut RenderContext<T>,
+        cx: &mut ViewContext<V>,
     ) -> Self {
         let state_handle = cx.element_state::<Tag, Rc<ResizeHandleState>>(
             element_id,
@@ -132,51 +132,50 @@ impl Resizable {
     }
 }
 
-impl Element for Resizable {
+impl<V: View> Drawable<V> for Resizable<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: crate::SizeConstraint,
-        cx: &mut crate::LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        (self.child.layout(constraint, cx), ())
+        (self.child.layout(constraint, view, cx), ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: pathfinder_geometry::rect::RectF,
         visible_bounds: pathfinder_geometry::rect::RectF,
         _child_size: &mut Self::LayoutState,
-        cx: &mut crate::PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        cx.scene.push_stacking_context(None, None);
+        scene.push_stacking_context(None, None);
 
         let handle_region = self.side.of_rect(bounds, self.handle_size);
 
         enum ResizeHandle {}
-        cx.scene.push_mouse_region(
-            MouseRegion::new::<ResizeHandle>(
-                cx.current_view_id(),
-                self.side as usize,
-                handle_region,
-            )
-            .on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
-            .on_drag(MouseButton::Left, {
-                let state = self.state.clone();
-                let side = self.side;
-                move |e, cx| {
-                    let prev_width = state.actual_dimension.get();
-                    state
-                        .custom_dimension
-                        .set(0f32.max(prev_width + side.compute_delta(e)).round());
-                    cx.notify();
-                }
-            }),
+        scene.push_mouse_region(
+            MouseRegion::new::<ResizeHandle>(cx.view_id(), self.side as usize, handle_region)
+                .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
+                .on_drag(MouseButton::Left, {
+                    let state = self.state.clone();
+                    let side = self.side;
+                    move |e, _: &mut V, cx| {
+                        let prev_width = state.actual_dimension.get();
+                        state
+                            .custom_dimension
+                            .set(0f32.max(prev_width + side.compute_delta(e)).round());
+                        cx.notify();
+                    }
+                }),
         );
 
-        cx.scene.push_cursor_region(crate::CursorRegion {
+        scene.push_cursor_region(crate::CursorRegion {
             bounds: handle_region,
             style: match self.side.axis() {
                 Axis::Horizontal => CursorStyle::ResizeLeftRight,
@@ -184,9 +183,10 @@ impl Element for Resizable {
             },
         });
 
-        cx.scene.pop_stacking_context();
+        scene.pop_stacking_context();
 
-        self.child.paint(bounds.origin(), visible_bounds, cx);
+        self.child
+            .paint(scene, bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -196,9 +196,10 @@ impl Element for Resizable {
         _visible_bounds: pathfinder_geometry::rect::RectF,
         _layout: &Self::LayoutState,
         _paint: &Self::PaintState,
-        cx: &crate::MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<pathfinder_geometry::rect::RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -206,10 +207,11 @@ impl Element for Resizable {
         _bounds: pathfinder_geometry::rect::RectF,
         _layout: &Self::LayoutState,
         _paint: &Self::PaintState,
-        cx: &crate::DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -3,41 +3,48 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::{self, json, ToJson},
-    presenter::MeasurementContext,
-    DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
+    Drawable, Element, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
 /// Element which renders it's children in a stack on top of each other.
 /// The first child determines the size of the others.
-#[derive(Default)]
-pub struct Stack {
-    children: Vec<ElementBox>,
+pub struct Stack<V: View> {
+    children: Vec<Element<V>>,
 }
 
-impl Stack {
+impl<V: View> Default for Stack<V> {
+    fn default() -> Self {
+        Self {
+            children: Vec::new(),
+        }
+    }
+}
+
+impl<V: View> Stack<V> {
     pub fn new() -> Self {
         Self::default()
     }
 }
 
-impl Element for Stack {
+impl<V: View> Drawable<V> for Stack<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         mut constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.min;
         let mut children = self.children.iter_mut();
         if let Some(bottom_child) = children.next() {
-            size = bottom_child.layout(constraint, cx);
+            size = bottom_child.layout(constraint, view, cx);
             constraint = SizeConstraint::strict(size);
         }
 
         for child in children {
-            child.layout(constraint, cx);
+            child.layout(constraint, view, cx);
         }
 
         (size, ())
@@ -45,14 +52,16 @@ impl Element for Stack {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         for child in &mut self.children {
-            cx.paint_layer(None, |cx| {
-                child.paint(bounds.origin(), visible_bounds, cx);
+            scene.paint_layer(None, |scene| {
+                child.paint(scene, bounds.origin(), visible_bounds, view, cx);
             });
         }
     }
@@ -64,12 +73,13 @@ impl Element for Stack {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
         self.children
             .iter()
             .rev()
-            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
+            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
     }
 
     fn debug(
@@ -77,18 +87,19 @@ impl Element for Stack {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
         json!({
             "type": "Stack",
             "bounds": bounds.to_json(),
-            "children": self.children.iter().map(|child| child.debug(cx)).collect::<Vec<json::Value>>()
+            "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
         })
     }
 }
 
-impl Extend<ElementBox> for Stack {
-    fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+impl<V: View> Extend<Element<V>> for Stack<V> {
+    fn extend<T: IntoIterator<Item = Element<V>>>(&mut self, children: T) {
         self.children.extend(children)
     }
 }

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

@@ -8,8 +8,7 @@ use crate::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    presenter::MeasurementContext,
-    scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
+    scene, Drawable, SceneBuilder, SizeConstraint, View, ViewContext,
 };
 
 pub struct Svg {
@@ -31,14 +30,15 @@ impl Svg {
     }
 }
 
-impl Element for Svg {
+impl<V: View> Drawable<V> for Svg {
     type LayoutState = Option<usvg::Tree>;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         match cx.asset_cache.svg(&self.path) {
             Ok(tree) => {
@@ -58,13 +58,15 @@ impl Element for Svg {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         _visible_bounds: RectF,
         svg: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        _: &mut V,
+        _: &mut ViewContext<V>,
     ) {
         if let Some(svg) = svg.clone() {
-            cx.scene.push_icon(scene::Icon {
+            scene.push_icon(scene::Icon {
                 bounds,
                 svg,
                 path: self.path.clone(),
@@ -80,7 +82,8 @@ impl Element for Svg {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -90,7 +93,8 @@ impl Element for Svg {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
             "type": "Svg",

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

@@ -6,9 +6,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{ToJson, Value},
-    presenter::MeasurementContext,
     text_layout::{Line, RunStyle, ShapedBoundary},
-    DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache,
+    Drawable, FontCache, SceneBuilder, SizeConstraint, TextLayoutCache, View, ViewContext,
 };
 use log::warn;
 use serde_json::json;
@@ -53,14 +52,15 @@ impl Text {
     }
 }
 
-impl Element for Text {
+impl<V: View> Drawable<V> for Text {
     type LayoutState = LayoutState;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         // Convert the string and highlight ranges into an iterator of highlighted chunks.
 
@@ -98,8 +98,8 @@ impl Element for Text {
         let shaped_lines = layout_highlighted_chunks(
             chunks,
             &self.style,
-            cx.text_layout_cache,
-            cx.font_cache,
+            cx.text_layout_cache(),
+            &cx.font_cache,
             usize::MAX,
             self.text.matches('\n').count() + 1,
         );
@@ -143,10 +143,12 @@ impl Element for Text {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        _: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let mut origin = bounds.origin();
         let empty = Vec::new();
@@ -163,6 +165,7 @@ impl Element for Text {
             if boundaries.intersects(visible_bounds) {
                 if self.soft_wrap {
                     line.paint_wrapped(
+                        scene,
                         origin,
                         visible_bounds,
                         layout.line_height,
@@ -170,7 +173,7 @@ impl Element for Text {
                         cx,
                     );
                 } else {
-                    line.paint(origin, visible_bounds, layout.line_height, cx);
+                    line.paint(scene, origin, visible_bounds, layout.line_height, cx);
                 }
             }
             origin.set_y(boundaries.max_y());
@@ -184,7 +187,8 @@ impl Element for Text {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Option<RectF> {
         None
     }
@@ -194,7 +198,8 @@ impl Element for Text {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &V,
+        _: &ViewContext<V>,
     ) -> Value {
         json!({
             "type": "Text",
@@ -208,9 +213,9 @@ impl Element for Text {
 /// Perform text layout on a series of highlighted chunks of text.
 pub fn layout_highlighted_chunks<'a>(
     chunks: impl Iterator<Item = (&'a str, Option<HighlightStyle>)>,
-    text_style: &'a TextStyle,
-    text_layout_cache: &'a TextLayoutCache,
-    font_cache: &'a Arc<FontCache>,
+    text_style: &TextStyle,
+    text_layout_cache: &TextLayoutCache,
+    font_cache: &Arc<FontCache>,
     max_line_len: usize,
     max_line_count: usize,
 ) -> Vec<Line> {
@@ -271,20 +276,23 @@ pub fn layout_highlighted_chunks<'a>(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{elements::Empty, fonts, AppContext, ElementBox, Entity, RenderContext, View};
+    use crate::{elements::Empty, fonts, AppContext, Element, Entity, View, ViewContext};
 
     #[crate::test(self)]
     fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
-        let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
-        let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
-        fonts::with_font_cache(cx.font_cache().clone(), || {
-            let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
-            let (_, state) = text.layout(
-                SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
-                &mut presenter.build_layout_context(Default::default(), false, cx),
-            );
-            assert_eq!(state.shaped_lines.len(), 2);
-            assert_eq!(state.wrap_boundaries.len(), 2);
+        cx.add_window(Default::default(), |cx| {
+            let mut view = TestView;
+            fonts::with_font_cache(cx.font_cache().clone(), || {
+                let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
+                let (_, state) = text.layout(
+                    SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
+                    &mut view,
+                    cx,
+                );
+                assert_eq!(state.shaped_lines.len(), 2);
+                assert_eq!(state.wrap_boundaries.len(), 2);
+            });
+            view
         });
     }
 
@@ -299,7 +307,7 @@ mod tests {
             "TestView"
         }
 
-        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
             Empty::new().boxed()
         }
     }

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

@@ -1,14 +1,12 @@
 use super::{
-    ContainerStyle, Element, ElementBox, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
+    ContainerStyle, Drawable, Element, Flex, KeystrokeLabel, MouseEventHandler, Overlay,
     OverlayFitMode, ParentElement, Text,
 };
 use crate::{
     fonts::TextStyle,
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    presenter::MeasurementContext,
-    Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
-    Task, View,
+    Action, Axis, ElementStateHandle, SceneBuilder, SizeConstraint, Task, View, ViewContext,
 };
 use serde::Deserialize;
 use std::{
@@ -17,12 +15,13 @@ use std::{
     rc::Rc,
     time::Duration,
 };
+use util::ResultExt;
 
 const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(500);
 
-pub struct Tooltip {
-    child: ElementBox,
-    tooltip: Option<ElementBox>,
+pub struct Tooltip<V: View> {
+    child: Element<V>,
+    tooltip: Option<Element<V>>,
     _state: ElementStateHandle<Rc<TooltipState>>,
 }
 
@@ -50,24 +49,23 @@ pub struct KeystrokeStyle {
     text: TextStyle,
 }
 
-impl Tooltip {
+impl<V: View> Tooltip<V> {
     pub fn new<Tag: 'static, T: View>(
         id: usize,
         text: String,
         action: Option<Box<dyn Action>>,
         style: TooltipStyle,
-        child: ElementBox,
-        cx: &mut RenderContext<T>,
+        child: Element<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self {
         struct ElementState<Tag>(Tag);
         struct MouseEventHandlerState<Tag>(Tag);
-        let focused_view_id = cx.focused_view_id(cx.window_id);
+        let focused_view_id = cx.focused_view_id();
 
         let state_handle = cx.default_element_state::<ElementState<Tag>, Rc<TooltipState>>(id);
         let state = state_handle.read(cx).clone();
         let tooltip = if state.visible.get() {
             let mut collapsed_tooltip = Self::render_tooltip(
-                cx.window_id,
                 focused_view_id,
                 text.clone(),
                 style.clone(),
@@ -77,12 +75,12 @@ impl Tooltip {
             .boxed();
             Some(
                 Overlay::new(
-                    Self::render_tooltip(cx.window_id, focused_view_id, text, style, action, false)
+                    Self::render_tooltip(focused_view_id, text, style, action, false)
                         .constrained()
-                        .dynamically(move |constraint, cx| {
+                        .dynamically(move |constraint, view, cx| {
                             SizeConstraint::strict_along(
                                 Axis::Vertical,
-                                collapsed_tooltip.layout(constraint, cx).y(),
+                                collapsed_tooltip.layout(constraint, view, cx).y(),
                             )
                         })
                         .boxed(),
@@ -94,32 +92,31 @@ impl Tooltip {
         } else {
             None
         };
-        let child = MouseEventHandler::<MouseEventHandlerState<Tag>>::new(id, cx, |_, _| child)
-            .on_hover(move |e, cx| {
+        let child = MouseEventHandler::<MouseEventHandlerState<Tag>, _>::new(id, cx, |_, _| child)
+            .on_hover(move |e, _, cx| {
                 let position = e.position;
-                let window_id = cx.window_id();
-                if let Some(view_id) = cx.view_id() {
-                    if e.started {
-                        if !state.visible.get() {
-                            state.position.set(position);
+                if e.started {
+                    if !state.visible.get() {
+                        state.position.set(position);
 
-                            let mut debounce = state.debounce.borrow_mut();
-                            if debounce.is_none() {
-                                *debounce = Some(cx.spawn({
-                                    let state = state.clone();
-                                    |mut cx| async move {
-                                        cx.background().timer(DEBOUNCE_TIMEOUT).await;
-                                        state.visible.set(true);
-                                        cx.update(|cx| cx.notify_view(window_id, view_id));
+                        let mut debounce = state.debounce.borrow_mut();
+                        if debounce.is_none() {
+                            *debounce = Some(cx.spawn_weak({
+                                let state = state.clone();
+                                |view, mut cx| async move {
+                                    cx.background().timer(DEBOUNCE_TIMEOUT).await;
+                                    state.visible.set(true);
+                                    if let Some(view) = view.upgrade(&cx) {
+                                        view.update(&mut cx, |_, cx| cx.notify()).log_err();
                                     }
-                                }));
-                            }
+                                }
+                            }));
                         }
-                    } else {
-                        state.visible.set(false);
-                        state.debounce.take();
-                        cx.notify();
                     }
+                } else {
+                    state.visible.set(false);
+                    state.debounce.take();
+                    cx.notify();
                 }
             })
             .boxed();
@@ -131,13 +128,12 @@ impl Tooltip {
     }
 
     pub fn render_tooltip(
-        window_id: usize,
         focused_view_id: Option<usize>,
         text: String,
         style: TooltipStyle,
         action: Option<Box<dyn Action>>,
         measure: bool,
-    ) -> impl Element {
+    ) -> impl Drawable<V> {
         Flex::row()
             .with_child({
                 let text = if let Some(max_text_width) = style.max_text_width {
@@ -156,7 +152,6 @@ impl Tooltip {
             })
             .with_children(action.and_then(|action| {
                 let keystroke_label = KeystrokeLabel::new(
-                    window_id,
                     focused_view_id?,
                     action,
                     style.keystroke.container,
@@ -173,32 +168,40 @@ impl Tooltip {
     }
 }
 
-impl Element for Tooltip {
+impl<V: View> Drawable<V> for Tooltip<V> {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, cx);
+        let size = self.child.layout(constraint, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {
-            tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
+            tooltip.layout(
+                SizeConstraint::new(Vector2F::zero(), cx.window_size()),
+                view,
+                cx,
+            );
         }
         (size, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) {
-        self.child.paint(bounds.origin(), visible_bounds, cx);
+        self.child
+            .paint(scene, bounds.origin(), visible_bounds, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {
-            tooltip.paint(bounds.origin(), visible_bounds, cx);
+            tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
         }
     }
 
@@ -209,9 +212,10 @@ impl Element for Tooltip {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range, cx)
+        self.child.rect_for_text_range(range, view, cx)
     }
 
     fn debug(
@@ -219,11 +223,12 @@ impl Element for Tooltip {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &crate::DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> serde_json::Value {
         json!({
-            "child": self.child.debug(cx),
-            "tooltip": self.tooltip.as_ref().map(|t| t.debug(cx)),
+            "child": self.child.debug(view, cx),
+            "tooltip": self.tooltip.as_ref().map(|t| t.debug(view, cx)),
         })
     }
 }

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

@@ -1,4 +1,4 @@
-use super::{Element, EventContext, LayoutContext, PaintContext, SizeConstraint};
+use super::{Drawable, SizeConstraint};
 use crate::{
     geometry::{
         rect::RectF,
@@ -6,9 +6,7 @@ use crate::{
     },
     json::{self, json},
     platform::ScrollWheelEvent,
-    presenter::MeasurementContext,
-    scene::MouseScrollWheel,
-    ElementBox, MouseRegion, RenderContext, View,
+    Element, MouseRegion, SceneBuilder, View, ViewContext,
 };
 use json::ToJson;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -38,45 +36,38 @@ struct StateInner {
     scroll_to: Option<ScrollTarget>,
 }
 
-pub struct LayoutState {
+pub struct LayoutState<V: View> {
     scroll_max: f32,
     item_height: f32,
-    items: Vec<ElementBox>,
+    items: Vec<Element<V>>,
 }
 
-pub struct UniformList {
+pub struct UniformList<V: View> {
     state: UniformListState,
     item_count: usize,
     #[allow(clippy::type_complexity)]
-    append_items: Box<dyn Fn(Range<usize>, &mut Vec<ElementBox>, &mut LayoutContext)>,
+    append_items: Box<dyn Fn(&mut V, Range<usize>, &mut Vec<Element<V>>, &mut ViewContext<V>)>,
     padding_top: f32,
     padding_bottom: f32,
     get_width_from_item: Option<usize>,
     view_id: usize,
 }
 
-impl UniformList {
-    pub fn new<F, V>(
+impl<V: View> UniformList<V> {
+    pub fn new<F>(
         state: UniformListState,
         item_count: usize,
-        cx: &mut RenderContext<V>,
+        cx: &mut ViewContext<V>,
         append_items: F,
     ) -> Self
     where
         V: View,
-        F: 'static + Fn(&mut V, Range<usize>, &mut Vec<ElementBox>, &mut RenderContext<V>),
+        F: 'static + Fn(&mut V, Range<usize>, &mut Vec<Element<V>>, &mut ViewContext<V>),
     {
-        let handle = cx.handle();
         Self {
             state,
             item_count,
-            append_items: Box::new(move |range, items, cx| {
-                if let Some(handle) = handle.upgrade(cx) {
-                    cx.render(&handle, |view, cx| {
-                        append_items(view, range, items, cx);
-                    });
-                }
-            }),
+            append_items: Box::new(append_items),
             padding_top: 0.,
             padding_bottom: 0.,
             get_width_from_item: None,
@@ -105,7 +96,7 @@ impl UniformList {
         mut delta: Vector2F,
         precise: bool,
         scroll_max: f32,
-        cx: &mut EventContext,
+        cx: &mut ViewContext<V>,
     ) -> bool {
         if !precise {
             delta *= 20.;
@@ -160,14 +151,15 @@ impl UniformList {
     }
 }
 
-impl Element for UniformList {
-    type LayoutState = LayoutState;
+impl<V: View> Drawable<V> for UniformList<V> {
+    type LayoutState = LayoutState<V>;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if constraint.max.y().is_infinite() {
             unimplemented!(
@@ -194,18 +186,18 @@ impl Element for UniformList {
         let sample_item_ix;
         let sample_item;
         if let Some(sample_ix) = self.get_width_from_item {
-            (self.append_items)(sample_ix..sample_ix + 1, &mut items, cx);
+            (self.append_items)(view, sample_ix..sample_ix + 1, &mut items, cx);
             sample_item_ix = sample_ix;
 
             if let Some(mut item) = items.pop() {
-                item_size = item.layout(constraint, cx);
+                item_size = item.layout(constraint, view, cx);
                 size.set_x(item_size.x());
                 sample_item = item;
             } else {
                 return no_items;
             }
         } else {
-            (self.append_items)(0..1, &mut items, cx);
+            (self.append_items)(view, 0..1, &mut items, cx);
             sample_item_ix = 0;
             if let Some(mut item) = items.pop() {
                 item_size = item.layout(
@@ -213,6 +205,7 @@ impl Element for UniformList {
                         vec2f(constraint.max.x(), 0.0),
                         vec2f(constraint.max.x(), f32::INFINITY),
                     ),
+                    view,
                     cx,
                 );
                 item_size.set_x(size.x());
@@ -249,20 +242,20 @@ impl Element for UniformList {
 
         if (start..end).contains(&sample_item_ix) {
             if sample_item_ix > start {
-                (self.append_items)(start..sample_item_ix, &mut items, cx);
+                (self.append_items)(view, start..sample_item_ix, &mut items, cx);
             }
 
             items.push(sample_item);
 
             if sample_item_ix < end {
-                (self.append_items)(sample_item_ix + 1..end, &mut items, cx);
+                (self.append_items)(view, sample_item_ix + 1..end, &mut items, cx);
             }
         } else {
-            (self.append_items)(start..end, &mut items, cx);
+            (self.append_items)(view, start..end, &mut items, cx);
         }
 
         for item in &mut items {
-            let item_size = item.layout(item_constraint, cx);
+            let item_size = item.layout(item_constraint, view, cx);
             if item_size.x() > size.x() {
                 size.set_x(item_size.x());
             }
@@ -280,27 +273,25 @@ impl Element for UniformList {
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
 
-        cx.scene.push_layer(Some(visible_bounds));
+        scene.push_layer(Some(visible_bounds));
 
-        cx.scene.push_mouse_region(
+        scene.push_mouse_region(
             MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
                 let scroll_max = layout.scroll_max;
                 let state = self.state.clone();
-                move |MouseScrollWheel {
-                          platform_event:
-                              ScrollWheelEvent {
-                                  position, delta, ..
-                              },
-                          ..
-                      },
-                      cx| {
+                move |event, _, cx| {
+                    let ScrollWheelEvent {
+                        position, delta, ..
+                    } = event.platform_event;
                     if !Self::scroll(
                         state.clone(),
                         position,
@@ -322,11 +313,11 @@ impl Element for UniformList {
             );
 
         for item in &mut layout.items {
-            item.paint(item_origin, visible_bounds, cx);
+            item.paint(scene, item_origin, visible_bounds, view, cx);
             item_origin += vec2f(0.0, layout.item_height);
         }
 
-        cx.scene.pop_layer();
+        scene.pop_layer();
     }
 
     fn rect_for_text_range(
@@ -336,12 +327,13 @@ impl Element for UniformList {
         _: RectF,
         layout: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &MeasurementContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> Option<RectF> {
         layout
             .items
             .iter()
-            .find_map(|child| child.rect_for_text_range(range.clone(), cx))
+            .find_map(|child| child.rect_for_text_range(range.clone(), view, cx))
     }
 
     fn debug(
@@ -349,14 +341,15 @@ impl Element for UniformList {
         bounds: RectF,
         layout: &Self::LayoutState,
         _: &Self::PaintState,
-        cx: &crate::DebugContext,
+        view: &V,
+        cx: &ViewContext<V>,
     ) -> json::Value {
         json!({
             "type": "UniformList",
             "bounds": bounds.to_json(),
             "scroll_max": layout.scroll_max,
             "item_height": layout.item_height,
-            "items": layout.items.iter().map(|item| item.debug(cx)).collect::<Vec<json::Value>>()
+            "items": layout.items.iter().map(|item| item.debug(view, cx)).collect::<Vec<json::Value>>()
 
         })
     }

crates/gpui/src/gpui.rs 🔗

@@ -14,13 +14,12 @@ mod clipboard;
 pub use clipboard::ClipboardItem;
 pub mod fonts;
 pub mod geometry;
-mod presenter;
 pub mod scene;
 pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
 pub mod text_layout;
 pub use text_layout::TextLayoutCache;
 mod util;
-pub use elements::{Element, ElementBox, ElementRc};
+pub use elements::{Drawable, Element};
 pub mod executor;
 pub use executor::Task;
 pub mod color;
@@ -28,10 +27,7 @@ pub mod json;
 pub mod keymap_matcher;
 pub mod platform;
 pub use gpui_macros::test;
-pub use presenter::{
-    Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext,
-    SizeConstraint, Vector2FExt,
-};
+pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
 
 pub use anyhow;
 pub use serde_json;

crates/gpui/src/platform.rs 🔗

@@ -63,7 +63,7 @@ pub trait Platform: Send + Sync {
     ) -> Box<dyn Window>;
     fn main_window_id(&self) -> Option<usize>;
 
-    fn add_status_item(&self) -> Box<dyn Window>;
+    fn add_status_item(&self, id: usize) -> Box<dyn Window>;
 
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;

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

@@ -601,7 +601,7 @@ impl platform::Platform for MacPlatform {
         Window::main_window_id()
     }
 
-    fn add_status_item(&self) -> Box<dyn platform::Window> {
+    fn add_status_item(&self, _id: usize) -> Box<dyn platform::Window> {
         Box::new(StatusItem::add(self.fonts()))
     }
 

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

@@ -102,6 +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>>>,
 }
 
 impl Platform {
@@ -111,6 +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(),
         }
     }
 }
@@ -144,22 +146,31 @@ impl super::Platform for Platform {
 
     fn open_window(
         &self,
-        _: usize,
+        id: usize,
         options: super::WindowOptions,
         _executor: Rc<super::executor::Foreground>,
     ) -> Box<dyn super::Window> {
-        Box::new(Window::new(match options.bounds {
-            WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
-            WindowBounds::Fixed(rect) => rect.size(),
-        }))
+        *self.active_window_id.lock() = Some(id);
+        Box::new(Window::new(
+            id,
+            match options.bounds {
+                WindowBounds::Maximized | WindowBounds::Fullscreen => vec2f(1024., 768.),
+                WindowBounds::Fixed(rect) => rect.size(),
+            },
+            self.active_window_id.clone(),
+        ))
     }
 
     fn main_window_id(&self) -> Option<usize> {
-        None
+        self.active_window_id.lock().clone()
     }
 
-    fn add_status_item(&self) -> Box<dyn crate::platform::Window> {
-        Box::new(Window::new(vec2f(24., 24.)))
+    fn add_status_item(&self, id: usize) -> Box<dyn crate::platform::Window> {
+        Box::new(Window::new(
+            id,
+            vec2f(24., 24.),
+            self.active_window_id.clone(),
+        ))
     }
 
     fn write_to_clipboard(&self, item: ClipboardItem) {
@@ -245,6 +256,7 @@ impl super::Screen for Screen {
 }
 
 pub struct Window {
+    id: usize,
     pub(crate) size: Vector2F,
     scale_factor: f32,
     current_scene: Option<crate::Scene>,
@@ -258,11 +270,13 @@ 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>>>,
 }
 
 impl Window {
-    fn new(size: Vector2F) -> Self {
+    pub fn new(id: usize, size: Vector2F, active_window_id: Arc<Mutex<Option<usize>>>) -> Self {
         Self {
+            id,
             size,
             event_handlers: Default::default(),
             resize_handlers: Default::default(),
@@ -276,6 +290,7 @@ impl Window {
             title: None,
             edited: false,
             pending_prompts: Default::default(),
+            active_window_id,
         }
     }
 
@@ -326,7 +341,9 @@ impl super::Window for Window {
         done_rx
     }
 
-    fn activate(&self) {}
+    fn activate(&self) {
+        *self.active_window_id.lock() = Some(self.id);
+    }
 
     fn set_title(&mut self, title: &str) {
         self.title = Some(title.to_string())

crates/gpui/src/presenter.rs 🔗

@@ -1,1154 +0,0 @@
-use crate::{
-    app::WindowInvalidation,
-    elements::Element,
-    font_cache::FontCache,
-    geometry::rect::RectF,
-    json::{self, ToJson},
-    platform::{Appearance, CursorStyle, Event, FontSystem, MouseButton, MouseMovedEvent},
-    scene::{
-        CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
-        MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
-    },
-    text_layout::TextLayoutCache,
-    Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
-    AssetCache, ElementBox, Entity, ModelHandle, MouseRegion, MouseRegionId, MouseState, ParentId,
-    ReadModel, ReadView, RenderContext, RenderParams, SceneBuilder, UpgradeModelHandle,
-    UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
-};
-use anyhow::bail;
-use collections::{HashMap, HashSet};
-use pathfinder_geometry::vector::{vec2f, Vector2F};
-use serde_json::json;
-use smallvec::SmallVec;
-use sqlez::{
-    bindable::{Bind, Column, StaticColumnCount},
-    statement::Statement,
-};
-use std::{
-    marker::PhantomData,
-    ops::{Deref, DerefMut, Range},
-    sync::Arc,
-};
-
-pub struct Presenter {
-    window_id: usize,
-    pub(crate) rendered_views: HashMap<usize, ElementBox>,
-    cursor_regions: Vec<CursorRegion>,
-    mouse_regions: Vec<(MouseRegion, usize)>,
-    font_cache: Arc<FontCache>,
-    text_layout_cache: TextLayoutCache,
-    asset_cache: Arc<AssetCache>,
-    last_mouse_moved_event: Option<Event>,
-    hovered_region_ids: HashSet<MouseRegionId>,
-    clicked_region_ids: HashSet<MouseRegionId>,
-    clicked_button: Option<MouseButton>,
-    mouse_position: Vector2F,
-    titlebar_height: f32,
-    appearance: Appearance,
-}
-
-impl Presenter {
-    pub fn new(
-        window_id: usize,
-        titlebar_height: f32,
-        appearance: Appearance,
-        font_cache: Arc<FontCache>,
-        text_layout_cache: TextLayoutCache,
-        asset_cache: Arc<AssetCache>,
-        cx: &mut AppContext,
-    ) -> Self {
-        Self {
-            window_id,
-            rendered_views: cx.render_views(window_id, titlebar_height, appearance),
-            cursor_regions: Default::default(),
-            mouse_regions: Default::default(),
-            font_cache,
-            text_layout_cache,
-            asset_cache,
-            last_mouse_moved_event: None,
-            hovered_region_ids: Default::default(),
-            clicked_region_ids: Default::default(),
-            clicked_button: None,
-            mouse_position: vec2f(0., 0.),
-            titlebar_height,
-            appearance,
-        }
-    }
-
-    pub fn invalidate(
-        &mut self,
-        invalidation: &mut WindowInvalidation,
-        appearance: Appearance,
-        cx: &mut AppContext,
-    ) {
-        cx.start_frame();
-        self.appearance = appearance;
-        for view_id in &invalidation.removed {
-            invalidation.updated.remove(view_id);
-            self.rendered_views.remove(view_id);
-        }
-        for view_id in &invalidation.updated {
-            self.rendered_views.insert(
-                *view_id,
-                cx.render_view(RenderParams {
-                    window_id: self.window_id,
-                    view_id: *view_id,
-                    titlebar_height: self.titlebar_height,
-                    hovered_region_ids: self.hovered_region_ids.clone(),
-                    clicked_region_ids: self
-                        .clicked_button
-                        .map(|button| (self.clicked_region_ids.clone(), button)),
-                    refreshing: false,
-                    appearance,
-                })
-                .unwrap(),
-            );
-        }
-    }
-
-    pub fn refresh(
-        &mut self,
-        invalidation: &mut WindowInvalidation,
-        appearance: Appearance,
-        cx: &mut AppContext,
-    ) {
-        self.invalidate(invalidation, appearance, cx);
-        for (view_id, view) in &mut self.rendered_views {
-            if !invalidation.updated.contains(view_id) {
-                *view = cx
-                    .render_view(RenderParams {
-                        window_id: self.window_id,
-                        view_id: *view_id,
-                        titlebar_height: self.titlebar_height,
-                        hovered_region_ids: self.hovered_region_ids.clone(),
-                        clicked_region_ids: self
-                            .clicked_button
-                            .map(|button| (self.clicked_region_ids.clone(), button)),
-                        refreshing: true,
-                        appearance,
-                    })
-                    .unwrap();
-            }
-        }
-    }
-
-    pub fn build_scene(
-        &mut self,
-        window_size: Vector2F,
-        scale_factor: f32,
-        refreshing: bool,
-        cx: &mut AppContext,
-    ) -> Scene {
-        let mut scene_builder = SceneBuilder::new(scale_factor);
-
-        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
-            self.layout(window_size, refreshing, cx);
-            let mut paint_cx = self.build_paint_context(&mut scene_builder, window_size, cx);
-            paint_cx.paint(
-                root_view_id,
-                Vector2F::zero(),
-                RectF::new(Vector2F::zero(), window_size),
-            );
-            self.text_layout_cache.finish_frame();
-            let scene = scene_builder.build();
-            self.cursor_regions = scene.cursor_regions();
-            self.mouse_regions = scene.mouse_regions();
-
-            // window.is_topmost for the mouse moved event's postion?
-            if cx.window_is_active(self.window_id) {
-                if let Some(event) = self.last_mouse_moved_event.clone() {
-                    self.dispatch_event(event, true, cx);
-                }
-            }
-
-            scene
-        } else {
-            log::error!("could not find root_view_id for window {}", self.window_id);
-            scene_builder.build()
-        }
-    }
-
-    fn layout(&mut self, window_size: Vector2F, refreshing: bool, cx: &mut AppContext) {
-        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
-            self.build_layout_context(window_size, refreshing, cx)
-                .layout(root_view_id, SizeConstraint::strict(window_size));
-        }
-    }
-
-    pub fn build_layout_context<'a>(
-        &'a mut self,
-        window_size: Vector2F,
-        refreshing: bool,
-        cx: &'a mut AppContext,
-    ) -> LayoutContext<'a> {
-        LayoutContext {
-            window_id: self.window_id,
-            rendered_views: &mut self.rendered_views,
-            font_cache: &self.font_cache,
-            font_system: cx.platform().fonts(),
-            text_layout_cache: &self.text_layout_cache,
-            asset_cache: &self.asset_cache,
-            view_stack: Vec::new(),
-            refreshing,
-            hovered_region_ids: self.hovered_region_ids.clone(),
-            clicked_region_ids: self
-                .clicked_button
-                .map(|button| (self.clicked_region_ids.clone(), button)),
-            titlebar_height: self.titlebar_height,
-            appearance: self.appearance,
-            window_size,
-            app: cx,
-        }
-    }
-
-    pub fn build_paint_context<'a>(
-        &'a mut self,
-        scene: &'a mut SceneBuilder,
-        window_size: Vector2F,
-        cx: &'a mut AppContext,
-    ) -> PaintContext {
-        PaintContext {
-            scene,
-            window_size,
-            font_cache: &self.font_cache,
-            text_layout_cache: &self.text_layout_cache,
-            rendered_views: &mut self.rendered_views,
-            view_stack: Vec::new(),
-            app: cx,
-        }
-    }
-
-    pub fn rect_for_text_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<RectF> {
-        cx.focused_view_id(self.window_id).and_then(|view_id| {
-            let cx = MeasurementContext {
-                app: cx,
-                rendered_views: &self.rendered_views,
-                window_id: self.window_id,
-            };
-            cx.rect_for_text_range(view_id, range_utf16)
-        })
-    }
-
-    pub fn dispatch_event(
-        &mut self,
-        event: Event,
-        event_reused: bool,
-        cx: &mut AppContext,
-    ) -> bool {
-        let mut mouse_events = SmallVec::<[_; 2]>::new();
-        let mut notified_views: HashSet<usize> = Default::default();
-
-        // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
-        //    get mapped into the mouse-specific MouseEvent type.
-        //  -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
-        //  -> Also updates mouse-related state
-        match &event {
-            Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e),
-
-            Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e),
-
-            Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e),
-
-            Event::MouseDown(e) => {
-                // Click events are weird because they can be fired after a drag event.
-                // MDN says that browsers handle this by starting from 'the most
-                // specific ancestor element that contained both [positions]'
-                // So we need to store the overlapping regions on mouse down.
-
-                // If there is already clicked_button stored, don't replace it.
-                if self.clicked_button.is_none() {
-                    self.clicked_region_ids = self
-                        .mouse_regions
-                        .iter()
-                        .filter_map(|(region, _)| {
-                            if region.bounds.contains_point(e.position) {
-                                Some(region.id())
-                            } else {
-                                None
-                            }
-                        })
-                        .collect();
-
-                    self.clicked_button = Some(e.button);
-                }
-
-                mouse_events.push(MouseEvent::Down(MouseDown {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-                mouse_events.push(MouseEvent::DownOut(MouseDownOut {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-            }
-
-            Event::MouseUp(e) => {
-                // NOTE: The order of event pushes is important! MouseUp events MUST be fired
-                // before click events, and so the MouseUp events need to be pushed before
-                // MouseClick events.
-                mouse_events.push(MouseEvent::Up(MouseUp {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-                mouse_events.push(MouseEvent::UpOut(MouseUpOut {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-                mouse_events.push(MouseEvent::Click(MouseClick {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                }));
-            }
-
-            Event::MouseMoved(
-                e @ MouseMovedEvent {
-                    position,
-                    pressed_button,
-                    ..
-                },
-            ) => {
-                let mut style_to_assign = CursorStyle::Arrow;
-                for region in self.cursor_regions.iter().rev() {
-                    if region.bounds.contains_point(*position) {
-                        style_to_assign = region.style;
-                        break;
-                    }
-                }
-
-                if cx.is_topmost_window_for_position(self.window_id, *position) {
-                    cx.platform().set_cursor_style(style_to_assign);
-                }
-
-                if !event_reused {
-                    if pressed_button.is_some() {
-                        mouse_events.push(MouseEvent::Drag(MouseDrag {
-                            region: Default::default(),
-                            prev_mouse_position: self.mouse_position,
-                            platform_event: e.clone(),
-                        }));
-                    } else if let Some(clicked_button) = self.clicked_button {
-                        // Mouse up event happened outside the current window. Simulate mouse up button event
-                        let button_event = e.to_button_event(clicked_button);
-                        mouse_events.push(MouseEvent::Up(MouseUp {
-                            region: Default::default(),
-                            platform_event: button_event.clone(),
-                        }));
-                        mouse_events.push(MouseEvent::UpOut(MouseUpOut {
-                            region: Default::default(),
-                            platform_event: button_event.clone(),
-                        }));
-                        mouse_events.push(MouseEvent::Click(MouseClick {
-                            region: Default::default(),
-                            platform_event: button_event.clone(),
-                        }));
-                    }
-
-                    mouse_events.push(MouseEvent::Move(MouseMove {
-                        region: Default::default(),
-                        platform_event: e.clone(),
-                    }));
-                }
-
-                mouse_events.push(MouseEvent::Hover(MouseHover {
-                    region: Default::default(),
-                    platform_event: e.clone(),
-                    started: false,
-                }));
-                mouse_events.push(MouseEvent::MoveOut(MouseMoveOut {
-                    region: Default::default(),
-                }));
-
-                self.last_mouse_moved_event = Some(event.clone());
-            }
-
-            Event::MouseExited(event) => {
-                // When the platform sends a MouseExited event, synthesize
-                // a MouseMoved event whose position is outside the window's
-                // bounds so that hover and cursor state can be updated.
-                return self.dispatch_event(
-                    Event::MouseMoved(MouseMovedEvent {
-                        position: event.position,
-                        pressed_button: event.pressed_button,
-                        modifiers: event.modifiers,
-                    }),
-                    event_reused,
-                    cx,
-                );
-            }
-
-            Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
-                region: Default::default(),
-                platform_event: e.clone(),
-            })),
-        }
-
-        if let Some(position) = event.position() {
-            self.mouse_position = position;
-        }
-
-        // 2. Dispatch mouse events on regions
-        let mut any_event_handled = false;
-        for mut mouse_event in mouse_events {
-            let mut valid_regions = Vec::new();
-
-            // GPUI elements are arranged by z_index but sibling elements can register overlapping
-            // mouse regions. As such, hover events are only fired on overlapping elements which
-            // are at the same z-index as the topmost element which overlaps with the mouse.
-            match &mouse_event {
-                MouseEvent::Hover(_) => {
-                    let mut highest_z_index = None;
-                    let mouse_position = self.mouse_position.clone();
-                    for (region, z_index) in self.mouse_regions.iter().rev() {
-                        // Allow mouse regions to appear transparent to hovers
-                        if !region.hoverable {
-                            continue;
-                        }
-
-                        let contains_mouse = region.bounds.contains_point(mouse_position);
-
-                        if contains_mouse && highest_z_index.is_none() {
-                            highest_z_index = Some(z_index);
-                        }
-
-                        // This unwrap relies on short circuiting boolean expressions
-                        // The right side of the && is only executed when contains_mouse
-                        // is true, and we know above that when contains_mouse is true
-                        // highest_z_index is set.
-                        if contains_mouse && z_index == highest_z_index.unwrap() {
-                            //Ensure that hover entrance events aren't sent twice
-                            if self.hovered_region_ids.insert(region.id()) {
-                                valid_regions.push(region.clone());
-                                if region.notify_on_hover {
-                                    notified_views.insert(region.id().view_id());
-                                }
-                            }
-                        } else {
-                            // Ensure that hover exit events aren't sent twice
-                            if self.hovered_region_ids.remove(&region.id()) {
-                                valid_regions.push(region.clone());
-                                if region.notify_on_hover {
-                                    notified_views.insert(region.id().view_id());
-                                }
-                            }
-                        }
-                    }
-                }
-
-                MouseEvent::Down(_) | MouseEvent::Up(_) => {
-                    for (region, _) in self.mouse_regions.iter().rev() {
-                        if region.bounds.contains_point(self.mouse_position) {
-                            valid_regions.push(region.clone());
-                            if region.notify_on_click {
-                                notified_views.insert(region.id().view_id());
-                            }
-                        }
-                    }
-                }
-
-                MouseEvent::Click(e) => {
-                    // Only raise click events if the released button is the same as the one stored
-                    if self
-                        .clicked_button
-                        .map(|clicked_button| clicked_button == e.button)
-                        .unwrap_or(false)
-                    {
-                        // Clear clicked regions and clicked button
-                        let clicked_region_ids =
-                            std::mem::replace(&mut self.clicked_region_ids, Default::default());
-                        self.clicked_button = None;
-
-                        // Find regions which still overlap with the mouse since the last MouseDown happened
-                        for (mouse_region, _) in self.mouse_regions.iter().rev() {
-                            if clicked_region_ids.contains(&mouse_region.id()) {
-                                if mouse_region.bounds.contains_point(self.mouse_position) {
-                                    valid_regions.push(mouse_region.clone());
-                                }
-                            }
-                        }
-                    }
-                }
-
-                MouseEvent::Drag(_) => {
-                    for (mouse_region, _) in self.mouse_regions.iter().rev() {
-                        if self.clicked_region_ids.contains(&mouse_region.id()) {
-                            valid_regions.push(mouse_region.clone());
-                        }
-                    }
-                }
-
-                MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => {
-                    for (mouse_region, _) in self.mouse_regions.iter().rev() {
-                        // NOT contains
-                        if !mouse_region.bounds.contains_point(self.mouse_position) {
-                            valid_regions.push(mouse_region.clone());
-                        }
-                    }
-                }
-
-                _ => {
-                    for (mouse_region, _) in self.mouse_regions.iter().rev() {
-                        // Contains
-                        if mouse_region.bounds.contains_point(self.mouse_position) {
-                            valid_regions.push(mouse_region.clone());
-                        }
-                    }
-                }
-            }
-
-            //3. Fire region events
-            let hovered_region_ids = self.hovered_region_ids.clone();
-            for valid_region in valid_regions.into_iter() {
-                let mut event_cx = self.build_event_context(&mut notified_views, cx);
-
-                mouse_event.set_region(valid_region.bounds);
-                if let MouseEvent::Hover(e) = &mut mouse_event {
-                    e.started = hovered_region_ids.contains(&valid_region.id())
-                }
-                // Handle Down events if the MouseRegion has a Click or Drag handler. This makes the api more intuitive as you would
-                // not expect a MouseRegion to be transparent to Down events if it also has a Click handler.
-                // This behavior can be overridden by adding a Down handler
-                if let MouseEvent::Down(e) = &mouse_event {
-                    let has_click = valid_region
-                        .handlers
-                        .contains(MouseEvent::click_disc(), Some(e.button));
-                    let has_drag = valid_region
-                        .handlers
-                        .contains(MouseEvent::drag_disc(), Some(e.button));
-                    let has_down = valid_region
-                        .handlers
-                        .contains(MouseEvent::down_disc(), Some(e.button));
-                    if !has_down && (has_click || has_drag) {
-                        event_cx.handled = true;
-                    }
-                }
-
-                // `event_consumed` should only be true if there are any handlers for this event.
-                let mut event_consumed = event_cx.handled;
-                if let Some(callbacks) = valid_region.handlers.get(&mouse_event.handler_key()) {
-                    for callback in callbacks {
-                        event_cx.handled = true;
-                        event_cx.with_current_view(valid_region.id().view_id(), {
-                            let region_event = mouse_event.clone();
-                            |cx| callback(region_event, cx)
-                        });
-                        event_consumed |= event_cx.handled;
-                        any_event_handled |= event_cx.handled;
-                    }
-                }
-
-                any_event_handled |= event_cx.handled;
-
-                // For bubbling events, if the event was handled, don't continue dispatching.
-                // This only makes sense for local events which return false from is_capturable.
-                if event_consumed && mouse_event.is_capturable() {
-                    break;
-                }
-            }
-        }
-
-        for view_id in notified_views {
-            cx.notify_view(self.window_id, view_id);
-        }
-
-        any_event_handled
-    }
-
-    pub fn build_event_context<'a>(
-        &'a mut self,
-        notified_views: &'a mut HashSet<usize>,
-        cx: &'a mut AppContext,
-    ) -> EventContext<'a> {
-        EventContext {
-            font_cache: &self.font_cache,
-            text_layout_cache: &self.text_layout_cache,
-            view_stack: Default::default(),
-            notified_views,
-            notify_count: 0,
-            handled: false,
-            window_id: self.window_id,
-            app: cx,
-        }
-    }
-
-    pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
-        let view = cx.root_view(self.window_id)?;
-        Some(json!({
-            "root_view": view.debug_json(cx),
-            "root_element": self.rendered_views.get(&view.id())
-                .map(|root_element| {
-                    root_element.debug(&DebugContext {
-                        rendered_views: &self.rendered_views,
-                        font_cache: &self.font_cache,
-                        app: cx,
-                    })
-                })
-        }))
-    }
-}
-
-pub struct LayoutContext<'a> {
-    window_id: usize,
-    rendered_views: &'a mut HashMap<usize, ElementBox>,
-    view_stack: Vec<usize>,
-    pub font_cache: &'a Arc<FontCache>,
-    pub font_system: Arc<dyn FontSystem>,
-    pub text_layout_cache: &'a TextLayoutCache,
-    pub asset_cache: &'a AssetCache,
-    pub app: &'a mut AppContext,
-    pub refreshing: bool,
-    pub window_size: Vector2F,
-    titlebar_height: f32,
-    appearance: Appearance,
-    hovered_region_ids: HashSet<MouseRegionId>,
-    clicked_region_ids: Option<(HashSet<MouseRegionId>, MouseButton)>,
-}
-
-impl<'a> LayoutContext<'a> {
-    pub fn mouse_state<Tag: 'static>(&self, region_id: usize) -> MouseState {
-        let view_id = self.view_stack.last().unwrap();
-
-        let region_id = MouseRegionId::new::<Tag>(*view_id, region_id);
-        MouseState {
-            hovered: self.hovered_region_ids.contains(&region_id),
-            clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
-                if ids.contains(&region_id) {
-                    Some(*button)
-                } else {
-                    None
-                }
-            }),
-            accessed_hovered: false,
-            accessed_clicked: false,
-        }
-    }
-
-    fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F {
-        let print_error = |view_id| {
-            format!(
-                "{} with id {}",
-                self.app.name_for_view(self.window_id, view_id).unwrap(),
-                view_id,
-            )
-        };
-        match (
-            self.view_stack.last(),
-            self.app.parents.get(&(self.window_id, view_id)),
-        ) {
-            (Some(layout_parent), Some(ParentId::View(app_parent))) => {
-                if layout_parent != app_parent {
-                    panic!(
-                        "View {} was laid out with parent {} when it was constructed with parent {}",
-                        print_error(view_id),
-                        print_error(*layout_parent),
-                        print_error(*app_parent))
-                }
-            }
-            (None, Some(ParentId::View(app_parent))) => panic!(
-                "View {} was laid out without a parent when it was constructed with parent {}",
-                print_error(view_id),
-                print_error(*app_parent)
-            ),
-            (Some(layout_parent), Some(ParentId::Root)) => panic!(
-                "View {} was laid out with parent {} when it was constructed as a window root",
-                print_error(view_id),
-                print_error(*layout_parent),
-            ),
-            (_, None) => panic!(
-                "View {} did not have a registered parent in the app context",
-                print_error(view_id),
-            ),
-            _ => {}
-        }
-
-        self.view_stack.push(view_id);
-        let mut rendered_view = self.rendered_views.remove(&view_id).unwrap();
-        let size = rendered_view.layout(constraint, self);
-        self.rendered_views.insert(view_id, rendered_view);
-        self.view_stack.pop();
-        size
-    }
-
-    pub fn render<F, V, T>(&mut self, handle: &ViewHandle<V>, f: F) -> T
-    where
-        F: FnOnce(&mut V, &mut RenderContext<V>) -> T,
-        V: View,
-    {
-        handle.update(self.app, |view, cx| {
-            let mut render_cx = RenderContext {
-                app: cx,
-                window_id: handle.window_id(),
-                view_id: handle.id(),
-                view_type: PhantomData,
-                titlebar_height: self.titlebar_height,
-                hovered_region_ids: self.hovered_region_ids.clone(),
-                clicked_region_ids: self.clicked_region_ids.clone(),
-                refreshing: self.refreshing,
-                appearance: self.appearance,
-            };
-            f(view, &mut render_cx)
-        })
-    }
-}
-
-impl<'a> Deref for LayoutContext<'a> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        self.app
-    }
-}
-
-impl<'a> DerefMut for LayoutContext<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.app
-    }
-}
-
-impl<'a> ReadView for LayoutContext<'a> {
-    fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T {
-        self.app.read_view(handle)
-    }
-}
-
-impl<'a> ReadModel for LayoutContext<'a> {
-    fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T {
-        self.app.read_model(handle)
-    }
-}
-
-impl<'a> UpgradeModelHandle for LayoutContext<'a> {
-    fn upgrade_model_handle<T: Entity>(
-        &self,
-        handle: &WeakModelHandle<T>,
-    ) -> Option<ModelHandle<T>> {
-        self.app.upgrade_model_handle(handle)
-    }
-
-    fn model_handle_is_upgradable<T: Entity>(&self, handle: &WeakModelHandle<T>) -> bool {
-        self.app.model_handle_is_upgradable(handle)
-    }
-
-    fn upgrade_any_model_handle(&self, handle: &AnyWeakModelHandle) -> Option<AnyModelHandle> {
-        self.app.upgrade_any_model_handle(handle)
-    }
-}
-
-impl<'a> UpgradeViewHandle for LayoutContext<'a> {
-    fn upgrade_view_handle<T: View>(&self, handle: &WeakViewHandle<T>) -> Option<ViewHandle<T>> {
-        self.app.upgrade_view_handle(handle)
-    }
-
-    fn upgrade_any_view_handle(&self, handle: &crate::AnyWeakViewHandle) -> Option<AnyViewHandle> {
-        self.app.upgrade_any_view_handle(handle)
-    }
-}
-
-pub struct PaintContext<'a> {
-    rendered_views: &'a mut HashMap<usize, ElementBox>,
-    view_stack: Vec<usize>,
-    pub window_size: Vector2F,
-    pub scene: &'a mut SceneBuilder,
-    pub font_cache: &'a FontCache,
-    pub text_layout_cache: &'a TextLayoutCache,
-    pub app: &'a AppContext,
-}
-
-impl<'a> PaintContext<'a> {
-    fn paint(&mut self, view_id: usize, origin: Vector2F, visible_bounds: RectF) {
-        if let Some(mut tree) = self.rendered_views.remove(&view_id) {
-            self.view_stack.push(view_id);
-            tree.paint(origin, visible_bounds, self);
-            self.rendered_views.insert(view_id, tree);
-            self.view_stack.pop();
-        }
-    }
-
-    #[inline]
-    pub fn paint_stacking_context<F>(
-        &mut self,
-        clip_bounds: Option<RectF>,
-        z_index: Option<usize>,
-        f: F,
-    ) where
-        F: FnOnce(&mut Self),
-    {
-        self.scene.push_stacking_context(clip_bounds, z_index);
-        f(self);
-        self.scene.pop_stacking_context();
-    }
-
-    #[inline]
-    pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
-    where
-        F: FnOnce(&mut Self),
-    {
-        self.scene.push_layer(clip_bounds);
-        f(self);
-        self.scene.pop_layer();
-    }
-
-    pub fn current_view_id(&self) -> usize {
-        *self.view_stack.last().unwrap()
-    }
-}
-
-impl<'a> Deref for PaintContext<'a> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        self.app
-    }
-}
-
-pub struct EventContext<'a> {
-    pub font_cache: &'a FontCache,
-    pub text_layout_cache: &'a TextLayoutCache,
-    pub app: &'a mut AppContext,
-    pub window_id: usize,
-    pub notify_count: usize,
-    view_stack: Vec<usize>,
-    handled: bool,
-    notified_views: &'a mut HashSet<usize>,
-}
-
-impl<'a> EventContext<'a> {
-    fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
-    where
-        F: FnOnce(&mut Self) -> T,
-    {
-        self.view_stack.push(view_id);
-        let result = f(self);
-        self.view_stack.pop();
-        result
-    }
-
-    pub fn window_id(&self) -> usize {
-        self.window_id
-    }
-
-    pub fn view_id(&self) -> Option<usize> {
-        self.view_stack.last().copied()
-    }
-
-    pub fn is_parent_view_focused(&self) -> bool {
-        if let Some(parent_view_id) = self.view_stack.last() {
-            self.app.focused_view_id(self.window_id) == Some(*parent_view_id)
-        } else {
-            false
-        }
-    }
-
-    pub fn focus_parent_view(&mut self) {
-        if let Some(parent_view_id) = self.view_stack.last() {
-            self.app.focus(self.window_id, Some(*parent_view_id))
-        }
-    }
-
-    pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
-        self.app
-            .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action)
-    }
-
-    pub fn dispatch_action<A: Action>(&mut self, action: A) {
-        self.dispatch_any_action(Box::new(action));
-    }
-
-    pub fn notify(&mut self) {
-        self.notify_count += 1;
-        if let Some(view_id) = self.view_stack.last() {
-            self.notified_views.insert(*view_id);
-        }
-    }
-
-    pub fn notify_count(&self) -> usize {
-        self.notify_count
-    }
-
-    pub fn propagate_event(&mut self) {
-        self.handled = false;
-    }
-}
-
-impl<'a> Deref for EventContext<'a> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        self.app
-    }
-}
-
-impl<'a> DerefMut for EventContext<'a> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        self.app
-    }
-}
-
-pub struct MeasurementContext<'a> {
-    app: &'a AppContext,
-    rendered_views: &'a HashMap<usize, ElementBox>,
-    pub window_id: usize,
-}
-
-impl<'a> Deref for MeasurementContext<'a> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        self.app
-    }
-}
-
-impl<'a> MeasurementContext<'a> {
-    fn rect_for_text_range(&self, view_id: usize, range_utf16: Range<usize>) -> Option<RectF> {
-        let element = self.rendered_views.get(&view_id)?;
-        element.rect_for_text_range(range_utf16, self)
-    }
-}
-
-pub struct DebugContext<'a> {
-    rendered_views: &'a HashMap<usize, ElementBox>,
-    pub font_cache: &'a FontCache,
-    pub app: &'a AppContext,
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum Axis {
-    #[default]
-    Horizontal,
-    Vertical,
-}
-
-impl Axis {
-    pub fn invert(self) -> Self {
-        match self {
-            Self::Horizontal => Self::Vertical,
-            Self::Vertical => Self::Horizontal,
-        }
-    }
-
-    pub fn component(&self, point: Vector2F) -> f32 {
-        match self {
-            Self::Horizontal => point.x(),
-            Self::Vertical => point.y(),
-        }
-    }
-}
-
-impl ToJson for Axis {
-    fn to_json(&self) -> serde_json::Value {
-        match self {
-            Axis::Horizontal => json!("horizontal"),
-            Axis::Vertical => json!("vertical"),
-        }
-    }
-}
-
-impl StaticColumnCount for Axis {}
-impl Bind for Axis {
-    fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
-        match self {
-            Axis::Horizontal => "Horizontal",
-            Axis::Vertical => "Vertical",
-        }
-        .bind(statement, start_index)
-    }
-}
-
-impl Column for Axis {
-    fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
-        String::column(statement, start_index).and_then(|(axis_text, next_index)| {
-            Ok((
-                match axis_text.as_str() {
-                    "Horizontal" => Axis::Horizontal,
-                    "Vertical" => Axis::Vertical,
-                    _ => bail!("Stored serialized item kind is incorrect"),
-                },
-                next_index,
-            ))
-        })
-    }
-}
-
-pub trait Vector2FExt {
-    fn along(self, axis: Axis) -> f32;
-}
-
-impl Vector2FExt for Vector2F {
-    fn along(self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.x(),
-            Axis::Vertical => self.y(),
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct SizeConstraint {
-    pub min: Vector2F,
-    pub max: Vector2F,
-}
-
-impl SizeConstraint {
-    pub fn new(min: Vector2F, max: Vector2F) -> Self {
-        Self { min, max }
-    }
-
-    pub fn strict(size: Vector2F) -> Self {
-        Self {
-            min: size,
-            max: size,
-        }
-    }
-
-    pub fn strict_along(axis: Axis, max: f32) -> Self {
-        match axis {
-            Axis::Horizontal => Self {
-                min: vec2f(max, 0.0),
-                max: vec2f(max, f32::INFINITY),
-            },
-            Axis::Vertical => Self {
-                min: vec2f(0.0, max),
-                max: vec2f(f32::INFINITY, max),
-            },
-        }
-    }
-
-    pub fn max_along(&self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.max.x(),
-            Axis::Vertical => self.max.y(),
-        }
-    }
-
-    pub fn min_along(&self, axis: Axis) -> f32 {
-        match axis {
-            Axis::Horizontal => self.min.x(),
-            Axis::Vertical => self.min.y(),
-        }
-    }
-
-    pub fn constrain(&self, size: Vector2F) -> Vector2F {
-        vec2f(
-            size.x().min(self.max.x()).max(self.min.x()),
-            size.y().min(self.max.y()).max(self.min.y()),
-        )
-    }
-}
-
-impl Default for SizeConstraint {
-    fn default() -> Self {
-        SizeConstraint {
-            min: Vector2F::zero(),
-            max: Vector2F::splat(f32::INFINITY),
-        }
-    }
-}
-
-impl ToJson for SizeConstraint {
-    fn to_json(&self) -> serde_json::Value {
-        json!({
-            "min": self.min.to_json(),
-            "max": self.max.to_json(),
-        })
-    }
-}
-
-pub struct ChildView {
-    view: AnyWeakViewHandle,
-    view_name: &'static str,
-}
-
-impl ChildView {
-    pub fn new(view: &AnyViewHandle, cx: &AppContext) -> Self {
-        let view_name = cx.view_ui_name(view.window_id(), view.id()).unwrap();
-        Self {
-            view: view.downgrade(),
-            view_name,
-        }
-    }
-}
-
-impl Element for ChildView {
-    type LayoutState = bool;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        cx: &mut LayoutContext,
-    ) -> (Vector2F, Self::LayoutState) {
-        if cx.rendered_views.contains_key(&self.view.id()) {
-            let size = cx.layout(self.view.id(), constraint);
-            (size, true)
-        } else {
-            log::error!(
-                "layout called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
-                self.view.id(),
-                self.view_name
-            );
-            (Vector2F::zero(), false)
-        }
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        view_is_valid: &mut Self::LayoutState,
-        cx: &mut PaintContext,
-    ) {
-        if *view_is_valid {
-            cx.paint(self.view.id(), bounds.origin(), visible_bounds);
-        } else {
-            log::error!(
-                "paint called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
-                self.view.id(),
-                self.view_name
-            );
-        }
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        view_is_valid: &Self::LayoutState,
-        _: &Self::PaintState,
-        cx: &MeasurementContext,
-    ) -> Option<RectF> {
-        if *view_is_valid {
-            cx.rect_for_text_range(self.view.id(), range_utf16)
-        } else {
-            log::error!(
-                "rect_for_text_range called on a ChildView element whose underlying view was dropped (view_id: {}, name: {:?})",
-                self.view.id(),
-                self.view_name
-            );
-            None
-        }
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        cx: &DebugContext,
-    ) -> serde_json::Value {
-        json!({
-            "type": "ChildView",
-            "view_id": self.view.id(),
-            "bounds": bounds.to_json(),
-            "view": if let Some(view) = self.view.upgrade(cx.app) {
-                view.debug_json(cx.app)
-            } else {
-                json!(null)
-            },
-            "child": if let Some(view) = cx.rendered_views.get(&self.view.id()) {
-                view.debug(cx)
-            } else {
-                json!(null)
-            }
-        })
-    }
-}

crates/gpui/src/scene.rs 🔗

@@ -236,6 +236,19 @@ impl SceneBuilder {
         self.scale_factor
     }
 
+    pub fn paint_stacking_context<F>(
+        &mut self,
+        clip_bounds: Option<RectF>,
+        z_index: Option<usize>,
+        f: F,
+    ) where
+        F: FnOnce(&mut Self),
+    {
+        self.push_stacking_context(clip_bounds, z_index);
+        f(self);
+        self.pop_stacking_context();
+    }
+
     pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
         let z_index = z_index.unwrap_or_else(|| self.active_stacking_context().z_index + 1);
         self.active_stacking_context_stack
@@ -249,6 +262,15 @@ impl SceneBuilder {
         assert!(!self.active_stacking_context_stack.is_empty());
     }
 
+    pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
+    where
+        F: FnOnce(&mut Self),
+    {
+        self.push_layer(clip_bounds);
+        f(self);
+        self.pop_layer();
+    }
+
     pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
         self.active_stacking_context().push_layer(clip_bounds);
     }

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

@@ -1,11 +1,13 @@
-use std::{any::TypeId, fmt::Debug, mem::Discriminant, rc::Rc};
-
+use crate::{platform::MouseButton, window::WindowContext, EventContext, View, ViewContext};
 use collections::HashMap;
-
 use pathfinder_geometry::rect::RectF;
 use smallvec::SmallVec;
-
-use crate::{platform::MouseButton, EventContext};
+use std::{
+    any::{Any, TypeId},
+    fmt::Debug,
+    mem::Discriminant,
+    rc::Rc,
+};
 
 use super::{
     mouse_event::{
@@ -60,82 +62,92 @@ impl MouseRegion {
         }
     }
 
-    pub fn on_down(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDown, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_down(button, handler);
         self
     }
 
-    pub fn on_up(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseUp, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_up(button, handler);
         self
     }
 
-    pub fn on_click(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseClick, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_click(button, handler);
         self
     }
 
-    pub fn on_down_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_down_out(button, handler);
         self
     }
 
-    pub fn on_up_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_up_out(button, handler);
         self
     }
 
-    pub fn on_drag(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_drag(button, handler);
         self
     }
 
-    pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
+    pub fn on_hover<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_hover(handler);
         self
     }
 
-    pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
+    pub fn on_move<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_move(handler);
         self
     }
 
-    pub fn on_move_out(
-        mut self,
-        handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_move_out<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_move_out(handler);
         self
     }
 
-    pub fn on_scroll(
-        mut self,
-        handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_scroll<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.handlers = self.handlers.on_scroll(handler);
         self
     }
@@ -186,7 +198,7 @@ impl MouseRegionId {
     }
 }
 
-pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut EventContext)>;
+pub type HandlerCallback = Rc<dyn Fn(MouseEvent, &mut dyn Any, &mut WindowContext, usize) -> bool>;
 
 #[derive(Clone, PartialEq, Eq, Hash)]
 pub struct HandlerKey {
@@ -211,41 +223,41 @@ impl HandlerSet {
 
         set.insert(
             HandlerKey::new(MouseEvent::move_disc(), None),
-            SmallVec::from_buf([Rc::new(|_, _| {})]),
+            SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
         );
         set.insert(
             HandlerKey::new(MouseEvent::hover_disc(), None),
-            SmallVec::from_buf([Rc::new(|_, _| {})]),
+            SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
         );
         for button in MouseButton::all() {
             set.insert(
                 HandlerKey::new(MouseEvent::drag_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _| {})]),
+                SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
             );
             set.insert(
                 HandlerKey::new(MouseEvent::down_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _| {})]),
+                SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
             );
             set.insert(
                 HandlerKey::new(MouseEvent::up_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _| {})]),
+                SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
             );
             set.insert(
                 HandlerKey::new(MouseEvent::click_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _| {})]),
+                SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
             );
             set.insert(
                 HandlerKey::new(MouseEvent::down_out_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _| {})]),
+                SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
             );
             set.insert(
                 HandlerKey::new(MouseEvent::up_out_disc(), Some(button)),
-                SmallVec::from_buf([Rc::new(|_, _| {})]),
+                SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
             );
         }
         set.insert(
             HandlerKey::new(MouseEvent::scroll_wheel_disc(), None),
-            SmallVec::from_buf([Rc::new(|_, _| {})]),
+            SmallVec::from_buf([Rc::new(|_, _, _, _| false)]),
         );
 
         HandlerSet { set }
@@ -283,11 +295,19 @@ impl HandlerSet {
         }
     }
 
-    pub fn on_move(mut self, handler: impl Fn(MouseMove, &mut EventContext) + 'static) -> Self {
+    pub fn on_move<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseMove, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::move_disc(), None,
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::Move(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
@@ -297,14 +317,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_move_out(
-        mut self,
-        handler: impl Fn(MouseMoveOut, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_move_out<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseMoveOut, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::move_out_disc(), None,
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::MoveOut(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::<V>::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::MoveOut, found {:?}",
@@ -314,15 +339,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_down(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDown, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_down<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseDown, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::down_disc(), Some(button),
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::Down(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
@@ -332,15 +361,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_up(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseUp, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_up<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseUp, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::up_disc(), Some(button),
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::Up(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
@@ -350,15 +383,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_click(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseClick, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_click<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::click_disc(), Some(button),
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::Click(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
@@ -368,15 +405,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_down_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDownOut, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_down_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseDownOut, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::down_out_disc(), Some(button),
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::DownOut(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
@@ -386,15 +427,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_up_out(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseUpOut, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_up_out<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseUpOut, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::up_out_disc(), Some(button),
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::UpOut(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
@@ -404,15 +449,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_drag(
-        mut self,
-        button: MouseButton,
-        handler: impl Fn(MouseDrag, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_drag<V, F>(mut self, button: MouseButton, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseDrag, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::drag_disc(), Some(button),
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::Drag(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
@@ -422,11 +471,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_hover(mut self, handler: impl Fn(MouseHover, &mut EventContext) + 'static) -> Self {
+    pub fn on_hover<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseHover, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::hover_disc(), None,
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::Hover(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
@@ -436,14 +493,19 @@ impl HandlerSet {
         self
     }
 
-    pub fn on_scroll(
-        mut self,
-        handler: impl Fn(MouseScrollWheel, &mut EventContext) + 'static,
-    ) -> Self {
+    pub fn on_scroll<V, F>(mut self, handler: F) -> Self
+    where
+        V: View,
+        F: Fn(MouseScrollWheel, &mut V, &mut EventContext<V>) + 'static,
+    {
         self.insert(MouseEvent::scroll_wheel_disc(), None,
-            Rc::new(move |region_event, cx| {
+            Rc::new(move |region_event, view, cx, view_id| {
                 if let MouseEvent::ScrollWheel(e) = region_event {
-                    handler(e, cx);
+                    let view = view.downcast_mut().unwrap();
+                    let mut cx = ViewContext::mutable(cx, view_id);
+                    let mut cx = EventContext::new(&mut cx);
+                    handler(e, view, &mut cx);
+                    cx.handled
                 } else {
                     panic!(
                         "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}",

crates/gpui/src/test.rs 🔗

@@ -19,8 +19,8 @@ use crate::{
     platform,
     platform::Platform,
     util::CwdBacktrace,
-    AppContext, Element, ElementBox, Entity, FontCache, Handle, RenderContext, Subscription,
-    TestAppContext, View,
+    AppContext, Drawable, Element, Entity, FontCache, Handle, Subscription, TestAppContext, View,
+    ViewContext,
 };
 
 #[cfg(test)]
@@ -242,7 +242,7 @@ impl View for EmptyView {
         "empty view"
     }
 
-    fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
-        Element::boxed(Empty::new())
+    fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
+        Drawable::boxed(Empty::new())
     }
 }

crates/gpui/src/text_layout.rs 🔗

@@ -7,7 +7,9 @@ use crate::{
     },
     platform,
     platform::FontSystem,
-    scene, PaintContext,
+    scene,
+    window::WindowContext,
+    SceneBuilder,
 };
 use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -271,10 +273,11 @@ impl Line {
 
     pub fn paint(
         &self,
+        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         line_height: f32,
-        cx: &mut PaintContext,
+        cx: &mut WindowContext,
     ) {
         let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
         let baseline_offset = vec2f(0., padding_top + self.layout.ascent);
@@ -331,7 +334,7 @@ impl Line {
                 }
 
                 if let Some((underline_origin, underline_style)) = finished_underline {
-                    cx.scene.push_underline(scene::Underline {
+                    scene.push_underline(scene::Underline {
                         origin: underline_origin,
                         width: glyph_origin.x() - underline_origin.x(),
                         thickness: underline_style.thickness.into(),
@@ -341,14 +344,14 @@ impl Line {
                 }
 
                 if glyph.is_emoji {
-                    cx.scene.push_image_glyph(scene::ImageGlyph {
+                    scene.push_image_glyph(scene::ImageGlyph {
                         font_id: run.font_id,
                         font_size: self.layout.font_size,
                         id: glyph.id,
                         origin: glyph_origin,
                     });
                 } else {
-                    cx.scene.push_glyph(scene::Glyph {
+                    scene.push_glyph(scene::Glyph {
                         font_id: run.font_id,
                         font_size: self.layout.font_size,
                         id: glyph.id,
@@ -361,7 +364,7 @@ impl Line {
 
         if let Some((underline_start, underline_style)) = underline.take() {
             let line_end_x = origin.x() + self.layout.width;
-            cx.scene.push_underline(scene::Underline {
+            scene.push_underline(scene::Underline {
                 origin: underline_start,
                 width: line_end_x - underline_start.x(),
                 color: underline_style.color.unwrap(),
@@ -373,11 +376,12 @@ impl Line {
 
     pub fn paint_wrapped(
         &self,
+        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         line_height: f32,
         boundaries: impl IntoIterator<Item = ShapedBoundary>,
-        cx: &mut PaintContext,
+        cx: &mut WindowContext,
     ) {
         let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
         let baseline_origin = vec2f(0., padding_top + self.layout.ascent);
@@ -416,14 +420,14 @@ impl Line {
                 );
                 if glyph_bounds.intersects(visible_bounds) {
                     if glyph.is_emoji {
-                        cx.scene.push_image_glyph(scene::ImageGlyph {
+                        scene.push_image_glyph(scene::ImageGlyph {
                             font_id: run.font_id,
                             font_size: self.layout.font_size,
                             id: glyph.id,
                             origin: glyph_bounds.origin() + baseline_origin,
                         });
                     } else {
-                        cx.scene.push_glyph(scene::Glyph {
+                        scene.push_glyph(scene::Glyph {
                             font_id: run.font_id,
                             font_size: self.layout.font_size,
                             id: glyph.id,

crates/gpui/src/views/select.rs 🔗

@@ -1,13 +1,13 @@
 use serde::Deserialize;
 
 use crate::{
-    actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, RenderContext,
+    actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, EventContext,
     View, ViewContext, WeakViewHandle,
 };
 
 pub struct Select {
     handle: WeakViewHandle<Self>,
-    render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> ElementBox>,
+    render_item: Box<dyn Fn(usize, ItemType, bool, &AppContext) -> Element<Self>>,
     selected_item_ix: usize,
     item_count: usize,
     is_open: bool,
@@ -41,7 +41,7 @@ pub fn init(cx: &mut AppContext) {
 }
 
 impl Select {
-    pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> ElementBox>(
+    pub fn new<F: 'static + Fn(usize, ItemType, bool, &AppContext) -> Element<Self>>(
         item_count: usize,
         cx: &mut ViewContext<Self>,
         render_item: F,
@@ -92,7 +92,7 @@ impl View for Select {
         "Select"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if self.item_count == 0 {
             return Empty::new().boxed();
         }
@@ -106,7 +106,7 @@ impl View for Select {
             Default::default()
         };
         let mut result = Flex::column().with_child(
-            MouseEventHandler::<Header>::new(self.handle.id(), cx, |mouse_state, cx| {
+            MouseEventHandler::<Header, _>::new(self.handle.id(), cx, |mouse_state, cx| {
                 Container::new((self.render_item)(
                     self.selected_item_ix,
                     ItemType::Header,
@@ -116,9 +116,10 @@ impl View for Select {
                 .with_style(style.header)
                 .boxed()
             })
-            .on_click(MouseButton::Left, move |_, cx| {
-                cx.dispatch_action(ToggleSelect)
-            })
+            .on_click(
+                MouseButton::Left,
+                move |_, _, cx: &mut EventContext<Self>| cx.dispatch_action(ToggleSelect),
+            )
             .boxed(),
         );
         if self.is_open {
@@ -134,21 +135,28 @@ impl View for Select {
                                     let selected_item_ix = this.selected_item_ix;
                                     range.end = range.end.min(this.item_count);
                                     items.extend(range.map(|ix| {
-                                        MouseEventHandler::<Item>::new(ix, cx, |mouse_state, cx| {
-                                            (this.render_item)(
-                                                ix,
-                                                if ix == selected_item_ix {
-                                                    ItemType::Selected
-                                                } else {
-                                                    ItemType::Unselected
-                                                },
-                                                mouse_state.hovered(),
-                                                cx,
-                                            )
-                                        })
-                                        .on_click(MouseButton::Left, move |_, cx| {
-                                            cx.dispatch_action(SelectItem(ix))
-                                        })
+                                        MouseEventHandler::<Item, _>::new(
+                                            ix,
+                                            cx,
+                                            |mouse_state, cx| {
+                                                (this.render_item)(
+                                                    ix,
+                                                    if ix == selected_item_ix {
+                                                        ItemType::Selected
+                                                    } else {
+                                                        ItemType::Unselected
+                                                    },
+                                                    mouse_state.hovered(),
+                                                    cx,
+                                                )
+                                            },
+                                        )
+                                        .on_click(
+                                            MouseButton::Left,
+                                            move |_, _, cx: &mut EventContext<Self>| {
+                                                cx.dispatch_action(SelectItem(ix))
+                                            },
+                                        )
                                         .boxed()
                                     }))
                                 },

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -174,23 +174,60 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
             }
         }
     } else {
+        // Pass to the test function the number of app contexts that it needs,
+        // based on its parameter list.
+        let mut cx_vars = proc_macro2::TokenStream::new();
+        let mut cx_teardowns = proc_macro2::TokenStream::new();
         let mut inner_fn_args = proc_macro2::TokenStream::new();
-        for arg in inner_fn.sig.inputs.iter() {
+        for (ix, arg) in inner_fn.sig.inputs.iter().enumerate() {
             if let FnArg::Typed(arg) = arg {
                 if let Type::Path(ty) = &*arg.ty {
                     let last_segment = ty.path.segments.last();
 
                     if let Some("StdRng") = last_segment.map(|s| s.ident.to_string()).as_deref() {
                         inner_fn_args.extend(quote!(rand::SeedableRng::seed_from_u64(seed),));
+                        continue;
+                    }
+                } else if let Type::Reference(ty) = &*arg.ty {
+                    if let Type::Path(ty) = &*ty.elem {
+                        let last_segment = ty.path.segments.last();
+                        match last_segment.map(|s| s.ident.to_string()).as_deref() {
+                            Some("AppContext") => {
+                                inner_fn_args.extend(quote!(cx,));
+                                continue;
+                            }
+                            Some("TestAppContext") => {
+                                let first_entity_id = ix * 100_000;
+                                let cx_varname = format_ident!("cx_{}", ix);
+                                cx_vars.extend(quote!(
+                                    let mut #cx_varname = #namespace::TestAppContext::new(
+                                        foreground_platform.clone(),
+                                        cx.platform().clone(),
+                                        deterministic.build_foreground(#ix),
+                                        deterministic.build_background(),
+                                        cx.font_cache().clone(),
+                                        cx.leak_detector(),
+                                        #first_entity_id,
+                                        stringify!(#outer_fn_name).to_string(),
+                                    );
+                                ));
+                                cx_teardowns.extend(quote!(
+                                    #cx_varname.update(|cx| cx.remove_all_windows());
+                                    deterministic.run_until_parked();
+                                    #cx_varname.update(|cx| cx.clear_globals());
+                                ));
+                                inner_fn_args.extend(quote!(&mut #cx_varname,));
+                                continue;
+                            }
+                            _ => {}
+                        }
                     }
-                } else {
-                    inner_fn_args.extend(quote!(cx,));
                 }
-            } else {
-                return TokenStream::from(
-                    syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
-                );
             }
+
+            return TokenStream::from(
+                syn::Error::new_spanned(arg, "invalid argument").into_compile_error(),
+            );
         }
 
         parse_quote! {
@@ -203,7 +240,11 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
                     #starting_seed as u64,
                     #max_retries,
                     #detect_nondeterminism,
-                    &mut |cx, _, _, seed| #inner_fn_name(#inner_fn_args),
+                    &mut |cx, foreground_platform, deterministic, seed| {
+                        #cx_vars
+                        #inner_fn_name(#inner_fn_args);
+                        #cx_teardowns
+                    },
                     #on_failure_fn_name,
                     stringify!(#outer_fn_name).to_string(),
                 );

crates/journal/src/journal.rs 🔗

@@ -7,7 +7,6 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use util::TryFutureExt as _;
 use workspace::AppState;
 
 actions!(journal, [NewJournalEntry]);
@@ -44,40 +43,37 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
         Ok::<_, std::io::Error>((journal_dir, entry_path))
     });
 
-    cx.spawn(|mut cx| {
-        async move {
-            let (journal_dir, entry_path) = create_entry.await?;
-            let (workspace, _) = cx
-                .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
-                .await;
-
-            let opened = workspace
-                .update(&mut cx, |workspace, cx| {
-                    workspace.open_paths(vec![entry_path], true, cx)
-                })
-                .await;
-
-            if let Some(Some(Ok(item))) = opened.first() {
-                if let Some(editor) = item.downcast::<Editor>() {
-                    editor.update(&mut cx, |editor, cx| {
-                        let len = editor.buffer().read(cx).len(cx);
-                        editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                            s.select_ranges([len..len])
-                        });
-                        if len > 0 {
-                            editor.insert("\n\n", cx);
-                        }
-                        editor.insert(&entry_heading, cx);
-                        editor.insert("\n\n", cx);
+    cx.spawn(|mut cx| async move {
+        let (journal_dir, entry_path) = create_entry.await?;
+        let (workspace, _) = cx
+            .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))
+            .await?;
+
+        let opened = workspace
+            .update(&mut cx, |workspace, cx| {
+                workspace.open_paths(vec![entry_path], true, cx)
+            })?
+            .await;
+
+        if let Some(Some(Ok(item))) = opened.first() {
+            if let Some(editor) = item.downcast::<Editor>() {
+                editor.update(&mut cx, |editor, cx| {
+                    let len = editor.buffer().read(cx).len(cx);
+                    editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+                        s.select_ranges([len..len])
                     });
-                }
+                    if len > 0 {
+                        editor.insert("\n\n", cx);
+                    }
+                    editor.insert(&entry_heading, cx);
+                    editor.insert("\n\n", cx);
+                })?;
             }
-
-            anyhow::Ok(())
         }
-        .log_err()
+
+        anyhow::Ok(())
     })
-    .detach();
+    .detach_and_log_err(cx);
 }
 
 fn journal_dir(settings: &Settings) -> Option<PathBuf> {

crates/language_selector/Cargo.toml 🔗

@@ -17,5 +17,6 @@ picker = { path = "../picker" }
 project = { path = "../project" }
 theme = { path = "../theme" }
 settings = { path = "../settings" }
+util = { path = "../util" }
 workspace = { path = "../workspace" }
 anyhow = "1.0"

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -2,7 +2,7 @@ use editor::Editor;
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
+    Entity, Subscription, View, ViewContext, ViewHandle,
 };
 use settings::Settings;
 use std::sync::Arc;
@@ -50,7 +50,7 @@ impl View for ActiveBufferLanguage {
         "ActiveBufferLanguage"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if let Some(active_language) = self.active_language.as_ref() {
             let active_language_text = if let Some(active_language_text) = active_language {
                 active_language_text.to_string()
@@ -58,7 +58,7 @@ impl View for ActiveBufferLanguage {
                 "Unknown".to_string()
             };
 
-            MouseEventHandler::<Self>::new(0, cx, |state, cx| {
+            MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
                 let theme = &cx.global::<Settings>().theme.workspace.status_bar;
                 let style = theme.active_language.style_for(state, false);
                 Label::new(active_language_text, style.text.clone())
@@ -67,7 +67,9 @@ impl View for ActiveBufferLanguage {
                     .boxed()
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(crate::Toggle))
+            .on_click(MouseButton::Left, |_, _, cx| {
+                cx.dispatch_action(crate::Toggle)
+            })
             .boxed()
         } else {
             Empty::new().boxed()

crates/language_selector/src/language_selector.rs 🔗

@@ -1,55 +1,64 @@
 mod active_buffer_language;
 
 pub use active_buffer_language::ActiveBufferLanguage;
+use anyhow::anyhow;
 use editor::Editor;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
-    RenderContext, View, ViewContext, ViewHandle,
-};
+use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContext};
 use language::{Buffer, LanguageRegistry};
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use project::Project;
 use settings::Settings;
 use std::sync::Arc;
+use util::ResultExt;
 use workspace::{AppState, Workspace};
 
 actions!(language_selector, [Toggle]);
 
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    Picker::<LanguageSelector>::init(cx);
+    Picker::<LanguageSelectorDelegate>::init(cx);
     cx.add_action({
         let language_registry = app_state.languages.clone();
-        move |workspace, _: &Toggle, cx| {
-            LanguageSelector::toggle(workspace, language_registry.clone(), cx)
-        }
+        move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx)
     });
 }
 
-pub enum Event {
-    Dismissed,
+fn toggle(
+    workspace: &mut Workspace,
+    registry: Arc<LanguageRegistry>,
+    cx: &mut ViewContext<Workspace>,
+) -> Option<()> {
+    let (_, buffer, _) = workspace
+        .active_item(cx)?
+        .act_as::<Editor>(cx)?
+        .read(cx)
+        .active_excerpt(cx)?;
+    workspace.toggle_modal(cx, |workspace, cx| {
+        cx.add_view(|cx| {
+            Picker::new(
+                LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),
+                cx,
+            )
+        })
+    });
+    Some(())
 }
 
-pub struct LanguageSelector {
+pub struct LanguageSelectorDelegate {
     buffer: ModelHandle<Buffer>,
     project: ModelHandle<Project>,
     language_registry: Arc<LanguageRegistry>,
     candidates: Vec<StringMatchCandidate>,
     matches: Vec<StringMatch>,
-    picker: ViewHandle<Picker<Self>>,
     selected_index: usize,
 }
 
-impl LanguageSelector {
+impl LanguageSelectorDelegate {
     fn new(
         buffer: ModelHandle<Buffer>,
         project: ModelHandle<Project>,
         language_registry: Arc<LanguageRegistry>,
-        cx: &mut ViewContext<Self>,
     ) -> Self {
-        let handle = cx.weak_handle();
-        let picker = cx.add_view(|cx| Picker::new("Select Language...", handle, cx));
-
         let candidates = language_registry
             .language_names()
             .into_iter()
@@ -73,104 +82,63 @@ impl LanguageSelector {
             language_registry,
             candidates,
             matches,
-            picker,
             selected_index: 0,
         }
     }
-
-    fn toggle(
-        workspace: &mut Workspace,
-        registry: Arc<LanguageRegistry>,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        if let Some((_, buffer, _)) = workspace
-            .active_item(cx)
-            .and_then(|active_item| active_item.act_as::<Editor>(cx))
-            .and_then(|editor| editor.read(cx).active_excerpt(cx))
-        {
-            workspace.toggle_modal(cx, |workspace, cx| {
-                let project = workspace.project().clone();
-                let this = cx.add_view(|cx| Self::new(buffer, project, registry, cx));
-                cx.subscribe(&this, Self::on_event).detach();
-                this
-            });
-        }
-    }
-
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<LanguageSelector>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => {
-                workspace.dismiss_modal(cx);
-            }
-        }
-    }
 }
 
-impl Entity for LanguageSelector {
-    type Event = Event;
-}
-
-impl View for LanguageSelector {
-    fn ui_name() -> &'static str {
-        "LanguageSelector"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for LanguageSelectorDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Select a language...".into()
     }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl PickerDelegate for LanguageSelector {
     fn match_count(&self) -> usize {
         self.matches.len()
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>) {
         if let Some(mat) = self.matches.get(self.selected_index) {
             let language_name = &self.candidates[mat.candidate_id].string;
             let language = self.language_registry.language_for_name(language_name);
-            cx.spawn(|this, mut cx| async move {
+            let project = self.project.downgrade();
+            let buffer = self.buffer.downgrade();
+            cx.spawn_weak(|_, mut cx| async move {
                 let language = language.await?;
-                this.update(&mut cx, |this, cx| {
-                    this.project.update(cx, |project, cx| {
-                        project.set_language_for_buffer(&this.buffer, language, cx);
-                    });
+                let project = project
+                    .upgrade(&cx)
+                    .ok_or_else(|| anyhow!("project was dropped"))?;
+                let buffer = buffer
+                    .upgrade(&cx)
+                    .ok_or_else(|| anyhow!("buffer was dropped"))?;
+                project.update(&mut cx, |project, cx| {
+                    project.set_language_for_buffer(&buffer, language, cx);
                 });
                 anyhow::Ok(())
             })
             .detach_and_log_err(cx);
         }
 
-        cx.emit(Event::Dismissed);
+        cx.emit(PickerEvent::Dismiss);
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
-    }
+    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 
     fn selected_index(&self) -> usize {
         self.selected_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
         self.selected_index = ix;
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut ViewContext<Picker<Self>>,
+    ) -> gpui::Task<()> {
         let background = cx.background().clone();
         let candidates = self.candidates.clone();
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn_weak(|this, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -194,13 +162,17 @@ impl PickerDelegate for LanguageSelector {
                 .await
             };
 
-            this.update(&mut cx, |this, cx| {
-                this.matches = matches;
-                this.selected_index = this
-                    .selected_index
-                    .min(this.matches.len().saturating_sub(1));
-                cx.notify();
-            });
+            if let Some(this) = this.upgrade(&cx) {
+                this.update(&mut cx, |this, cx| {
+                    let delegate = this.delegate_mut();
+                    delegate.matches = matches;
+                    delegate.selected_index = delegate
+                        .selected_index
+                        .min(delegate.matches.len().saturating_sub(1));
+                    cx.notify();
+                })
+                .log_err();
+            }
         })
     }
 
@@ -210,7 +182,7 @@ impl PickerDelegate for LanguageSelector {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let settings = cx.global::<Settings>();
         let theme = &settings.theme;
         let mat = &self.matches[ix];

crates/outline/src/outline.rs 🔗

@@ -4,25 +4,51 @@ use editor::{
 };
 use fuzzy::StringMatch;
 use gpui::{
-    actions, elements::*, geometry::vector::Vector2F, AnyViewHandle, AppContext, Entity,
-    MouseState, RenderContext, Task, View, ViewContext, ViewHandle,
+    actions, elements::*, geometry::vector::Vector2F, AppContext, MouseState, Task, ViewContext,
+    ViewHandle, WindowContext,
 };
 use language::Outline;
 use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
-use std::cmp::{self, Reverse};
+use std::{
+    cmp::{self, Reverse},
+    sync::Arc,
+};
 use workspace::Workspace;
 
 actions!(outline, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(OutlineView::toggle);
-    Picker::<OutlineView>::init(cx);
+    cx.add_action(toggle);
+    OutlineView::init(cx);
+}
+
+fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+    if let Some(editor) = workspace
+        .active_item(cx)
+        .and_then(|item| item.downcast::<Editor>())
+    {
+        let outline = editor
+            .read(cx)
+            .buffer()
+            .read(cx)
+            .snapshot(cx)
+            .outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
+        if let Some(outline) = outline {
+            workspace.toggle_modal(cx, |_, cx| {
+                cx.add_view(|cx| {
+                    OutlineView::new(OutlineViewDelegate::new(outline, editor, cx), cx)
+                        .with_max_size(800., 1200.)
+                })
+            });
+        }
+    }
 }
 
-struct OutlineView {
-    picker: ViewHandle<Picker<Self>>,
+type OutlineView = Picker<OutlineViewDelegate>;
+
+struct OutlineViewDelegate {
     active_editor: ViewHandle<Editor>,
     outline: Outline<Anchor>,
     selected_match_index: usize,
@@ -31,45 +57,13 @@ struct OutlineView {
     last_query: String,
 }
 
-pub enum Event {
-    Dismissed,
-}
-
-impl Entity for OutlineView {
-    type Event = Event;
-
-    fn release(&mut self, cx: &mut AppContext) {
-        self.restore_active_editor(cx);
-    }
-}
-
-impl View for OutlineView {
-    fn ui_name() -> &'static str {
-        "OutlineView"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl OutlineView {
+impl OutlineViewDelegate {
     fn new(
         outline: Outline<Anchor>,
         editor: ViewHandle<Editor>,
-        cx: &mut ViewContext<Self>,
+        cx: &mut ViewContext<OutlineView>,
     ) -> Self {
-        let handle = cx.weak_handle();
         Self {
-            picker: cx.add_view(|cx| {
-                Picker::new("Search buffer symbols...", handle, cx).with_max_size(800., 1200.)
-            }),
             last_query: Default::default(),
             matches: Default::default(),
             selected_match_index: 0,
@@ -79,28 +73,7 @@ impl OutlineView {
         }
     }
 
-    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-        if let Some(editor) = workspace
-            .active_item(cx)
-            .and_then(|item| item.downcast::<Editor>())
-        {
-            let outline = editor
-                .read(cx)
-                .buffer()
-                .read(cx)
-                .snapshot(cx)
-                .outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
-            if let Some(outline) = outline {
-                workspace.toggle_modal(cx, |_, cx| {
-                    let view = cx.add_view(|cx| OutlineView::new(outline, editor, cx));
-                    cx.subscribe(&view, Self::on_event).detach();
-                    view
-                });
-            }
-        }
-    }
-
-    fn restore_active_editor(&mut self, cx: &mut AppContext) {
+    fn restore_active_editor(&mut self, cx: &mut WindowContext) {
         self.active_editor.update(cx, |editor, cx| {
             editor.highlight_rows(None);
             if let Some(scroll_position) = self.prev_scroll_position {
@@ -109,7 +82,7 @@ impl OutlineView {
         })
     }
 
-    fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, navigate: bool, cx: &mut ViewContext<OutlineView>) {
         self.selected_match_index = ix;
         if navigate && !self.matches.is_empty() {
             let selected_match = &self.matches[self.selected_match_index];
@@ -125,22 +98,14 @@ impl OutlineView {
                 active_editor.request_autoscroll(Autoscroll::center(), cx);
             });
         }
-        cx.notify();
     }
+}
 
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<Self>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => workspace.dismiss_modal(cx),
-        }
+impl PickerDelegate for OutlineViewDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Search buffer symbols...".into()
     }
-}
 
-impl PickerDelegate for OutlineView {
     fn match_count(&self) -> usize {
         self.matches.len()
     }
@@ -149,7 +114,7 @@ impl PickerDelegate for OutlineView {
         self.selected_match_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<OutlineView>) {
         self.set_selected_index(ix, true, cx);
     }
 
@@ -157,7 +122,7 @@ impl PickerDelegate for OutlineView {
         true
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<OutlineView>) -> Task<()> {
         let selected_index;
         if query.is_empty() {
             self.restore_active_editor(cx);
@@ -213,7 +178,7 @@ impl PickerDelegate for OutlineView {
         Task::ready(())
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<OutlineView>) {
         self.prev_scroll_position.take();
         self.active_editor.update(cx, |active_editor, cx| {
             if let Some(rows) = active_editor.highlighted_rows() {
@@ -224,12 +189,11 @@ impl PickerDelegate for OutlineView {
                 });
             }
         });
-        cx.emit(Event::Dismissed);
+        cx.emit(PickerEvent::Dismiss);
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+    fn dismissed(&mut self, cx: &mut ViewContext<OutlineView>) {
         self.restore_active_editor(cx);
-        cx.emit(Event::Dismissed);
     }
 
     fn render_match(
@@ -238,7 +202,7 @@ impl PickerDelegate for OutlineView {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let settings = cx.global::<Settings>();
         let string_match = &self.matches[ix];
         let style = settings.theme.picker.item.style_for(mouse_state, selected);

crates/picker/src/picker.rs 🔗

@@ -4,43 +4,51 @@ use gpui::{
     geometry::vector::{vec2f, Vector2F},
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton},
-    AnyViewHandle, AppContext, Axis, Entity, MouseState, RenderContext, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    AnyViewHandle, AppContext, Axis, Element, Entity, MouseState, Task, View, ViewContext,
+    ViewHandle,
 };
 use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
 use parking_lot::Mutex;
 use std::{cmp, sync::Arc};
+use util::ResultExt;
+use workspace::Modal;
+
+pub enum PickerEvent {
+    Dismiss,
+}
 
 pub struct Picker<D: PickerDelegate> {
-    delegate: WeakViewHandle<D>,
+    delegate: D,
     query_editor: ViewHandle<Editor>,
     list_state: UniformListState,
     max_size: Vector2F,
     theme: Arc<Mutex<Box<dyn Fn(&theme::Theme) -> theme::Picker>>>,
     confirmed: bool,
+    pending_update_matches: Task<Option<()>>,
 }
 
-pub trait PickerDelegate: View {
+pub trait PickerDelegate: Sized + 'static {
+    fn placeholder_text(&self) -> Arc<str>;
     fn match_count(&self) -> usize;
     fn selected_index(&self) -> usize;
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>);
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
-    fn confirm(&mut self, cx: &mut ViewContext<Self>);
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>);
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
+    fn confirm(&mut self, cx: &mut ViewContext<Picker<Self>>);
+    fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>);
     fn render_match(
         &self,
         ix: usize,
         state: &mut MouseState,
         selected: bool,
         cx: &AppContext,
-    ) -> ElementBox;
+    ) -> Element<Picker<Self>>;
     fn center_selection_after_match_updates(&self) -> bool {
         false
     }
 }
 
 impl<D: PickerDelegate> Entity for Picker<D> {
-    type Event = ();
+    type Event = PickerEvent;
 }
 
 impl<D: PickerDelegate> View for Picker<D> {
@@ -48,15 +56,10 @@ impl<D: PickerDelegate> View for Picker<D> {
         "Picker"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
         let query = self.query(cx);
-        let delegate = self.delegate.clone();
-        let match_count = if let Some(delegate) = delegate.upgrade(cx.app) {
-            delegate.read(cx).match_count()
-        } else {
-            0
-        };
+        let match_count = self.delegate.match_count();
 
         let container_style;
         let editor_style;
@@ -93,19 +96,16 @@ impl<D: PickerDelegate> View for Picker<D> {
                         match_count,
                         cx,
                         move |this, mut range, items, cx| {
-                            let delegate = this.delegate.upgrade(cx).unwrap();
-                            let selected_ix = delegate.read(cx).selected_index();
-                            range.end = cmp::min(range.end, delegate.read(cx).match_count());
+                            let selected_ix = this.delegate.selected_index();
+                            range.end = cmp::min(range.end, this.delegate.match_count());
                             items.extend(range.map(move |ix| {
-                                MouseEventHandler::<D>::new(ix, cx, |state, cx| {
-                                    delegate
-                                        .read(cx)
-                                        .render_match(ix, state, ix == selected_ix, cx)
+                                MouseEventHandler::<D, _>::new(ix, cx, |state, cx| {
+                                    this.delegate.render_match(ix, state, ix == selected_ix, cx)
                                 })
                                 // Capture mouse events
-                                .on_down(MouseButton::Left, |_, _| {})
-                                .on_up(MouseButton::Left, |_, _| {})
-                                .on_click(MouseButton::Left, move |_, cx| {
+                                .on_down(MouseButton::Left, |_, _, _| {})
+                                .on_up(MouseButton::Left, |_, _, _| {})
+                                .on_click(MouseButton::Left, move |_, _, cx| {
                                     cx.dispatch_action(SelectIndex(ix))
                                 })
                                 .with_cursor_style(CursorStyle::PointingHand)
@@ -140,6 +140,12 @@ impl<D: PickerDelegate> View for Picker<D> {
     }
 }
 
+impl<D: PickerDelegate> Modal for Picker<D> {
+    fn dismiss_on_event(event: &Self::Event) -> bool {
+        matches!(event, PickerEvent::Dismiss)
+    }
+}
+
 impl<D: PickerDelegate> Picker<D> {
     pub fn init(cx: &mut AppContext) {
         cx.add_action(Self::select_first);
@@ -151,14 +157,12 @@ impl<D: PickerDelegate> Picker<D> {
         cx.add_action(Self::cancel);
     }
 
-    pub fn new<P>(placeholder: P, delegate: WeakViewHandle<D>, cx: &mut ViewContext<Self>) -> Self
-    where
-        P: Into<Arc<str>>,
-    {
+    pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
         let theme = Arc::new(Mutex::new(
             Box::new(|theme: &theme::Theme| theme.picker.clone())
                 as Box<dyn Fn(&theme::Theme) -> theme::Picker>,
         ));
+        let placeholder_text = delegate.placeholder_text();
         let query_editor = cx.add_view({
             let picker_theme = theme.clone();
             |cx| {
@@ -168,26 +172,22 @@ impl<D: PickerDelegate> Picker<D> {
                     })),
                     cx,
                 );
-                editor.set_placeholder_text(placeholder, cx);
+                editor.set_placeholder_text(placeholder_text, cx);
                 editor
             }
         });
         cx.subscribe(&query_editor, Self::on_query_editor_event)
             .detach();
-        let this = Self {
+        let mut this = Self {
             query_editor,
             list_state: Default::default(),
             delegate,
             max_size: vec2f(540., 420.),
             theme,
             confirmed: false,
+            pending_update_matches: Task::ready(None),
         };
-        cx.defer(|this, cx| {
-            if let Some(delegate) = this.delegate.upgrade(cx) {
-                cx.observe(&delegate, |_, _, cx| cx.notify()).detach();
-                this.update_matches(String::new(), cx)
-            }
-        });
+        this.update_matches(String::new(), cx);
         this
     }
 
@@ -204,6 +204,14 @@ impl<D: PickerDelegate> Picker<D> {
         self
     }
 
+    pub fn delegate(&self) -> &D {
+        &self.delegate
+    }
+
+    pub fn delegate_mut(&mut self) -> &mut D {
+        &mut self.delegate
+    }
+
     pub fn query(&self, cx: &AppContext) -> String {
         self.query_editor.read(cx).text(cx)
     }
@@ -222,119 +230,95 @@ impl<D: PickerDelegate> Picker<D> {
         match event {
             editor::Event::BufferEdited { .. } => self.update_matches(self.query(cx), cx),
             editor::Event::Blurred if !self.confirmed => {
-                if let Some(delegate) = self.delegate.upgrade(cx) {
-                    delegate.update(cx, |delegate, cx| {
-                        delegate.dismiss(cx);
-                    })
-                }
+                self.dismiss(cx);
             }
             _ => {}
         }
     }
 
     pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            let update = delegate.update(cx, |d, cx| d.update_matches(query, cx));
-            cx.spawn(|this, mut cx| async move {
-                update.await;
-                this.update(&mut cx, |this, cx| {
-                    if let Some(delegate) = this.delegate.upgrade(cx) {
-                        let delegate = delegate.read(cx);
-                        let index = delegate.selected_index();
-                        let target = if delegate.center_selection_after_match_updates() {
-                            ScrollTarget::Center(index)
-                        } else {
-                            ScrollTarget::Show(index)
-                        };
-                        this.list_state.scroll_to(target);
-                        cx.notify();
-                    }
-                });
-            })
-            .detach()
-        }
+        let update = self.delegate.update_matches(query, cx);
+        self.matches_updated(cx);
+        self.pending_update_matches = cx.spawn_weak(|this, mut cx| async move {
+            update.await;
+            this.upgrade(&cx)?
+                .update(&mut cx, |this, cx| this.matches_updated(cx))
+                .log_err()
+        });
     }
 
-    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            delegate.update(cx, |delegate, cx| {
-                if delegate.match_count() > 0 {
-                    delegate.set_selected_index(0, cx);
-                    self.list_state.scroll_to(ScrollTarget::Show(0));
-                }
-            });
+    fn matches_updated(&mut self, cx: &mut ViewContext<Self>) {
+        let index = self.delegate.selected_index();
+        let target = if self.delegate.center_selection_after_match_updates() {
+            ScrollTarget::Center(index)
+        } else {
+            ScrollTarget::Show(index)
+        };
+        self.list_state.scroll_to(target);
+        cx.notify();
+    }
 
-            cx.notify();
+    pub fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
+        if self.delegate.match_count() > 0 {
+            self.delegate.set_selected_index(0, cx);
+            self.list_state.scroll_to(ScrollTarget::Show(0));
         }
+
+        cx.notify();
     }
 
     pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            let index = action.0;
-            delegate.update(cx, |delegate, cx| {
-                if delegate.match_count() > 0 {
-                    self.confirmed = true;
-                    delegate.set_selected_index(index, cx);
-                    delegate.confirm(cx);
-                }
-            });
+        let index = action.0;
+        if self.delegate.match_count() > 0 {
+            self.confirmed = true;
+            self.delegate.set_selected_index(index, cx);
+            self.delegate.confirm(cx);
         }
     }
 
     pub fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            delegate.update(cx, |delegate, cx| {
-                let match_count = delegate.match_count();
-                if match_count > 0 {
-                    let index = match_count - 1;
-                    delegate.set_selected_index(index, cx);
-                    self.list_state.scroll_to(ScrollTarget::Show(index));
-                }
-            });
-            cx.notify();
+        let match_count = self.delegate.match_count();
+        if match_count > 0 {
+            let index = match_count - 1;
+            self.delegate.set_selected_index(index, cx);
+            self.list_state.scroll_to(ScrollTarget::Show(index));
         }
+        cx.notify();
     }
 
     pub fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            delegate.update(cx, |delegate, cx| {
-                let next_index = delegate.selected_index() + 1;
-                if next_index < delegate.match_count() {
-                    delegate.set_selected_index(next_index, cx);
-                    self.list_state.scroll_to(ScrollTarget::Show(next_index));
-                }
-            });
-
-            cx.notify();
+        let next_index = self.delegate.selected_index() + 1;
+        if next_index < self.delegate.match_count() {
+            self.delegate.set_selected_index(next_index, cx);
+            self.list_state.scroll_to(ScrollTarget::Show(next_index));
         }
+
+        cx.notify();
     }
 
     pub fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            delegate.update(cx, |delegate, cx| {
-                let mut selected_index = delegate.selected_index();
-                if selected_index > 0 {
-                    selected_index -= 1;
-                    delegate.set_selected_index(selected_index, cx);
-                    self.list_state
-                        .scroll_to(ScrollTarget::Show(selected_index));
-                }
-            });
-
-            cx.notify();
+        let mut selected_index = self.delegate.selected_index();
+        if selected_index > 0 {
+            selected_index -= 1;
+            self.delegate.set_selected_index(selected_index, cx);
+            self.list_state
+                .scroll_to(ScrollTarget::Show(selected_index));
         }
+
+        cx.notify();
     }
 
-    fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            self.confirmed = true;
-            delegate.update(cx, |delegate, cx| delegate.confirm(cx));
-        }
+    pub fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+        self.confirmed = true;
+        self.delegate.confirm(cx);
     }
 
     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-        if let Some(delegate) = self.delegate.upgrade(cx) {
-            delegate.update(cx, |delegate, cx| delegate.dismiss(cx));
-        }
+        self.dismiss(cx);
+    }
+
+    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+        cx.emit(PickerEvent::Dismiss);
+        self.delegate.dismissed(cx);
     }
 }

crates/project_panel/src/project_panel.rs 🔗

@@ -13,8 +13,8 @@ use gpui::{
     impl_internal_actions,
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, PromptLevel},
-    AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, RenderContext, Task, View,
-    ViewContext, ViewHandle,
+    AppContext, ClipboardItem, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
+    ViewHandle,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@@ -498,7 +498,7 @@ impl ProjectPanel {
             this.update(&mut cx, |this, cx| {
                 this.edit_state.take();
                 cx.notify();
-            });
+            })?;
 
             let new_entry = new_entry?;
             this.update(&mut cx, |this, cx| {
@@ -519,7 +519,7 @@ impl ProjectPanel {
                     );
                 }
                 cx.notify();
-            });
+            })?;
             Ok(())
         }))
     }
@@ -655,7 +655,7 @@ impl ProjectPanel {
                 this.project
                     .update(cx, |project, cx| project.delete_entry(entry_id, cx))
                     .ok_or_else(|| anyhow!("no such entry"))
-            })?
+            })??
             .await
         }))
     }
@@ -1015,8 +1015,8 @@ impl ProjectPanel {
     fn for_each_visible_entry(
         &self,
         range: Range<usize>,
-        cx: &mut RenderContext<ProjectPanel>,
-        mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut RenderContext<ProjectPanel>),
+        cx: &mut ViewContext<ProjectPanel>,
+        mut callback: impl FnMut(ProjectEntryId, EntryDetails, &mut ViewContext<ProjectPanel>),
     ) {
         let mut ix = 0;
         for (worktree_id, visible_worktree_entries) in &self.visible_entries {
@@ -1097,8 +1097,8 @@ impl ProjectPanel {
         padding: f32,
         row_container_style: ContainerStyle,
         style: &ProjectPanelEntry,
-        cx: &mut RenderContext<V>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<V>,
+    ) -> Element<V> {
         let kind = details.kind;
         let show_editor = details.is_editing && !details.is_processing;
 
@@ -1154,9 +1154,8 @@ impl ProjectPanel {
         editor: &ViewHandle<Editor>,
         dragged_entry_destination: &mut Option<Arc<Path>>,
         theme: &theme::ProjectPanel,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
-        let this = cx.handle();
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let kind = details.kind;
         let path = details.path.clone();
         let padding = theme.container.padding.left + details.depth as f32 * theme.indent_width;
@@ -1171,7 +1170,7 @@ impl ProjectPanel {
 
         let show_editor = details.is_editing && !details.is_processing;
 
-        MouseEventHandler::<Self>::new(entry_id.to_usize(), cx, |state, cx| {
+        MouseEventHandler::<Self, _>::new(entry_id.to_usize(), cx, |state, cx| {
             let mut style = entry_style.style_for(state, details.is_selected).clone();
 
             if cx
@@ -1201,7 +1200,7 @@ impl ProjectPanel {
                 cx,
             )
         })
-        .on_click(MouseButton::Left, move |e, cx| {
+        .on_click(MouseButton::Left, move |e, _, cx| {
             if !show_editor {
                 if kind == EntryKind::Dir {
                     cx.dispatch_action(ToggleExpanded(entry_id))
@@ -1213,13 +1212,13 @@ impl ProjectPanel {
                 }
             }
         })
-        .on_down(MouseButton::Right, move |e, cx| {
+        .on_down(MouseButton::Right, move |e, _, cx| {
             cx.dispatch_action(DeployContextMenu {
                 entry_id,
                 position: e.position,
             })
         })
-        .on_up(MouseButton::Left, move |_, cx| {
+        .on_up(MouseButton::Left, move |_, _, cx| {
             if let Some((_, dragged_entry)) = cx
                 .global::<DragAndDrop<Workspace>>()
                 .currently_dragged::<ProjectEntryId>(cx.window_id())
@@ -1231,27 +1230,23 @@ impl ProjectPanel {
                 });
             }
         })
-        .on_move(move |_, cx| {
+        .on_move(move |_, this, cx| {
             if cx
                 .global::<DragAndDrop<Workspace>>()
                 .currently_dragged::<ProjectEntryId>(cx.window_id())
                 .is_some()
             {
-                if let Some(this) = this.upgrade(cx.app) {
-                    this.update(cx.app, |this, _| {
-                        this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
-                            path.parent().map(|parent| Arc::from(parent))
-                        } else {
-                            Some(path.clone())
-                        };
-                    })
-                }
+                this.dragged_entry_destination = if matches!(kind, EntryKind::File(_)) {
+                    path.parent().map(|parent| Arc::from(parent))
+                } else {
+                    Some(path.clone())
+                };
             }
         })
         .as_draggable(entry_id, {
             let row_container_style = theme.dragged_entry.container;
 
-            move |_, cx: &mut RenderContext<Workspace>| {
+            move |_, cx: &mut ViewContext<Workspace>| {
                 let theme = cx.global::<Settings>().theme.clone();
                 Self::render_entry_visual_element(
                     &details,
@@ -1273,7 +1268,7 @@ impl View for ProjectPanel {
         "ProjectPanel"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
         enum ProjectPanel {}
         let theme = &cx.global::<Settings>().theme.project_panel;
         let mut container_style = theme.container;
@@ -1285,7 +1280,7 @@ impl View for ProjectPanel {
         if has_worktree {
             Stack::new()
                 .with_child(
-                    MouseEventHandler::<ProjectPanel>::new(0, cx, |_, cx| {
+                    MouseEventHandler::<ProjectPanel, _>::new(0, cx, |_, cx| {
                         UniformList::new(
                             self.list.clone(),
                             self.visible_entries
@@ -1317,7 +1312,7 @@ impl View for ProjectPanel {
                         .expanded()
                         .boxed()
                     })
-                    .on_down(MouseButton::Right, move |e, cx| {
+                    .on_down(MouseButton::Right, move |e, _, cx| {
                         // When deploying the context menu anywhere below the last project entry,
                         // act as if the user clicked the root of the last worktree.
                         if let Some(entry_id) = last_worktree_root_id {
@@ -1334,7 +1329,7 @@ impl View for ProjectPanel {
         } else {
             Flex::column()
                 .with_child(
-                    MouseEventHandler::<Self>::new(2, cx, {
+                    MouseEventHandler::<Self, _>::new(2, cx, {
                         let button_style = theme.open_project_button.clone();
                         let context_menu_item_style =
                             cx.global::<Settings>().theme.context_menu.item.clone();
@@ -1353,7 +1348,7 @@ impl View for ProjectPanel {
                             .boxed()
                         }
                     })
-                    .on_click(MouseButton::Left, move |_, cx| {
+                    .on_click(MouseButton::Left, move |_, _, cx| {
                         cx.dispatch_action(workspace::Open)
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
@@ -1549,7 +1544,7 @@ mod tests {
         .await;
 
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
-        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
         let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
 
         select_path(&panel, "root1", cx);
@@ -1571,7 +1566,10 @@ 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));
-        assert!(panel.read_with(cx, |panel, cx| panel.filename_editor.is_focused(cx)));
+        cx.read_window(window_id, |cx| {
+            let panel = panel.read(cx);
+            assert!(panel.filename_editor.is_focused(cx));
+        });
         assert_eq!(
             visible_entries_as_strings(&panel, 0..10, cx),
             &[
@@ -1943,7 +1941,8 @@ mod tests {
         let mut result = Vec::new();
         let mut project_entries = HashSet::new();
         let mut has_editor = false;
-        cx.render(panel, |panel, cx| {
+
+        panel.update(cx, |panel, cx| {
             panel.for_each_visible_entry(range, cx, |project_entry, details, _| {
                 if details.is_editing {
                     assert!(!has_editor, "duplicate editor entry");

crates/project_symbols/src/project_symbols.rs 🔗

@@ -1,90 +1,63 @@
+use anyhow::anyhow;
 use editor::{
     combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
     styled_runs_for_code_label, Bias, Editor,
 };
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MouseState,
-    RenderContext, Task, View, ViewContext, ViewHandle,
+    actions, elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext, WeakViewHandle,
 };
 use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use project::{Project, Symbol};
 use settings::Settings;
-use std::{borrow::Cow, cmp::Reverse};
+use std::{borrow::Cow, cmp::Reverse, sync::Arc};
 use util::ResultExt;
 use workspace::Workspace;
 
 actions!(project_symbols, [Toggle]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(ProjectSymbolsView::toggle);
-    Picker::<ProjectSymbolsView>::init(cx);
+    cx.add_action(toggle);
+    ProjectSymbols::init(cx);
 }
 
-pub struct ProjectSymbolsView {
-    picker: ViewHandle<Picker<Self>>,
+fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+    workspace.toggle_modal(cx, |workspace, cx| {
+        let project = workspace.project().clone();
+        let workspace = cx.weak_handle();
+        cx.add_view(|cx| ProjectSymbols::new(ProjectSymbolsDelegate::new(workspace, project), cx))
+    });
+}
+
+pub type ProjectSymbols = Picker<ProjectSymbolsDelegate>;
+
+pub struct ProjectSymbolsDelegate {
+    workspace: WeakViewHandle<Workspace>,
     project: ModelHandle<Project>,
     selected_match_index: usize,
     symbols: Vec<Symbol>,
     visible_match_candidates: Vec<StringMatchCandidate>,
     external_match_candidates: Vec<StringMatchCandidate>,
     show_worktree_root_name: bool,
-    pending_update: Task<()>,
     matches: Vec<StringMatch>,
 }
 
-pub enum Event {
-    Dismissed,
-    Selected(Symbol),
-}
-
-impl Entity for ProjectSymbolsView {
-    type Event = Event;
-}
-
-impl View for ProjectSymbolsView {
-    fn ui_name() -> &'static str {
-        "ProjectSymbolsView"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl ProjectSymbolsView {
-    fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
-        let handle = cx.weak_handle();
+impl ProjectSymbolsDelegate {
+    fn new(workspace: WeakViewHandle<Workspace>, project: ModelHandle<Project>) -> Self {
         Self {
+            workspace,
             project,
-            picker: cx.add_view(|cx| Picker::new("Search project symbols...", handle, cx)),
             selected_match_index: 0,
             symbols: Default::default(),
             visible_match_candidates: Default::default(),
             external_match_candidates: Default::default(),
             matches: Default::default(),
             show_worktree_root_name: false,
-            pending_update: Task::ready(()),
         }
     }
 
-    fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
-        workspace.toggle_modal(cx, |workspace, cx| {
-            let project = workspace.project().clone();
-            let symbols = cx.add_view(|cx| Self::new(project, cx));
-            cx.subscribe(&symbols, Self::on_event).detach();
-            symbols
-        });
-    }
-
-    fn filter(&mut self, query: &str, cx: &mut ViewContext<Self>) {
+    fn filter(&mut self, query: &str, cx: &mut ViewContext<ProjectSymbols>) {
         const MAX_MATCHES: usize = 100;
         let mut visible_matches = cx.background_executor().block(fuzzy::match_strings(
             &self.visible_match_candidates,
@@ -125,60 +98,50 @@ impl ProjectSymbolsView {
 
         self.matches = matches;
         self.set_selected_index(0, cx);
-        cx.notify();
     }
+}
 
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<Self>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => workspace.dismiss_modal(cx),
-            Event::Selected(symbol) => {
-                let buffer = workspace
-                    .project()
-                    .update(cx, |project, cx| project.open_buffer_for_symbol(symbol, cx));
-
-                let symbol = symbol.clone();
-                cx.spawn(|workspace, mut cx| async move {
-                    let buffer = buffer.await?;
-                    workspace.update(&mut cx, |workspace, cx| {
-                        let position = buffer
-                            .read(cx)
-                            .clip_point_utf16(symbol.range.start, Bias::Left);
-
-                        let editor = workspace.open_project_item::<Editor>(buffer, cx);
-                        editor.update(cx, |editor, cx| {
-                            editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-                                s.select_ranges([position..position])
-                            });
-                        });
-                    });
-                    Ok::<_, anyhow::Error>(())
-                })
-                .detach_and_log_err(cx);
-                workspace.dismiss_modal(cx);
-            }
-        }
+impl PickerDelegate for ProjectSymbolsDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Search project symbols...".into()
     }
-}
 
-impl PickerDelegate for ProjectSymbolsView {
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<ProjectSymbols>) {
         if let Some(symbol) = self
             .matches
             .get(self.selected_match_index)
             .map(|mat| self.symbols[mat.candidate_id].clone())
         {
-            cx.emit(Event::Selected(symbol));
+            let buffer = self.project.update(cx, |project, cx| {
+                project.open_buffer_for_symbol(&symbol, cx)
+            });
+            let symbol = symbol.clone();
+            let workspace = self.workspace.clone();
+            cx.spawn_weak(|_, mut cx| async move {
+                let buffer = buffer.await?;
+                let workspace = workspace
+                    .upgrade(&cx)
+                    .ok_or_else(|| anyhow!("workspace was dropped"))?;
+                workspace.update(&mut cx, |workspace, cx| {
+                    let position = buffer
+                        .read(cx)
+                        .clip_point_utf16(symbol.range.start, Bias::Left);
+
+                    let editor = workspace.open_project_item::<Editor>(buffer, cx);
+                    editor.update(cx, |editor, cx| {
+                        editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+                            s.select_ranges([position..position])
+                        });
+                    });
+                })?;
+                Ok::<_, anyhow::Error>(())
+            })
+            .detach_and_log_err(cx);
+            cx.emit(PickerEvent::Dismiss);
         }
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
-    }
+    fn dismissed(&mut self, _cx: &mut ViewContext<ProjectSymbols>) {}
 
     fn match_count(&self) -> usize {
         self.matches.len()
@@ -188,23 +151,23 @@ impl PickerDelegate for ProjectSymbolsView {
         self.selected_match_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<ProjectSymbols>) {
         self.selected_match_index = ix;
-        cx.notify();
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+    fn update_matches(&mut self, query: String, cx: &mut ViewContext<ProjectSymbols>) -> Task<()> {
         self.filter(&query, cx);
         self.show_worktree_root_name = self.project.read(cx).visible_worktrees(cx).count() > 1;
         let symbols = self
             .project
             .update(cx, |project, cx| project.symbols(&query, cx));
-        self.pending_update = cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn_weak(|this, mut cx| async move {
             let symbols = symbols.await.log_err();
             if let Some(this) = this.upgrade(&cx) {
                 if let Some(symbols) = symbols {
                     this.update(&mut cx, |this, cx| {
-                        let project = this.project.read(cx);
+                        let delegate = this.delegate_mut();
+                        let project = delegate.project.read(cx);
                         let (visible_match_candidates, external_match_candidates) = symbols
                             .iter()
                             .enumerate()
@@ -221,15 +184,15 @@ impl PickerDelegate for ProjectSymbolsView {
                                     .map_or(false, |e| !e.is_ignored)
                             });
 
-                        this.visible_match_candidates = visible_match_candidates;
-                        this.external_match_candidates = external_match_candidates;
-                        this.symbols = symbols;
-                        this.filter(&query, cx);
-                    });
+                        delegate.visible_match_candidates = visible_match_candidates;
+                        delegate.external_match_candidates = external_match_candidates;
+                        delegate.symbols = symbols;
+                        delegate.filter(&query, cx);
+                    })
+                    .log_err();
                 }
             }
-        });
-        Task::ready(())
+        })
     }
 
     fn render_match(
@@ -238,7 +201,7 @@ impl PickerDelegate for ProjectSymbolsView {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let string_match = &self.matches[ix];
         let settings = cx.global::<Settings>();
         let style = &settings.theme.picker.item;
@@ -363,46 +326,53 @@ mod tests {
             },
         );
 
+        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+
         // Create the project symbols view.
-        let (_, symbols_view) = cx.add_window(|cx| ProjectSymbolsView::new(project.clone(), cx));
-        let picker = symbols_view.read_with(cx, |symbols_view, _| symbols_view.picker.clone());
+        let symbols = cx.add_view(&workspace, |cx| {
+            ProjectSymbols::new(
+                ProjectSymbolsDelegate::new(workspace.downgrade(), project.clone()),
+                cx,
+            )
+        });
 
         // Spawn multiples updates before the first update completes,
         // such that in the end, there are no matches. Testing for regression:
         // https://github.com/zed-industries/zed/issues/861
-        picker.update(cx, |p, cx| {
+        symbols.update(cx, |p, cx| {
             p.update_matches("o".to_string(), cx);
             p.update_matches("on".to_string(), cx);
             p.update_matches("onex".to_string(), cx);
         });
 
         cx.foreground().run_until_parked();
-        symbols_view.read_with(cx, |symbols_view, _| {
-            assert_eq!(symbols_view.matches.len(), 0);
+        symbols.read_with(cx, |symbols, _| {
+            assert_eq!(symbols.delegate().matches.len(), 0);
         });
 
         // Spawn more updates such that in the end, there are matches.
-        picker.update(cx, |p, cx| {
+        symbols.update(cx, |p, cx| {
             p.update_matches("one".to_string(), cx);
             p.update_matches("on".to_string(), cx);
         });
 
         cx.foreground().run_until_parked();
-        symbols_view.read_with(cx, |symbols_view, _| {
-            assert_eq!(symbols_view.matches.len(), 2);
-            assert_eq!(symbols_view.matches[0].string, "ton");
-            assert_eq!(symbols_view.matches[1].string, "one");
+        symbols.read_with(cx, |symbols, _| {
+            let delegate = symbols.delegate();
+            assert_eq!(delegate.matches.len(), 2);
+            assert_eq!(delegate.matches[0].string, "ton");
+            assert_eq!(delegate.matches[1].string, "one");
         });
 
         // Spawn more updates such that in the end, there are again no matches.
-        picker.update(cx, |p, cx| {
+        symbols.update(cx, |p, cx| {
             p.update_matches("o".to_string(), cx);
             p.update_matches("".to_string(), cx);
         });
 
         cx.foreground().run_until_parked();
-        symbols_view.read_with(cx, |symbols_view, _| {
-            assert_eq!(symbols_view.matches.len(), 0);
+        symbols.read_with(cx, |symbols, _| {
+            assert_eq!(symbols.delegate().matches.len(), 0);
         });
     }
 

crates/recent_projects/src/highlighted_workspace_location.rs 🔗

@@ -3,7 +3,7 @@ use std::path::Path;
 use fuzzy::StringMatch;
 use gpui::{
     elements::{Label, LabelStyle},
-    Element, ElementBox,
+    Drawable, Element, View,
 };
 use workspace::WorkspaceLocation;
 
@@ -42,7 +42,7 @@ impl HighlightedText {
         }
     }
 
-    pub fn render(self, style: impl Into<LabelStyle>) -> ElementBox {
+    pub fn render<V: View>(self, style: impl Into<LabelStyle>) -> Element<V> {
         Label::new(self.text, style)
             .with_highlights(self.highlight_positions)
             .boxed()

crates/recent_projects/src/recent_projects.rs 🔗

@@ -3,14 +3,15 @@ mod highlighted_workspace_location;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     actions,
-    elements::{ChildView, Flex, ParentElement},
-    AnyViewHandle, AppContext, Element, ElementBox, Entity, RenderContext, Task, View, ViewContext,
-    ViewHandle,
+    anyhow::Result,
+    elements::{Flex, ParentElement},
+    AppContext, Drawable, Element, Task, ViewContext,
 };
 use highlighted_workspace_location::HighlightedWorkspaceLocation;
 use ordered_float::OrderedFloat;
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
+use std::sync::Arc;
 use workspace::{
     notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace,
     WorkspaceLocation, WORKSPACE_DB,
@@ -19,101 +20,70 @@ use workspace::{
 actions!(projects, [OpenRecent]);
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(RecentProjectsView::toggle);
-    Picker::<RecentProjectsView>::init(cx);
+    cx.add_async_action(toggle);
+    RecentProjects::init(cx);
 }
 
-struct RecentProjectsView {
-    picker: ViewHandle<Picker<Self>>,
+fn toggle(
+    _: &mut Workspace,
+    _: &OpenRecent,
+    cx: &mut ViewContext<Workspace>,
+) -> Option<Task<Result<()>>> {
+    Some(cx.spawn(|workspace, mut cx| async move {
+        let workspace_locations: Vec<_> = cx
+            .background()
+            .spawn(async {
+                WORKSPACE_DB
+                    .recent_workspaces_on_disk()
+                    .await
+                    .unwrap_or_default()
+                    .into_iter()
+                    .map(|(_, location)| location)
+                    .collect()
+            })
+            .await;
+
+        workspace.update(&mut cx, |workspace, cx| {
+            if !workspace_locations.is_empty() {
+                workspace.toggle_modal(cx, |_, cx| {
+                    cx.add_view(|cx| {
+                        RecentProjects::new(RecentProjectsDelegate::new(workspace_locations), cx)
+                            .with_max_size(800., 1200.)
+                    })
+                });
+            } else {
+                workspace.show_notification(0, cx, |cx| {
+                    cx.add_view(|_| MessageNotification::new_message("No recent projects to open."))
+                })
+            }
+        })?;
+        Ok(())
+    }))
+}
+
+type RecentProjects = Picker<RecentProjectsDelegate>;
+
+struct RecentProjectsDelegate {
     workspace_locations: Vec<WorkspaceLocation>,
     selected_match_index: usize,
     matches: Vec<StringMatch>,
 }
 
-impl RecentProjectsView {
-    fn new(workspace_locations: Vec<WorkspaceLocation>, cx: &mut ViewContext<Self>) -> Self {
-        let handle = cx.weak_handle();
+impl RecentProjectsDelegate {
+    fn new(workspace_locations: Vec<WorkspaceLocation>) -> Self {
         Self {
-            picker: cx.add_view(|cx| {
-                Picker::new("Recent Projects...", handle, cx).with_max_size(800., 1200.)
-            }),
             workspace_locations,
             selected_match_index: 0,
             matches: Default::default(),
         }
     }
-
-    fn toggle(_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext<Workspace>) {
-        cx.spawn(|workspace, mut cx| async move {
-            let workspace_locations: Vec<_> = cx
-                .background()
-                .spawn(async {
-                    WORKSPACE_DB
-                        .recent_workspaces_on_disk()
-                        .await
-                        .unwrap_or_default()
-                        .into_iter()
-                        .map(|(_, location)| location)
-                        .collect()
-                })
-                .await;
-
-            workspace.update(&mut cx, |workspace, cx| {
-                if !workspace_locations.is_empty() {
-                    workspace.toggle_modal(cx, |_, cx| {
-                        let view = cx.add_view(|cx| Self::new(workspace_locations, cx));
-                        cx.subscribe(&view, Self::on_event).detach();
-                        view
-                    });
-                } else {
-                    workspace.show_notification(0, cx, |cx| {
-                        cx.add_view(|_| {
-                            MessageNotification::new_message("No recent projects to open.")
-                        })
-                    })
-                }
-            });
-        })
-        .detach();
-    }
-
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<Self>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => workspace.dismiss_modal(cx),
-        }
-    }
-}
-
-pub enum Event {
-    Dismissed,
 }
 
-impl Entity for RecentProjectsView {
-    type Event = Event;
-}
-
-impl View for RecentProjectsView {
-    fn ui_name() -> &'static str {
-        "RecentProjectsView"
+impl PickerDelegate for RecentProjectsDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Recent Projects...".into()
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl PickerDelegate for RecentProjectsView {
     fn match_count(&self) -> usize {
         self.matches.len()
     }
@@ -122,11 +92,15 @@ impl PickerDelegate for RecentProjectsView {
         self.selected_match_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<RecentProjects>) {
         self.selected_match_index = ix;
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut ViewContext<RecentProjects>,
+    ) -> gpui::Task<()> {
         let query = query.trim_start();
         let smart_case = query.chars().any(|c| c.is_uppercase());
         let candidates = self
@@ -164,19 +138,17 @@ impl PickerDelegate for RecentProjectsView {
         Task::ready(())
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) {
         if let Some(selected_match) = &self.matches.get(self.selected_index()) {
             let workspace_location = &self.workspace_locations[selected_match.candidate_id];
             cx.dispatch_action(OpenPaths {
                 paths: workspace_location.paths().as_ref().clone(),
             });
-            cx.emit(Event::Dismissed);
+            cx.emit(PickerEvent::Dismiss);
         }
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
-    }
+    fn dismissed(&mut self, _cx: &mut ViewContext<RecentProjects>) {}
 
     fn render_match(
         &self,
@@ -184,7 +156,7 @@ impl PickerDelegate for RecentProjectsView {
         mouse_state: &mut gpui::MouseState,
         selected: bool,
         cx: &gpui::AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let settings = cx.global::<Settings>();
         let string_match = &self.matches[ix];
         let style = settings.theme.picker.item.style_for(mouse_state, selected);

crates/search/src/buffer_search.rs 🔗

@@ -9,13 +9,13 @@ use gpui::{
     elements::*,
     impl_actions,
     platform::{CursorStyle, MouseButton},
-    Action, AnyViewHandle, AppContext, Entity, RenderContext, Subscription, Task, View,
-    ViewContext, ViewHandle,
+    Action, AnyViewHandle, AppContext, Entity, Subscription, Task, View, ViewContext, ViewHandle,
 };
 use project::search::SearchQuery;
 use serde::Deserialize;
 use settings::Settings;
 use std::{any::Any, sync::Arc};
+use util::ResultExt;
 use workspace::{
     item::ItemHandle,
     searchable::{Direction, SearchEvent, SearchableItemHandle, WeakSearchableItemHandle},
@@ -92,7 +92,7 @@ impl View for BufferSearchBar {
         }
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
         let editor_container = if self.query_contains_error {
             theme.search.invalid_editor
@@ -324,8 +324,8 @@ impl BufferSearchBar {
         option_supported: bool,
         icon: &'static str,
         option: SearchOption,
-        cx: &mut RenderContext<Self>,
-    ) -> Option<ElementBox> {
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Element<Self>> {
         if !option_supported {
             return None;
         }
@@ -333,7 +333,7 @@ impl BufferSearchBar {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let is_active = self.is_search_option_enabled(option);
         Some(
-            MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
+            MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
                 let style = cx
                     .global::<Settings>()
                     .theme
@@ -345,11 +345,11 @@ impl BufferSearchBar {
                     .with_style(style.container)
                     .boxed()
             })
-            .on_click(MouseButton::Left, move |_, cx| {
+            .on_click(MouseButton::Left, move |_, _, cx| {
                 cx.dispatch_any_action(option.to_toggle_action())
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .with_tooltip::<Self, _>(
+            .with_tooltip::<Self>(
                 option as usize,
                 format!("Toggle {}", option.label()),
                 Some(option.to_toggle_action()),
@@ -364,8 +364,8 @@ impl BufferSearchBar {
         &self,
         icon: &'static str,
         direction: Direction,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let action: Box<dyn Action>;
         let tooltip;
         match direction {
@@ -381,7 +381,7 @@ impl BufferSearchBar {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
         enum NavButton {}
-        MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
+        MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
             let style = cx
                 .global::<Settings>()
                 .theme
@@ -395,10 +395,10 @@ impl BufferSearchBar {
         })
         .on_click(MouseButton::Left, {
             let action = action.boxed_clone();
-            move |_, cx| cx.dispatch_any_action(action.boxed_clone())
+            move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<NavButton, _>(
+        .with_tooltip::<NavButton>(
             direction as usize,
             tooltip.to_string(),
             Some(action),
@@ -411,14 +411,14 @@ impl BufferSearchBar {
     fn render_close_button(
         &self,
         theme: &theme::Search,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let action = Box::new(Dismiss);
         let tooltip = "Dismiss Buffer Search";
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
         enum CloseButton {}
-        MouseEventHandler::<CloseButton>::new(0, cx, |state, _| {
+        MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {
             let style = theme.dismiss_button.style_for(state, false);
             Svg::new("icons/x_mark_8.svg")
                 .with_color(style.color)
@@ -433,10 +433,10 @@ impl BufferSearchBar {
         })
         .on_click(MouseButton::Left, {
             let action = action.boxed_clone();
-            move |_, cx| cx.dispatch_any_action(action.boxed_clone())
+            move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<CloseButton, _>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
+        .with_tooltip::<CloseButton>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
         .boxed()
     }
 
@@ -618,7 +618,8 @@ impl BufferSearchBar {
                                 }
                                 cx.notify();
                             }
-                        });
+                        })
+                        .log_err();
                     }
                 }));
             }

crates/search/src/project_search.rs 🔗

@@ -12,9 +12,8 @@ use gpui::{
     actions,
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Action, AnyViewHandle, AppContext, ElementBox, Entity, ModelContext, ModelHandle,
-    RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakModelHandle,
-    WeakViewHandle,
+    Action, AnyViewHandle, AppContext, Element, Entity, ModelContext, ModelHandle, Subscription,
+    Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
 };
 use menu::Confirm;
 use project::{search::SearchQuery, Project};
@@ -30,7 +29,7 @@ use std::{
 };
 use util::ResultExt as _;
 use workspace::{
-    item::{Item, ItemEvent, ItemHandle},
+    item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
     searchable::{Direction, SearchableItem, SearchableItemHandle},
     ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
 };
@@ -179,7 +178,7 @@ impl View for ProjectSearchView {
         "ProjectSearchView"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let model = &self.model.read(cx);
         if model.match_ranges.is_empty() {
             enum Status {}
@@ -192,7 +191,7 @@ impl View for ProjectSearchView {
             } else {
                 "No results"
             };
-            MouseEventHandler::<Status>::new(0, cx, |_, _| {
+            MouseEventHandler::<Status, _>::new(0, cx, |_, _| {
                 Label::new(text, theme.search.results_status.clone())
                     .aligned()
                     .contained()
@@ -200,7 +199,7 @@ impl View for ProjectSearchView {
                     .flex(1., true)
                     .boxed()
             })
-            .on_down(MouseButton::Left, |_, cx| {
+            .on_down(MouseButton::Left, |_, _, cx| {
                 cx.focus_parent_view();
             })
             .boxed()
@@ -250,12 +249,12 @@ impl Item for ProjectSearchView {
             .update(cx, |editor, cx| editor.deactivated(cx));
     }
 
-    fn tab_content(
+    fn tab_content<T: View>(
         &self,
         _detail: Option<usize>,
         tab_theme: &theme::Tab,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<T> {
         Flex::row()
             .with_child(
                 Svg::new("icons/magnifying_glass_12.svg")
@@ -370,7 +369,7 @@ impl Item for ProjectSearchView {
         }
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
         self.results_editor.breadcrumbs(theme, cx)
     }
 
@@ -752,8 +751,8 @@ impl ProjectSearchBar {
         &self,
         icon: &'static str,
         direction: Direction,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let action: Box<dyn Action>;
         let tooltip;
         match direction {
@@ -769,7 +768,7 @@ impl ProjectSearchBar {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
         enum NavButton {}
-        MouseEventHandler::<NavButton>::new(direction as usize, cx, |state, cx| {
+        MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
             let style = &cx
                 .global::<Settings>()
                 .theme
@@ -783,10 +782,10 @@ impl ProjectSearchBar {
         })
         .on_click(MouseButton::Left, {
             let action = action.boxed_clone();
-            move |_, cx| cx.dispatch_any_action(action.boxed_clone())
+            move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<NavButton, _>(
+        .with_tooltip::<NavButton>(
             direction as usize,
             tooltip.to_string(),
             Some(action),
@@ -800,11 +799,11 @@ impl ProjectSearchBar {
         &self,
         icon: &'static str,
         option: SearchOption,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
         let is_active = self.is_option_enabled(option, cx);
-        MouseEventHandler::<Self>::new(option as usize, cx, |state, cx| {
+        MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
             let style = &cx
                 .global::<Settings>()
                 .theme
@@ -816,11 +815,11 @@ impl ProjectSearchBar {
                 .with_style(style.container)
                 .boxed()
         })
-        .on_click(MouseButton::Left, move |_, cx| {
+        .on_click(MouseButton::Left, move |_, _, cx| {
             cx.dispatch_any_action(option.to_toggle_action())
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<Self, _>(
+        .with_tooltip::<Self>(
             option as usize,
             format!("Toggle {}", option.label()),
             Some(option.to_toggle_action()),
@@ -853,7 +852,7 @@ impl View for ProjectSearchBar {
         "ProjectSearchBar"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if let Some(search) = self.active_project_search.as_ref() {
             let search = search.read(cx);
             let theme = cx.global::<Settings>().theme.clone();

crates/settings/src/settings_file.rs 🔗

@@ -80,9 +80,25 @@ mod tests {
         watch_files, watched_json::watch_settings_file, EditorSettings, Settings, SoftWrap,
     };
     use fs::FakeFs;
-    use gpui::{actions, Action};
+    use gpui::{actions, elements::*, Action, Entity, View, ViewContext, WindowContext};
     use theme::ThemeRegistry;
 
+    struct TestView;
+
+    impl Entity for TestView {
+        type Event = ();
+    }
+
+    impl View for TestView {
+        fn ui_name() -> &'static str {
+            "TestView"
+        }
+
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
+            Empty::new().boxed()
+        }
+    }
+
     #[gpui::test]
     async fn test_base_keymap(cx: &mut gpui::TestAppContext) {
         let executor = cx.background();
@@ -148,8 +164,10 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
+        let (window_id, _view) = cx.add_window(|_| TestView);
+
         // Test loading the keymap base at all
-        cx.update(|cx| {
+        cx.read_window(window_id, |cx| {
             assert_key_bindings_for(
                 cx,
                 vec![("backspace", &A), ("k", &ActivatePreviousPane)],
@@ -177,7 +195,7 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        cx.update(|cx| {
+        cx.read_window(window_id, |cx| {
             assert_key_bindings_for(
                 cx,
                 vec![("backspace", &B), ("k", &ActivatePreviousPane)],
@@ -201,7 +219,7 @@ mod tests {
 
         cx.foreground().run_until_parked();
 
-        cx.update(|cx| {
+        cx.read_window(window_id, |cx| {
             assert_key_bindings_for(
                 cx,
                 vec![("backspace", &B), ("[", &ActivatePrevItem)],
@@ -211,14 +229,14 @@ mod tests {
     }
 
     fn assert_key_bindings_for<'a>(
-        cx: &mut AppContext,
+        cx: &WindowContext,
         actions: Vec<(&'static str, &'a dyn Action)>,
         line: u32,
     ) {
         for (key, action) in actions {
             // assert that...
             assert!(
-                cx.available_actions(0, 0).any(|(_, bound_action, b)| {
+                cx.available_actions(0).any(|(_, bound_action, b)| {
                     // action names match...
                     bound_action.name() == action.name()
                     && bound_action.namespace() == action.namespace()

crates/sum_tree/src/sum_tree.rs 🔗

@@ -735,7 +735,7 @@ mod tests {
             .map_or(5, |o| o.parse().expect("invalid OPERATIONS variable"));
 
         for seed in starting_seed..(starting_seed + num_iterations) {
-            dbg!(seed);
+            eprintln!("seed = {}", seed);
             let mut rng = StdRng::seed_from_u64(seed);
 
             let rng = &mut rng;

crates/terminal_view/src/terminal_button.rs 🔗

@@ -3,8 +3,8 @@ use gpui::{
     elements::*,
     impl_internal_actions,
     platform::{CursorStyle, MouseButton},
-    AppContext, Element, ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle,
-    WeakModelHandle, WeakViewHandle,
+    AppContext, Drawable, Element, Entity, View, ViewContext, ViewHandle, WeakModelHandle,
+    WeakViewHandle,
 };
 use settings::Settings;
 use std::any::TypeId;
@@ -42,14 +42,14 @@ impl View for TerminalButton {
         "TerminalButton"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let workspace = self.workspace.upgrade(cx);
         let project = match workspace {
             Some(workspace) => workspace.read(cx).project().read(cx),
             None => return Empty::new().boxed(),
         };
 
-        let focused_view = cx.focused_view_id(cx.window_id());
+        let focused_view = cx.focused_view_id();
         let active = focused_view
             .map(|view_id| {
                 cx.view_type_id(cx.window_id(), view_id) == Some(TypeId::of::<TerminalView>())
@@ -62,7 +62,7 @@ impl View for TerminalButton {
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<Self>::new(0, cx, {
+                MouseEventHandler::<Self, _>::new(0, cx, {
                     let theme = theme.clone();
                     move |state, _cx| {
                         let style = theme
@@ -96,7 +96,7 @@ impl View for TerminalButton {
                     }
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, cx| {
+                .on_click(MouseButton::Left, move |_, _, cx| {
                     if has_terminals {
                         cx.dispatch_action(DeployTerminalMenu);
                     } else {
@@ -105,7 +105,7 @@ impl View for TerminalButton {
                         }
                     };
                 })
-                .with_tooltip::<Self, _>(
+                .with_tooltip::<Self>(
                     0,
                     "Show Terminal".into(),
                     Some(Box::new(FocusDock)),

crates/terminal_view/src/terminal_element.rs 🔗

@@ -10,8 +10,8 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     serde_json::json,
     text_layout::{Line, RunStyle},
-    Element, ElementBox, EventContext, FontCache, ModelContext, MouseRegion, PaintContext, Quad,
-    SizeConstraint, TextLayoutCache, WeakModelHandle, WeakViewHandle,
+    Drawable, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SceneBuilder,
+    SizeConstraint, TextLayoutCache, ViewContext, WeakModelHandle,
 };
 use itertools::Itertools;
 use language::CursorShape;
@@ -45,7 +45,7 @@ pub struct LayoutState {
     size: TerminalSize,
     mode: TermMode,
     display_offset: usize,
-    hyperlink_tooltip: Option<ElementBox>,
+    hyperlink_tooltip: Option<Element<TerminalView>>,
 }
 
 ///Helper struct for converting data between alacritty's cursor points, and displayed cursor points
@@ -84,10 +84,12 @@ impl LayoutCell {
 
     fn paint(
         &self,
+        scene: &mut SceneBuilder,
         origin: Vector2F,
         layout: &LayoutState,
         visible_bounds: RectF,
-        cx: &mut PaintContext,
+        _view: &mut TerminalView,
+        cx: &mut ViewContext<TerminalView>,
     ) {
         let pos = {
             let point = self.point;
@@ -98,7 +100,7 @@ impl LayoutCell {
         };
 
         self.text
-            .paint(pos, visible_bounds, layout.size.line_height, cx);
+            .paint(scene, pos, visible_bounds, layout.size.line_height, cx);
     }
 }
 
@@ -126,7 +128,14 @@ impl LayoutRect {
         }
     }
 
-    fn paint(&self, origin: Vector2F, layout: &LayoutState, cx: &mut PaintContext) {
+    fn paint(
+        &self,
+        scene: &mut SceneBuilder,
+        origin: Vector2F,
+        layout: &LayoutState,
+        _view: &mut TerminalView,
+        _cx: &mut ViewContext<TerminalView>,
+    ) {
         let position = {
             let point = self.point;
             vec2f(
@@ -139,7 +148,7 @@ impl LayoutRect {
             layout.size.line_height,
         );
 
-        cx.scene.push_quad(Quad {
+        scene.push_quad(Quad {
             bounds: RectF::new(position, size),
             background: Some(self.color),
             border: Default::default(),
@@ -152,20 +161,17 @@ impl LayoutRect {
 ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
 pub struct TerminalElement {
     terminal: WeakModelHandle<Terminal>,
-    view: WeakViewHandle<TerminalView>,
     focused: bool,
     cursor_visible: bool,
 }
 
 impl TerminalElement {
     pub fn new(
-        view: WeakViewHandle<TerminalView>,
         terminal: WeakModelHandle<Terminal>,
         focused: bool,
         cursor_visible: bool,
     ) -> TerminalElement {
         TerminalElement {
-            view,
             terminal,
             focused,
             cursor_visible,
@@ -361,11 +367,11 @@ impl TerminalElement {
         connection: WeakModelHandle<Terminal>,
         origin: Vector2F,
         f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
-    ) -> impl Fn(E, &mut EventContext) {
-        move |event, cx| {
+    ) -> impl Fn(E, &mut TerminalView, &mut EventContext<TerminalView>) {
+        move |event, _: &mut TerminalView, cx| {
             cx.focus_parent_view();
-            if let Some(conn_handle) = connection.upgrade(cx.app) {
-                conn_handle.update(cx.app, |terminal, cx| {
+            if let Some(conn_handle) = connection.upgrade(cx) {
+                conn_handle.update(cx, |terminal, cx| {
                     f(terminal, origin, event, cx);
 
                     cx.notify();
@@ -376,15 +382,15 @@ impl TerminalElement {
 
     fn attach_mouse_handlers(
         &self,
+        scene: &mut SceneBuilder,
         origin: Vector2F,
-        view_id: usize,
         visible_bounds: RectF,
         mode: TermMode,
-        cx: &mut PaintContext,
+        cx: &mut ViewContext<TerminalView>,
     ) {
         let connection = self.terminal;
 
-        let mut region = MouseRegion::new::<Self>(view_id, 0, visible_bounds);
+        let mut region = MouseRegion::new::<Self>(cx.view_id(), 0, visible_bounds);
 
         // Terminal Emulator controlled behavior:
         region = region
@@ -400,10 +406,10 @@ impl TerminalElement {
                 ),
             )
             // Update drag selections
-            .on_drag(MouseButton::Left, move |event, cx| {
+            .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| {
                 if cx.is_parent_view_focused() {
-                    if let Some(conn_handle) = connection.upgrade(cx.app) {
-                        conn_handle.update(cx.app, |terminal, cx| {
+                    if let Some(conn_handle) = connection.upgrade(cx) {
+                        conn_handle.update(cx, |terminal, cx| {
                             terminal.mouse_drag(event, origin);
                             cx.notify();
                         })
@@ -422,9 +428,9 @@ impl TerminalElement {
                 ),
             )
             // Context menu
-            .on_click(MouseButton::Right, move |e, cx| {
-                let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
-                    conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
+            .on_click(MouseButton::Right, move |e, _: &mut TerminalView, cx| {
+                let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
+                    conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(e.shift))
                 } else {
                     // If we can't get the model handle, probably can't deploy the context menu
                     true
@@ -435,20 +441,19 @@ impl TerminalElement {
                     });
                 }
             })
-            .on_move(move |event, cx| {
+            .on_move(move |event, _: &mut TerminalView, cx| {
                 if cx.is_parent_view_focused() {
-                    if let Some(conn_handle) = connection.upgrade(cx.app) {
-                        conn_handle.update(cx.app, |terminal, cx| {
+                    if let Some(conn_handle) = connection.upgrade(cx) {
+                        conn_handle.update(cx, |terminal, cx| {
                             terminal.mouse_move(&event, origin);
                             cx.notify();
                         })
                     }
                 }
             })
-            .on_scroll(move |event, cx| {
-                // cx.focus_parent_view();
-                if let Some(conn_handle) = connection.upgrade(cx.app) {
-                    conn_handle.update(cx.app, |terminal, cx| {
+            .on_scroll(move |event, _: &mut TerminalView, cx| {
+                if let Some(conn_handle) = connection.upgrade(cx) {
+                    conn_handle.update(cx, |terminal, cx| {
                         terminal.scroll_wheel(event, origin);
                         cx.notify();
                     })
@@ -501,7 +506,7 @@ impl TerminalElement {
                 )
         }
 
-        cx.scene.push_mouse_region(region);
+        scene.push_mouse_region(region);
     }
 
     ///Configures a text style from the current settings.
@@ -546,14 +551,15 @@ impl TerminalElement {
     }
 }
 
-impl Element for TerminalElement {
+impl Drawable<TerminalView> for TerminalElement {
     type LayoutState = LayoutState;
     type PaintState = ();
 
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        cx: &mut gpui::LayoutContext,
+        view: &mut TerminalView,
+        cx: &mut ViewContext<TerminalView>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         let settings = cx.global::<Settings>();
         let font_cache = cx.font_cache();
@@ -581,34 +587,31 @@ impl Element for TerminalElement {
         let background_color = terminal_theme.background;
         let terminal_handle = self.terminal.upgrade(cx).unwrap();
 
-        let last_hovered_hyperlink = terminal_handle.update(cx.app, |terminal, cx| {
+        let last_hovered_hyperlink = terminal_handle.update(cx, |terminal, cx| {
             terminal.set_size(dimensions);
             terminal.try_sync(cx);
             terminal.last_content.last_hovered_hyperlink.clone()
         });
 
-        let view_handle = self.view.clone();
-        let hyperlink_tooltip = last_hovered_hyperlink.and_then(|(uri, _, id)| {
-            // last_mouse.and_then(|_last_mouse| {
-            view_handle.upgrade(cx).map(|handle| {
-                let mut tooltip = cx.render(&handle, |_, cx| {
-                    Overlay::new(
-                        Empty::new()
-                            .contained()
-                            .constrained()
-                            .with_width(dimensions.width())
-                            .with_height(dimensions.height())
-                            .with_tooltip::<TerminalElement, _>(id, uri, None, tooltip_style, cx)
-                            .boxed(),
-                    )
-                    .with_position_mode(gpui::elements::OverlayPositionMode::Local)
-                    .boxed()
-                });
-
-                tooltip.layout(SizeConstraint::new(Vector2F::zero(), cx.window_size), cx);
-                tooltip
-            })
-            // })
+        let hyperlink_tooltip = last_hovered_hyperlink.map(|(uri, _, id)| {
+            let mut tooltip = Overlay::new(
+                Empty::new()
+                    .contained()
+                    .constrained()
+                    .with_width(dimensions.width())
+                    .with_height(dimensions.height())
+                    .with_tooltip::<TerminalElement>(id, uri, None, tooltip_style, cx)
+                    .boxed(),
+            )
+            .with_position_mode(gpui::elements::OverlayPositionMode::Local)
+            .boxed();
+
+            tooltip.layout(
+                SizeConstraint::new(Vector2F::zero(), cx.window_size()),
+                view,
+                cx,
+            );
+            tooltip
         });
 
         let TerminalContent {
@@ -637,7 +640,7 @@ impl Element for TerminalElement {
             cells,
             &text_style,
             &terminal_theme,
-            cx.text_layout_cache,
+            cx.text_layout_cache(),
             cx.font_cache(),
             last_hovered_hyperlink
                 .as_ref()
@@ -659,7 +662,7 @@ impl Element for TerminalElement {
                     terminal_theme.foreground
                 };
 
-                cx.text_layout_cache.layout_str(
+                cx.text_layout_cache().layout_str(
                     &str_trxt,
                     text_style.font_size,
                     &[(
@@ -717,23 +720,25 @@ impl Element for TerminalElement {
 
     fn paint(
         &mut self,
-        bounds: gpui::geometry::rect::RectF,
-        visible_bounds: gpui::geometry::rect::RectF,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
         layout: &mut Self::LayoutState,
-        cx: &mut gpui::PaintContext,
+        view: &mut TerminalView,
+        cx: &mut ViewContext<TerminalView>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
         //Setup element stuff
         let clip_bounds = Some(visible_bounds);
 
-        cx.paint_layer(clip_bounds, |cx| {
+        scene.paint_layer(clip_bounds, |scene| {
             let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
 
             // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
-            self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx);
+            self.attach_mouse_handlers(scene, origin, visible_bounds, layout.mode, cx);
 
-            cx.scene.push_cursor_region(gpui::CursorRegion {
+            scene.push_cursor_region(gpui::CursorRegion {
                 bounds,
                 style: if layout.hyperlink_tooltip.is_some() {
                     CursorStyle::PointingHand
@@ -742,9 +747,9 @@ impl Element for TerminalElement {
                 },
             });
 
-            cx.paint_layer(clip_bounds, |cx| {
+            scene.paint_layer(clip_bounds, |scene| {
                 //Start with a background color
-                cx.scene.push_quad(Quad {
+                scene.push_quad(Quad {
                     bounds: RectF::new(bounds.origin(), bounds.size()),
                     background: Some(layout.background_color),
                     border: Default::default(),
@@ -752,12 +757,12 @@ impl Element for TerminalElement {
                 });
 
                 for rect in &layout.rects {
-                    rect.paint(origin, layout, cx)
+                    rect.paint(scene, origin, layout, view, cx)
                 }
             });
 
             //Draw Highlighted Backgrounds
-            cx.paint_layer(clip_bounds, |cx| {
+            scene.paint_layer(clip_bounds, |scene| {
                 for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
                 {
                     if let Some((start_y, highlighted_range_lines)) =
@@ -771,29 +776,29 @@ impl Element for TerminalElement {
                             //Copied from editor. TODO: move to theme or something
                             corner_radius: 0.15 * layout.size.line_height,
                         };
-                        hr.paint(bounds, cx.scene);
+                        hr.paint(bounds, scene);
                     }
                 }
             });
 
             //Draw the text cells
-            cx.paint_layer(clip_bounds, |cx| {
+            scene.paint_layer(clip_bounds, |scene| {
                 for cell in &layout.cells {
-                    cell.paint(origin, layout, visible_bounds, cx);
+                    cell.paint(scene, origin, layout, visible_bounds, view, cx);
                 }
             });
 
             //Draw cursor
             if self.cursor_visible {
                 if let Some(cursor) = &layout.cursor {
-                    cx.paint_layer(clip_bounds, |cx| {
-                        cursor.paint(origin, cx);
+                    scene.paint_layer(clip_bounds, |scene| {
+                        cursor.paint(scene, origin, cx);
                     })
                 }
             }
 
             if let Some(element) = &mut layout.hyperlink_tooltip {
-                element.paint(origin, visible_bounds, cx)
+                element.paint(scene, origin, visible_bounds, view, cx)
             }
         });
     }
@@ -804,10 +809,11 @@ impl Element for TerminalElement {
 
     fn debug(
         &self,
-        _bounds: gpui::geometry::rect::RectF,
-        _layout: &Self::LayoutState,
-        _paint: &Self::PaintState,
-        _cx: &gpui::DebugContext,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        _: &TerminalView,
+        _: &gpui::ViewContext<TerminalView>,
     ) -> gpui::serde_json::Value {
         json!({
             "type": "TerminalElement",
@@ -821,7 +827,8 @@ impl Element for TerminalElement {
         _: RectF,
         layout: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &gpui::MeasurementContext,
+        _: &TerminalView,
+        _: &gpui::ViewContext<TerminalView>,
     ) -> Option<RectF> {
         // Use the same origin that's passed to `Cursor::paint` in the paint
         // method bove.

crates/terminal_view/src/terminal_view.rs 🔗

@@ -13,12 +13,12 @@ use context_menu::{ContextMenu, ContextMenuItem};
 use dirs::home_dir;
 use gpui::{
     actions,
-    elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack, Text},
+    elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
     geometry::vector::Vector2F,
     impl_actions, impl_internal_actions,
     keymap_matcher::{KeymapContext, Keystroke},
     platform::KeyDownEvent,
-    AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, Task, View, ViewContext,
+    AnyViewHandle, AppContext, Drawable, Element, Entity, ModelHandle, Task, View, ViewContext,
     ViewHandle, WeakViewHandle,
 };
 use project::{LocalWorktree, Project};
@@ -35,7 +35,7 @@ use terminal::{
 };
 use util::ResultExt;
 use workspace::{
-    item::{Item, ItemEvent},
+    item::{BreadcrumbText, Item, ItemEvent},
     notifications::NotifyResultExt,
     pane, register_deserializable_item,
     searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
@@ -237,11 +237,7 @@ impl TerminalView {
         cx.notify();
     }
 
-    pub fn should_show_cursor(
-        &self,
-        focused: bool,
-        cx: &mut gpui::RenderContext<'_, Self>,
-    ) -> bool {
+    pub fn should_show_cursor(&self, focused: bool, cx: &mut gpui::ViewContext<Self>) -> bool {
         //Don't blink the cursor when not focused, blinking is disabled, or paused
         if !focused
             || !self.blinking_on
@@ -284,7 +280,8 @@ impl TerminalView {
                 async move {
                     Timer::after(CURSOR_BLINK_INTERVAL).await;
                     if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx));
+                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
+                            .log_err();
                     }
                 }
             })
@@ -303,6 +300,7 @@ impl TerminalView {
                 Timer::after(CURSOR_BLINK_INTERVAL).await;
                 if let Some(this) = this.upgrade(&cx) {
                     this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
+                        .log_err();
                 }
             }
         })
@@ -389,19 +387,18 @@ impl View for TerminalView {
         "Terminal"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
         let terminal_handle = self.terminal.clone().downgrade();
 
         let self_id = cx.view_id();
         let focused = cx
-            .focused_view_id(cx.window_id())
+            .focused_view_id()
             .filter(|view_id| *view_id == self_id)
             .is_some();
 
         Stack::new()
             .with_child(
                 TerminalElement::new(
-                    cx.handle(),
                     terminal_handle,
                     focused,
                     self.should_show_cursor(focused, cx),
@@ -548,12 +545,12 @@ impl Item for TerminalView {
         Some(self.terminal().read(cx).title().into())
     }
 
-    fn tab_content(
+    fn tab_content<T: View>(
         &self,
         _detail: Option<usize>,
         tab_theme: &theme::Tab,
         cx: &gpui::AppContext,
-    ) -> ElementBox {
+    ) -> Element<T> {
         let title = self.terminal().read(cx).title();
 
         Flex::row()
@@ -615,12 +612,11 @@ impl Item for TerminalView {
         ToolbarItemLocation::PrimaryLeft { flex: None }
     }
 
-    fn breadcrumbs(&self, theme: &theme::Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
-        Some(vec![Text::new(
-            self.terminal().read(cx).breadcrumb_text.clone(),
-            theme.workspace.breadcrumbs.default.text.clone(),
-        )
-        .boxed()])
+    fn breadcrumbs(&self, _: &theme::Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+        Some(vec![BreadcrumbText {
+            text: self.terminal().read(cx).breadcrumb_text.clone(),
+            highlights: None,
+        }])
     }
 
     fn serialized_item_kind() -> Option<&'static str> {

crates/theme/src/ui.rs 🔗

@@ -11,7 +11,7 @@ use gpui::{
     platform,
     platform::MouseButton,
     scene::MouseClick,
-    Action, Element, ElementBox, EventContext, MouseState, RenderContext, View,
+    Action, Drawable, Element, EventContext, MouseState, View, ViewContext,
 };
 use serde::Deserialize;
 
@@ -27,13 +27,13 @@ pub struct CheckboxStyle {
     pub hovered_and_checked: ContainerStyle,
 }
 
-pub fn checkbox<T: 'static, V: View>(
+pub fn checkbox<Tag: 'static, V: View>(
     label: &'static str,
     style: &CheckboxStyle,
     checked: bool,
-    cx: &mut RenderContext<V>,
-    change: fn(checked: bool, cx: &mut EventContext) -> (),
-) -> MouseEventHandler<T> {
+    cx: &mut ViewContext<V>,
+    change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
+) -> MouseEventHandler<Tag, V> {
     let label = Label::new(label, style.label.text.clone())
         .contained()
         .with_style(style.label.container)
@@ -42,14 +42,14 @@ pub fn checkbox<T: 'static, V: View>(
     checkbox_with_label(label, style, checked, cx, change)
 }
 
-pub fn checkbox_with_label<T: 'static, V: View>(
-    label: ElementBox,
+pub fn checkbox_with_label<Tag: 'static, V: View>(
+    label: Element<V>,
     style: &CheckboxStyle,
     checked: bool,
-    cx: &mut RenderContext<V>,
-    change: fn(checked: bool, cx: &mut EventContext) -> (),
-) -> MouseEventHandler<T> {
-    MouseEventHandler::<T>::new(0, cx, |state, _| {
+    cx: &mut ViewContext<V>,
+    change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
+) -> MouseEventHandler<Tag, V> {
+    MouseEventHandler::new(0, cx, |state, _| {
         let indicator = if checked {
             svg(&style.icon)
         } else {
@@ -82,7 +82,7 @@ pub fn checkbox_with_label<T: 'static, V: View>(
             .align_children_center()
             .boxed()
     })
-    .on_click(platform::MouseButton::Left, move |_, cx| {
+    .on_click(platform::MouseButton::Left, move |_, _, cx| {
         change(!checked, cx)
     })
     .with_cursor_style(platform::CursorStyle::PointingHand)
@@ -107,7 +107,7 @@ impl Dimensions {
     }
 }
 
-pub fn svg(style: &SvgStyle) -> ConstrainedBox {
+pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
     Svg::new(style.asset.clone())
         .with_color(style.color)
         .constrained()
@@ -121,7 +121,7 @@ pub struct IconStyle {
     container: ContainerStyle,
 }
 
-pub fn icon(style: &IconStyle) -> Container {
+pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
     svg(&style.icon).contained().with_style(style.container)
 }
 
@@ -130,12 +130,11 @@ pub fn keystroke_label<V: View>(
     label_style: &ContainedText,
     keystroke_style: &ContainedText,
     action: Box<dyn Action>,
-    cx: &mut RenderContext<V>,
-) -> Container {
+    cx: &mut ViewContext<V>,
+) -> Container<V> {
     // FIXME: Put the theme in it's own global so we can
     // query the keystroke style on our own
     keystroke_label_for(
-        cx.window_id(),
         cx.handle().id(),
         label_text,
         label_style,
@@ -144,14 +143,13 @@ pub fn keystroke_label<V: View>(
     )
 }
 
-pub fn keystroke_label_for(
-    window_id: usize,
+pub fn keystroke_label_for<V: View>(
     view_id: usize,
     label_text: &'static str,
     label_style: &ContainedText,
     keystroke_style: &ContainedText,
     action: Box<dyn Action>,
-) -> Container {
+) -> Container<V> {
     Flex::row()
         .with_child(
             Label::new(label_text, label_style.text.clone())
@@ -160,7 +158,6 @@ pub fn keystroke_label_for(
         )
         .with_child({
             KeystrokeLabel::new(
-                window_id,
                 view_id,
                 action,
                 keystroke_style.container,
@@ -180,32 +177,33 @@ pub fn cta_button<L, A, V>(
     action: A,
     max_width: f32,
     style: &ButtonStyle,
-    cx: &mut RenderContext<V>,
-) -> ElementBox
+    cx: &mut ViewContext<V>,
+) -> Element<V>
 where
     L: Into<Cow<'static, str>>,
     A: 'static + Action + Clone,
     V: View,
 {
-    cta_button_with_click(label, max_width, style, cx, move |_, cx| {
+    cta_button_with_click::<A, _, _, _>(label, max_width, style, cx, move |_, _, cx| {
         cx.dispatch_action(action.clone())
     })
     .boxed()
 }
 
-pub fn cta_button_with_click<L, V, F>(
+pub fn cta_button_with_click<Tag, L, V, F>(
     label: L,
     max_width: f32,
     style: &ButtonStyle,
-    cx: &mut RenderContext<V>,
+    cx: &mut ViewContext<V>,
     f: F,
-) -> MouseEventHandler<F>
+) -> MouseEventHandler<Tag, V>
 where
+    Tag: 'static,
     L: Into<Cow<'static, str>>,
     V: View,
-    F: Fn(MouseClick, &mut EventContext) + 'static,
+    F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
 {
-    MouseEventHandler::<F>::new(0, cx, |state, _| {
+    MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
         let style = style.style_for(state, false);
         Label::new(label, style.text.to_owned())
             .aligned()
@@ -234,16 +232,17 @@ impl ModalStyle {
     }
 }
 
-pub fn modal<V, I, F>(
+pub fn modal<Tag, V, I, F>(
     title: I,
     style: &ModalStyle,
-    cx: &mut RenderContext<V>,
+    cx: &mut ViewContext<V>,
     build_modal: F,
-) -> ElementBox
+) -> Element<V>
 where
+    Tag: 'static,
     V: View,
     I: Into<Cow<'static, str>>,
-    F: FnOnce(&mut gpui::RenderContext<V>) -> ElementBox,
+    F: FnOnce(&mut gpui::ViewContext<V>) -> Element<V>,
 {
     const TITLEBAR_HEIGHT: f32 = 28.;
     // let active = cx.window_is_active(cx.window_id());
@@ -261,13 +260,12 @@ where
                     )
                     .boxed(),
                     // FIXME: Get a better tag type
-                    MouseEventHandler::<V>::new(999999, cx, |state, _cx| {
+                    MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
                         let style = style.close_icon.style_for(state, false);
                         icon(style).boxed()
                     })
-                    .on_click(platform::MouseButton::Left, move |_, cx| {
-                        let window_id = cx.window_id();
-                        cx.remove_window(window_id);
+                    .on_click(platform::MouseButton::Left, move |_, _, cx| {
+                        cx.remove_window();
                     })
                     .with_cursor_style(platform::CursorStyle::PointingHand)
                     .aligned()

crates/theme_selector/src/theme_selector.rs 🔗

@@ -1,45 +1,57 @@
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
-use gpui::{
-    actions, elements::*, AnyViewHandle, AppContext, Element, ElementBox, Entity, MouseState,
-    RenderContext, View, ViewContext, ViewHandle,
-};
-use picker::{Picker, PickerDelegate};
+use gpui::{actions, elements::*, AppContext, Drawable, Element, MouseState, ViewContext};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::{settings_file::SettingsFile, Settings};
 use staff_mode::StaffMode;
 use std::sync::Arc;
 use theme::{Theme, ThemeMeta, ThemeRegistry};
+use util::ResultExt;
 use workspace::{AppState, Workspace};
 
-pub struct ThemeSelector {
-    registry: Arc<ThemeRegistry>,
-    theme_data: Vec<ThemeMeta>,
-    matches: Vec<StringMatch>,
-    original_theme: Arc<Theme>,
-    picker: ViewHandle<Picker<Self>>,
-    selection_completed: bool,
-    selected_index: usize,
-}
-
 actions!(theme_selector, [Toggle, Reload]);
 
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    Picker::<ThemeSelector>::init(cx);
     cx.add_action({
         let theme_registry = app_state.themes.clone();
-        move |workspace, _: &Toggle, cx| {
-            ThemeSelector::toggle(workspace, theme_registry.clone(), cx)
-        }
+        move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx)
     });
+    ThemeSelector::init(cx);
 }
 
-pub enum Event {
-    Dismissed,
+fn toggle(workspace: &mut Workspace, themes: Arc<ThemeRegistry>, cx: &mut ViewContext<Workspace>) {
+    workspace.toggle_modal(cx, |_, cx| {
+        cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx))
+    });
 }
 
-impl ThemeSelector {
-    fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
-        let handle = cx.weak_handle();
-        let picker = cx.add_view(|cx| Picker::new("Select Theme...", handle, cx));
+#[cfg(debug_assertions)]
+pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut AppContext) {
+    let current_theme_name = cx.global::<Settings>().theme.meta.name.clone();
+    themes.clear();
+    match themes.get(&current_theme_name) {
+        Ok(theme) => {
+            ThemeSelectorDelegate::set_theme(theme, cx);
+            log::info!("reloaded theme {}", current_theme_name);
+        }
+        Err(error) => {
+            log::error!("failed to load theme {}: {:?}", current_theme_name, error)
+        }
+    }
+}
+
+pub type ThemeSelector = Picker<ThemeSelectorDelegate>;
+
+pub struct ThemeSelectorDelegate {
+    registry: Arc<ThemeRegistry>,
+    theme_data: Vec<ThemeMeta>,
+    matches: Vec<StringMatch>,
+    original_theme: Arc<Theme>,
+    selection_completed: bool,
+    selected_index: usize,
+}
+
+impl ThemeSelectorDelegate {
+    fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<ThemeSelector>) -> Self {
         let settings = cx.global::<Settings>();
 
         let original_theme = settings.theme.clone();
@@ -61,7 +73,6 @@ impl ThemeSelector {
             registry,
             theme_data: theme_names,
             matches,
-            picker,
             original_theme: original_theme.clone(),
             selected_index: 0,
             selection_completed: false,
@@ -70,34 +81,7 @@ impl ThemeSelector {
         this
     }
 
-    fn toggle(
-        workspace: &mut Workspace,
-        themes: Arc<ThemeRegistry>,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        workspace.toggle_modal(cx, |_, cx| {
-            let this = cx.add_view(|cx| Self::new(themes, cx));
-            cx.subscribe(&this, Self::on_event).detach();
-            this
-        });
-    }
-
-    #[cfg(debug_assertions)]
-    pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut AppContext) {
-        let current_theme_name = cx.global::<Settings>().theme.meta.name.clone();
-        themes.clear();
-        match themes.get(&current_theme_name) {
-            Ok(theme) => {
-                Self::set_theme(theme, cx);
-                log::info!("reloaded theme {}", current_theme_name);
-            }
-            Err(error) => {
-                log::error!("failed to load theme {}: {:?}", current_theme_name, error)
-            }
-        }
-    }
-
-    fn show_selected_theme(&mut self, cx: &mut ViewContext<Self>) {
+    fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
         if let Some(mat) = self.matches.get(self.selected_index) {
             match self.registry.get(&mat.string) {
                 Ok(theme) => {
@@ -118,19 +102,6 @@ impl ThemeSelector {
             .unwrap_or(self.selected_index);
     }
 
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<ThemeSelector>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => {
-                workspace.dismiss_modal(cx);
-            }
-        }
-    }
-
     fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
         cx.update_global::<Settings, _, _>(|settings, cx| {
             settings.theme = theme;
@@ -139,12 +110,16 @@ impl ThemeSelector {
     }
 }
 
-impl PickerDelegate for ThemeSelector {
+impl PickerDelegate for ThemeSelectorDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Select Theme...".into()
+    }
+
     fn match_count(&self) -> usize {
         self.matches.len()
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) {
         self.selection_completed = true;
 
         let theme_name = cx.global::<Settings>().theme.meta.name.clone();
@@ -152,27 +127,30 @@ impl PickerDelegate for ThemeSelector {
             settings_content.theme = Some(theme_name);
         });
 
-        cx.emit(Event::Dismissed);
+        cx.emit(PickerEvent::Dismiss);
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+    fn dismissed(&mut self, cx: &mut ViewContext<ThemeSelector>) {
         if !self.selection_completed {
             Self::set_theme(self.original_theme.clone(), cx);
             self.selection_completed = true;
         }
-        cx.emit(Event::Dismissed);
     }
 
     fn selected_index(&self) -> usize {
         self.selected_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<ThemeSelector>) {
         self.selected_index = ix;
         self.show_selected_theme(cx);
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut ViewContext<ThemeSelector>,
+    ) -> gpui::Task<()> {
         let background = cx.background().clone();
         let candidates = self
             .theme_data
@@ -185,7 +163,7 @@ impl PickerDelegate for ThemeSelector {
             })
             .collect::<Vec<_>>();
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn_weak(|this, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -209,14 +187,17 @@ impl PickerDelegate for ThemeSelector {
                 .await
             };
 
-            this.update(&mut cx, |this, cx| {
-                this.matches = matches;
-                this.selected_index = this
-                    .selected_index
-                    .min(this.matches.len().saturating_sub(1));
-                this.show_selected_theme(cx);
-                cx.notify();
-            });
+            if let Some(this) = this.upgrade(&cx) {
+                this.update(&mut cx, |this, cx| {
+                    let delegate = this.delegate_mut();
+                    delegate.matches = matches;
+                    delegate.selected_index = delegate
+                        .selected_index
+                        .min(delegate.matches.len().saturating_sub(1));
+                    delegate.show_selected_theme(cx);
+                })
+                .log_err();
+            }
         })
     }
 
@@ -226,7 +207,7 @@ impl PickerDelegate for ThemeSelector {
         mouse_state: &mut MouseState,
         selected: bool,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<Picker<Self>> {
         let settings = cx.global::<Settings>();
         let theme = &settings.theme;
         let theme_match = &self.matches[ix];
@@ -239,29 +220,3 @@ impl PickerDelegate for ThemeSelector {
             .boxed()
     }
 }
-
-impl Entity for ThemeSelector {
-    type Event = Event;
-
-    fn release(&mut self, cx: &mut AppContext) {
-        if !self.selection_completed {
-            Self::set_theme(self.original_theme.clone(), cx);
-        }
-    }
-}
-
-impl View for ThemeSelector {
-    fn ui_name() -> &'static str {
-        "ThemeSelector"
-    }
-
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}

crates/theme_testbench/src/theme_testbench.rs 🔗

@@ -2,12 +2,12 @@ use gpui::{
     actions,
     color::Color,
     elements::{
-        Canvas, Container, ContainerStyle, ElementBox, Flex, Label, Margin, MouseEventHandler,
+        Canvas, Container, ContainerStyle, Element, Flex, Label, Margin, MouseEventHandler,
         Padding, ParentElement,
     },
     fonts::TextStyle,
-    AppContext, Border, Element, Entity, ModelHandle, Quad, RenderContext, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle,
+    AppContext, Border, Drawable, Entity, ModelHandle, Quad, Task, View, ViewContext, ViewHandle,
+    WeakViewHandle,
 };
 use project::Project;
 use settings::Settings;
@@ -34,12 +34,12 @@ impl ThemeTestbench {
         workspace.add_item(Box::new(view), cx);
     }
 
-    fn render_ramps(color_scheme: &ColorScheme) -> Flex {
-        fn display_ramp(ramp: &Vec<Color>) -> ElementBox {
+    fn render_ramps(color_scheme: &ColorScheme) -> Flex<Self> {
+        fn display_ramp(ramp: &Vec<Color>) -> Element<ThemeTestbench> {
             Flex::row()
                 .with_children(ramp.iter().cloned().map(|color| {
-                    Canvas::new(move |bounds, _, cx| {
-                        cx.scene.push_quad(Quad {
+                    Canvas::new(move |scene, bounds, _, _, _| {
+                        scene.push_quad(Quad {
                             bounds,
                             background: Some(color),
                             ..Default::default()
@@ -67,8 +67,8 @@ impl ThemeTestbench {
     fn render_layer(
         layer_index: usize,
         layer: &Layer,
-        cx: &mut RenderContext<'_, Self>,
-    ) -> Container {
+        cx: &mut ViewContext<Self>,
+    ) -> Container<Self> {
         Flex::column()
             .with_child(
                 Self::render_button_set(0, layer_index, "base", &layer.base, cx)
@@ -123,8 +123,8 @@ impl ThemeTestbench {
         layer_index: usize,
         set_name: &'static str,
         style_set: &StyleSet,
-        cx: &mut RenderContext<'_, Self>,
-    ) -> Flex {
+        cx: &mut ViewContext<Self>,
+    ) -> Flex<Self> {
         Flex::row()
             .with_child(Self::render_button(
                 set_index * 6,
@@ -182,10 +182,10 @@ impl ThemeTestbench {
         text: &'static str,
         style_set: &StyleSet,
         style_override: Option<fn(&StyleSet) -> &Style>,
-        cx: &mut RenderContext<'_, Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         enum TestBenchButton {}
-        MouseEventHandler::<TestBenchButton>::new(layer_index + button_index, cx, |state, cx| {
+        MouseEventHandler::<TestBenchButton, _>::new(layer_index + button_index, cx, |state, cx| {
             let style = if let Some(style_override) = style_override {
                 style_override(&style_set)
             } else if state.clicked().is_some() {
@@ -230,7 +230,7 @@ impl ThemeTestbench {
         .boxed()
     }
 
-    fn render_label(text: String, style: &Style, cx: &mut RenderContext<'_, Self>) -> Label {
+    fn render_label(text: String, style: &Style, cx: &mut ViewContext<Self>) -> Label {
         let settings = cx.global::<Settings>();
         let font_cache = cx.font_cache();
         let family_id = settings.buffer_font_family;
@@ -262,7 +262,7 @@ impl View for ThemeTestbench {
         "ThemeTestbench"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
         let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
 
         Flex::row()
@@ -298,12 +298,12 @@ impl View for ThemeTestbench {
 }
 
 impl Item for ThemeTestbench {
-    fn tab_content(
+    fn tab_content<T: View>(
         &self,
         _: Option<usize>,
         style: &theme::Tab,
         _: &AppContext,
-    ) -> gpui::ElementBox {
+    ) -> Element<T> {
         Label::new("Theme Testbench", style.label.clone())
             .aligned()
             .contained()

crates/util/src/test/marked_text.rs 🔗

@@ -104,6 +104,10 @@ pub fn marked_text_ranges_by(
 ///    ```text
 ///    one «ˇreversed» selection and one «forwardˇ» selection
 ///    ```
+///
+/// Any • characters in the input string will be replaced with spaces. This makes
+/// it easier to test cases with trailing spaces, which tend to get trimmed from the
+/// source code.
 pub fn marked_text_ranges(
     marked_text: &str,
     ranges_are_directed: bool,
@@ -114,6 +118,7 @@ pub fn marked_text_ranges(
     let mut current_range_start = None;
     let mut current_range_cursor = None;
 
+    let marked_text = marked_text.replace("•", " ");
     for (marked_ix, marker) in marked_text.match_indices(&['«', '»', 'ˇ']) {
         unmarked_text.push_str(&marked_text[prev_marked_ix..marked_ix]);
         let unmarked_len = unmarked_text.len();

crates/vim/src/editor_events.rs 🔗

@@ -1,8 +1,7 @@
-use editor::{EditorBlurred, EditorFocused, EditorMode, EditorReleased, Event};
+use crate::Vim;
+use editor::{EditorBlurred, EditorFocused, EditorReleased};
 use gpui::AppContext;
 
-use crate::{state::Mode, Vim};
-
 pub fn init(cx: &mut AppContext) {
     cx.subscribe_global(focused).detach();
     cx.subscribe_global(blurred).detach();
@@ -10,69 +9,40 @@ pub fn init(cx: &mut AppContext) {
 }
 
 fn focused(EditorFocused(editor): &EditorFocused, cx: &mut AppContext) {
-    Vim::update(cx, |vim, cx| {
-        if let Some(previously_active_editor) = vim
-            .active_editor
-            .as_ref()
-            .and_then(|editor| editor.upgrade(cx))
-        {
-            vim.unhook_vim_settings(previously_active_editor, cx);
-        }
-
-        vim.active_editor = Some(editor.downgrade());
-        vim.editor_subscription = Some(cx.subscribe(editor, |editor, event, cx| match event {
-            Event::SelectionsChanged { local: true } => {
-                let editor = editor.read(cx);
-                if editor.leader_replica_id().is_none() {
-                    let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
-                    local_selections_changed(newest_empty, cx);
-                }
-            }
-            Event::InputIgnored { text } => {
-                Vim::active_editor_input_ignored(text.clone(), cx);
-            }
-            _ => {}
-        }));
-
-        if vim.enabled {
-            let editor = editor.read(cx);
-            let editor_mode = editor.mode();
-            let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
-
-            if editor_mode == EditorMode::Full && !newest_selection_empty {
-                vim.switch_mode(Mode::Visual { line: false }, true, cx);
-            }
-        }
-
-        vim.sync_vim_settings(cx);
+    cx.update_window(editor.window_id(), |cx| {
+        Vim::update(cx, |vim, cx| {
+            vim.update_active_editor(cx, |previously_active_editor, cx| {
+                Vim::unhook_vim_settings(previously_active_editor, cx);
+            });
+            vim.set_active_editor(editor.clone(), cx);
+        });
     });
 }
 
 fn blurred(EditorBlurred(editor): &EditorBlurred, cx: &mut AppContext) {
-    Vim::update(cx, |vim, cx| {
-        if let Some(previous_editor) = vim.active_editor.clone() {
-            if previous_editor == editor.clone() {
-                vim.active_editor = None;
+    cx.update_window(editor.window_id(), |cx| {
+        Vim::update(cx, |vim, cx| {
+            if let Some(previous_editor) = vim.active_editor.clone() {
+                if previous_editor == editor.clone() {
+                    vim.active_editor = None;
+                }
             }
-        }
-        vim.unhook_vim_settings(editor.clone(), cx);
-    })
+
+            cx.update_window(editor.window_id(), |cx| {
+                editor.update(cx, |editor, cx| Vim::unhook_vim_settings(editor, cx))
+            });
+        });
+    });
 }
 
 fn released(EditorReleased(editor): &EditorReleased, cx: &mut AppContext) {
-    cx.update_default_global(|vim: &mut Vim, _| {
-        if let Some(previous_editor) = vim.active_editor.clone() {
-            if previous_editor == editor.clone() {
-                vim.active_editor = None;
+    cx.update_window(editor.window_id(), |cx| {
+        cx.update_default_global(|vim: &mut Vim, _| {
+            if let Some(previous_editor) = vim.active_editor.clone() {
+                if previous_editor == editor.clone() {
+                    vim.active_editor = None;
+                }
             }
-        }
+        });
     });
 }
-
-fn local_selections_changed(newest_empty: bool, cx: &mut AppContext) {
-    Vim::update(cx, |vim, cx| {
-        if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
-            vim.switch_mode(Mode::Visual { line: false }, false, cx)
-        }
-    })
-}

crates/vim/src/motion.rs 🔗

@@ -5,7 +5,7 @@ use editor::{
     display_map::{DisplaySnapshot, ToDisplayPoint},
     movement, Bias, CharKind, DisplayPoint, ToOffset,
 };
-use gpui::{actions, impl_actions, AppContext};
+use gpui::{actions, impl_actions, AppContext, WindowContext};
 use language::{Point, Selection, SelectionGoal};
 use serde::Deserialize;
 use workspace::Workspace;
@@ -116,7 +116,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx))
 }
 
-pub(crate) fn motion(motion: Motion, cx: &mut AppContext) {
+pub(crate) fn motion(motion: Motion, cx: &mut WindowContext) {
     if let Some(Operator::Namespace(_))
     | Some(Operator::FindForward { .. })
     | Some(Operator::FindBackward { .. }) = Vim::read(cx).active_operator()

crates/vim/src/normal.rs 🔗

@@ -16,7 +16,7 @@ use editor::{
     scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount},
     Anchor, Bias, ClipboardSelection, DisplayPoint, Editor,
 };
-use gpui::{actions, impl_actions, AppContext, ViewContext};
+use gpui::{actions, impl_actions, AppContext, ViewContext, WindowContext};
 use language::{AutoindentMode, Point, SelectionGoal};
 use log::error;
 use serde::Deserialize;
@@ -94,7 +94,7 @@ pub fn normal_motion(
     motion: Motion,
     operator: Option<Operator>,
     times: usize,
-    cx: &mut AppContext,
+    cx: &mut WindowContext,
 ) {
     Vim::update(cx, |vim, cx| {
         match operator {
@@ -110,7 +110,7 @@ pub fn normal_motion(
     });
 }
 
-pub fn normal_object(object: Object, cx: &mut AppContext) {
+pub fn normal_object(object: Object, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         match vim.state.operator_stack.pop() {
             Some(Operator::Object { around }) => match vim.state.operator_stack.pop() {
@@ -129,7 +129,7 @@ pub fn normal_object(object: Object, cx: &mut AppContext) {
     })
 }
 
-fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+fn move_cursor(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
             s.move_cursors_with(|map, cursor, goal| {
@@ -251,7 +251,7 @@ fn paste(_: &mut Workspace, _: &Paste, cx: &mut ViewContext<Workspace>) {
         vim.update_active_editor(cx, |editor, cx| {
             editor.transact(cx, |editor, cx| {
                 editor.set_clip_at_line_ends(false, cx);
-                if let Some(item) = cx.as_mut().read_from_clipboard() {
+                if let Some(item) = cx.read_from_clipboard() {
                     let mut clipboard_text = Cow::Borrowed(item.text());
                     if let Some(mut clipboard_selections) =
                         item.metadata::<Vec<ClipboardSelection>>()
@@ -424,7 +424,7 @@ fn scroll(editor: &mut Editor, amount: &ScrollAmount, cx: &mut ViewContext<Edito
     }
 }
 
-pub(crate) fn normal_replace(text: Arc<str>, cx: &mut AppContext) {
+pub(crate) fn normal_replace(text: Arc<str>, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
             editor.transact(cx, |editor, cx| {
@@ -570,18 +570,18 @@ mod test {
 
         cx.assert_all(indoc! {"
                 The ˇquick
-                
+
                 brown fox jumps
                 overˇ the lazy doˇg"})
             .await;
         cx.assert(indoc! {"
             The quiˇck
-            
+
             brown"})
             .await;
         cx.assert(indoc! {"
             The quiˇck
-            
+
             "})
             .await;
     }
@@ -611,16 +611,16 @@ mod test {
         let mut cx = NeovimBackedTestContext::new(cx).await.binding(["e"]);
         cx.assert_all(indoc! {"
             Thˇe quicˇkˇ-browˇn
-            
-            
+
+
             fox_jumpˇs oveˇr
             thˇe"})
             .await;
         let mut cx = cx.binding(["shift-e"]);
         cx.assert_all(indoc! {"
             Thˇe quicˇkˇ-browˇn
-            
-            
+
+
             fox_jumpˇs oveˇr
             thˇe"})
             .await;
@@ -669,7 +669,7 @@ mod test {
             ["g", "g"],
             indoc! {"
                 The qˇuick
-            
+
                 brown fox jumps
                 over ˇthe laˇzy dog"},
         )
@@ -677,8 +677,8 @@ mod test {
         cx.assert_binding_matches(
             ["g", "g"],
             indoc! {"
-                
-            
+
+
                 brown fox jumps
                 over the laˇzy dog"},
         )
@@ -687,7 +687,7 @@ mod test {
             ["2", "g", "g"],
             indoc! {"
                 ˇ
-                
+
                 brown fox jumps
                 over the lazydog"},
         )
@@ -701,7 +701,7 @@ mod test {
             ["shift-g"],
             indoc! {"
                 The qˇuick
-                
+
                 brown fox jumps
                 over ˇthe laˇzy dog"},
         )
@@ -709,8 +709,8 @@ mod test {
         cx.assert_binding_matches(
             ["shift-g"],
             indoc! {"
-                
-                
+
+
                 brown fox jumps
                 over the laˇzy dog"},
         )
@@ -719,7 +719,7 @@ mod test {
             ["2", "shift-g"],
             indoc! {"
                 ˇ
-                
+
                 brown fox jumps
                 over the lazydog"},
         )
@@ -999,7 +999,7 @@ mod test {
             let test_case = indoc! {"
                 ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
                 ˇ    ˇbˇaaˇa ˇbˇbˇb
-                ˇ   
+                ˇ
                 ˇb
             "};
 
@@ -1017,9 +1017,10 @@ mod test {
         let test_case = indoc! {"
             ˇaaaˇbˇ ˇbˇ   ˇbˇbˇ aˇaaˇbaaa
             ˇ    ˇbˇaaˇa ˇbˇbˇb
-            ˇ   
+            ˇ•••
             ˇb
-            "};
+            "
+        };
 
         for count in 1..=3 {
             cx.assert_binding_matches_all([&count.to_string(), "shift-f", "b"], test_case)

crates/vim/src/normal/change.rs 🔗

@@ -3,10 +3,10 @@ use editor::{
     char_kind, display_map::DisplaySnapshot, movement, scroll::autoscroll::Autoscroll, CharKind,
     DisplayPoint,
 };
-use gpui::AppContext;
+use gpui::WindowContext;
 use language::Selection;
 
-pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
     // Some motions ignore failure when switching to normal mode
     let mut motion_succeeded = matches!(
         motion,
@@ -38,7 +38,7 @@ pub fn change_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppCo
     }
 }
 
-pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut AppContext) {
+pub fn change_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
     let mut objects_found = false;
     vim.update_active_editor(cx, |editor, cx| {
         // We are swapping to insert mode anyway. Just set the line end clipping behavior now

crates/vim/src/normal/delete.rs 🔗

@@ -1,9 +1,9 @@
 use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
 use collections::{HashMap, HashSet};
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Bias};
-use gpui::AppContext;
+use gpui::WindowContext;
 
-pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.transact(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);
@@ -36,7 +36,7 @@ pub fn delete_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppCo
     });
 }
 
-pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut AppContext) {
+pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.transact(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);

crates/vim/src/normal/yank.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim};
 use collections::HashMap;
-use gpui::AppContext;
+use gpui::WindowContext;
 
-pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.transact(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);
@@ -25,7 +25,7 @@ pub fn yank_motion(vim: &mut Vim, motion: Motion, times: usize, cx: &mut AppCont
     });
 }
 
-pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut AppContext) {
+pub fn yank_object(vim: &mut Vim, object: Object, around: bool, cx: &mut WindowContext) {
     vim.update_active_editor(cx, |editor, cx| {
         editor.transact(cx, |editor, cx| {
             editor.set_clip_at_line_ends(false, cx);

crates/vim/src/object.rs 🔗

@@ -1,7 +1,7 @@
 use std::ops::Range;
 
 use editor::{char_kind, display_map::DisplaySnapshot, movement, Bias, CharKind, DisplayPoint};
-use gpui::{actions, impl_actions, AppContext};
+use gpui::{actions, impl_actions, AppContext, WindowContext};
 use language::Selection;
 use serde::Deserialize;
 use workspace::Workspace;
@@ -61,7 +61,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(|_: &mut Workspace, _: &AngleBrackets, cx: _| object(Object::AngleBrackets, cx));
 }
 
-fn object(object: Object, cx: &mut AppContext) {
+fn object(object: Object, cx: &mut WindowContext) {
     match Vim::read(cx).state.mode {
         Mode::Normal => normal_object(object, cx),
         Mode::Visual { .. } => visual_object(object, cx),
@@ -434,19 +434,20 @@ mod test {
     use crate::test::{ExemptionFeatures, NeovimBackedTestContext};
 
     const WORD_LOCATIONS: &'static str = indoc! {"
-        The quick ˇbrowˇnˇ   
+        The quick ˇbrowˇnˇ•••
         fox ˇjuˇmpsˇ over
-        the lazy dogˇ  
+        the lazy dogˇ••
         ˇ
         ˇ
         ˇ
-        Thˇeˇ-ˇquˇickˇ ˇbrownˇ 
-        ˇ  
-        ˇ  
+        Thˇeˇ-ˇquˇickˇ ˇbrownˇ•
+        ˇ••
+        ˇ••
         ˇ  fox-jumpˇs over
-        the lazy dogˇ 
+        the lazy dogˇ•
         ˇ
-        "};
+        "
+    };
 
     #[gpui::test]
     async fn test_change_word_object(cx: &mut gpui::TestAppContext) {
@@ -527,7 +528,7 @@ mod test {
     const SENTENCE_EXAMPLES: &[&'static str] = &[
         "ˇThe quick ˇbrownˇ?ˇ ˇFox Jˇumpsˇ!ˇ Ovˇer theˇ lazyˇ.",
         indoc! {"
-            ˇThe quick ˇbrownˇ   
+            ˇThe quick ˇbrownˇ
             fox jumps over
             the lazy doˇgˇ.ˇ ˇThe quick ˇ
             brown fox jumps over

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

@@ -16,26 +16,20 @@ pub struct VimTestContext<'a> {
 
 impl<'a> VimTestContext<'a> {
     pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
-        cx.update(|cx| {
-            search::init(cx);
-            crate::init(cx);
-
-            settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
-        });
-
         let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
-
         cx.update(|cx| {
             cx.update_global(|settings: &mut Settings, _| {
                 settings.vim_mode = enabled;
             });
-        });
+            search::init(cx);
+            crate::init(cx);
 
-        let window_id = cx.window_id;
+            settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
+        });
 
         // Setup search toolbars and keypress hook
         cx.update_workspace(|workspace, cx| {
-            observe_keystrokes(window_id, cx);
+            observe_keystrokes(cx);
             workspace.active_pane().update(cx, |pane, cx| {
                 pane.toolbar().update(cx, |toolbar, cx| {
                     let buffer_search_bar = cx.add_view(BufferSearchBar::new);
@@ -82,7 +76,8 @@ impl<'a> VimTestContext<'a> {
     }
 
     pub fn set_state(&mut self, text: &str, mode: Mode) -> ContextHandle {
-        self.cx.update(|cx| {
+        let window_id = self.window_id;
+        self.update_window(window_id, |cx| {
             Vim::update(cx, |vim, cx| {
                 vim.switch_mode(mode, false, cx);
             })

crates/vim/src/vim.rs 🔗

@@ -13,9 +13,10 @@ mod visual;
 use std::sync::Arc;
 
 use collections::CommandPaletteFilter;
-use editor::{Bias, Cancel, Editor, EditorMode};
+use editor::{Bias, Cancel, Editor, EditorMode, Event};
 use gpui::{
     actions, impl_actions, AppContext, Subscription, ViewContext, ViewHandle, WeakViewHandle,
+    WindowContext,
 };
 use language::CursorShape;
 use motion::Motion;
@@ -65,7 +66,7 @@ pub fn init(cx: &mut AppContext) {
         // Otherwise forward cancel on to the editor
         let vim = Vim::read(cx);
         if vim.state.mode != Mode::Normal || vim.active_operator().is_some() {
-            AppContext::defer(cx, |cx| {
+            WindowContext::defer(cx, |cx| {
                 Vim::update(cx, |state, cx| {
                     state.switch_mode(Mode::Normal, false, cx);
                 });
@@ -83,20 +84,20 @@ pub fn init(cx: &mut AppContext) {
         Vim::active_editor_input_ignored("\n".into(), cx)
     });
 
-    // Sync initial settings with the rest of the app
-    Vim::update(cx, |vim, cx| vim.sync_vim_settings(cx));
-
-    // Any time settings change, update vim mode to match
+    // Any time settings change, update vim mode to match.
+    cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| {
+        vim.set_enabled(cx.global::<Settings>().vim_mode, cx)
+    });
     cx.observe_global::<Settings, _>(|cx| {
-        Vim::update(cx, |state, cx| {
-            state.set_enabled(cx.global::<Settings>().vim_mode, cx)
-        })
+        cx.update_default_global(|vim: &mut Vim, cx: &mut AppContext| {
+            vim.set_enabled(cx.global::<Settings>().vim_mode, cx)
+        });
     })
     .detach();
 }
 
-pub fn observe_keystrokes(window_id: usize, cx: &mut AppContext) {
-    cx.observe_keystrokes(window_id, |_keystroke, _result, handled_by, cx| {
+pub fn observe_keystrokes(cx: &mut WindowContext) {
+    cx.observe_keystrokes(|_keystroke, _result, handled_by, cx| {
         if let Some(handled_by) = handled_by {
             // Keystroke is handled by the vim system, so continue forward
             // Also short circuit if it is the special cancel action
@@ -135,25 +136,52 @@ impl Vim {
         cx.default_global()
     }
 
-    fn update<F, S>(cx: &mut AppContext, update: F) -> S
+    fn update<F, S>(cx: &mut WindowContext, update: F) -> S
     where
-        F: FnOnce(&mut Self, &mut AppContext) -> S,
+        F: FnOnce(&mut Self, &mut WindowContext) -> S,
     {
         cx.update_default_global(update)
     }
 
+    fn set_active_editor(&mut self, editor: ViewHandle<Editor>, cx: &mut WindowContext) {
+        self.active_editor = Some(editor.downgrade());
+        self.editor_subscription = Some(cx.subscribe(&editor, |editor, event, cx| match event {
+            Event::SelectionsChanged { local: true } => {
+                let editor = editor.read(cx);
+                if editor.leader_replica_id().is_none() {
+                    let newest_empty = editor.selections.newest::<usize>(cx).is_empty();
+                    local_selections_changed(newest_empty, cx);
+                }
+            }
+            Event::InputIgnored { text } => {
+                Vim::active_editor_input_ignored(text.clone(), cx);
+            }
+            _ => {}
+        }));
+
+        if self.enabled {
+            let editor = editor.read(cx);
+            let editor_mode = editor.mode();
+            let newest_selection_empty = editor.selections.newest::<usize>(cx).is_empty();
+
+            if editor_mode == EditorMode::Full && !newest_selection_empty {
+                self.switch_mode(Mode::Visual { line: false }, true, cx);
+            }
+        }
+
+        self.sync_vim_settings(cx);
+    }
+
     fn update_active_editor<S>(
         &self,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
         update: impl FnOnce(&mut Editor, &mut ViewContext<Editor>) -> S,
     ) -> Option<S> {
-        self.active_editor
-            .clone()
-            .and_then(|ae| ae.upgrade(cx))
-            .map(|ae| ae.update(cx, update))
+        let editor = self.active_editor.clone()?.upgrade(cx)?;
+        Some(editor.update(cx, update))
     }
 
-    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut AppContext) {
+    fn switch_mode(&mut self, mode: Mode, leave_selections: bool, cx: &mut WindowContext) {
         self.state.mode = mode;
         self.state.operator_stack.clear();
 
@@ -165,35 +193,27 @@ impl Vim {
         }
 
         // Adjust selections
-        if let Some(editor) = self
-            .active_editor
-            .as_ref()
-            .and_then(|editor| editor.upgrade(cx))
-        {
-            editor.update(cx, |editor, cx| {
-                editor.change_selections(None, cx, |s| {
-                    s.move_with(|map, selection| {
-                        if self.state.empty_selections_only() {
-                            let new_head = map.clip_point(selection.head(), Bias::Left);
-                            selection.collapse_to(new_head, selection.goal)
-                        } else {
-                            selection.set_head(
-                                map.clip_point(selection.head(), Bias::Left),
-                                selection.goal,
-                            );
-                        }
-                    });
-                })
+        self.update_active_editor(cx, |editor, cx| {
+            editor.change_selections(None, cx, |s| {
+                s.move_with(|map, selection| {
+                    if self.state.empty_selections_only() {
+                        let new_head = map.clip_point(selection.head(), Bias::Left);
+                        selection.collapse_to(new_head, selection.goal)
+                    } else {
+                        selection
+                            .set_head(map.clip_point(selection.head(), Bias::Left), selection.goal);
+                    }
+                });
             })
-        }
+        });
     }
 
-    fn push_operator(&mut self, operator: Operator, cx: &mut AppContext) {
+    fn push_operator(&mut self, operator: Operator, cx: &mut WindowContext) {
         self.state.operator_stack.push(operator);
         self.sync_vim_settings(cx);
     }
 
-    fn push_number(&mut self, Number(number): &Number, cx: &mut AppContext) {
+    fn push_number(&mut self, Number(number): &Number, cx: &mut WindowContext) {
         if let Some(Operator::Number(current_number)) = self.active_operator() {
             self.pop_operator(cx);
             self.push_operator(Operator::Number(current_number * 10 + *number as usize), cx);
@@ -202,14 +222,14 @@ impl Vim {
         }
     }
 
-    fn pop_operator(&mut self, cx: &mut AppContext) -> Operator {
+    fn pop_operator(&mut self, cx: &mut WindowContext) -> Operator {
         let popped_operator = self.state.operator_stack.pop()
             .expect("Operator popped when no operator was on the stack. This likely means there is an invalid keymap config");
         self.sync_vim_settings(cx);
         popped_operator
     }
 
-    fn pop_number_operator(&mut self, cx: &mut AppContext) -> usize {
+    fn pop_number_operator(&mut self, cx: &mut WindowContext) -> usize {
         let mut times = 1;
         if let Some(Operator::Number(number)) = self.active_operator() {
             times = number;
@@ -218,7 +238,7 @@ impl Vim {
         times
     }
 
-    fn clear_operator(&mut self, cx: &mut AppContext) {
+    fn clear_operator(&mut self, cx: &mut WindowContext) {
         self.state.operator_stack.clear();
         self.sync_vim_settings(cx);
     }
@@ -227,7 +247,7 @@ impl Vim {
         self.state.operator_stack.last().copied()
     }
 
-    fn active_editor_input_ignored(text: Arc<str>, cx: &mut AppContext) {
+    fn active_editor_input_ignored(text: Arc<str>, cx: &mut WindowContext) {
         if text.is_empty() {
             return;
         }
@@ -252,52 +272,63 @@ impl Vim {
         if self.enabled != enabled {
             self.enabled = enabled;
             self.state = Default::default();
-            if enabled {
-                self.switch_mode(Mode::Normal, false, cx);
-            }
-            self.sync_vim_settings(cx);
+
+            cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
+                if self.enabled {
+                    filter.filtered_namespaces.remove("vim");
+                } else {
+                    filter.filtered_namespaces.insert("vim");
+                }
+            });
+
+            cx.update_active_window(|cx| {
+                if self.enabled {
+                    let active_editor = cx
+                        .root_view()
+                        .downcast_ref::<Workspace>()
+                        .and_then(|workspace| workspace.read(cx).active_item(cx))
+                        .and_then(|item| item.downcast::<Editor>());
+                    if let Some(active_editor) = active_editor {
+                        self.set_active_editor(active_editor, cx);
+                    }
+                    self.switch_mode(Mode::Normal, false, cx);
+                }
+                self.sync_vim_settings(cx);
+            });
         }
     }
 
-    fn sync_vim_settings(&self, cx: &mut AppContext) {
+    fn sync_vim_settings(&self, cx: &mut WindowContext) {
         let state = &self.state;
         let cursor_shape = state.cursor_shape();
 
-        cx.update_default_global::<CommandPaletteFilter, _, _>(|filter, _| {
-            if self.enabled {
-                filter.filtered_namespaces.remove("vim");
+        self.update_active_editor(cx, |editor, cx| {
+            if self.enabled && editor.mode() == EditorMode::Full {
+                editor.set_cursor_shape(cursor_shape, cx);
+                editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
+                editor.set_input_enabled(!state.vim_controlled());
+                editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
+                let context_layer = state.keymap_context_layer();
+                editor.set_keymap_context_layer::<Self>(context_layer);
             } else {
-                filter.filtered_namespaces.insert("vim");
+                Self::unhook_vim_settings(editor, cx);
             }
         });
-
-        if let Some(editor) = self
-            .active_editor
-            .as_ref()
-            .and_then(|editor| editor.upgrade(cx))
-        {
-            if self.enabled && editor.read(cx).mode() == EditorMode::Full {
-                editor.update(cx, |editor, cx| {
-                    editor.set_cursor_shape(cursor_shape, cx);
-                    editor.set_clip_at_line_ends(state.clip_at_line_end(), cx);
-                    editor.set_input_enabled(!state.vim_controlled());
-                    editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
-                    let context_layer = state.keymap_context_layer();
-                    editor.set_keymap_context_layer::<Self>(context_layer);
-                });
-            } else {
-                self.unhook_vim_settings(editor, cx);
-            }
-        }
     }
 
-    fn unhook_vim_settings(&self, editor: ViewHandle<Editor>, cx: &mut AppContext) {
-        editor.update(cx, |editor, cx| {
-            editor.set_cursor_shape(CursorShape::Bar, cx);
-            editor.set_clip_at_line_ends(false, cx);
-            editor.set_input_enabled(true);
-            editor.selections.line_mode = false;
-            editor.remove_keymap_context_layer::<Self>();
-        });
+    fn unhook_vim_settings(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
+        editor.set_cursor_shape(CursorShape::Bar, cx);
+        editor.set_clip_at_line_ends(false, cx);
+        editor.set_input_enabled(true);
+        editor.selections.line_mode = false;
+        editor.remove_keymap_context_layer::<Self>();
     }
 }
+
+fn local_selections_changed(newest_empty: bool, cx: &mut WindowContext) {
+    Vim::update(cx, |vim, cx| {
+        if vim.enabled && vim.state.mode == Mode::Normal && !newest_empty {
+            vim.switch_mode(Mode::Visual { line: false }, false, cx)
+        }
+    })
+}

crates/vim/src/visual.rs 🔗

@@ -4,7 +4,7 @@ use collections::HashMap;
 use editor::{
     display_map::ToDisplayPoint, movement, scroll::autoscroll::Autoscroll, Bias, ClipboardSelection,
 };
-use gpui::{actions, AppContext, ViewContext};
+use gpui::{actions, AppContext, ViewContext, WindowContext};
 use language::{AutoindentMode, SelectionGoal};
 use workspace::Workspace;
 
@@ -25,7 +25,7 @@ pub fn init(cx: &mut AppContext) {
     cx.add_action(paste);
 }
 
-pub fn visual_motion(motion: Motion, times: usize, cx: &mut AppContext) {
+pub fn visual_motion(motion: Motion, times: usize, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
             editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
@@ -56,7 +56,7 @@ pub fn visual_motion(motion: Motion, times: usize, cx: &mut AppContext) {
     });
 }
 
-pub fn visual_object(object: Object, cx: &mut AppContext) {
+pub fn visual_object(object: Object, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         if let Operator::Object { around } = vim.pop_operator(cx) {
             vim.update_active_editor(cx, |editor, cx| {
@@ -209,7 +209,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
             editor.transact(cx, |editor, cx| {
-                if let Some(item) = cx.as_mut().read_from_clipboard() {
+                if let Some(item) = cx.read_from_clipboard() {
                     copy_selections_content(editor, editor.selections.line_mode, cx);
                     let mut clipboard_text = Cow::Borrowed(item.text());
                     if let Some(mut clipboard_selections) =
@@ -313,7 +313,7 @@ pub fn paste(_: &mut Workspace, _: &VisualPaste, cx: &mut ViewContext<Workspace>
     });
 }
 
-pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut AppContext) {
+pub(crate) fn visual_replace(text: Arc<str>, line: bool, cx: &mut WindowContext) {
     Vim::update(cx, |vim, cx| {
         vim.update_active_editor(cx, |editor, cx| {
             editor.transact(cx, |editor, cx| {

crates/vim/test_data/test_change_sentence_object.json 🔗

@@ -53,46 +53,46 @@
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"The quick brown? Fox Jumps! ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"i"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇ\n","mode":"Insert"}}
 {"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
 {"Key":"c"}
 {"Key":"i"}
@@ -188,46 +188,46 @@
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"The quick brown? Fox Jumps!ˇ","mode":"Insert"}}
-{"Put":{"state":"ˇThe quick brown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
 {"Key":"c"}
 {"Key":"a"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n","mode":"Insert"}}
 {"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
 {"Key":"c"}
 {"Key":"a"}

crates/vim/test_data/test_delete_sentence_object.json 🔗

@@ -53,46 +53,46 @@
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"The quick brown? Fox Jumps!ˇ ","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
 {"Get":{"state":"ˇ The quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"i"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ \n","mode":"Normal"}}
 {"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
 {"Key":"d"}
 {"Key":"i"}
@@ -188,46 +188,46 @@
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"The quick brown? Fox Jumpsˇ!","mode":"Normal"}}
-{"Put":{"state":"ˇThe quick brown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick ˇbrown   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick ˇbrown\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brownˇ   \nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy doˇg. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ. The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
 {"Get":{"state":"ˇThe quick \nbrown fox jumps over\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ The quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. ˇThe quick \nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
-{"Put":{"state":"The quick brown   \nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog. The quick ˇ\nbrown fox jumps over\n"}}
 {"Key":"d"}
 {"Key":"a"}
 {"Key":"s"}
-{"Get":{"state":"The quick brown   \nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
+{"Get":{"state":"The quick brown\nfox jumps over\nthe lazy dogˇ.\n","mode":"Normal"}}
 {"Put":{"state":"ˇThe quick brown.)]'\" Brown fox jumps. "}}
 {"Key":"d"}
 {"Key":"a"}

crates/vim/test_data/test_f_and_t.json 🔗

@@ -1,557 +1,557 @@
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
 {"Key":"1"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaˇab b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaˇab b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n   ˇ baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n   ˇ baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
 {"Key":"1"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
 {"Key":"2"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaaˇ bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
 {"Key":"2"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
 {"Key":"3"}
 {"Key":"f"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
-{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}
+{"Put":{"state":"ˇaaab b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b  ˇ bb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaaˇb b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaabˇ b   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab ˇb   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab bˇ   bb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaˇabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   ˇbb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bˇb aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bbˇ aaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aˇaabaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaaˇbaaa\n    baaa bbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\nˇ    baaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    ˇbaaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    bˇaaa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaˇa bbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa ˇbbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bˇbb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbˇb\n\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ   \nb\n","mode":"Normal"}}
-{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\nˇ\nb\n","mode":"Normal"}}
+{"Put":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n"}}
 {"Key":"3"}
 {"Key":"t"}
 {"Key":"b"}
-{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n   \nˇb\n","mode":"Normal"}}
+{"Get":{"state":"aaab b   bb aaabaaa\n    baaa bbb\n\nˇb\n","mode":"Normal"}}

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -1,91 +1,59 @@
+use std::sync::Arc;
+
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
     actions,
-    elements::{ChildView, Element as _, Label},
-    AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
+    elements::{Drawable as _, Label},
+    AppContext, Task, ViewContext,
 };
-use picker::{Picker, PickerDelegate};
+use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::{settings_file::SettingsFile, BaseKeymap, Settings};
+use util::ResultExt;
 use workspace::Workspace;
 
-pub struct BaseKeymapSelector {
-    matches: Vec<StringMatch>,
-    picker: ViewHandle<Picker<Self>>,
-    selected_index: usize,
-}
-
 actions!(welcome, [ToggleBaseKeymapSelector]);
 
 pub fn init(cx: &mut AppContext) {
-    Picker::<BaseKeymapSelector>::init(cx);
-    cx.add_action({
-        move |workspace, _: &ToggleBaseKeymapSelector, cx| BaseKeymapSelector::toggle(workspace, cx)
-    });
+    cx.add_action(toggle);
+    BaseKeymapSelector::init(cx);
 }
 
-pub enum Event {
-    Dismissed,
+fn toggle(
+    workspace: &mut Workspace,
+    _: &ToggleBaseKeymapSelector,
+    cx: &mut ViewContext<Workspace>,
+) {
+    workspace.toggle_modal(cx, |_, cx| {
+        cx.add_view(|cx| BaseKeymapSelector::new(BaseKeymapSelectorDelegate::new(cx), cx))
+    });
 }
 
-impl BaseKeymapSelector {
-    fn toggle(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
-        workspace.toggle_modal(cx, |_, cx| {
-            let this = cx.add_view(|cx| Self::new(cx));
-            cx.subscribe(&this, Self::on_event).detach();
-            this
-        });
-    }
+pub type BaseKeymapSelector = Picker<BaseKeymapSelectorDelegate>;
 
-    fn new(cx: &mut ViewContext<Self>) -> Self {
+pub struct BaseKeymapSelectorDelegate {
+    matches: Vec<StringMatch>,
+    selected_index: usize,
+}
+
+impl BaseKeymapSelectorDelegate {
+    fn new(cx: &mut ViewContext<BaseKeymapSelector>) -> Self {
         let base = cx.global::<Settings>().base_keymap;
         let selected_index = BaseKeymap::OPTIONS
             .iter()
             .position(|(_, value)| *value == base)
             .unwrap_or(0);
-
-        let this = cx.weak_handle();
         Self {
-            picker: cx.add_view(|cx| Picker::new("Select a base keymap", this, cx)),
             matches: Vec::new(),
             selected_index,
         }
     }
-
-    fn on_event(
-        workspace: &mut Workspace,
-        _: ViewHandle<BaseKeymapSelector>,
-        event: &Event,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        match event {
-            Event::Dismissed => {
-                workspace.dismiss_modal(cx);
-            }
-        }
-    }
 }
 
-impl Entity for BaseKeymapSelector {
-    type Event = Event;
-}
-
-impl View for BaseKeymapSelector {
-    fn ui_name() -> &'static str {
-        "BaseKeymapSelector"
-    }
-
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
-        ChildView::new(&self.picker, cx).boxed()
+impl PickerDelegate for BaseKeymapSelectorDelegate {
+    fn placeholder_text(&self) -> Arc<str> {
+        "Select a base keymap...".into()
     }
 
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            cx.focus(&self.picker);
-        }
-    }
-}
-
-impl PickerDelegate for BaseKeymapSelector {
     fn match_count(&self) -> usize {
         self.matches.len()
     }
@@ -94,11 +62,15 @@ impl PickerDelegate for BaseKeymapSelector {
         self.selected_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Self>) {
+    fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<BaseKeymapSelector>) {
         self.selected_index = ix;
     }
 
-    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
+    fn update_matches(
+        &mut self,
+        query: String,
+        cx: &mut ViewContext<BaseKeymapSelector>,
+    ) -> Task<()> {
         let background = cx.background().clone();
         let candidates = BaseKeymap::names()
             .enumerate()
@@ -109,7 +81,7 @@ impl PickerDelegate for BaseKeymapSelector {
             })
             .collect::<Vec<_>>();
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn_weak(|this, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -133,27 +105,28 @@ impl PickerDelegate for BaseKeymapSelector {
                 .await
             };
 
-            this.update(&mut cx, |this, cx| {
-                this.matches = matches;
-                this.selected_index = this
-                    .selected_index
-                    .min(this.matches.len().saturating_sub(1));
-                cx.notify();
-            });
+            if let Some(this) = this.upgrade(&cx) {
+                this.update(&mut cx, |this, _| {
+                    let delegate = this.delegate_mut();
+                    delegate.matches = matches;
+                    delegate.selected_index = delegate
+                        .selected_index
+                        .min(delegate.matches.len().saturating_sub(1));
+                })
+                .log_err();
+            }
         })
     }
 
-    fn confirm(&mut self, cx: &mut ViewContext<Self>) {
+    fn confirm(&mut self, cx: &mut ViewContext<BaseKeymapSelector>) {
         if let Some(selection) = self.matches.get(self.selected_index) {
             let base_keymap = BaseKeymap::from_names(&selection.string);
             SettingsFile::update(cx, move |settings| settings.base_keymap = Some(base_keymap));
         }
-        cx.emit(Event::Dismissed);
+        cx.emit(PickerEvent::Dismiss);
     }
 
-    fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed)
-    }
+    fn dismissed(&mut self, _cx: &mut ViewContext<BaseKeymapSelector>) {}
 
     fn render_match(
         &self,
@@ -161,7 +134,7 @@ impl PickerDelegate for BaseKeymapSelector {
         mouse_state: &mut gpui::MouseState,
         selected: bool,
         cx: &gpui::AppContext,
-    ) -> gpui::ElementBox {
+    ) -> gpui::Element<Picker<Self>> {
         let theme = &cx.global::<Settings>().theme;
         let keymap_match = &self.matches[ix];
         let style = theme.picker.item.style_for(mouse_state, selected);

crates/welcome/src/welcome.rs 🔗

@@ -5,7 +5,7 @@ use std::{borrow::Cow, sync::Arc};
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     elements::{Flex, Label, ParentElement},
-    AppContext, Element, ElementBox, Entity, Subscription, View, ViewContext,
+    AppContext, Drawable, Element, Entity, Subscription, View, ViewContext,
 };
 use settings::{settings_file::SettingsFile, Settings};
 
@@ -55,7 +55,7 @@ impl View for WelcomePage {
         "WelcomePage"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
         let self_handle = cx.handle();
         let settings = cx.global::<Settings>();
         let theme = settings.theme.clone();
@@ -202,12 +202,12 @@ impl Item for WelcomePage {
         Some("Welcome to Zed!".into())
     }
 
-    fn tab_content(
+    fn tab_content<T: View>(
         &self,
         _detail: Option<usize>,
         style: &theme::Tab,
         _cx: &gpui::AppContext,
-    ) -> gpui::ElementBox {
+    ) -> Element<T> {
         Flex::row()
             .with_child(
                 Label::new("Welcome to Zed!", style.label.clone())

crates/workspace/src/dock.rs 🔗

@@ -9,8 +9,7 @@ use gpui::{
     geometry::vector::Vector2F,
     impl_internal_actions,
     platform::{CursorStyle, MouseButton},
-    AppContext, Border, Element, ElementBox, RenderContext, SizeConstraint, ViewContext,
-    ViewHandle,
+    AppContext, Border, Drawable, Element, SizeConstraint, ViewContext, ViewHandle,
 };
 use settings::{DockAnchor, Settings};
 use theme::Theme;
@@ -315,8 +314,8 @@ impl Dock {
         &self,
         theme: &Theme,
         anchor: DockAnchor,
-        cx: &mut RenderContext<Workspace>,
-    ) -> Option<ElementBox> {
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<Element<Workspace>> {
         let style = &theme.workspace.dock;
 
         self.position
@@ -351,7 +350,7 @@ impl Dock {
 
                     let resizable = Container::new(ChildView::new(&self.pane, cx).boxed())
                         .with_style(panel_style)
-                        .with_resize_handle::<DockResizeHandle, _>(
+                        .with_resize_handle::<DockResizeHandle>(
                             resize_side as usize,
                             resize_side,
                             4.,
@@ -363,32 +362,27 @@ impl Dock {
                         );
 
                     let size = resizable.current_size();
-                    let workspace = cx.handle();
-                    cx.defer(move |cx| {
-                        if let Some(workspace) = workspace.upgrade(cx) {
-                            workspace.update(cx, |workspace, _| {
-                                workspace.dock.panel_sizes.insert(anchor, size);
-                            })
-                        }
+                    cx.defer(move |workspace, _| {
+                        workspace.dock.panel_sizes.insert(anchor, size);
                     });
 
                     if anchor == DockAnchor::Right {
                         resizable
                             .constrained()
-                            .dynamically(|constraint, cx| {
+                            .dynamically(|constraint, _, cx| {
                                 SizeConstraint::new(
                                     Vector2F::new(20., constraint.min.y()),
-                                    Vector2F::new(cx.window_size.x() * 0.8, constraint.max.y()),
+                                    Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
                                 )
                             })
                             .boxed()
                     } else {
                         resizable
                             .constrained()
-                            .dynamically(|constraint, cx| {
+                            .dynamically(|constraint, _, cx| {
                                 SizeConstraint::new(
                                     Vector2F::new(constraint.min.x(), 50.),
-                                    Vector2F::new(constraint.max.x(), cx.window_size.y() * 0.8),
+                                    Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
                                 )
                             })
                             .boxed()
@@ -400,21 +394,21 @@ impl Dock {
                     Stack::new()
                         .with_child(
                             // Render wash under the dock which when clicked hides it
-                            MouseEventHandler::<ExpandedDockWash>::new(0, cx, |_, _| {
+                            MouseEventHandler::<ExpandedDockWash, _>::new(0, cx, |_, _| {
                                 Empty::new()
                                     .contained()
                                     .with_background_color(style.wash_color)
                                     .boxed()
                             })
                             .capture_all()
-                            .on_down(MouseButton::Left, |_, cx| {
+                            .on_down(MouseButton::Left, |_, _, cx| {
                                 cx.dispatch_action(HideDock);
                             })
                             .with_cursor_style(CursorStyle::Arrow)
                             .boxed(),
                         )
                         .with_child(
-                            MouseEventHandler::<ExpandedDockPane>::new(0, cx, |_state, cx| {
+                            MouseEventHandler::<ExpandedDockPane, _>::new(0, cx, |_state, cx| {
                                 ChildView::new(&self.pane, cx).boxed()
                             })
                             // Make sure all events directly under the dock pane
@@ -824,6 +818,8 @@ mod tests {
     }
 
     impl<'a> UpdateView for DockTestContext<'a> {
+        type Output<S> = S;
+
         fn update_view<T, S>(
             &mut self,
             handle: &ViewHandle<T>,

crates/workspace/src/dock/toggle_dock_button.rs 🔗

@@ -2,7 +2,7 @@ use gpui::{
     elements::{Empty, MouseEventHandler, Svg},
     platform::CursorStyle,
     platform::MouseButton,
-    Element, ElementBox, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
+    Drawable, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
 
@@ -34,7 +34,7 @@ impl View for ToggleDockButton {
         "Dock Toggle"
     }
 
-    fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Element<Self> {
         let workspace = self.workspace.upgrade(cx);
 
         if workspace.is_none() {
@@ -43,11 +43,11 @@ impl View for ToggleDockButton {
 
         let workspace = workspace.unwrap();
         let dock_position = workspace.read(cx).dock.position;
-        let dock_pane = workspace.read(cx.app).dock_pane().clone();
+        let dock_pane = workspace.read(cx).dock_pane().clone();
 
         let theme = cx.global::<Settings>().theme.clone();
 
-        let button = MouseEventHandler::<Self>::new(0, cx, {
+        let button = MouseEventHandler::<Self, _>::new(0, cx, {
             let theme = theme.clone();
             move |state, _| {
                 let style = theme
@@ -68,17 +68,17 @@ impl View for ToggleDockButton {
             }
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_up(MouseButton::Left, move |event, cx| {
-            let drop_index = dock_pane.read(cx.app).items_len() + 1;
+        .on_up(MouseButton::Left, move |event, _, cx| {
+            let drop_index = dock_pane.read(cx).items_len() + 1;
             handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
         });
 
         if dock_position.is_visible() {
             button
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(HideDock);
                 })
-                .with_tooltip::<Self, _>(
+                .with_tooltip::<Self>(
                     0,
                     "Hide Dock".into(),
                     Some(Box::new(HideDock)),
@@ -87,10 +87,10 @@ impl View for ToggleDockButton {
                 )
         } else {
             button
-                .on_click(MouseButton::Left, |_, cx| {
+                .on_click(MouseButton::Left, |_, _, cx| {
                     cx.dispatch_action(FocusDock);
                 })
-                .with_tooltip::<Self, _>(
+                .with_tooltip::<Self>(
                     0,
                     "Focus Dock".into(),
                     Some(Box::new(FocusDock)),

crates/workspace/src/item.rs 🔗

@@ -1,8 +1,23 @@
+use crate::{
+    pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
+    FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
+    WorkspaceId,
+};
+use anyhow::{anyhow, Result};
+use client::{proto, Client};
+use gpui::{
+    fonts::HighlightStyle, AnyViewHandle, AppContext, Element, ModelHandle, Task, View,
+    ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+};
+use project::{Project, ProjectEntryId, ProjectPath};
+use settings::{Autosave, Settings};
+use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
     borrow::Cow,
     cell::RefCell,
     fmt,
+    ops::Range,
     path::PathBuf,
     rc::Rc,
     sync::{
@@ -11,24 +26,7 @@ use std::{
     },
     time::Duration,
 };
-
-use anyhow::Result;
-use client::{proto, Client};
-use gpui::{
-    AnyViewHandle, AppContext, ElementBox, ModelHandle, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle,
-};
-use project::{Project, ProjectEntryId, ProjectPath};
-use settings::{Autosave, Settings};
-use smallvec::SmallVec;
 use theme::Theme;
-use util::ResultExt;
-
-use crate::{
-    pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction,
-    FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
-    WorkspaceId,
-};
 
 #[derive(Eq, PartialEq, Hash)]
 pub enum ItemEvent {
@@ -38,6 +36,12 @@ pub enum ItemEvent {
     Edit,
 }
 
+// TODO: Combine this with existing HighlightedText struct?
+pub struct BreadcrumbText {
+    pub text: String,
+    pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
+}
+
 pub trait Item: View {
     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
@@ -50,8 +54,12 @@ pub trait Item: View {
     fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
         None
     }
-    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
-        -> ElementBox;
+    fn tab_content<V: View>(
+        &self,
+        detail: Option<usize>,
+        style: &theme::Tab,
+        cx: &AppContext,
+    ) -> Element<V>;
     fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
     fn is_singleton(&self, _cx: &AppContext) -> bool {
         false
@@ -133,7 +141,7 @@ pub trait Item: View {
         ToolbarItemLocation::Hidden
     }
 
-    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<ElementBox>> {
+    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
         None
     }
 
@@ -162,13 +170,23 @@ pub trait Item: View {
 pub trait ItemHandle: 'static + fmt::Debug {
     fn subscribe_to_item_events(
         &self,
-        cx: &mut AppContext,
-        handler: Box<dyn Fn(ItemEvent, &mut AppContext)>,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
     ) -> gpui::Subscription;
     fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
     fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
-    fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
-        -> ElementBox;
+    fn tab_content(
+        &self,
+        detail: Option<usize>,
+        style: &theme::Tab,
+        cx: &AppContext,
+    ) -> Element<Pane>;
+    fn dragged_tab_content(
+        &self,
+        detail: Option<usize>,
+        style: &theme::Tab,
+        cx: &AppContext,
+    ) -> Element<Workspace>;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
     fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
     fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
@@ -178,7 +196,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
     fn clone_on_split(
         &self,
         workspace_id: WorkspaceId,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<Box<dyn ItemHandle>>;
     fn added_to_pane(
         &self,
@@ -186,27 +204,27 @@ pub trait ItemHandle: 'static + fmt::Debug {
         pane: ViewHandle<Pane>,
         cx: &mut ViewContext<Workspace>,
     );
-    fn deactivated(&self, cx: &mut AppContext);
-    fn workspace_deactivated(&self, cx: &mut AppContext);
-    fn navigate(&self, data: Box<dyn Any>, cx: &mut AppContext) -> bool;
+    fn deactivated(&self, cx: &mut WindowContext);
+    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 as_any(&self) -> &AnyViewHandle;
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
     fn can_save(&self, cx: &AppContext) -> bool;
-    fn save(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>>;
+    fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
     fn save_as(
         &self,
         project: ModelHandle<Project>,
         abs_path: PathBuf,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn reload(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>>;
+    fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
     fn git_diff_recalc(
         &self,
         project: ModelHandle<Project>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Result<()>>;
     fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
@@ -217,7 +235,7 @@ pub trait ItemHandle: 'static + fmt::Debug {
     ) -> gpui::Subscription;
     fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
     fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
-    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>>;
+    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
     fn serialized_item_kind(&self) -> Option<&'static str>;
     fn show_toolbar(&self, cx: &AppContext) -> bool;
 }
@@ -242,8 +260,8 @@ impl dyn ItemHandle {
 impl<T: Item> ItemHandle for ViewHandle<T> {
     fn subscribe_to_item_events(
         &self,
-        cx: &mut AppContext,
-        handler: Box<dyn Fn(ItemEvent, &mut AppContext)>,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
     ) -> gpui::Subscription {
         cx.subscribe(self, move |_, event, cx| {
             for item_event in T::to_item_events(event) {
@@ -265,7 +283,16 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         detail: Option<usize>,
         style: &theme::Tab,
         cx: &AppContext,
-    ) -> ElementBox {
+    ) -> Element<Pane> {
+        self.read(cx).tab_content(detail, style, cx)
+    }
+
+    fn dragged_tab_content(
+        &self,
+        detail: Option<usize>,
+        style: &theme::Tab,
+        cx: &AppContext,
+    ) -> Element<Workspace> {
         self.read(cx).tab_content(detail, style, cx)
     }
 
@@ -313,7 +340,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
     fn clone_on_split(
         &self,
         workspace_id: WorkspaceId,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<Box<dyn ItemHandle>> {
         self.update(cx, |item, cx| {
             cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
@@ -428,16 +455,9 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                                 {
                                     let delay = Duration::from_millis(milliseconds);
                                     let item = item.clone();
-                                    pending_autosave.fire_new(
-                                        delay,
-                                        workspace,
-                                        cx,
-                                        |project, mut cx| async move {
-                                            cx.update(|cx| Pane::autosave_item(&item, project, cx))
-                                                .await
-                                                .log_err();
-                                        },
-                                    );
+                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
+                                    });
                                 }
 
                                 let settings = cx.global::<Settings>();
@@ -453,24 +473,26 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
 
                                     pending_git_update.fire_new(
                                         duration,
-                                        workspace,
                                         cx,
-                                        |project, mut cx| async move {
-                                            cx.update(|cx| item.git_diff_recalc(project, cx))
-                                                .await
-                                                .log_err();
+                                        move |workspace, cx| {
+                                            item.git_diff_recalc(workspace.project().clone(), cx)
                                         },
                                     );
                                 } else {
-                                    let project = workspace.project().downgrade();
-                                    cx.spawn_weak(|_, mut cx| async move {
-                                        if let Some(project) = project.upgrade(&cx) {
-                                            cx.update(|cx| item.git_diff_recalc(project, cx))
-                                                .await
-                                                .log_err();
-                                        }
+                                    cx.spawn_weak(|workspace, mut cx| async move {
+                                        workspace
+                                            .upgrade(&cx)
+                                            .ok_or_else(|| anyhow!("workspace was dropped"))?
+                                            .update(&mut cx, |workspace, cx| {
+                                                item.git_diff_recalc(
+                                                    workspace.project().clone(),
+                                                    cx,
+                                                )
+                                            })?
+                                            .await?;
+                                        anyhow::Ok(())
                                     })
-                                    .detach();
+                                    .detach_and_log_err(cx);
                                 }
                             }
 
@@ -500,15 +522,15 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         });
     }
 
-    fn deactivated(&self, cx: &mut AppContext) {
+    fn deactivated(&self, cx: &mut WindowContext) {
         self.update(cx, |this, cx| this.deactivated(cx));
     }
 
-    fn workspace_deactivated(&self, cx: &mut AppContext) {
+    fn workspace_deactivated(&self, cx: &mut WindowContext) {
         self.update(cx, |this, cx| this.workspace_deactivated(cx));
     }
 
-    fn navigate(&self, data: Box<dyn Any>, cx: &mut AppContext) -> bool {
+    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
         self.update(cx, |this, cx| this.navigate(data, cx))
     }
 
@@ -536,7 +558,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         self.read(cx).can_save(cx)
     }
 
-    fn save(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>> {
+    fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.save(project, cx))
     }
 
@@ -544,19 +566,19 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         &self,
         project: ModelHandle<Project>,
         abs_path: PathBuf,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<anyhow::Result<()>> {
         self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
     }
 
-    fn reload(&self, project: ModelHandle<Project>, cx: &mut AppContext) -> Task<Result<()>> {
+    fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.reload(project, cx))
     }
 
     fn git_diff_recalc(
         &self,
         project: ModelHandle<Project>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.git_diff_recalc(project, cx))
     }
@@ -591,7 +613,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         self.read(cx).breadcrumb_location()
     }
 
-    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<ElementBox>> {
+    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
         self.read(cx).breadcrumbs(theme, cx)
     }
 
@@ -675,7 +697,7 @@ pub trait FollowableItem: Item {
 
 pub trait FollowableItemHandle: ItemHandle {
     fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
-    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut AppContext);
+    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut WindowContext);
     fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
     fn add_event_to_update_proto(
         &self,
@@ -687,7 +709,7 @@ pub trait FollowableItemHandle: ItemHandle {
         &self,
         project: &ModelHandle<Project>,
         message: proto::update_view::Variant,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Result<()>>;
     fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
 }
@@ -702,7 +724,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
         })
     }
 
-    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut AppContext) {
+    fn set_leader_replica_id(&self, leader_replica_id: Option<u16>, cx: &mut WindowContext) {
         self.update(cx, |this, cx| {
             this.set_leader_replica_id(leader_replica_id, cx)
         })
@@ -729,7 +751,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
         &self,
         project: &ModelHandle<Project>,
         message: proto::update_view::Variant,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Result<()>> {
         self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
     }
@@ -748,8 +770,8 @@ pub(crate) mod test {
     use super::{Item, ItemEvent};
     use crate::{sidebar::SidebarItem, ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
     use gpui::{
-        elements::Empty, AppContext, Element, ElementBox, Entity, ModelHandle, RenderContext, Task,
-        View, ViewContext, ViewHandle, WeakViewHandle,
+        elements::Empty, AppContext, Drawable, Element, Entity, ModelHandle, Task, View,
+        ViewContext, ViewHandle, WeakViewHandle,
     };
     use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
     use smallvec::SmallVec;
@@ -907,7 +929,7 @@ pub(crate) mod test {
             "TestItem"
         }
 
-        fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox {
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Element<Self> {
             Empty::new().boxed()
         }
     }
@@ -920,7 +942,12 @@ pub(crate) mod test {
             })
         }
 
-        fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
+        fn tab_content<V: View>(
+            &self,
+            detail: Option<usize>,
+            _: &theme::Tab,
+            _: &AppContext,
+        ) -> Element<V> {
             self.tab_detail.set(detail);
             Empty::new().boxed()
         }

crates/workspace/src/notifications.rs 🔗

@@ -140,7 +140,7 @@ pub mod simple_message_notification {
         elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
         impl_actions,
         platform::{CursorStyle, MouseButton},
-        Action, AppContext, Element, Entity, View, ViewContext,
+        Action, AppContext, Drawable, Entity, View, ViewContext,
     };
     use menu::Cancel;
     use serde::Deserialize;
@@ -229,7 +229,7 @@ pub mod simple_message_notification {
             "MessageNotification"
         }
 
-        fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
+        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::Element<Self> {
             let theme = cx.global::<Settings>().theme.clone();
             let theme = &theme.simple_message_notification;
 
@@ -244,7 +244,7 @@ pub mod simple_message_notification {
 
             let has_click_action = click_action.is_some();
 
-            MouseEventHandler::<MessageNotificationTag>::new(0, cx, |state, cx| {
+            MouseEventHandler::<MessageNotificationTag, _>::new(0, cx, |state, cx| {
                 Flex::column()
                     .with_child(
                         Flex::row()
@@ -259,7 +259,7 @@ pub mod simple_message_notification {
                                     .boxed(),
                             )
                             .with_child(
-                                MouseEventHandler::<Cancel>::new(0, cx, |state, _| {
+                                MouseEventHandler::<Cancel, _>::new(0, cx, |state, _| {
                                     let style = theme.dismiss_button.style_for(state, false);
                                     Svg::new("icons/x_mark_8.svg")
                                         .with_color(style.color)
@@ -274,7 +274,7 @@ pub mod simple_message_notification {
                                         .boxed()
                                 })
                                 .with_padding(Padding::uniform(5.))
-                                .on_click(MouseButton::Left, move |_, cx| {
+                                .on_click(MouseButton::Left, move |_, _, cx| {
                                     cx.dispatch_action(CancelMessageNotification)
                                 })
                                 .with_cursor_style(CursorStyle::PointingHand)
@@ -312,9 +312,9 @@ pub mod simple_message_notification {
                     .boxed()
             })
             // Since we're not using a proper overlay, we have to capture these extra events
-            .on_down(MouseButton::Left, |_, _| {})
-            .on_up(MouseButton::Left, |_, _| {})
-            .on_click(MouseButton::Left, move |_, cx| {
+            .on_down(MouseButton::Left, |_, _, _| {})
+            .on_up(MouseButton::Left, |_, _, _| {})
+            .on_click(MouseButton::Left, move |_, _, cx| {
                 if let Some(click_action) = click_action.as_ref() {
                     cx.dispatch_any_action(click_action.boxed_clone());
                     cx.dispatch_action(CancelMessageNotification)

crates/workspace/src/pane.rs 🔗

@@ -7,7 +7,7 @@ use crate::{
     toolbar::Toolbar,
     Item, NewFile, NewSearch, NewTerminal, Workspace,
 };
-use anyhow::Result;
+use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
 use context_menu::{ContextMenu, ContextMenuItem};
 use drag_and_drop::Draggable;
@@ -23,9 +23,8 @@ use gpui::{
     impl_actions, impl_internal_actions,
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
-    Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-    ModelHandle, MouseRegion, Quad, RenderContext, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle,
+    MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
@@ -395,7 +394,7 @@ impl Pane {
         workspace: &mut Workspace,
         pane: Option<ViewHandle<Pane>>,
         cx: &mut ViewContext<Workspace>,
-    ) -> Task<()> {
+    ) -> Task<Result<()>> {
         Self::navigate_history(
             workspace,
             pane.unwrap_or_else(|| workspace.active_pane().clone()),
@@ -408,7 +407,7 @@ impl Pane {
         workspace: &mut Workspace,
         pane: Option<ViewHandle<Pane>>,
         cx: &mut ViewContext<Workspace>,
-    ) -> Task<()> {
+    ) -> Task<Result<()>> {
         Self::navigate_history(
             workspace,
             pane.unwrap_or_else(|| workspace.active_pane().clone()),
@@ -420,7 +419,7 @@ impl Pane {
     pub fn reopen_closed_item(
         workspace: &mut Workspace,
         cx: &mut ViewContext<Workspace>,
-    ) -> Task<()> {
+    ) -> Task<Result<()>> {
         Self::navigate_history(
             workspace,
             workspace.active_pane().clone(),
@@ -454,7 +453,7 @@ impl Pane {
         pane: ViewHandle<Pane>,
         mode: NavigationMode,
         cx: &mut ViewContext<Workspace>,
-    ) -> Task<()> {
+    ) -> Task<Result<()>> {
         cx.focus(&pane);
 
         let to_load = pane.update(cx, |pane, cx| {
@@ -504,47 +503,50 @@ impl Pane {
             let task = workspace.load_path(project_path, cx);
             cx.spawn(|workspace, mut cx| async move {
                 let task = task.await;
-                if let Some(pane) = pane.upgrade(&cx) {
-                    let mut navigated = false;
-                    if let Some((project_entry_id, build_item)) = task.log_err() {
-                        let prev_active_item_id = pane.update(&mut cx, |pane, _| {
-                            pane.nav_history.borrow_mut().set_mode(mode);
-                            pane.active_item().map(|p| p.id())
-                        });
-
-                        let item = workspace.update(&mut cx, |workspace, cx| {
-                            Self::open_item(
-                                workspace,
-                                pane.clone(),
-                                project_entry_id,
-                                true,
-                                cx,
-                                build_item,
-                            )
-                        });
-
-                        pane.update(&mut cx, |pane, cx| {
-                            navigated |= Some(item.id()) != prev_active_item_id;
-                            pane.nav_history
-                                .borrow_mut()
-                                .set_mode(NavigationMode::Normal);
-                            if let Some(data) = entry.data {
-                                navigated |= item.navigate(data, cx);
-                            }
-                        });
-                    }
+                let pane = pane
+                    .upgrade(&cx)
+                    .ok_or_else(|| anyhow!("pane was dropped"))?;
+                let mut navigated = false;
+                if let Some((project_entry_id, build_item)) = task.log_err() {
+                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
+                        pane.nav_history.borrow_mut().set_mode(mode);
+                        pane.active_item().map(|p| p.id())
+                    })?;
+
+                    let item = workspace.update(&mut cx, |workspace, cx| {
+                        Self::open_item(
+                            workspace,
+                            pane.clone(),
+                            project_entry_id,
+                            true,
+                            cx,
+                            build_item,
+                        )
+                    })?;
+
+                    pane.update(&mut cx, |pane, cx| {
+                        navigated |= Some(item.id()) != prev_active_item_id;
+                        pane.nav_history
+                            .borrow_mut()
+                            .set_mode(NavigationMode::Normal);
+                        if let Some(data) = entry.data {
+                            navigated |= item.navigate(data, cx);
+                        }
+                    })?;
+                }
 
-                    if !navigated {
-                        workspace
-                            .update(&mut cx, |workspace, cx| {
-                                Self::navigate_history(workspace, pane, mode, cx)
-                            })
-                            .await;
-                    }
+                if !navigated {
+                    workspace
+                        .update(&mut cx, |workspace, cx| {
+                            Self::navigate_history(workspace, pane, mode, cx)
+                        })?
+                        .await?;
                 }
+
+                Ok(())
             })
         } else {
-            Task::ready(())
+            Task::ready(Ok(()))
         }
     }
 
@@ -1012,10 +1014,10 @@ impl Pane {
                     if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
                         pane.remove_item(item_ix, false, cx);
                     }
-                });
+                })?;
             }
 
-            pane.update(&mut cx, |_, cx| cx.notify());
+            pane.update(&mut cx, |_, cx| cx.notify())?;
             Ok(())
         })
     }
@@ -1105,10 +1107,10 @@ impl Pane {
                     CONFLICT_MESSAGE,
                     &["Overwrite", "Discard", "Cancel"],
                 )
-            });
+            })?;
             match answer.next().await {
-                Some(0) => cx.update(|cx| item.save(project, cx)).await?,
-                Some(1) => cx.update(|cx| item.reload(project, cx)).await?,
+                Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+                Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
                 _ => return Ok(false),
             }
         } else if is_dirty && (can_save || is_singleton) {
@@ -1126,7 +1128,7 @@ impl Pane {
                         DIRTY_MESSAGE,
                         &["Save", "Don't Save", "Cancel"],
                     )
-                });
+                })?;
                 match answer.next().await {
                     Some(0) => true,
                     Some(1) => false,
@@ -1138,7 +1140,7 @@ impl Pane {
 
             if should_save {
                 if can_save {
-                    cx.update(|cx| item.save(project, cx)).await?;
+                    pane.update(cx, |_, cx| item.save(project, cx))?.await?;
                 } else if is_singleton {
                     let start_abs_path = project
                         .read_with(cx, |project, cx| {
@@ -1149,7 +1151,8 @@ impl Pane {
 
                     let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
                     if let Some(abs_path) = abs_path.next().await.flatten() {
-                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
+                        pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+                            .await?;
                     } else {
                         return Ok(false);
                     }
@@ -1167,7 +1170,7 @@ impl Pane {
     pub fn autosave_item(
         item: &dyn ItemHandle,
         project: ModelHandle<Project>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Result<()>> {
         if Self::can_autosave_item(item, cx) {
             item.save(project, cx)
@@ -1353,10 +1356,10 @@ impl Pane {
         });
     }
 
-    fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
+    fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Drawable<Self> {
         let theme = cx.global::<Settings>().theme.clone();
 
-        let pane = cx.handle();
+        let pane = cx.handle().downgrade();
         let autoscroll = if mem::take(&mut self.autoscroll) {
             Some(self.active_item_index)
         } else {
@@ -1366,7 +1369,7 @@ impl Pane {
         let pane_active = self.is_active;
 
         enum Tabs {}
-        let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
+        let mut row = Flex::row().scrollable::<Tabs>(1, autoscroll, cx);
         for (ix, (item, detail)) in self
             .items
             .iter()
@@ -1397,7 +1400,7 @@ impl Pane {
 
                             enum Tab {}
                             let mouse_event_handler =
-                                MouseEventHandler::<Tab>::new(ix, cx, |_, cx| {
+                                MouseEventHandler::<Tab, Pane>::new(ix, cx, |_, cx| {
                                     Self::render_tab(
                                         &item,
                                         pane.clone(),
@@ -1408,13 +1411,13 @@ impl Pane {
                                         cx,
                                     )
                                 })
-                                .on_down(MouseButton::Left, move |_, cx| {
+                                .on_down(MouseButton::Left, move |_, _, cx| {
                                     cx.dispatch_action(ActivateItem(ix));
                                 })
                                 .on_click(MouseButton::Middle, {
                                     let item = item.clone();
                                     let pane = pane.clone();
-                                    move |_, cx: &mut EventContext| {
+                                    move |_, _, cx| {
                                         cx.dispatch_action(CloseItemById {
                                             item_id: item.id(),
                                             pane: pane.clone(),
@@ -1423,7 +1426,7 @@ impl Pane {
                                 })
                                 .on_down(
                                     MouseButton::Right,
-                                    move |e, cx| {
+                                    move |e, _, cx| {
                                         let item = item.clone();
                                         cx.dispatch_action(DeployTabContextMenu {
                                             position: e.position,
@@ -1435,7 +1438,7 @@ impl Pane {
 
                             if let Some(tab_tooltip_text) = tab_tooltip_text {
                                 return mouse_event_handler
-                                    .with_tooltip::<Self, _>(
+                                    .with_tooltip::<Self>(
                                         ix,
                                         tab_tooltip_text,
                                         None,
@@ -1463,9 +1466,9 @@ impl Pane {
                             let theme = cx.global::<Settings>().theme.clone();
 
                             let detail = detail.clone();
-                            move |dragged_item, cx: &mut RenderContext<Workspace>| {
+                            move |dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
                                 let tab_style = &theme.workspace.tab_bar.dragged_tab;
-                                Self::render_tab(
+                                Self::render_dragged_tab(
                                     &dragged_item.item,
                                     dragged_item.pane.clone(),
                                     false,
@@ -1538,16 +1541,41 @@ impl Pane {
         tab_details
     }
 
-    fn render_tab<V: View>(
+    fn render_tab(
         item: &Box<dyn ItemHandle>,
         pane: WeakViewHandle<Pane>,
         first: bool,
         detail: Option<usize>,
         hovered: bool,
         tab_style: &theme::Tab,
-        cx: &mut RenderContext<V>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         let title = item.tab_content(detail, &tab_style, cx);
+        Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+    }
+
+    fn render_dragged_tab(
+        item: &Box<dyn ItemHandle>,
+        pane: WeakViewHandle<Pane>,
+        first: bool,
+        detail: Option<usize>,
+        hovered: bool,
+        tab_style: &theme::Tab,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Element<Workspace> {
+        let title = item.dragged_tab_content(detail, &tab_style, cx);
+        Self::render_tab_with_title(title, item, pane, first, hovered, tab_style, cx)
+    }
+
+    fn render_tab_with_title<T: View>(
+        title: Element<T>,
+        item: &Box<dyn ItemHandle>,
+        pane: WeakViewHandle<Pane>,
+        first: bool,
+        hovered: bool,
+        tab_style: &theme::Tab,
+        cx: &mut ViewContext<T>,
+    ) -> Element<T> {
         let mut container = tab_style.container.clone();
         if first {
             container.border.left = false;
@@ -1566,10 +1594,10 @@ impl Pane {
                     };
 
                     ConstrainedBox::new(
-                        Canvas::new(move |bounds, _, cx| {
+                        Canvas::new(move |scene, bounds, _, _, _| {
                             if let Some(color) = icon_color {
                                 let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
-                                cx.scene.push_quad(Quad {
+                                scene.push_quad(Quad {
                                     bounds: square,
                                     background: Some(color),
                                     border: Default::default(),
@@ -1603,18 +1631,22 @@ impl Pane {
                         let item_id = item.id();
                         enum TabCloseButton {}
                         let icon = Svg::new("icons/x_mark_8.svg");
-                        MouseEventHandler::<TabCloseButton>::new(item_id, cx, |mouse_state, _| {
-                            if mouse_state.hovered() {
-                                icon.with_color(tab_style.icon_close_active).boxed()
-                            } else {
-                                icon.with_color(tab_style.icon_close).boxed()
-                            }
-                        })
+                        MouseEventHandler::<TabCloseButton, _>::new(
+                            item_id,
+                            cx,
+                            |mouse_state, _| {
+                                if mouse_state.hovered() {
+                                    icon.with_color(tab_style.icon_close_active).boxed()
+                                } else {
+                                    icon.with_color(tab_style.icon_close).boxed()
+                                }
+                            },
+                        )
                         .with_padding(Padding::uniform(4.))
                         .with_cursor_style(CursorStyle::PointingHand)
                         .on_click(MouseButton::Left, {
                             let pane = pane.clone();
-                            move |_, cx| {
+                            move |_, _, cx| {
                                 cx.dispatch_action(CloseItemById {
                                     item_id,
                                     pane: pane.clone(),
@@ -1640,8 +1672,8 @@ impl Pane {
     fn render_tab_bar_buttons(
         &mut self,
         theme: &Theme,
-        cx: &mut RenderContext<Self>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Self>,
+    ) -> Element<Self> {
         Flex::row()
             // New menu
             .with_child(render_tab_bar_button(
@@ -1690,7 +1722,7 @@ impl Pane {
             .boxed()
     }
 
-    fn render_blank_pane(&mut self, theme: &Theme, _cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_blank_pane(&mut self, theme: &Theme, _cx: &mut ViewContext<Self>) -> Element<Self> {
         let background = theme.workspace.background;
         Empty::new()
             .contained()
@@ -1708,14 +1740,12 @@ impl View for Pane {
         "Pane"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
-        let this = cx.handle();
-
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         enum MouseNavigationHandler {}
 
         Stack::new()
             .with_child(
-                MouseEventHandler::<MouseNavigationHandler>::new(0, cx, |_, cx| {
+                MouseEventHandler::<MouseNavigationHandler, _>::new(0, cx, |_, cx| {
                     let active_item_index = self.active_item_index;
 
                     if let Some(active_item) = self.active_item() {
@@ -1727,13 +1757,17 @@ impl View for Pane {
 
                                 enum TabBarEventHandler {}
                                 stack.add_child(
-                                    MouseEventHandler::<TabBarEventHandler>::new(0, cx, |_, _| {
-                                        Empty::new()
-                                            .contained()
-                                            .with_style(theme.workspace.tab_bar.container)
-                                            .boxed()
-                                    })
-                                    .on_down(MouseButton::Left, move |_, cx| {
+                                    MouseEventHandler::<TabBarEventHandler, _>::new(
+                                        0,
+                                        cx,
+                                        |_, _| {
+                                            Empty::new()
+                                                .contained()
+                                                .with_style(theme.workspace.tab_bar.container)
+                                                .boxed()
+                                        },
+                                    )
+                                    .on_down(MouseButton::Left, move |_, _, cx| {
                                         cx.dispatch_action(ActivateItem(active_item_index));
                                     })
                                     .boxed(),
@@ -1794,26 +1828,23 @@ impl View for Pane {
                         dragged_item_receiver::<EmptyPane, _>(0, 0, false, None, cx, |_, cx| {
                             self.render_blank_pane(&theme, cx)
                         })
-                        .on_down(MouseButton::Left, |_, cx| {
+                        .on_down(MouseButton::Left, |_, _, cx| {
                             cx.focus_parent_view();
                         })
                         .boxed()
                     }
                 })
-                .on_down(MouseButton::Navigate(NavigationDirection::Back), {
-                    let this = this.clone();
-                    move |_, cx| {
-                        cx.dispatch_action(GoBack {
-                            pane: Some(this.clone()),
-                        });
-                    }
-                })
+                .on_down(
+                    MouseButton::Navigate(NavigationDirection::Back),
+                    move |_, _, cx| {
+                        let pane = cx.weak_handle();
+                        cx.dispatch_action(GoBack { pane: Some(pane) });
+                    },
+                )
                 .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
-                    let this = this.clone();
-                    move |_, cx| {
-                        cx.dispatch_action(GoForward {
-                            pane: Some(this.clone()),
-                        })
+                    move |_, _, cx| {
+                        let pane = cx.weak_handle();
+                        cx.dispatch_action(GoForward { pane: Some(pane) })
                     }
                 })
                 .boxed(),
@@ -1867,15 +1898,15 @@ impl View for Pane {
 fn render_tab_bar_button<A: Action + Clone>(
     index: usize,
     icon: &'static str,
-    cx: &mut RenderContext<Pane>,
+    cx: &mut ViewContext<Pane>,
     action: A,
     context_menu: Option<ViewHandle<ContextMenu>>,
-) -> ElementBox {
+) -> Element<Pane> {
     enum TabBarButton {}
 
     Stack::new()
         .with_child(
-            MouseEventHandler::<TabBarButton>::new(index, cx, |mouse_state, cx| {
+            MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
                 let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
                 let style = theme.pane_button.style_for(mouse_state, false);
                 Svg::new(icon)
@@ -1889,7 +1920,7 @@ fn render_tab_bar_button<A: Action + Clone>(
                     .boxed()
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, move |_, cx| {
+            .on_click(MouseButton::Left, move |_, _, cx| {
                 cx.dispatch_action(action.clone());
             })
             .boxed(),
@@ -1902,15 +1933,15 @@ fn render_tab_bar_button<A: Action + Clone>(
 }
 
 impl ItemNavHistory {
-    pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut AppContext) {
+    pub fn push<D: 'static + Any>(&self, data: Option<D>, cx: &mut WindowContext) {
         self.history.borrow_mut().push(data, self.item.clone(), cx);
     }
 
-    pub fn pop_backward(&self, cx: &mut AppContext) -> Option<NavigationEntry> {
+    pub fn pop_backward(&self, cx: &mut WindowContext) -> Option<NavigationEntry> {
         self.history.borrow_mut().pop(NavigationMode::GoingBack, cx)
     }
 
-    pub fn pop_forward(&self, cx: &mut AppContext) -> Option<NavigationEntry> {
+    pub fn pop_forward(&self, cx: &mut WindowContext) -> Option<NavigationEntry> {
         self.history
             .borrow_mut()
             .pop(NavigationMode::GoingForward, cx)
@@ -1930,7 +1961,7 @@ impl NavHistory {
         self.mode = NavigationMode::Normal;
     }
 
-    fn pop(&mut self, mode: NavigationMode, cx: &mut AppContext) -> Option<NavigationEntry> {
+    fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
         let entry = match mode {
             NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
                 return None
@@ -1950,7 +1981,7 @@ impl NavHistory {
         &mut self,
         data: Option<D>,
         item: Rc<dyn WeakItemHandle>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) {
         match self.mode {
             NavigationMode::Disabled => {}
@@ -1995,19 +2026,20 @@ impl NavHistory {
         self.did_update(cx);
     }
 
-    fn did_update(&self, cx: &mut AppContext) {
+    fn did_update(&self, cx: &mut WindowContext) {
         if let Some(pane) = self.pane.upgrade(cx) {
             cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx)));
         }
     }
 }
 
-pub struct PaneBackdrop {
+pub struct PaneBackdrop<V: View> {
     child_view: usize,
-    child: ElementBox,
+    child: Element<V>,
 }
-impl PaneBackdrop {
-    pub fn new(pane_item_view: usize, child: ElementBox) -> Self {
+
+impl<V: View> PaneBackdrop<V> {
+    pub fn new(pane_item_view: usize, child: Element<V>) -> Self {
         PaneBackdrop {
             child,
             child_view: pane_item_view,
@@ -2015,7 +2047,7 @@ impl PaneBackdrop {
     }
 }
 
-impl Element for PaneBackdrop {
+impl<V: View> Drawable<V> for PaneBackdrop<V> {
     type LayoutState = ();
 
     type PaintState = ();
@@ -2023,42 +2055,46 @@ impl Element for PaneBackdrop {
     fn layout(
         &mut self,
         constraint: gpui::SizeConstraint,
-        cx: &mut gpui::LayoutContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
-        let size = self.child.layout(constraint, cx);
+        let size = self.child.layout(constraint, view, cx);
         (size, ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut gpui::SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut gpui::PaintContext,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let background = cx.global::<Settings>().theme.editor.background;
 
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
-        cx.scene.push_quad(gpui::Quad {
+        scene.push_quad(gpui::Quad {
             bounds: RectF::new(bounds.origin(), bounds.size()),
             background: Some(background),
             ..Default::default()
         });
 
         let child_view_id = self.child_view;
-        cx.scene.push_mouse_region(
+        scene.push_mouse_region(
             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
                 gpui::platform::MouseButton::Left,
-                move |_, cx| {
-                    let window_id = cx.window_id;
-                    cx.focus(window_id, Some(child_view_id))
+                move |_, _: &mut V, cx| {
+                    let window_id = cx.window_id();
+                    cx.app_context().focus(window_id, Some(child_view_id))
                 },
             ),
         );
 
-        cx.paint_layer(Some(bounds), |cx| {
-            self.child.paint(bounds.origin(), visible_bounds, cx)
+        scene.paint_layer(Some(bounds), |scene| {
+            self.child
+                .paint(scene, bounds.origin(), visible_bounds, view, cx)
         })
     }
 
@@ -2069,9 +2105,10 @@ impl Element for PaneBackdrop {
         _visible_bounds: RectF,
         _layout: &Self::LayoutState,
         _paint: &Self::PaintState,
-        cx: &gpui::MeasurementContext,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
     ) -> Option<RectF> {
-        self.child.rect_for_text_range(range_utf16, cx)
+        self.child.rect_for_text_range(range_utf16, view, cx)
     }
 
     fn debug(
@@ -2079,12 +2116,13 @@ impl Element for PaneBackdrop {
         _bounds: RectF,
         _layout: &Self::LayoutState,
         _paint: &Self::PaintState,
-        cx: &gpui::DebugContext,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
     ) -> serde_json::Value {
         gpui::json::json!({
             "type": "Pane Back Drop",
             "view": self.child_view,
-            "child": self.child.debug(cx),
+            "child": self.child.debug(view, cx),
         })
     }
 }

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

@@ -5,7 +5,8 @@ use gpui::{
     geometry::{rect::RectF, vector::Vector2F},
     platform::MouseButton,
     scene::MouseUp,
-    AppContext, Element, ElementBox, EventContext, MouseState, Quad, RenderContext, WeakViewHandle,
+    AppContext, Drawable, Element, EventContext, MouseState, Quad, View, ViewContext,
+    WeakViewHandle,
 };
 use project::ProjectEntryId;
 use settings::Settings;
@@ -22,14 +23,14 @@ pub fn dragged_item_receiver<Tag, F>(
     drop_index: usize,
     allow_same_pane: bool,
     split_margin: Option<f32>,
-    cx: &mut RenderContext<Pane>,
+    cx: &mut ViewContext<Pane>,
     render_child: F,
-) -> MouseEventHandler<Tag>
+) -> MouseEventHandler<Tag, Pane>
 where
     Tag: 'static,
-    F: FnOnce(&mut MouseState, &mut RenderContext<Pane>) -> ElementBox,
+    F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> Element<Pane>,
 {
-    MouseEventHandler::<Tag>::above(region_id, cx, |state, cx| {
+    MouseEventHandler::<Tag, _>::above(region_id, cx, |state, cx| {
         // Observing hovered will cause a render when the mouse enters regardless
         // of if mouse position was accessed before
         let drag_position = if state.hovered() {
@@ -48,7 +49,7 @@ where
         Stack::new()
             .with_child(render_child(state, cx))
             .with_children(drag_position.map(|drag_position| {
-                Canvas::new(move |bounds, _, cx| {
+                Canvas::new(move |scene, bounds, _, _, cx| {
                     if bounds.contains_point(drag_position) {
                         let overlay_region = split_margin
                             .and_then(|split_margin| {
@@ -58,8 +59,8 @@ where
                             .map(|(dir, margin)| dir.along_edge(bounds, margin))
                             .unwrap_or(bounds);
 
-                        cx.paint_stacking_context(None, None, |cx| {
-                            cx.scene.push_quad(Quad {
+                        scene.paint_stacking_context(None, None, |scene| {
+                            scene.push_quad(Quad {
                                 bounds: overlay_region,
                                 background: Some(overlay_color(cx)),
                                 border: Default::default(),
@@ -73,13 +74,13 @@ where
             .boxed()
     })
     .on_up(MouseButton::Left, {
-        let pane = cx.handle();
-        move |event, cx| {
+        move |event, _, cx| {
+            let pane = cx.weak_handle();
             handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
             cx.notify();
         }
     })
-    .on_move(|_, cx| {
+    .on_move(|_, _, cx| {
         let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
 
         if drag_and_drop
@@ -96,13 +97,13 @@ where
     })
 }
 
-pub fn handle_dropped_item(
+pub fn handle_dropped_item<V: View>(
     event: MouseUp,
     pane: &WeakViewHandle<Pane>,
     index: usize,
     allow_same_pane: bool,
     split_margin: Option<f32>,
-    cx: &mut EventContext,
+    cx: &mut EventContext<V>,
 ) {
     enum Action {
         Move(WeakViewHandle<Pane>, usize),
@@ -110,11 +111,11 @@ pub fn handle_dropped_item(
     }
     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_id())
     {
         Action::Move(dragged_item.pane.clone(), dragged_item.item.id())
     } else if let Some((_, project_entry)) =
-        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id)
+        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window_id())
     {
         Action::Open(*project_entry)
     } else {

crates/workspace/src/pane_group.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::Vector2F},
     platform::{CursorStyle, MouseButton},
-    Axis, Border, ModelHandle, RenderContext, ViewHandle,
+    Axis, Border, ModelHandle, ViewContext, ViewHandle,
 };
 use project::Project;
 use serde::Deserialize;
@@ -70,8 +70,8 @@ impl PaneGroup {
         follower_states: &FollowerStatesByLeader,
         active_call: Option<&ModelHandle<ActiveCall>>,
         active_pane: &ViewHandle<Pane>,
-        cx: &mut RenderContext<Workspace>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Workspace>,
+    ) -> Element<Workspace> {
         self.root.render(
             project,
             theme,
@@ -131,8 +131,8 @@ impl Member {
         follower_states: &FollowerStatesByLeader,
         active_call: Option<&ModelHandle<ActiveCall>>,
         active_pane: &ViewHandle<Pane>,
-        cx: &mut RenderContext<Workspace>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Workspace>,
+    ) -> Element<Workspace> {
         enum FollowIntoExternalProject {}
 
         match self {
@@ -176,7 +176,7 @@ impl Member {
                                 let leader_user = leader.user.clone();
                                 let leader_user_id = leader.user.id;
                                 Some(
-                                    MouseEventHandler::<FollowIntoExternalProject>::new(
+                                    MouseEventHandler::<FollowIntoExternalProject, _>::new(
                                         pane.id(),
                                         cx,
                                         |_, _| {
@@ -199,7 +199,7 @@ impl Member {
                                         },
                                     )
                                     .with_cursor_style(CursorStyle::PointingHand)
-                                    .on_click(MouseButton::Left, move |_, cx| {
+                                    .on_click(MouseButton::Left, move |_, _, cx| {
                                         cx.dispatch_action(JoinProject {
                                             project_id: leader_project_id,
                                             follow_user_id: leader_user_id,
@@ -366,8 +366,8 @@ impl PaneAxis {
         follower_state: &FollowerStatesByLeader,
         active_call: Option<&ModelHandle<ActiveCall>>,
         active_pane: &ViewHandle<Pane>,
-        cx: &mut RenderContext<Workspace>,
-    ) -> ElementBox {
+        cx: &mut ViewContext<Workspace>,
+    ) -> Element<Workspace> {
         let last_member_ix = self.members.len() - 1;
         Flex::new(self.axis)
             .with_children(self.members.iter().enumerate().map(|(ix, member)| {

crates/workspace/src/persistence/model.rs 🔗

@@ -131,16 +131,21 @@ impl SerializedPaneGroup {
                 ))
             }
             SerializedPaneGroup::Pane(serialized_pane) => {
-                let pane = workspace.update(cx, |workspace, cx| workspace.add_pane(cx));
+                let pane = workspace
+                    .update(cx, |workspace, cx| workspace.add_pane(cx))
+                    .log_err()?;
                 let active = serialized_pane.active;
                 serialized_pane
                     .deserialize_to(project, &pane, workspace_id, workspace, cx)
-                    .await;
+                    .await
+                    .log_err()?;
 
                 if pane.read_with(cx, |pane, _| pane.items_len() != 0) {
                     Some((Member::Pane(pane.clone()), active.then(|| pane)))
                 } else {
-                    workspace.update(cx, |workspace, cx| workspace.remove_pane(pane, cx));
+                    workspace
+                        .update(cx, |workspace, cx| workspace.remove_pane(pane, cx))
+                        .log_err()?;
                     None
                 }
             }
@@ -166,7 +171,7 @@ impl SerializedPane {
         workspace_id: WorkspaceId,
         workspace: &ViewHandle<Workspace>,
         cx: &mut AsyncAppContext,
-    ) {
+    ) -> Result<()> {
         let mut active_item_index = None;
         for (index, item) in self.children.iter().enumerate() {
             let project = project.clone();
@@ -186,14 +191,14 @@ impl SerializedPane {
                             item.kind
                         )))
                     }
-                })
+                })?
                 .await
                 .log_err();
 
             if let Some(item_handle) = item_handle {
                 workspace.update(cx, |workspace, cx| {
                     Pane::add_item(workspace, &pane_handle, item_handle, false, false, None, cx);
-                })
+                })?;
             }
 
             if item.active {
@@ -204,8 +209,10 @@ impl SerializedPane {
         if let Some(active_item_index) = active_item_index {
             pane_handle.update(cx, |pane, cx| {
                 pane.activate_item(active_item_index, false, false, cx);
-            })
+            })?;
         }
+
+        anyhow::Ok(())
     }
 }
 

crates/workspace/src/searchable.rs 🔗

@@ -2,7 +2,7 @@ use std::any::Any;
 
 use gpui::{
     AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
-    WeakViewHandle,
+    WeakViewHandle, WindowContext,
 };
 use project::search::SearchQuery;
 
@@ -90,29 +90,34 @@ pub trait SearchableItemHandle: ItemHandle {
     fn supported_options(&self) -> SearchOptions;
     fn subscribe_to_search_events(
         &self,
-        cx: &mut AppContext,
-        handler: Box<dyn Fn(SearchEvent, &mut AppContext)>,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
     ) -> Subscription;
-    fn clear_matches(&self, cx: &mut AppContext);
-    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut AppContext);
-    fn query_suggestion(&self, cx: &mut AppContext) -> String;
-    fn activate_match(&self, index: usize, matches: &Vec<Box<dyn Any + Send>>, cx: &mut AppContext);
+    fn clear_matches(&self, cx: &mut WindowContext);
+    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
+    fn query_suggestion(&self, cx: &mut WindowContext) -> String;
+    fn activate_match(
+        &self,
+        index: usize,
+        matches: &Vec<Box<dyn Any + Send>>,
+        cx: &mut WindowContext,
+    );
     fn match_index_for_direction(
         &self,
         matches: &Vec<Box<dyn Any + Send>>,
         current_index: usize,
         direction: Direction,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> usize;
     fn find_matches(
         &self,
         query: SearchQuery,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Vec<Box<dyn Any + Send>>>;
     fn active_match_index(
         &self,
         matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<usize>;
 }
 
@@ -131,8 +136,8 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
 
     fn subscribe_to_search_events(
         &self,
-        cx: &mut AppContext,
-        handler: Box<dyn Fn(SearchEvent, &mut AppContext)>,
+        cx: &mut WindowContext,
+        handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
     ) -> Subscription {
         cx.subscribe(self, move |_, event, cx| {
             if let Some(search_event) = T::to_search_event(event) {
@@ -141,21 +146,21 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
         })
     }
 
-    fn clear_matches(&self, cx: &mut AppContext) {
+    fn clear_matches(&self, cx: &mut WindowContext) {
         self.update(cx, |this, cx| this.clear_matches(cx));
     }
-    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut AppContext) {
+    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
         let matches = downcast_matches(matches);
         self.update(cx, |this, cx| this.update_matches(matches, cx));
     }
-    fn query_suggestion(&self, cx: &mut AppContext) -> String {
+    fn query_suggestion(&self, cx: &mut WindowContext) -> String {
         self.update(cx, |this, cx| this.query_suggestion(cx))
     }
     fn activate_match(
         &self,
         index: usize,
         matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) {
         let matches = downcast_matches(matches);
         self.update(cx, |this, cx| this.activate_match(index, matches, cx));
@@ -165,7 +170,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
         matches: &Vec<Box<dyn Any + Send>>,
         current_index: usize,
         direction: Direction,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> usize {
         let matches = downcast_matches(matches);
         self.update(cx, |this, cx| {
@@ -175,7 +180,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
     fn find_matches(
         &self,
         query: SearchQuery,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Task<Vec<Box<dyn Any + Send>>> {
         let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
         cx.foreground().spawn(async {
@@ -189,7 +194,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
     fn active_match_index(
         &self,
         matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<usize> {
         let matches = downcast_matches(matches);
         self.update(cx, |this, cx| this.active_match_index(matches, cx))

crates/workspace/src/shared_screen.rs 🔗

@@ -2,6 +2,7 @@ use crate::{
     item::{Item, ItemEvent},
     ItemNavHistory, WorkspaceId,
 };
+use anyhow::Result;
 use call::participant::{Frame, RemoteVideoTrack};
 use client::{proto::PeerId, User};
 use futures::StreamExt;
@@ -9,7 +10,7 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     platform::MouseButton,
-    AppContext, Entity, RenderContext, Task, View, ViewContext,
+    AppContext, Entity, Task, View, ViewContext,
 };
 use settings::Settings;
 use smallvec::SmallVec;
@@ -28,7 +29,7 @@ pub struct SharedScreen {
     pub peer_id: PeerId,
     user: Arc<User>,
     nav_history: Option<ItemNavHistory>,
-    _maintain_frame: Task<()>,
+    _maintain_frame: Task<Result<()>>,
 }
 
 impl SharedScreen {
@@ -50,9 +51,10 @@ impl SharedScreen {
                     this.update(&mut cx, |this, cx| {
                         this.frame = Some(frame);
                         cx.notify();
-                    })
+                    })?;
                 }
-                this.update(&mut cx, |_, cx| cx.emit(Event::Close));
+                this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
+                Ok(())
             }),
         }
     }
@@ -67,19 +69,19 @@ impl View for SharedScreen {
         "SharedScreen"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         enum Focus {}
 
         let frame = self.frame.clone();
-        MouseEventHandler::<Focus>::new(0, cx, |_, cx| {
-            Canvas::new(move |bounds, _, cx| {
+        MouseEventHandler::<Focus, _>::new(0, cx, |_, cx| {
+            Canvas::new(move |scene, bounds, _, _, _| {
                 if let Some(frame) = frame.clone() {
                     let size = constrain_size_preserving_aspect_ratio(
                         bounds.size(),
                         vec2f(frame.width() as f32, frame.height() as f32),
                     );
                     let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
-                    cx.scene.push_surface(gpui::platform::mac::Surface {
+                    scene.push_surface(gpui::platform::mac::Surface {
                         bounds: RectF::new(origin, size),
                         image_buffer: frame.image(),
                     });
@@ -89,7 +91,7 @@ impl View for SharedScreen {
             .with_style(cx.global::<Settings>().theme.shared_screen)
             .boxed()
         })
-        .on_down(MouseButton::Left, |_, cx| cx.focus_parent_view())
+        .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent_view())
         .boxed()
     }
 }
@@ -104,12 +106,12 @@ impl Item for SharedScreen {
         }
     }
 
-    fn tab_content(
+    fn tab_content<V: View>(
         &self,
         _: Option<usize>,
         style: &theme::Tab,
         _: &AppContext,
-    ) -> gpui::ElementBox {
+    ) -> gpui::Element<V> {
         Flex::row()
             .with_child(
                 Svg::new("icons/disable_screen_sharing_12.svg")

crates/workspace/src/sidebar.rs 🔗

@@ -1,7 +1,7 @@
 use crate::StatusItemView;
 use gpui::{
     elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
-    AppContext, Entity, RenderContext, Subscription, View, ViewContext, ViewHandle,
+    AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WindowContext,
 };
 use serde::Deserialize;
 use settings::Settings;
@@ -21,8 +21,8 @@ pub trait SidebarItem: View {
 
 pub trait SidebarItemHandle {
     fn id(&self) -> usize;
-    fn should_show_badge(&self, cx: &AppContext) -> bool;
-    fn is_focused(&self, cx: &AppContext) -> bool;
+    fn should_show_badge(&self, cx: &WindowContext) -> bool;
+    fn is_focused(&self, cx: &WindowContext) -> bool;
     fn as_any(&self) -> &AnyViewHandle;
 }
 
@@ -34,11 +34,11 @@ where
         self.id()
     }
 
-    fn should_show_badge(&self, cx: &AppContext) -> bool {
+    fn should_show_badge(&self, cx: &WindowContext) -> bool {
         self.read(cx).should_show_badge(cx)
     }
 
-    fn is_focused(&self, cx: &AppContext) -> bool {
+    fn is_focused(&self, cx: &WindowContext) -> bool {
         ViewHandle::is_focused(self, cx) || self.read(cx).contains_focused_view(cx)
     }
 
@@ -188,14 +188,14 @@ impl View for Sidebar {
         "Sidebar"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         if let Some(active_item) = self.active_item() {
             enum ResizeHandleTag {}
             let style = &cx.global::<Settings>().theme.workspace.sidebar;
             ChildView::new(active_item.as_any(), cx)
                 .contained()
                 .with_style(style.container)
-                .with_resize_handle::<ResizeHandleTag, _>(
+                .with_resize_handle::<ResizeHandleTag>(
                     self.sidebar_side as usize,
                     self.sidebar_side.to_resizable_side(),
                     4.,
@@ -225,7 +225,7 @@ impl View for SidebarButtons {
         "SidebarToggleButton"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = &cx.global::<Settings>().theme;
         let tooltip_style = theme.tooltip.clone();
         let theme = &theme.workspace.status_bar.sidebar_buttons;
@@ -254,7 +254,7 @@ impl View for SidebarButtons {
                         sidebar_side,
                         item_index: ix,
                     };
-                    MouseEventHandler::<Self>::new(ix, cx, |state, cx| {
+                    MouseEventHandler::<Self, _>::new(ix, cx, |state, cx| {
                         let is_active = is_open && ix == active_ix;
                         let style = item_style.style_for(state, is_active);
                         Stack::new()
@@ -283,9 +283,9 @@ impl View for SidebarButtons {
                     .with_cursor_style(CursorStyle::PointingHand)
                     .on_click(MouseButton::Left, {
                         let action = action.clone();
-                        move |_, cx| cx.dispatch_action(action.clone())
+                        move |_, _, cx| cx.dispatch_action(action.clone())
                     })
-                    .with_tooltip::<Self, _>(
+                    .with_tooltip::<Self>(
                         ix,
                         tooltip,
                         Some(Box::new(action)),

crates/workspace/src/status_bar.rs 🔗

@@ -8,8 +8,8 @@ use gpui::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    AnyViewHandle, AppContext, DebugContext, ElementBox, Entity, LayoutContext, MeasurementContext,
-    PaintContext, RenderContext, SizeConstraint, Subscription, View, ViewContext, ViewHandle,
+    AnyViewHandle, Element, Entity, SceneBuilder, SizeConstraint, Subscription, View, ViewContext,
+    ViewHandle, WindowContext,
 };
 use settings::Settings;
 
@@ -23,7 +23,11 @@ pub trait StatusItemView: View {
 
 trait StatusItemViewHandle {
     fn as_any(&self) -> &AnyViewHandle;
-    fn set_active_pane_item(&self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut AppContext);
+    fn set_active_pane_item(
+        &self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut WindowContext,
+    );
 }
 
 pub struct StatusBar {
@@ -42,7 +46,7 @@ impl View for StatusBar {
         "StatusBar"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = &cx.global::<Settings>().theme.workspace.status_bar;
 
         StatusBarElement {
@@ -125,7 +129,11 @@ impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
         self
     }
 
-    fn set_active_pane_item(&self, active_pane_item: Option<&dyn ItemHandle>, cx: &mut AppContext) {
+    fn set_active_pane_item(
+        &self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut WindowContext,
+    ) {
         self.update(cx, |this, cx| {
             this.set_active_pane_item(active_pane_item, cx)
         });
@@ -139,48 +147,53 @@ impl From<&dyn StatusItemViewHandle> for AnyViewHandle {
 }
 
 struct StatusBarElement {
-    left: ElementBox,
-    right: ElementBox,
+    left: Element<StatusBar>,
+    right: Element<StatusBar>,
 }
 
-impl Element for StatusBarElement {
+impl Drawable<StatusBar> for StatusBarElement {
     type LayoutState = ();
     type PaintState = ();
 
     fn layout(
         &mut self,
         mut constraint: SizeConstraint,
-        cx: &mut LayoutContext,
+        view: &mut StatusBar,
+        cx: &mut ViewContext<StatusBar>,
     ) -> (Vector2F, Self::LayoutState) {
         let max_width = constraint.max.x();
         constraint.min = vec2f(0., constraint.min.y());
 
-        let right_size = self.right.layout(constraint, cx);
+        let right_size = self.right.layout(constraint, view, cx);
         let constraint = SizeConstraint::new(
             vec2f(0., constraint.min.y()),
             vec2f(max_width - right_size.x(), constraint.max.y()),
         );
 
-        self.left.layout(constraint, cx);
+        self.left.layout(constraint, view, cx);
 
         (vec2f(max_width, right_size.y()), ())
     }
 
     fn paint(
         &mut self,
+        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
-        cx: &mut PaintContext,
+        view: &mut StatusBar,
+        cx: &mut ViewContext<StatusBar>,
     ) -> Self::PaintState {
         let origin_y = bounds.upper_right().y();
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
         let left_origin = vec2f(bounds.lower_left().x(), origin_y);
-        self.left.paint(left_origin, visible_bounds, cx);
+        self.left
+            .paint(scene, left_origin, visible_bounds, view, cx);
 
         let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
-        self.right.paint(right_origin, visible_bounds, cx);
+        self.right
+            .paint(scene, right_origin, visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -190,7 +203,8 @@ impl Element for StatusBarElement {
         _: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &MeasurementContext,
+        _: &StatusBar,
+        _: &ViewContext<StatusBar>,
     ) -> Option<RectF> {
         None
     }
@@ -200,7 +214,8 @@ impl Element for StatusBarElement {
         bounds: RectF,
         _: &Self::LayoutState,
         _: &Self::PaintState,
-        _: &DebugContext,
+        _: &StatusBar,
+        _: &ViewContext<StatusBar>,
     ) -> serde_json::Value {
         json!({
             "type": "StatusBarElement",

crates/workspace/src/toolbar.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{ItemHandle, Pane};
 use gpui::{
     elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext,
-    ElementBox, Entity, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle,
+    Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
 use settings::Settings;
 
@@ -21,7 +21,7 @@ pub trait ToolbarItemView: View {
         current_location
     }
 
-    fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut AppContext) {}
+    fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
 }
 
 trait ToolbarItemViewHandle {
@@ -30,9 +30,9 @@ trait ToolbarItemViewHandle {
     fn set_active_pane_item(
         &self,
         active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> ToolbarItemLocation;
-    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext);
+    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext);
 }
 
 #[derive(Copy, Clone, Debug, PartialEq)]
@@ -59,7 +59,7 @@ impl View for Toolbar {
         "Toolbar"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = &cx.global::<Settings>().theme.workspace.toolbar;
 
         let mut primary_left_items = Vec::new();
@@ -171,9 +171,9 @@ fn nav_button<A: Action + Clone>(
     action: A,
     tooltip_action: A,
     action_name: &str,
-    cx: &mut RenderContext<Toolbar>,
-) -> ElementBox {
-    MouseEventHandler::<A>::new(0, cx, |state, _| {
+    cx: &mut ViewContext<Toolbar>,
+) -> Element<Toolbar> {
+    MouseEventHandler::<A, _>::new(0, cx, |state, _| {
         let style = if enabled {
             style.style_for(state, false)
         } else {
@@ -197,10 +197,10 @@ fn nav_button<A: Action + Clone>(
     } else {
         CursorStyle::default()
     })
-    .on_click(MouseButton::Left, move |_, cx| {
+    .on_click(MouseButton::Left, move |_, _, cx| {
         cx.dispatch_action(action.clone())
     })
-    .with_tooltip::<A, _>(
+    .with_tooltip::<A>(
         0,
         action_name.to_string(),
         Some(Box::new(tooltip_action)),
@@ -266,7 +266,7 @@ impl Toolbar {
         }
     }
 
-    pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext) {
+    pub fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut ViewContext<Self>) {
         for (toolbar_item, _) in self.items.iter_mut() {
             toolbar_item.pane_focus_update(pane_focused, cx);
         }
@@ -295,14 +295,14 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
     fn set_active_pane_item(
         &self,
         active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> ToolbarItemLocation {
         self.update(cx, |this, cx| {
             this.set_active_pane_item(active_pane_item, cx)
         })
     }
 
-    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut AppContext) {
+    fn pane_focus_update(&mut self, pane_focused: bool, cx: &mut WindowContext) {
         self.update(cx, |this, cx| {
             this.pane_focus_update(pane_focused, cx);
             cx.notify();

crates/workspace/src/workspace.rs 🔗

@@ -45,8 +45,7 @@ use gpui::{
         WindowOptions,
     },
     Action, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext,
-    ModelHandle, RenderContext, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 use language::LanguageRegistry;
@@ -97,6 +96,10 @@ lazy_static! {
         .and_then(parse_pixel_position_env_var);
 }
 
+pub trait Modal: View {
+    fn dismiss_on_event(event: &Self::Event) -> bool;
+}
+
 #[derive(Clone, PartialEq)]
 pub struct RemoveWorktreeFromProject(pub WorktreeId);
 
@@ -321,7 +324,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
                     None
                 };
                 cx.update(|cx| open_paths(&action.paths, &app_state, window_id_to_replace, cx))
-                    .await;
+                    .await?;
                 Ok(())
             }))
         }
@@ -411,20 +414,18 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
                 .await
                 .context("Failed to create CLI symlink");
 
-            cx.update(|cx| {
-                workspace.update(cx, |workspace, cx| {
-                    if matches!(err, Err(_)) {
-                        err.notify_err(workspace, cx);
-                    } else {
-                        workspace.show_notification(1, cx, |cx| {
-                            cx.add_view(|_| {
-                                MessageNotification::new_message(
-                                    "Successfully installed the `zed` binary",
-                                )
-                            })
-                        });
-                    }
-                })
+            workspace.update(&mut cx, |workspace, cx| {
+                if matches!(err, Err(_)) {
+                    err.notify_err(workspace, cx);
+                } else {
+                    workspace.show_notification(1, cx, |cx| {
+                        cx.add_view(|_| {
+                            MessageNotification::new_message(
+                                "Successfully installed the `zed` binary",
+                            )
+                        })
+                    });
+                }
             })
         })
         .detach();
@@ -575,27 +576,19 @@ impl DelayedDebouncedEditAction {
         }
     }
 
-    fn fire_new<F, Fut>(
-        &mut self,
-        delay: Duration,
-        workspace: &Workspace,
-        cx: &mut ViewContext<Workspace>,
-        f: F,
-    ) where
-        F: FnOnce(ModelHandle<Project>, AsyncAppContext) -> Fut + 'static,
-        Fut: 'static + Future<Output = ()>,
+    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, f: F)
+    where
+        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
     {
         if let Some(channel) = self.cancel_channel.take() {
             _ = channel.send(());
         }
 
-        let project = workspace.project().downgrade();
-
         let (sender, mut receiver) = oneshot::channel::<()>();
         self.cancel_channel = Some(sender);
 
         let previous_task = self.task.take();
-        self.task = Some(cx.spawn_weak(|_, cx| async move {
+        self.task = Some(cx.spawn_weak(|workspace, mut cx| async move {
             let mut timer = cx.background().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
@@ -606,8 +599,13 @@ impl DelayedDebouncedEditAction {
                     _ = timer => {}
             }
 
-            if let Some(project) = project.upgrade(&cx) {
-                (f)(project, cx).await;
+            if let Some(workspace) = workspace.upgrade(&cx) {
+                if let Some(result) = workspace
+                    .update(&mut cx, |workspace, cx| (f)(workspace, cx))
+                    .log_err()
+                {
+                    result.await.log_err();
+                }
             }
         }));
     }
@@ -648,7 +646,7 @@ pub struct Workspace {
     background_actions: BackgroundActions,
     _window_subscriptions: [Subscription; 3],
     _apply_leader_updates: Task<Result<()>>,
-    _observe_current_user: Task<()>,
+    _observe_current_user: Task<Result<()>>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
@@ -702,8 +700,7 @@ impl Workspace {
                 }
 
                 project::Event::Closed => {
-                    let window_id = cx.window_id();
-                    cx.remove_window(window_id);
+                    cx.remove_window();
                 }
 
                 _ => {}
@@ -743,12 +740,11 @@ impl Workspace {
                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 
             while stream.recv().await.is_some() {
-                cx.update(|cx| {
-                    if let Some(this) = this.upgrade(cx) {
-                        this.update(cx, |_, cx| cx.notify());
-                    }
-                })
+                if let Some(this) = this.upgrade(&cx) {
+                    this.update(&mut cx, |_, cx| cx.notify())?;
+                }
             }
+            anyhow::Ok(())
         });
         let handle = cx.handle();
 
@@ -885,7 +881,8 @@ impl Workspace {
         );
 
         cx.spawn(|mut cx| async move {
-            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+            let mut serialized_workspace =
+                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 
             let paths_to_open = serialized_workspace
                 .as_ref()
@@ -932,7 +929,7 @@ impl Workspace {
                     let mut workspace = Workspace::new(
                         serialized_workspace,
                         workspace_id,
-                        project_handle,
+                        project_handle.clone(),
                         app_state.dock_default_item_factory,
                         app_state.background_actions,
                         cx,
@@ -941,50 +938,54 @@ impl Workspace {
                     workspace
                 };
 
-            let workspace = if let Some(window_id) = requesting_window_id {
-                cx.update(|cx| {
-                    cx.replace_root_view(window_id, |cx| build_workspace(cx, serialized_workspace))
+            let workspace = requesting_window_id
+                .and_then(|window_id| {
+                    cx.update(|cx| {
+                        cx.replace_root_view(window_id, |cx| {
+                            build_workspace(cx, serialized_workspace.take())
+                        })
+                    })
                 })
-            } else {
-                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;
+                .unwrap_or_else(|| {
+                    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()
-                };
+                                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| build_workspace(cx, serialized_workspace),
-                )
-                .1
-            };
+                    // Use the serialized workspace to construct the new window
+                    cx.add_window(
+                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
+                        |cx| build_workspace(cx, serialized_workspace),
+                    )
+                    .1
+                });
 
             notify_if_database_failed(&workspace, &mut cx);
 
@@ -1007,6 +1008,7 @@ impl Workspace {
                                         .update(&mut cx, |workspace, cx| {
                                             workspace.open_path(project_path, None, true, cx)
                                         })
+                                        .log_err()?
                                         .await,
                                 )
                             } else {
@@ -1068,13 +1070,13 @@ impl Workspace {
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Self>,
         callback: F,
-    ) -> Task<T>
+    ) -> Task<Result<T>>
     where
         T: 'static,
         F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
     {
         if self.project.read(cx).is_local() {
-            Task::Ready(Some(callback(self, cx)))
+            Task::Ready(Some(Ok(callback(self, cx))))
         } else {
             let task = Self::new_local(Vec::new(), app_state.clone(), None, cx);
             cx.spawn(|_vh, mut cx| async move {
@@ -1125,13 +1127,11 @@ impl Workspace {
         _: &CloseWindow,
         cx: &mut ViewContext<Self>,
     ) -> Option<Task<Result<()>>> {
+        let window_id = cx.window_id();
         let prepare = self.prepare_to_close(false, cx);
-        Some(cx.spawn(|this, mut cx| async move {
+        Some(cx.spawn_weak(|_, mut cx| async move {
             if prepare.await? {
-                this.update(&mut cx, |_, cx| {
-                    let window_id = cx.window_id();
-                    cx.remove_window(window_id);
-                });
+                cx.remove_window(window_id);
             }
             Ok(())
         }))
@@ -1146,7 +1146,14 @@ impl Workspace {
         let window_id = cx.window_id();
         let workspace_count = cx
             .window_ids()
-            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+            .collect::<Vec<_>>()
+            .into_iter()
+            .filter_map(|window_id| {
+                cx.app_context()
+                    .root_view(window_id)?
+                    .clone()
+                    .downcast::<Workspace>()
+            })
             .count();
 
         cx.spawn(|this, mut cx| async move {
@@ -1155,29 +1162,28 @@ impl Workspace {
                     && workspace_count == 1
                     && active_call.read_with(&cx, |call, _| call.room().is_some())
                 {
-                    let answer = cx
-                        .prompt(
-                            window_id,
-                            PromptLevel::Warning,
-                            "Do you want to leave the current call?",
-                            &["Close window and hang up", "Cancel"],
-                        )
-                        .next()
-                        .await;
+                    let answer = cx.prompt(
+                        window_id,
+                        PromptLevel::Warning,
+                        "Do you want to leave the current call?",
+                        &["Close window and hang up", "Cancel"],
+                    );
 
-                    if answer == Some(1) {
-                        return anyhow::Ok(false);
-                    } else {
-                        active_call
-                            .update(&mut cx, |call, cx| call.hang_up(cx))
-                            .await
-                            .log_err();
+                    if let Some(mut answer) = answer {
+                        if answer.next().await == Some(1) {
+                            return anyhow::Ok(false);
+                        } else {
+                            active_call
+                                .update(&mut cx, |call, cx| call.hang_up(cx))
+                                .await
+                                .log_err();
+                        }
                     }
                 }
             }
 
             Ok(this
-                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))
+                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
                 .await?)
         })
     }
@@ -1255,13 +1261,16 @@ impl Workspace {
         cx.spawn(|this, mut cx| async move {
             let mut project_paths = Vec::new();
             for path in &abs_paths {
-                project_paths.push(
-                    this.update(&mut cx, |this, cx| {
+                if let Some(project_path) = this
+                    .update(&mut cx, |this, cx| {
                         Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
                     })
-                    .await
-                    .log_err(),
-                );
+                    .log_err()
+                {
+                    project_paths.push(project_path.await.log_err());
+                } else {
+                    project_paths.push(None);
+                }
             }
 
             let tasks = abs_paths
@@ -1279,6 +1288,7 @@ impl Workspace {
                                     this.update(&mut cx, |this, cx| {
                                         this.open_path(project_path, None, true, cx)
                                     })
+                                    .log_err()?
                                     .await,
                                 )
                             } else {
@@ -1302,14 +1312,15 @@ impl Workspace {
         cx.spawn(|this, mut cx| async move {
             if let Some(paths) = paths.recv().await.flatten() {
                 let results = this
-                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))
+                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
                     .await;
                 for result in results.into_iter().flatten() {
                     result.log_err();
                 }
             }
+            anyhow::Ok(())
         })
-        .detach();
+        .detach_and_log_err(cx);
     }
 
     fn remove_folder_from_project(
@@ -1350,7 +1361,7 @@ impl Workspace {
         add_view: F,
     ) -> Option<ViewHandle<V>>
     where
-        V: 'static + View,
+        V: 'static + Modal,
         F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
     {
         cx.notify();
@@ -1362,6 +1373,12 @@ impl Workspace {
             Some(already_open_modal)
         } else {
             let modal = add_view(self, cx);
+            cx.subscribe(&modal, |this, _, event, cx| {
+                if V::dismiss_on_event(event) {
+                    this.dismiss_modal(cx);
+                }
+            })
+            .detach();
             cx.focus(&modal);
             self.modal = Some(modal.into_any());
             None
@@ -1425,15 +1442,16 @@ impl Workspace {
                         CONFLICT_MESSAGE,
                         &["Overwrite", "Cancel"],
                     );
-                    cx.spawn(|_, mut cx| async move {
+                    cx.spawn(|this, mut cx| async move {
                         let answer = answer.recv().await;
                         if answer == Some(0) {
-                            cx.update(|cx| item.save(project, cx)).await?;
+                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
+                                .await?;
                         }
                         Ok(())
                     })
                 } else {
-                    item.save(project, cx)
+                    item.save(self.project.clone(), cx)
                 }
             } else if item.is_singleton(cx) {
                 let worktree = self.worktrees(cx).next();
@@ -1442,9 +1460,10 @@ impl Workspace {
                     .map_or(Path::new(""), |w| w.abs_path())
                     .to_path_buf();
                 let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
-                cx.spawn(|_, mut cx| async move {
+                cx.spawn(|this, mut cx| async move {
                     if let Some(abs_path) = abs_path.recv().await.flatten() {
-                        cx.update(|cx| item.save_as(project, abs_path, cx)).await?;
+                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
+                            .await?;
                     }
                     Ok(())
                 })
@@ -1608,14 +1627,7 @@ impl Workspace {
                 .upgrade(&cx)
                 .ok_or_else(|| anyhow!("pane was closed"))?;
             this.update(&mut cx, |this, cx| {
-                Ok(Pane::open_item(
-                    this,
-                    pane,
-                    project_entry_id,
-                    focus_item,
-                    cx,
-                    build_item,
-                ))
+                Pane::open_item(this, pane, project_entry_id, focus_item, cx, build_item)
             })
         })
     }
@@ -1632,7 +1644,7 @@ impl Workspace {
     > {
         let project = self.project().clone();
         let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
-        cx.as_mut().spawn(|mut cx| async move {
+        cx.spawn(|_, mut cx| async move {
             let (project_entry_id, project_item) = project_item.await?;
             let build_item = cx.update(|cx| {
                 cx.default_global::<ProjectItemBuilders>()
@@ -1813,15 +1825,14 @@ impl Workspace {
         }
 
         let item = pane.read(cx).active_item()?;
-        let maybe_pane_handle =
-            if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
-                let new_pane = self.add_pane(cx);
-                Pane::add_item(self, &new_pane, clone, true, true, None, cx);
-                self.center.split(&pane, &new_pane, direction).unwrap();
-                Some(new_pane)
-            } else {
-                None
-            };
+        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
+            let new_pane = self.add_pane(cx);
+            Pane::add_item(self, &new_pane, clone, true, true, None, cx);
+            self.center.split(&pane, &new_pane, direction).unwrap();
+            Some(new_pane)
+        } else {
+            None
+        };
         cx.notify();
         maybe_pane_handle
     }
@@ -1979,7 +1990,7 @@ impl Workspace {
                         None
                     };
                     Ok::<_, anyhow::Error>(())
-                })?;
+                })??;
                 Self::add_views_from_leader(
                     this.clone(),
                     leader_id,
@@ -1988,7 +1999,7 @@ impl Workspace {
                     &mut cx,
                 )
                 .await?;
-                this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx));
+                this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
             }
             Ok(())
         }))
@@ -2064,10 +2075,10 @@ impl Workspace {
         self.leader_state.followers.contains(&peer_id)
     }
 
-    fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> Element<Self> {
         // TODO: There should be a better system in place for this
         // (https://github.com/zed-industries/zed/issues/1290)
-        let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
+        let is_fullscreen = cx.window_is_fullscreen();
         let container_theme = if is_fullscreen {
             let mut container_theme = theme.workspace.titlebar.container;
             container_theme.padding.left = container_theme.padding.right;
@@ -2078,7 +2089,7 @@ impl Workspace {
 
         enum TitleBar {}
         ConstrainedBox::new(
-            MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
+            MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
                 Container::new(
                     Stack::new()
                         .with_children(
@@ -2091,9 +2102,9 @@ impl Workspace {
                 .with_style(container_theme)
                 .boxed()
             })
-            .on_click(MouseButton::Left, |event, cx| {
+            .on_click(MouseButton::Left, |event, _, cx| {
                 if event.click_count == 2 {
-                    cx.zoom_window(cx.window_id());
+                    cx.zoom_window();
                 }
             })
             .boxed(),
@@ -2164,11 +2175,14 @@ impl Workspace {
         }
     }
 
-    fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
+    fn render_disconnected_overlay(
+        &self,
+        cx: &mut ViewContext<Workspace>,
+    ) -> Option<Element<Workspace>> {
         if self.project.read(cx).is_read_only() {
             enum DisconnectedOverlay {}
             Some(
-                MouseEventHandler::<DisconnectedOverlay>::new(0, cx, |_, cx| {
+                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
                     let theme = &cx.global::<Settings>().theme;
                     Label::new(
                         "Your connection to the remote project has been lost.",
@@ -2192,7 +2206,7 @@ impl Workspace {
         &self,
         theme: &theme::Workspace,
         cx: &AppContext,
-    ) -> Option<ElementBox> {
+    ) -> Option<Element<Workspace>> {
         if self.notifications.is_empty() {
             None
         } else {
@@ -2263,7 +2277,7 @@ impl Workspace {
                     })
                     .collect(),
             })
-        })
+        })?
     }
 
     async fn handle_unfollow(
@@ -2278,7 +2292,7 @@ impl Workspace {
                 .remove(&envelope.original_sender_id()?);
             cx.notify();
             Ok(())
-        })
+        })?
     }
 
     async fn handle_update_followers(
@@ -2315,7 +2329,7 @@ impl Workspace {
                         }
                     }
                     anyhow::Ok(())
-                })?;
+                })??;
             }
             proto::update_followers::Variant::UpdateView(update_view) => {
                 let variant = update_view
@@ -2336,7 +2350,7 @@ impl Workspace {
                         }
                     }
                     anyhow::Ok(())
-                })?;
+                })??;
                 try_join_all(tasks).await.log_err();
             }
             proto::update_followers::Variant::CreateView(view) => {
@@ -2351,7 +2365,7 @@ impl Workspace {
                 Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
             }
         }
-        this.update(cx, |this, cx| this.leader_updated(leader_id, cx));
+        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
         Ok(())
     }
 
@@ -2421,7 +2435,7 @@ impl Workspace {
                 }
 
                 Some(())
-            });
+            })?;
         }
         Ok(())
     }
@@ -2703,7 +2717,7 @@ impl Workspace {
                         &workspace,
                         &mut cx,
                     )
-                    .await;
+                    .await?;
 
                 // Traverse the splits tree and add to things
                 let center_group = serialized_workspace
@@ -2755,13 +2769,14 @@ impl Workspace {
                     });
 
                     cx.notify();
-                });
+                })?;
 
                 // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
                 workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))
             }
+            anyhow::Ok(())
         })
-        .detach();
+        .detach_and_log_err(cx);
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -2771,8 +2786,8 @@ impl Workspace {
 }
 
 fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
-    if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
-        workspace.update(cx, |workspace, cx| {
+    workspace.update(cx, |workspace, cx| {
+        if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
             workspace.show_notification_once(0, cx, |cx| {
                 cx.add_view(|_| {
                     MessageNotification::new(
@@ -2784,11 +2799,9 @@ fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAp
                     )
                 })
             });
-        });
-    } else {
-        let backup_path = (*db::BACKUP_DB_PATH).read();
-        if let Some(backup_path) = &*backup_path {
-            workspace.update(cx, |workspace, cx| {
+        } else {
+            let backup_path = (*db::BACKUP_DB_PATH).read();
+            if let Some(backup_path) = &*backup_path {
                 workspace.show_notification_once(0, cx, |cx| {
                     cx.add_view(|_| {
                         let backup_path = backup_path.to_string_lossy();
@@ -2806,9 +2819,9 @@ fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAp
                         )
                     })
                 });
-            });
+            }
         }
-    }
+    }).log_err();
 }
 
 impl Entity for Workspace {
@@ -2820,7 +2833,7 @@ impl View for Workspace {
         "Workspace"
     }
 
-    fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Element<Self> {
         let theme = cx.global::<Settings>().theme.clone();
         Stack::new()
             .with_child(
@@ -2836,11 +2849,11 @@ impl View for Workspace {
                                             Some(
                                                 ChildView::new(&self.left_sidebar, cx)
                                                     .constrained()
-                                                    .dynamically(|constraint, cx| {
+                                                    .dynamically(|constraint, _, cx| {
                                                         SizeConstraint::new(
                                                             Vector2F::new(20., constraint.min.y()),
                                                             Vector2F::new(
-                                                                cx.window_size.x() * 0.8,
+                                                                cx.window_size().x() * 0.8,
                                                                 constraint.max.y(),
                                                             ),
                                                         )
@@ -2882,11 +2895,11 @@ impl View for Workspace {
                                             Some(
                                                 ChildView::new(&self.right_sidebar, cx)
                                                     .constrained()
-                                                    .dynamically(|constraint, cx| {
+                                                    .dynamically(|constraint, _, cx| {
                                                         SizeConstraint::new(
                                                             Vector2F::new(20., constraint.min.y()),
                                                             Vector2F::new(
-                                                                cx.window_size.x() * 0.8,
+                                                                cx.window_size().x() * 0.8,
                                                                 constraint.max.y(),
                                                             ),
                                                         )
@@ -3006,12 +3019,21 @@ pub fn activate_workspace_for_project(
     predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
 ) -> Option<ViewHandle<Workspace>> {
     for window_id in cx.window_ids().collect::<Vec<_>>() {
-        if let Some(workspace_handle) = cx.root_view(window_id)?.downcast_ref::<Workspace>() {
-            let project = workspace_handle.read(cx).project.clone();
-            if project.update(cx, &predicate) {
-                cx.activate_window(window_id);
-                return Some(workspace_handle.clone());
-            }
+        let handle = cx
+            .update_window(window_id, |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) {
+                        cx.activate_window();
+                        return Some(workspace_handle.clone());
+                    }
+                }
+                None
+            })
+            .flatten();
+
+        if handle.is_some() {
+            return handle;
         }
     }
     None
@@ -3027,10 +3049,12 @@ pub fn open_paths(
     app_state: &Arc<AppState>,
     requesting_window_id: Option<usize>,
     cx: &mut AppContext,
-) -> Task<(
-    ViewHandle<Workspace>,
-    Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-)> {
+) -> Task<
+    Result<(
+        ViewHandle<Workspace>,
+        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+    )>,
+> {
     log::info!("open paths {:?}", abs_paths);
 
     // Open paths in existing workspace if possible
@@ -3041,14 +3065,14 @@ pub fn open_paths(
     let abs_paths = abs_paths.to_vec();
     cx.spawn(|mut cx| async move {
         if let Some(existing) = existing {
-            (
+            Ok((
                 existing.clone(),
                 existing
                     .update(&mut cx, |workspace, cx| {
                         workspace.open_paths(abs_paths, true, cx)
-                    })
+                    })?
                     .await,
-            )
+            ))
         } else {
             let contains_directory =
                 futures::future::join_all(abs_paths.iter().map(|path| app_state.fs.is_file(path)))
@@ -3066,9 +3090,9 @@ pub fn open_paths(
                         if contains_directory {
                             workspace.toggle_sidebar(SidebarSide::Left, cx);
                         }
-                    });
+                    })?;
 
-                    (workspace, items)
+                    anyhow::Ok((workspace, items))
                 })
             })
             .await
@@ -3085,11 +3109,13 @@ pub fn open_new(
     cx.spawn(|mut cx| async move {
         let (workspace, opened_paths) = task.await;
 
-        workspace.update(&mut cx, |workspace, cx| {
-            if opened_paths.is_empty() {
-                init(workspace, cx)
-            }
-        })
+        workspace
+            .update(&mut cx, |workspace, cx| {
+                if opened_paths.is_empty() {
+                    init(workspace, cx)
+                }
+            })
+            .log_err();
     })
 }
 
@@ -3713,7 +3739,8 @@ mod tests {
             .update(cx, |workspace, cx| {
                 Pane::go_back(workspace, Some(pane.clone()), cx)
             })
-            .await;
+            .await
+            .unwrap();
 
         assert_eq!(*toolbar_notify_count.borrow(), 3);
         pane.read_with(cx, |pane, _| {

crates/zed/src/main.rs 🔗

@@ -545,7 +545,7 @@ async fn watch_themes(
             .await
             .log_err()?;
         if output.status.success() {
-            cx.update(|cx| theme_selector::ThemeSelector::reload(themes.clone(), cx))
+            cx.update(|cx| theme_selector::reload(themes.clone(), cx))
         } else {
             eprintln!(
                 "build script failed {}",
@@ -631,71 +631,87 @@ async fn handle_cli_connection(
                 } else {
                     paths
                 };
-                let (workspace, items) = cx
-                    .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
-                    .await;
 
                 let mut errored = false;
-                let mut item_release_futures = Vec::new();
-                cx.update(|cx| {
-                    for (item, path) in items.into_iter().zip(&paths) {
-                        match item {
-                            Some(Ok(item)) => {
-                                let released = oneshot::channel();
-                                item.on_release(
-                                    cx,
-                                    Box::new(move |_| {
-                                        let _ = released.0.send(());
-                                    }),
-                                )
-                                .detach();
-                                item_release_futures.push(released.1);
+                match cx
+                    .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+                    .await
+                {
+                    Ok((workspace, items)) => {
+                        let mut item_release_futures = Vec::new();
+                        cx.update(|cx| {
+                            for (item, path) in items.into_iter().zip(&paths) {
+                                match item {
+                                    Some(Ok(item)) => {
+                                        let released = oneshot::channel();
+                                        item.on_release(
+                                            cx,
+                                            Box::new(move |_| {
+                                                let _ = released.0.send(());
+                                            }),
+                                        )
+                                        .detach();
+                                        item_release_futures.push(released.1);
+                                    }
+                                    Some(Err(err)) => {
+                                        responses
+                                            .send(CliResponse::Stderr {
+                                                message: format!(
+                                                    "error opening {:?}: {}",
+                                                    path, err
+                                                ),
+                                            })
+                                            .log_err();
+                                        errored = true;
+                                    }
+                                    None => {}
+                                }
                             }
-                            Some(Err(err)) => {
-                                responses
-                                    .send(CliResponse::Stderr {
-                                        message: format!("error opening {:?}: {}", path, err),
-                                    })
-                                    .log_err();
-                                errored = true;
+                        });
+
+                        if wait {
+                            let background = cx.background();
+                            let wait = async move {
+                                if paths.is_empty() {
+                                    let (done_tx, done_rx) = oneshot::channel();
+                                    let _subscription = cx.update(|cx| {
+                                        cx.observe_release(&workspace, move |_, _| {
+                                            let _ = done_tx.send(());
+                                        })
+                                    });
+                                    drop(workspace);
+                                    let _ = done_rx.await;
+                                } else {
+                                    let _ =
+                                        futures::future::try_join_all(item_release_futures).await;
+                                };
                             }
-                            None => {}
-                        }
-                    }
-                });
-
-                if wait {
-                    let background = cx.background();
-                    let wait = async move {
-                        if paths.is_empty() {
-                            let (done_tx, done_rx) = oneshot::channel();
-                            let _subscription = cx.update(|cx| {
-                                cx.observe_release(&workspace, move |_, _| {
-                                    let _ = done_tx.send(());
-                                })
-                            });
-                            drop(workspace);
-                            let _ = done_rx.await;
-                        } else {
-                            let _ = futures::future::try_join_all(item_release_futures).await;
-                        };
-                    }
-                    .fuse();
-                    futures::pin_mut!(wait);
-
-                    loop {
-                        // Repeatedly check if CLI is still open to avoid wasting resources
-                        // waiting for files or workspaces to close.
-                        let mut timer = background.timer(Duration::from_secs(1)).fuse();
-                        futures::select_biased! {
-                            _ = wait => break,
-                            _ = timer => {
-                                if responses.send(CliResponse::Ping).is_err() {
-                                    break;
+                            .fuse();
+                            futures::pin_mut!(wait);
+
+                            loop {
+                                // Repeatedly check if CLI is still open to avoid wasting resources
+                                // waiting for files or workspaces to close.
+                                let mut timer = background.timer(Duration::from_secs(1)).fuse();
+                                futures::select_biased! {
+                                    _ = wait => break,
+                                    _ = timer => {
+                                        if responses.send(CliResponse::Ping).is_err() {
+                                            break;
+                                        }
+                                    }
                                 }
                             }
                         }
                     }
+                    Err(error) => {
+                        errored = true;
+                        responses
+                            .send(CliResponse::Stderr {
+                                message: format!("error opening {:?}: {}", paths, error),
+                            })
+                            .log_err();
+                    }
                 }
 
                 responses

crates/zed/src/zed.rs 🔗

@@ -105,7 +105,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
                 .titlebar_item()
                 .and_then(|item| item.downcast::<CollabTitlebarItem>())
             {
-                cx.as_mut().defer(move |cx| {
+                cx.defer(move |_, cx| {
                     item.update(cx, |item, cx| {
                         item.toggle_contacts_popover(&Default::default(), cx);
                     });
@@ -247,10 +247,10 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
                                 cx,
                             );
                         })
-                    })
-                    .await;
+                    })?
+                    .await
             })
-            .detach();
+            .detach_and_log_err(cx);
         }
     });
     cx.add_action(
@@ -333,8 +333,7 @@ pub fn initialize_workspace(
 
     auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
 
-    let window_id = cx.window_id();
-    vim::observe_keystrokes(window_id, cx);
+    vim::observe_keystrokes(cx);
 
     cx.on_window_should_close(|workspace, cx| {
         if let Some(task) = workspace.close(&Default::default(), cx) {
@@ -380,17 +379,18 @@ fn restart(_: &Restart, cx: &mut gpui::AppContext) {
     let should_confirm = cx.global::<Settings>().confirm_quit;
     cx.spawn(|mut cx| async move {
         if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
-            let answer = cx
-                .prompt(
-                    workspace.window_id(),
-                    PromptLevel::Info,
-                    "Are you sure you want to restart?",
-                    &["Restart", "Cancel"],
-                )
-                .next()
-                .await;
-            if answer != Some(0) {
-                return Ok(());
+            let answer = cx.prompt(
+                workspace.window_id(),
+                PromptLevel::Info,
+                "Are you sure you want to restart?",
+                &["Restart", "Cancel"],
+            );
+
+            if let Some(mut answer) = answer {
+                let answer = answer.next().await;
+                if answer != Some(0) {
+                    return Ok(());
+                }
             }
         }
 
@@ -399,7 +399,7 @@ fn restart(_: &Restart, cx: &mut gpui::AppContext) {
             if !workspace
                 .update(&mut cx, |workspace, cx| {
                     workspace.prepare_to_close(true, cx)
-                })
+                })?
                 .await?
             {
                 return Ok(());
@@ -424,17 +424,18 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
     let should_confirm = cx.global::<Settings>().confirm_quit;
     cx.spawn(|mut cx| async move {
         if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
-            let answer = cx
-                .prompt(
-                    workspace.window_id(),
-                    PromptLevel::Info,
-                    "Are you sure you want to quit?",
-                    &["Quit", "Cancel"],
-                )
-                .next()
-                .await;
-            if answer != Some(0) {
-                return Ok(());
+            let answer = cx.prompt(
+                workspace.window_id(),
+                PromptLevel::Info,
+                "Are you sure you want to quit?",
+                &["Quit", "Cancel"],
+            );
+
+            if let Some(mut answer) = answer {
+                let answer = answer.next().await;
+                if answer != Some(0) {
+                    return Ok(());
+                }
             }
         }
 
@@ -443,7 +444,7 @@ fn quit(_: &Quit, cx: &mut gpui::AppContext) {
             if !workspace
                 .update(&mut cx, |workspace, cx| {
                     workspace.prepare_to_close(true, cx)
-                })
+                })?
                 .await?
             {
                 return Ok(());
@@ -480,8 +481,8 @@ fn open_config_file(
                 workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
                     workspace.open_paths(vec![path.to_path_buf()], false, cx)
                 })
-            })
-            .await
+            })?
+            .await?
             .await;
         Ok::<_, anyhow::Error>(())
     })
@@ -520,25 +521,25 @@ fn open_log_file(
                         .flat_map(|line| [line, "\n"])
                         .collect::<String>();
 
-                    workspace.update(&mut cx, |workspace, cx| {
-                        let project = workspace.project().clone();
-                        let buffer = project
-                            .update(cx, |project, cx| project.create_buffer("", None, cx))
-                            .expect("creating buffers on a local workspace always succeeds");
-                        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
-
-                        let buffer = cx.add_model(|cx| {
-                            MultiBuffer::singleton(buffer, cx).with_title("Log".into())
-                        });
-                        workspace.add_item(
-                            Box::new(
-                                cx.add_view(|cx| {
+                    workspace
+                        .update(&mut cx, |workspace, cx| {
+                            let project = workspace.project().clone();
+                            let buffer = project
+                                .update(cx, |project, cx| project.create_buffer("", None, cx))
+                                .expect("creating buffers on a local workspace always succeeds");
+                            buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
+
+                            let buffer = cx.add_model(|cx| {
+                                MultiBuffer::singleton(buffer, cx).with_title("Log".into())
+                            });
+                            workspace.add_item(
+                                Box::new(cx.add_view(|cx| {
                                     Editor::for_multibuffer(buffer, Some(project), cx)
-                                }),
-                            ),
-                            cx,
-                        );
-                    });
+                                })),
+                                cx,
+                            );
+                        })
+                        .log_err();
                 }
             })
             .detach();
@@ -600,7 +601,7 @@ fn open_telemetry_log_file(
                     Box::new(cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
                     cx,
                 );
-            });
+            }).log_err()?;
 
             Some(())
         })
@@ -641,10 +642,10 @@ fn open_bundled_file(
                         cx,
                     );
                 })
-            })
-            .await;
+            })?
+            .await
     })
-    .detach();
+    .detach_and_log_err(cx);
 }
 
 #[cfg(test)]
@@ -705,14 +706,16 @@ mod tests {
                 cx,
             )
         })
-        .await;
+        .await
+        .unwrap();
         assert_eq!(cx.window_ids().len(), 1);
 
         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(cx.window_ids().len(), 1);
         let workspace_1 = cx
-            .root_view(cx.window_ids()[0])
+            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
             .unwrap()
             .downcast::<Workspace>()
             .unwrap();
@@ -730,7 +733,8 @@ mod tests {
                 cx,
             )
         })
-        .await;
+        .await
+        .unwrap();
         assert_eq!(cx.window_ids().len(), 2);
 
         // Replace existing windows
@@ -743,15 +747,16 @@ mod tests {
                 cx,
             )
         })
-        .await;
+        .await
+        .unwrap();
         assert_eq!(cx.window_ids().len(), 2);
         let workspace_1 = cx
-            .root_view(window_id)
+            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
             .unwrap()
             .clone()
             .downcast::<Workspace>()
             .unwrap();
-        workspace_1.read_with(cx, |workspace, cx| {
+        workspace_1.update(cx, |workspace, cx| {
             assert_eq!(
                 workspace
                     .worktrees(cx)
@@ -774,14 +779,14 @@ mod tests {
             .await;
 
         cx.update(|cx| open_paths(&[PathBuf::from("/root/a")], &app_state, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(cx.window_ids().len(), 1);
 
         // When opening the workspace, the window is not in a edited state.
         let workspace = cx
-            .root_view(cx.window_ids()[0])
+            .read_window(cx.window_ids()[0], |cx| cx.root_view().clone())
             .unwrap()
-            .clone()
             .downcast::<Workspace>()
             .unwrap();
         let editor = workspace.read_with(cx, |workspace, cx| {
@@ -817,7 +822,8 @@ mod tests {
 
         // 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))
-            .await;
+            .await
+            .unwrap();
         let editor = workspace.read_with(cx, |workspace, cx| {
             workspace
                 .active_item(cx)
@@ -851,11 +857,11 @@ mod tests {
 
         let window_id = *cx.window_ids().first().unwrap();
         let workspace = cx
-            .root_view(window_id)
+            .read_window(window_id, |cx| cx.root_view().clone())
             .unwrap()
-            .clone()
             .downcast::<Workspace>()
             .unwrap();
+
         let editor = workspace.update(cx, |workspace, cx| {
             workspace
                 .active_item(cx)
@@ -1019,12 +1025,11 @@ mod tests {
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 
         // Open a file within an existing worktree.
-        cx.update(|cx| {
-            workspace.update(cx, |view, cx| {
+        workspace
+            .update(cx, |view, cx| {
                 view.open_paths(vec!["/dir1/a.txt".into()], true, cx)
             })
-        })
-        .await;
+            .await;
         cx.read(|cx| {
             assert_eq!(
                 workspace
@@ -1043,12 +1048,11 @@ mod tests {
         });
 
         // Open a file outside of any existing worktree.
-        cx.update(|cx| {
-            workspace.update(cx, |view, cx| {
+        workspace
+            .update(cx, |view, cx| {
                 view.open_paths(vec!["/dir2/b.txt".into()], true, cx)
             })
-        })
-        .await;
+            .await;
         cx.read(|cx| {
             let worktree_roots = workspace
                 .read(cx)
@@ -1079,12 +1083,11 @@ mod tests {
         });
 
         // Ensure opening a directory and one of its children only adds one worktree.
-        cx.update(|cx| {
-            workspace.update(cx, |view, cx| {
+        workspace
+            .update(cx, |view, cx| {
                 view.open_paths(vec!["/dir3".into(), "/dir3/c.txt".into()], true, cx)
             })
-        })
-        .await;
+            .await;
         cx.read(|cx| {
             let worktree_roots = workspace
                 .read(cx)
@@ -1115,12 +1118,11 @@ mod tests {
         });
 
         // Ensure opening invisibly a file outside an existing worktree adds a new, invisible worktree.
-        cx.update(|cx| {
-            workspace.update(cx, |view, cx| {
+        workspace
+            .update(cx, |view, cx| {
                 view.open_paths(vec!["/d.txt".into()], false, cx)
             })
-        })
-        .await;
+            .await;
         cx.read(|cx| {
             let worktree_roots = workspace
                 .read(cx)
@@ -1178,19 +1180,18 @@ mod tests {
         let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
 
         // Open a file within an existing worktree.
-        cx.update(|cx| {
-            workspace.update(cx, |view, cx| {
+        workspace
+            .update(cx, |view, cx| {
                 view.open_paths(vec![PathBuf::from("/root/a.txt")], true, cx)
             })
-        })
-        .await;
+            .await;
         let editor = cx.read(|cx| {
             let pane = workspace.read(cx).active_pane().read(cx);
             let item = pane.active_item().unwrap();
             item.downcast::<Editor>().unwrap()
         });
 
-        cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input("x", cx)));
+        editor.update(cx, |editor, cx| editor.handle_input("x", cx));
         app_state
             .fs
             .as_fake()
@@ -1487,7 +1488,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1495,7 +1497,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file2.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1503,7 +1506,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(10, 0), 0.)
@@ -1511,7 +1515,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1520,7 +1525,8 @@ mod tests {
         // Go back one more time and ensure we don't navigate past the first item in the history.
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1528,7 +1534,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_forward(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(10, 0), 0.)
@@ -1536,7 +1543,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_forward(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file2.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1554,7 +1562,8 @@ mod tests {
             .unwrap();
         workspace
             .update(cx, |w, cx| Pane::go_forward(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1562,7 +1571,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_forward(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(16, 0), 12.5)
@@ -1570,7 +1580,8 @@ mod tests {
 
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1592,14 +1603,16 @@ mod tests {
             .unwrap();
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(10, 0), 0.)
         );
         workspace
             .update(cx, |w, cx| Pane::go_forward(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file3.clone(), DisplayPoint::new(0, 0), 0.)
@@ -1642,14 +1655,16 @@ mod tests {
         });
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(2, 0), 0.)
         );
         workspace
             .update(cx, |w, cx| Pane::go_back(w, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(
             active_location(&workspace, cx),
             (file1.clone(), DisplayPoint::new(3, 0), 0.)
@@ -1763,61 +1778,84 @@ mod tests {
 
         // Reopen all the closed items, ensuring they are reopened in the same order
         // in which they were closed.
-        workspace.update(cx, Pane::reopen_closed_item).await;
+        workspace
+            .update(cx, Pane::reopen_closed_item)
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
 
-        workspace.update(cx, Pane::reopen_closed_item).await;
+        workspace
+            .update(cx, Pane::reopen_closed_item)
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
 
-        workspace.update(cx, Pane::reopen_closed_item).await;
+        workspace
+            .update(cx, Pane::reopen_closed_item)
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
 
-        workspace.update(cx, Pane::reopen_closed_item).await;
+        workspace
+            .update(cx, Pane::reopen_closed_item)
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
 
         // Reopening past the last closed item is a no-op.
-        workspace.update(cx, Pane::reopen_closed_item).await;
+        workspace
+            .update(cx, Pane::reopen_closed_item)
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
 
         // Reopening closed items doesn't interfere with navigation history.
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file4.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file3.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file2.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
 
         workspace
             .update(cx, |workspace, cx| Pane::go_back(workspace, None, cx))
-            .await;
+            .await
+            .unwrap();
         assert_eq!(active_path(&workspace, cx), Some(file1.clone()));
 
         fn active_path(