Remove `ViewContext::dispatch_action`

Antonio Scandurra created

Change summary

crates/auto_update/src/auto_update.rs                  |  40 +-
crates/auto_update/src/update_notification.rs          |   6 
crates/breadcrumbs/src/breadcrumbs.rs                  |  16 
crates/collab_ui/src/collab_titlebar_item.rs           |  64 +---
crates/collab_ui/src/collab_ui.rs                      |   1 
crates/collab_ui/src/collaborator_list_popover.rs      | 161 -----------
crates/collab_ui/src/contact_list.rs                   |  56 ++-
crates/collab_ui/src/contacts_popover.rs               |  10 
crates/collab_ui/src/sharing_status_indicator.rs       |   5 
crates/context_menu/src/context_menu.rs                |   8 
crates/copilot/src/sign_in.rs                          |   6 
crates/diagnostics/src/items.rs                        |  25 +
crates/editor/src/editor.rs                            |  42 ++
crates/editor/src/element.rs                           |  15 
crates/editor/src/hover_popover.rs                     |  13 
crates/feedback/src/deploy_feedback_button.rs          |  19 
crates/feedback/src/feedback.rs                        |  57 +--
crates/feedback/src/feedback_editor.rs                 |  67 ++--
crates/feedback/src/feedback_info_text.rs              |   4 
crates/feedback/src/submit_feedback_button.rs          |  24 +
crates/gpui/src/app.rs                                 |  11 
crates/gpui/src/views/select.rs                        |  20 
crates/language_selector/src/active_buffer_language.rs |  22 
crates/language_selector/src/language_selector.rs      |  14 
crates/outline/src/outline.rs                          |   2 
crates/project_panel/src/project_panel.rs              |  23 +
crates/recent_projects/src/recent_projects.rs          |  39 +-
crates/terminal_view/src/terminal_button.rs            |  12 
crates/theme/src/ui.rs                                 |  19 -
crates/theme_selector/src/theme_selector.rs            |  14 
crates/welcome/src/base_keymap_picker.rs               |   2 
crates/welcome/src/welcome.rs                          |  48 ++
crates/workspace/src/dock.rs                           |   8 
crates/workspace/src/dock/toggle_dock_button.rs        |  22 +
crates/workspace/src/notifications.rs                  |   4 
crates/workspace/src/pane.rs                           |  46 ++
crates/workspace/src/sidebar.rs                        |   6 
crates/workspace/src/toolbar.rs                        |  40 ++
crates/workspace/src/workspace.rs                      |  68 +---
crates/zed/src/main.rs                                 |  22 +
crates/zed/src/zed.rs                                  | 163 ++++++-----
41 files changed, 574 insertions(+), 670 deletions(-)

Detailed changes

crates/auto_update/src/auto_update.rs 🔗

@@ -51,9 +51,8 @@ impl Entity for AutoUpdater {
 
 pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
     if let Some(version) = (*ZED_APP_VERSION).or_else(|| cx.platform().app_version().ok()) {
-        let server_url = server_url;
         let auto_updater = cx.add_model(|cx| {
-            let updater = AutoUpdater::new(version, http_client, server_url.clone());
+            let updater = AutoUpdater::new(version, http_client, server_url);
 
             let mut update_subscription = cx
                 .global::<Settings>()
@@ -74,25 +73,32 @@ pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppCo
             updater
         });
         cx.set_global(Some(auto_updater));
-        cx.add_global_action(|_: &Check, cx| {
-            if let Some(updater) = AutoUpdater::get(cx) {
-                updater.update(cx, |updater, cx| updater.poll(cx));
-            }
-        });
-        cx.add_global_action(move |_: &ViewReleaseNotes, cx| {
-            let latest_release_url = if cx.has_global::<ReleaseChannel>()
-                && *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
-            {
-                format!("{server_url}/releases/preview/latest")
-            } else {
-                format!("{server_url}/releases/latest")
-            };
-            cx.platform().open_url(&latest_release_url);
-        });
+        cx.add_global_action(check);
+        cx.add_global_action(view_release_notes);
         cx.add_action(UpdateNotification::dismiss);
     }
 }
 
+pub fn check(_: &Check, cx: &mut AppContext) {
+    if let Some(updater) = AutoUpdater::get(cx) {
+        updater.update(cx, |updater, cx| updater.poll(cx));
+    }
+}
+
+fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
+    if let Some(auto_updater) = AutoUpdater::get(cx) {
+        let server_url = &auto_updater.read(cx).server_url;
+        let latest_release_url = if cx.has_global::<ReleaseChannel>()
+            && *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
+        {
+            format!("{server_url}/releases/preview/latest")
+        } else {
+            format!("{server_url}/releases/latest")
+        };
+        cx.platform().open_url(&latest_release_url);
+    }
+}
+
 pub fn notify_of_any_new_update(
     workspace: WeakViewHandle<Workspace>,
     cx: &mut AppContext,

crates/auto_update/src/update_notification.rs 🔗

@@ -63,8 +63,8 @@ impl View for UpdateNotification {
                                     .with_height(style.button_width)
                             })
                             .with_padding(Padding::uniform(5.))
-                            .on_click(MouseButton::Left, move |_, _, cx| {
-                                cx.dispatch_action(Cancel)
+                            .on_click(MouseButton::Left, move |_, this, cx| {
+                                this.dismiss(&Default::default(), cx)
                             })
                             .aligned()
                             .constrained()
@@ -84,7 +84,7 @@ impl View for UpdateNotification {
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .on_click(MouseButton::Left, |_, _, cx| {
-            cx.dispatch_action(ViewReleaseNotes)
+            crate::view_release_notes(&Default::default(), cx)
         })
         .into_any_named("update notification")
     }

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -1,13 +1,13 @@
 use gpui::{
     elements::*, platform::MouseButton, AppContext, Entity, Subscription, View, ViewContext,
-    ViewHandle,
+    ViewHandle, WeakViewHandle,
 };
 use itertools::Itertools;
 use search::ProjectSearchView;
 use settings::Settings;
 use workspace::{
     item::{ItemEvent, ItemHandle},
-    ToolbarItemLocation, ToolbarItemView,
+    ToolbarItemLocation, ToolbarItemView, Workspace,
 };
 
 pub enum Event {
@@ -19,15 +19,17 @@ pub struct Breadcrumbs {
     active_item: Option<Box<dyn ItemHandle>>,
     project_search: Option<ViewHandle<ProjectSearchView>>,
     subscription: Option<Subscription>,
+    workspace: WeakViewHandle<Workspace>,
 }
 
 impl Breadcrumbs {
-    pub fn new() -> Self {
+    pub fn new(workspace: &Workspace) -> Self {
         Self {
             pane_focused: false,
             active_item: Default::default(),
             subscription: Default::default(),
             project_search: Default::default(),
+            workspace: workspace.weak_handle(),
         }
     }
 }
@@ -85,8 +87,12 @@ impl View for Breadcrumbs {
             let style = style.style_for(state, false);
             crumbs.with_style(style.container)
         })
-        .on_click(MouseButton::Left, |_, _, cx| {
-            cx.dispatch_action(outline::Toggle);
+        .on_click(MouseButton::Left, |_, this, cx| {
+            if let Some(workspace) = this.workspace.upgrade(cx) {
+                workspace.update(cx, |workspace, cx| {
+                    outline::toggle(workspace, &Default::default(), cx)
+                })
+            }
         })
         .with_tooltip::<Breadcrumbs>(
             0,

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
-    collaborator_list_popover, collaborator_list_popover::CollaboratorListPopover,
     contact_notification::ContactNotification, contacts_popover, face_pile::FacePile,
-    ToggleScreenSharing,
+    toggle_screen_sharing, ToggleScreenSharing,
 };
 use call::{ActiveCall, ParticipantLocation, Room};
 use client::{proto::PeerId, ContactEventKind, SignIn, SignOut, User, UserStore};
@@ -27,7 +26,6 @@ use workspace::{FollowNextCollaborator, Workspace};
 actions!(
     collab,
     [
-        ToggleCollaboratorList,
         ToggleContactsMenu,
         ToggleUserMenu,
         ShareProject,
@@ -36,7 +34,6 @@ actions!(
 );
 
 pub fn init(cx: &mut AppContext) {
-    cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover);
     cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
     cx.add_action(CollabTitlebarItem::share_project);
     cx.add_action(CollabTitlebarItem::unshare_project);
@@ -48,7 +45,6 @@ pub struct CollabTitlebarItem {
     user_store: ModelHandle<UserStore>,
     contacts_popover: Option<ViewHandle<ContactsPopover>>,
     user_menu: ViewHandle<ContextMenu>,
-    collaborator_list_popover: Option<ViewHandle<CollaboratorListPopover>>,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -172,7 +168,6 @@ impl CollabTitlebarItem {
                 menu.set_position_mode(OverlayPositionMode::Local);
                 menu
             }),
-            collaborator_list_popover: None,
             _subscriptions: subscriptions,
         }
     }
@@ -217,36 +212,6 @@ impl CollabTitlebarItem {
         }
     }
 
-    pub fn toggle_collaborator_list_popover(
-        &mut self,
-        _: &ToggleCollaboratorList,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match self.collaborator_list_popover.take() {
-            Some(_) => {}
-            None => {
-                if let Some(workspace) = self.workspace.upgrade(cx) {
-                    let user_store = workspace.read(cx).user_store().clone();
-                    let view = cx.add_view(|cx| CollaboratorListPopover::new(user_store, cx));
-
-                    cx.subscribe(&view, |this, _, event, cx| {
-                        match event {
-                            collaborator_list_popover::Event::Dismissed => {
-                                this.collaborator_list_popover = None;
-                            }
-                        }
-
-                        cx.notify();
-                    })
-                    .detach();
-
-                    self.collaborator_list_popover = Some(view);
-                }
-            }
-        }
-        cx.notify();
-    }
-
     pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext<Self>) {
         if self.contacts_popover.take().is_none() {
             if let Some(workspace) = self.workspace.upgrade(cx) {
@@ -357,8 +322,8 @@ impl CollabTitlebarItem {
                         .with_style(style.container)
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_action(ToggleContactsMenu);
+                .on_click(MouseButton::Left, move |_, this, cx| {
+                    this.toggle_contacts_popover(&Default::default(), cx)
                 })
                 .with_tooltip::<ToggleContactsMenu>(
                     0,
@@ -405,7 +370,7 @@ impl CollabTitlebarItem {
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .on_click(MouseButton::Left, move |_, _, cx| {
-            cx.dispatch_action(ToggleScreenSharing);
+            toggle_screen_sharing(&Default::default(), cx)
         })
         .with_tooltip::<ToggleScreenSharing>(
             0,
@@ -451,11 +416,11 @@ impl CollabTitlebarItem {
                             .with_style(style.container)
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, move |_, _, cx| {
+                    .on_click(MouseButton::Left, move |_, this, cx| {
                         if is_shared {
-                            cx.dispatch_action(UnshareProject);
+                            this.unshare_project(&Default::default(), cx);
                         } else {
-                            cx.dispatch_action(ShareProject);
+                            this.share_project(&Default::default(), cx);
                         }
                     })
                     .with_tooltip::<ShareUnshare>(
@@ -496,8 +461,8 @@ impl CollabTitlebarItem {
                         .with_style(style.container)
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_action(ToggleUserMenu);
+                .on_click(MouseButton::Left, move |_, this, cx| {
+                    this.toggle_user_menu(&Default::default(), cx)
                 })
                 .with_tooltip::<ToggleUserMenu>(
                     0,
@@ -527,8 +492,13 @@ impl CollabTitlebarItem {
                 .with_style(style.container)
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, move |_, _, cx| {
-            cx.dispatch_action(SignIn);
+        .on_click(MouseButton::Left, move |_, this, cx| {
+            if let Some(workspace) = this.workspace.upgrade(cx) {
+                let client = workspace.read(cx).app_state().client.clone();
+                cx.app_context()
+                    .spawn(|cx| async move { client.authenticate_and_connect(true, &cx).await })
+                    .detach_and_log_err(cx);
+            }
         })
         .into_any()
     }
@@ -862,7 +832,7 @@ impl CollabTitlebarItem {
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, |_, _, cx| {
-                    cx.dispatch_action(auto_update::Check);
+                    auto_update::check(&Default::default(), cx);
                 })
                 .into_any(),
             ),

crates/collab_ui/src/collaborator_list_popover.rs 🔗

@@ -1,161 +0,0 @@
-use call::ActiveCall;
-use client::UserStore;
-use gpui::Action;
-use gpui::{actions, elements::*, platform::MouseButton, Entity, ModelHandle, View, ViewContext};
-use settings::Settings;
-
-use crate::collab_titlebar_item::ToggleCollaboratorList;
-
-pub(crate) enum Event {
-    Dismissed,
-}
-
-enum Collaborator {
-    SelfUser { username: String },
-    RemoteUser { username: String },
-}
-
-actions!(collaborator_list_popover, [NoOp]);
-
-pub(crate) struct CollaboratorListPopover {
-    list_state: ListState<Self>,
-}
-
-impl Entity for CollaboratorListPopover {
-    type Event = Event;
-}
-
-impl View for CollaboratorListPopover {
-    fn ui_name() -> &'static str {
-        "CollaboratorListPopover"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
-
-        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
-                .constrained()
-                .with_width(theme.contacts_popover.width)
-                .with_height(theme.contacts_popover.height)
-        })
-        .on_down_out(MouseButton::Left, move |_, _, cx| {
-            cx.dispatch_action(ToggleCollaboratorList);
-        })
-        .into_any()
-    }
-
-    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Dismissed);
-    }
-}
-
-impl CollaboratorListPopover {
-    pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
-        let active_call = ActiveCall::global(cx);
-
-        let mut collaborators = user_store
-            .read(cx)
-            .current_user()
-            .map(|u| Collaborator::SelfUser {
-                username: u.github_login.clone(),
-            })
-            .into_iter()
-            .collect::<Vec<_>>();
-
-        //TODO: What should the canonical sort here look like, consult contacts list implementation
-        if let Some(room) = active_call.read(cx).room() {
-            for participant in room.read(cx).remote_participants() {
-                collaborators.push(Collaborator::RemoteUser {
-                    username: participant.1.user.github_login.clone(),
-                });
-            }
-        }
-
-        Self {
-            list_state: ListState::new(
-                collaborators.len(),
-                Orientation::Top,
-                0.,
-                move |_, index, cx| match &collaborators[index] {
-                    Collaborator::SelfUser { username } => render_collaborator_list_entry(
-                        index,
-                        username,
-                        None::<NoOp>,
-                        None,
-                        Svg::new("icons/chevron_right_12.svg"),
-                        NoOp,
-                        "Leave call".to_owned(),
-                        cx,
-                    ),
-
-                    Collaborator::RemoteUser { username } => render_collaborator_list_entry(
-                        index,
-                        username,
-                        Some(NoOp),
-                        Some(format!("Follow {username}")),
-                        Svg::new("icons/x_mark_12.svg"),
-                        NoOp,
-                        format!("Remove {username} from call"),
-                        cx,
-                    ),
-                },
-            ),
-        }
-    }
-}
-
-fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
-    index: usize,
-    username: &str,
-    username_action: Option<UA>,
-    username_tooltip: Option<String>,
-    icon: Svg,
-    icon_action: IA,
-    icon_tooltip: String,
-    cx: &mut ViewContext<CollaboratorListPopover>,
-) -> AnyElement<CollaboratorListPopover> {
-    enum Username {}
-    enum UsernameTooltip {}
-    enum Icon {}
-    enum IconTooltip {}
-
-    let theme = &cx.global::<Settings>().theme;
-    let username_theme = theme.contact_list.contact_username.text.clone();
-    let tooltip_theme = theme.tooltip.clone();
-
-    let username =
-        MouseEventHandler::<Username, CollaboratorListPopover>::new(index, cx, |_, _| {
-            Label::new(username.to_owned(), username_theme.clone())
-        })
-        .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>(
-                    index,
-                    username_tooltip,
-                    None,
-                    tooltip_theme.clone(),
-                    cx,
-                )
-                .into_any()
-        } else {
-            username.into_any()
-        })
-        .with_child(
-            MouseEventHandler::<Icon, CollaboratorListPopover>::new(index, cx, |_, _| icon)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_action(icon_action.clone())
-                })
-                .with_tooltip::<IconTooltip>(index, icon_tooltip, None, tooltip_theme, cx),
-        )
-        .into_any()
-}

crates/collab_ui/src/contact_list.rs 🔗

@@ -1,4 +1,3 @@
-use crate::contacts_popover;
 use call::ActiveCall;
 use client::{proto::PeerId, Contact, User, UserStore};
 use editor::{Cancel, Editor};
@@ -140,6 +139,7 @@ pub struct RespondToContactRequest {
 }
 
 pub enum Event {
+    ToggleContactFinder,
     Dismissed,
 }
 
@@ -1116,11 +1116,14 @@ impl ContactList {
                         )
                         .with_padding(Padding::uniform(2.))
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_click(MouseButton::Left, move |_, _, cx| {
-                            cx.dispatch_action(RemoveContact {
-                                user_id,
-                                github_login: github_login.clone(),
-                            })
+                        .on_click(MouseButton::Left, move |_, this, cx| {
+                            this.remove_contact(
+                                &RemoveContact {
+                                    user_id,
+                                    github_login: github_login.clone(),
+                                },
+                                cx,
+                            );
                         })
                         .flex_float(),
                     )
@@ -1203,11 +1206,14 @@ impl ContactList {
                     render_icon_button(button_style, "icons/x_mark_8.svg").aligned()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_action(RespondToContactRequest {
-                        user_id,
-                        accept: false,
-                    })
+                .on_click(MouseButton::Left, move |_, this, cx| {
+                    this.respond_to_contact_request(
+                        &RespondToContactRequest {
+                            user_id,
+                            accept: false,
+                        },
+                        cx,
+                    );
                 })
                 .contained()
                 .with_margin_right(button_spacing),
@@ -1225,11 +1231,14 @@ impl ContactList {
                         .flex_float()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_action(RespondToContactRequest {
-                        user_id,
-                        accept: true,
-                    })
+                .on_click(MouseButton::Left, move |_, this, cx| {
+                    this.respond_to_contact_request(
+                        &RespondToContactRequest {
+                            user_id,
+                            accept: true,
+                        },
+                        cx,
+                    );
                 }),
             );
         } else {
@@ -1246,11 +1255,14 @@ impl ContactList {
                 })
                 .with_padding(Padding::uniform(2.))
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_action(RemoveContact {
-                        user_id,
-                        github_login: github_login.clone(),
-                    })
+                .on_click(MouseButton::Left, move |_, this, cx| {
+                    this.remove_contact(
+                        &RemoveContact {
+                            user_id,
+                            github_login: github_login.clone(),
+                        },
+                        cx,
+                    );
                 })
                 .flex_float(),
             );
@@ -1318,7 +1330,7 @@ impl View for ContactList {
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
                         .on_click(MouseButton::Left, |_, _, cx| {
-                            cx.dispatch_action(contacts_popover::ToggleContactFinder)
+                            cx.emit(Event::ToggleContactFinder)
                         })
                         .with_tooltip::<AddContact>(
                             0,

crates/collab_ui/src/contacts_popover.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
     contact_finder::{build_contact_finder, ContactFinder},
     contact_list::ContactList,
-    ToggleContactsMenu,
 };
 use client::UserStore;
 use gpui::{
@@ -72,8 +71,11 @@ impl ContactsPopover {
             let child = cx
                 .add_view(|cx| ContactList::new(&workspace, cx).with_editor_text(editor_text, cx));
             cx.focus(&child);
-            self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
+            self._subscription = Some(cx.subscribe(&child, |this, _, event, cx| match event {
                 crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed),
+                crate::contact_list::Event::ToggleContactFinder => {
+                    this.toggle_contact_finder(&Default::default(), cx)
+                }
             }));
             self.child = Child::ContactList(child);
             cx.notify();
@@ -106,9 +108,7 @@ impl View for ContactsPopover {
                 .with_width(theme.contacts_popover.width)
                 .with_height(theme.contacts_popover.height)
         })
-        .on_down_out(MouseButton::Left, move |_, _, cx| {
-            cx.dispatch_action(ToggleContactsMenu);
-        })
+        .on_down_out(MouseButton::Left, move |_, _, cx| cx.emit(Event::Dismissed))
         .into_any()
     }
 

crates/collab_ui/src/sharing_status_indicator.rs 🔗

@@ -1,3 +1,4 @@
+use crate::toggle_screen_sharing;
 use call::ActiveCall;
 use gpui::{
     color::Color,
@@ -7,8 +8,6 @@ use gpui::{
 };
 use settings::Settings;
 
-use crate::ToggleScreenSharing;
-
 pub fn init(cx: &mut AppContext) {
     let active_call = ActiveCall::global(cx);
 
@@ -54,7 +53,7 @@ impl View for SharingStatusIndicator {
                 .aligned()
         })
         .on_click(MouseButton::Left, |_, _, cx| {
-            cx.dispatch_action(ToggleScreenSharing);
+            toggle_screen_sharing(&Default::default(), cx)
         })
         .into_any()
     }

crates/context_menu/src/context_menu.rs 🔗

@@ -485,7 +485,11 @@ impl ContextMenu {
                 .contained()
                 .with_style(style.container)
         })
-        .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, |_, this, cx| {
+            this.cancel(&Default::default(), cx);
+        })
+        .on_down_out(MouseButton::Right, |_, this, cx| {
+            this.cancel(&Default::default(), cx);
+        })
     }
 }

crates/copilot/src/sign_in.rs 🔗

@@ -196,7 +196,7 @@ impl CopilotCodeVerification {
                     .contained()
                     .with_style(style.auth.prompting.hint.container.clone()),
             )
-            .with_child(theme::ui::cta_button_with_click::<ConnectButton, _, _, _>(
+            .with_child(theme::ui::cta_button::<ConnectButton, _, _, _>(
                 if connect_clicked {
                     "Waiting for connection..."
                 } else {
@@ -250,7 +250,7 @@ impl CopilotCodeVerification {
                     .contained()
                     .with_style(enabled_style.hint.container),
             )
-            .with_child(theme::ui::cta_button_with_click::<DoneButton, _, _, _>(
+            .with_child(theme::ui::cta_button::<DoneButton, _, _, _>(
                 "Done",
                 style.auth.content_width,
                 &style.auth.cta_button,
@@ -304,7 +304,7 @@ impl CopilotCodeVerification {
                     .contained()
                     .with_style(unauthorized_style.warning.container),
             )
-            .with_child(theme::ui::cta_button_with_click::<Self, _, _, _>(
+            .with_child(theme::ui::cta_button::<Self, _, _, _>(
                 "Subscribe on GitHub",
                 style.auth.content_width,
                 &style.auth.cta_button,

crates/diagnostics/src/items.rs 🔗

@@ -3,18 +3,19 @@ use editor::{Editor, GoToDiagnostic};
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    serde_json, AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
-    WeakViewHandle,
+    serde_json, AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
-use project::Project;
 use settings::Settings;
-use workspace::{item::ItemHandle, StatusItemView};
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
+
+use crate::ProjectDiagnosticsEditor;
 
 pub struct DiagnosticIndicator {
     summary: project::DiagnosticSummary,
     active_editor: Option<WeakViewHandle<Editor>>,
+    workspace: WeakViewHandle<Workspace>,
     current_diagnostic: Option<Diagnostic>,
     in_progress_checks: HashSet<LanguageServerId>,
     _observe_active_editor: Option<Subscription>,
@@ -25,7 +26,8 @@ pub fn init(cx: &mut AppContext) {
 }
 
 impl DiagnosticIndicator {
-    pub fn new(project: &ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
+        let project = workspace.project();
         cx.subscribe(project, |this, project, event, cx| match event {
             project::Event::DiskBasedDiagnosticsStarted { language_server_id } => {
                 this.in_progress_checks.insert(*language_server_id);
@@ -46,6 +48,7 @@ impl DiagnosticIndicator {
                 .language_servers_running_disk_based_diagnostics()
                 .collect(),
             active_editor: None,
+            workspace: workspace.weak_handle(),
             current_diagnostic: None,
             _observe_active_editor: None,
         }
@@ -163,8 +166,12 @@ impl View for DiagnosticIndicator {
                     })
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, _, cx| {
-                cx.dispatch_action(crate::Deploy)
+            .on_click(MouseButton::Left, |_, this, cx| {
+                if let Some(workspace) = this.workspace.upgrade(cx) {
+                    workspace.update(cx, |workspace, cx| {
+                        ProjectDiagnosticsEditor::deploy(workspace, &Default::default(), cx)
+                    })
+                }
             })
             .with_tooltip::<Summary>(
                 0,
@@ -200,8 +207,8 @@ impl View for DiagnosticIndicator {
                     .with_margin_left(item_spacing)
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, |_, _, cx| {
-                    cx.dispatch_action(GoToDiagnostic)
+                .on_click(MouseButton::Left, |_, this, cx| {
+                    this.go_to_next_diagnostic(&Default::default(), cx)
                 }),
             );
         }

crates/editor/src/editor.rs 🔗

@@ -809,10 +809,13 @@ impl CompletionsMenu {
                             },
                         )
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_down(MouseButton::Left, move |_, _, cx| {
-                            cx.dispatch_action(ConfirmCompletion {
-                                item_ix: Some(item_ix),
-                            });
+                        .on_down(MouseButton::Left, move |_, this, cx| {
+                            this.confirm_completion(
+                                &ConfirmCompletion {
+                                    item_ix: Some(item_ix),
+                                },
+                                cx,
+                            );
                         })
                         .into_any(),
                     );
@@ -970,9 +973,23 @@ impl CodeActionsMenu {
                                 .with_style(item_style)
                         })
                         .with_cursor_style(CursorStyle::PointingHand)
-                        .on_down(MouseButton::Left, move |_, _, cx| {
-                            cx.dispatch_action(ConfirmCodeAction {
-                                item_ix: Some(item_ix),
+                        .on_down(MouseButton::Left, move |_, this, cx| {
+                            let workspace = this
+                                .workspace
+                                .as_ref()
+                                .and_then(|(workspace, _)| workspace.upgrade(cx));
+                            cx.window_context().defer(move |cx| {
+                                if let Some(workspace) = workspace {
+                                    workspace.update(cx, |workspace, cx| {
+                                        if let Some(task) = Editor::confirm_code_action(
+                                            workspace,
+                                            &Default::default(),
+                                            cx,
+                                        ) {
+                                            task.detach_and_log_err(cx);
+                                        }
+                                    });
+                                }
                             });
                         })
                         .into_any(),
@@ -3138,10 +3155,13 @@ impl Editor {
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
                 .with_padding(Padding::uniform(3.))
-                .on_down(MouseButton::Left, |_, _, cx| {
-                    cx.dispatch_action(ToggleCodeActions {
-                        deployed_from_indicator: true,
-                    });
+                .on_down(MouseButton::Left, |_, this, cx| {
+                    this.toggle_code_actions(
+                        &ToggleCodeActions {
+                            deployed_from_indicator: true,
+                        },
+                        cx,
+                    );
                 })
                 .into_any(),
             )

crates/editor/src/element.rs 🔗

@@ -211,10 +211,13 @@ impl EditorElement {
         enum GutterHandlers {}
         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,
-                    })
+                .on_hover(|hover, editor: &mut Editor, cx| {
+                    editor.gutter_hover(
+                        &GutterHover {
+                            hovered: hover.started,
+                        },
+                        cx,
+                    );
                 }),
         )
     }
@@ -754,8 +757,8 @@ impl EditorElement {
 
                 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 })
+                        .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
+                            editor.unfold_at(&UnfoldAt { buffer_row }, cx)
                         })
                         .with_notify_on_hover(true)
                         .with_notify_on_click(true),

crates/editor/src/hover_popover.rs 🔗

@@ -1,3 +1,7 @@
+use crate::{
+    display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
+    EditorStyle, RangeToAnchorExt,
+};
 use futures::FutureExt;
 use gpui::{
     actions,
@@ -12,11 +16,6 @@ use settings::Settings;
 use std::{ops::Range, sync::Arc, time::Duration};
 use util::TryFutureExt;
 
-use crate::{
-    display_map::ToDisplayPoint, Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSnapshot,
-    EditorStyle, GoToDiagnostic, RangeToAnchorExt,
-};
-
 pub const HOVER_DELAY_MILLIS: u64 = 350;
 pub const HOVER_REQUEST_DELAY_MILLIS: u64 = 200;
 
@@ -668,8 +667,8 @@ impl DiagnosticPopover {
             ..Default::default()
         })
         .on_move(|_, _, _| {}) // Consume move events so they don't reach regions underneath.
-        .on_click(MouseButton::Left, |_, _, cx| {
-            cx.dispatch_action(GoToDiagnostic)
+        .on_click(MouseButton::Left, |_, this, cx| {
+            this.go_to_diagnostic(&Default::default(), cx)
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .with_tooltip::<PrimaryDiagnostic>(

crates/feedback/src/deploy_feedback_button.rs 🔗

@@ -1,15 +1,16 @@
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Entity, View, ViewContext,
+    Entity, View, ViewContext, WeakViewHandle,
 };
 use settings::Settings;
-use workspace::{item::ItemHandle, StatusItemView};
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 use crate::feedback_editor::{FeedbackEditor, GiveFeedback};
 
 pub struct DeployFeedbackButton {
     active: bool,
+    workspace: WeakViewHandle<Workspace>,
 }
 
 impl Entity for DeployFeedbackButton {
@@ -17,8 +18,11 @@ impl Entity for DeployFeedbackButton {
 }
 
 impl DeployFeedbackButton {
-    pub fn new() -> Self {
-        DeployFeedbackButton { active: false }
+    pub fn new(workspace: &Workspace) -> Self {
+        DeployFeedbackButton {
+            active: false,
+            workspace: workspace.weak_handle(),
+        }
     }
 }
 
@@ -52,9 +56,12 @@ impl View for DeployFeedbackButton {
                         .with_style(style.container)
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
+                .on_click(MouseButton::Left, move |_, this, cx| {
                     if !active {
-                        cx.dispatch_action(GiveFeedback)
+                        if let Some(workspace) = this.workspace.upgrade(cx) {
+                            workspace
+                                .update(cx, |workspace, cx| FeedbackEditor::deploy(workspace, cx))
+                        }
                     }
                 })
                 .with_tooltip::<Self>(

crates/feedback/src/feedback.rs 🔗

@@ -3,20 +3,10 @@ pub mod feedback_editor;
 pub mod feedback_info_text;
 pub mod submit_feedback_button;
 
-use std::sync::Arc;
-
 mod system_specs;
-use gpui::{actions, impl_actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext};
-use serde::Deserialize;
+use gpui::{actions, platform::PromptLevel, AppContext, ClipboardItem, ViewContext};
 use system_specs::SystemSpecs;
-use workspace::{AppState, Workspace};
-
-#[derive(Deserialize, Clone, PartialEq)]
-pub struct OpenBrowser {
-    pub url: Arc<str>,
-}
-
-impl_actions!(zed, [OpenBrowser]);
+use workspace::Workspace;
 
 actions!(
     zed,
@@ -28,29 +18,20 @@ actions!(
     ]
 );
 
-pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    let system_specs = SystemSpecs::new(&cx);
-    let system_specs_text = system_specs.to_string();
-
-    feedback_editor::init(system_specs, app_state, cx);
-
-    cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
-
-    let url = format!(
-        "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
-        urlencoding::encode(&system_specs_text)
-    );
+pub fn init(cx: &mut AppContext) {
+    feedback_editor::init(cx);
 
     cx.add_action(
         move |_: &mut Workspace,
               _: &CopySystemSpecsIntoClipboard,
               cx: &mut ViewContext<Workspace>| {
+            let specs = SystemSpecs::new(&cx).to_string();
             cx.prompt(
                 PromptLevel::Info,
-                &format!("Copied into clipboard:\n\n{system_specs_text}"),
+                &format!("Copied into clipboard:\n\n{specs}"),
                 &["OK"],
             );
-            let item = ClipboardItem::new(system_specs_text.clone());
+            let item = ClipboardItem::new(specs.clone());
             cx.write_to_clipboard(item);
         },
     );
@@ -58,24 +39,24 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.add_action(
         |_: &mut Workspace, _: &RequestFeature, cx: &mut ViewContext<Workspace>| {
             let url = "https://github.com/zed-industries/community/issues/new?assignees=&labels=enhancement%2Ctriage&template=0_feature_request.yml";
-            cx.dispatch_action(OpenBrowser {
-                url: url.into(),
-            });
+            cx.platform().open_url(url);
         },
     );
 
     cx.add_action(
         move |_: &mut Workspace, _: &FileBugReport, cx: &mut ViewContext<Workspace>| {
-            cx.dispatch_action(OpenBrowser {
-                url: url.clone().into(),
-            });
+            let url = format!(
+                "https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml&environment={}",
+                urlencoding::encode(&SystemSpecs::new(&cx).to_string())
+            );
+            cx.platform().open_url(&url);
         },
     );
 
-    cx.add_action(
-        |_: &mut Workspace, _: &OpenZedCommunityRepo, cx: &mut ViewContext<Workspace>| {
-            let url = "https://github.com/zed-industries/community";
-            cx.dispatch_action(OpenBrowser { url: url.into() });
-        },
-    );
+    cx.add_global_action(open_zed_community_repo);
+}
+
+pub fn open_zed_community_repo(_: &OpenZedCommunityRepo, cx: &mut AppContext) {
+    let url = "https://github.com/zed-industries/community";
+    cx.platform().open_url(&url);
 }

crates/feedback/src/feedback_editor.rs 🔗

@@ -1,10 +1,4 @@
-use std::{
-    any::TypeId,
-    borrow::Cow,
-    ops::{Range, RangeInclusive},
-    sync::Arc,
-};
-
+use crate::system_specs::SystemSpecs;
 use anyhow::bail;
 use client::{Client, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
 use editor::{Anchor, Editor};
@@ -19,40 +13,34 @@ use gpui::{
 use isahc::Request;
 use language::Buffer;
 use postage::prelude::Stream;
-
 use project::Project;
 use serde::Serialize;
+use std::{
+    any::TypeId,
+    borrow::Cow,
+    ops::{Range, RangeInclusive},
+    sync::Arc,
+};
 use util::ResultExt;
 use workspace::{
-    item::{Item, ItemHandle},
+    item::{Item, ItemEvent, ItemHandle},
     searchable::{SearchableItem, SearchableItemHandle},
-    AppState, Workspace,
+    smallvec::SmallVec,
+    Workspace,
 };
 
-use crate::{submit_feedback_button::SubmitFeedbackButton, system_specs::SystemSpecs};
-
 const FEEDBACK_CHAR_LIMIT: RangeInclusive<usize> = 10..=5000;
 const FEEDBACK_SUBMISSION_ERROR_TEXT: &str =
     "Feedback failed to submit, see error log for details.";
 
 actions!(feedback, [GiveFeedback, SubmitFeedback]);
 
-pub fn init(system_specs: SystemSpecs, app_state: Arc<AppState>, cx: &mut AppContext) {
+pub fn init(cx: &mut AppContext) {
     cx.add_action({
         move |workspace: &mut Workspace, _: &GiveFeedback, cx: &mut ViewContext<Workspace>| {
-            FeedbackEditor::deploy(system_specs.clone(), workspace, app_state.clone(), cx);
+            FeedbackEditor::deploy(workspace, cx);
         }
     });
-
-    cx.add_async_action(
-        |submit_feedback_button: &mut SubmitFeedbackButton, _: &SubmitFeedback, cx| {
-            if let Some(active_item) = submit_feedback_button.active_item.as_ref() {
-                Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.handle_save(cx)))
-            } else {
-                None
-            }
-        },
-    );
 }
 
 #[derive(Serialize)]
@@ -94,7 +82,7 @@ impl FeedbackEditor {
         }
     }
 
-    fn handle_save(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
+    pub fn submit(&mut self, cx: &mut ViewContext<Self>) -> Task<anyhow::Result<()>> {
         let feedback_text = self.editor.read(cx).text(cx);
         let feedback_char_count = feedback_text.chars().count();
         let feedback_text = feedback_text.trim().to_string();
@@ -133,10 +121,8 @@ impl FeedbackEditor {
             if answer == Some(0) {
                 match FeedbackEditor::submit_feedback(&feedback_text, client, specs).await {
                     Ok(_) => {
-                        this.update(&mut cx, |_, cx| {
-                            cx.dispatch_action(workspace::CloseActiveItem);
-                        })
-                        .log_err();
+                        this.update(&mut cx, |_, cx| cx.emit(editor::Event::Closed))
+                            .log_err();
                     }
                     Err(error) => {
                         log::error!("{}", error);
@@ -198,22 +184,21 @@ impl FeedbackEditor {
 }
 
 impl FeedbackEditor {
-    pub fn deploy(
-        system_specs: SystemSpecs,
-        _: &mut Workspace,
-        app_state: Arc<AppState>,
-        cx: &mut ViewContext<Workspace>,
-    ) {
-        let markdown = app_state.languages.language_for_name("Markdown");
+    pub fn deploy(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+        let markdown = workspace
+            .app_state()
+            .languages
+            .language_for_name("Markdown");
         cx.spawn(|workspace, mut cx| async move {
             let markdown = markdown.await.log_err();
             workspace
                 .update(&mut cx, |workspace, cx| {
-                    workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
+                    workspace.with_local_workspace(cx, |workspace, cx| {
                         let project = workspace.project().clone();
                         let buffer = project
                             .update(cx, |project, cx| project.create_buffer("", markdown, cx))
                             .expect("creating buffers on a local workspace always succeeds");
+                        let system_specs = SystemSpecs::new(cx);
                         let feedback_editor = cx
                             .add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
                         workspace.add_item(Box::new(feedback_editor), cx);
@@ -291,7 +276,7 @@ impl Item for FeedbackEditor {
         _: ModelHandle<Project>,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
-        self.handle_save(cx)
+        self.submit(cx)
     }
 
     fn save_as(
@@ -300,7 +285,7 @@ impl Item for FeedbackEditor {
         _: std::path::PathBuf,
         cx: &mut ViewContext<Self>,
     ) -> Task<anyhow::Result<()>> {
-        self.handle_save(cx)
+        self.submit(cx)
     }
 
     fn reload(
@@ -353,6 +338,10 @@ impl Item for FeedbackEditor {
             None
         }
     }
+
+    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+        Editor::to_item_events(event)
+    }
 }
 
 impl SearchableItem for FeedbackEditor {

crates/feedback/src/feedback_info_text.rs 🔗

@@ -6,7 +6,7 @@ use gpui::{
 use settings::Settings;
 use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
 
-use crate::{feedback_editor::FeedbackEditor, OpenZedCommunityRepo};
+use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo};
 
 pub struct FeedbackInfoText {
     active_item: Option<ViewHandle<FeedbackEditor>>,
@@ -57,7 +57,7 @@ impl View for FeedbackInfoText {
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
                 .on_click(MouseButton::Left, |_, _, cx| {
-                    cx.dispatch_action(OpenZedCommunityRepo)
+                    open_zed_community_repo(&Default::default(), cx)
                 }),
             )
             .with_child(

crates/feedback/src/submit_feedback_button.rs 🔗

@@ -1,12 +1,16 @@
+use crate::feedback_editor::{FeedbackEditor, SubmitFeedback};
+use anyhow::Result;
 use gpui::{
     elements::{Label, MouseEventHandler},
     platform::{CursorStyle, MouseButton},
-    AnyElement, Element, Entity, View, ViewContext, ViewHandle,
+    AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle,
 };
 use settings::Settings;
 use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
 
-use crate::feedback_editor::{FeedbackEditor, SubmitFeedback};
+pub fn init(cx: &mut AppContext) {
+    cx.add_async_action(SubmitFeedbackButton::submit);
+}
 
 pub struct SubmitFeedbackButton {
     pub(crate) active_item: Option<ViewHandle<FeedbackEditor>>,
@@ -18,6 +22,18 @@ impl SubmitFeedbackButton {
             active_item: Default::default(),
         }
     }
+
+    pub fn submit(
+        &mut self,
+        _: &SubmitFeedback,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<Task<Result<()>>> {
+        if let Some(active_item) = self.active_item.as_ref() {
+            Some(active_item.update(cx, |feedback_editor, cx| feedback_editor.submit(cx)))
+        } else {
+            None
+        }
+    }
 }
 
 impl Entity for SubmitFeedbackButton {
@@ -39,8 +55,8 @@ impl View for SubmitFeedbackButton {
                 .with_style(style.container)
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .on_click(MouseButton::Left, |_, _, cx| {
-            cx.dispatch_action(SubmitFeedback)
+        .on_click(MouseButton::Left, |_, this, cx| {
+            this.submit(&Default::default(), cx);
         })
         .aligned()
         .contained()

crates/gpui/src/app.rs 🔗

@@ -1745,10 +1745,6 @@ impl AppContext {
         self.pending_effects.push_back(Effect::RefreshWindows);
     }
 
-    fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) {
-        self.dispatch_any_action_at(window_id, view_id, Box::new(action));
-    }
-
     pub fn dispatch_any_action_at(
         &mut self,
         window_id: usize,
@@ -3189,13 +3185,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
         self.window_context.notify_view(window_id, view_id);
     }
 
-    pub fn dispatch_action(&mut self, action: impl Action) {
-        let window_id = self.window_id;
-        let view_id = self.view_id;
-        self.window_context
-            .dispatch_action_at(window_id, view_id, action)
-    }
-
     pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut V, &mut ViewContext<V>)) {
         let handle = self.handle();
         self.window_context

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

@@ -1,8 +1,8 @@
 use serde::Deserialize;
 
 use crate::{
-    actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, EventContext,
-    View, ViewContext, WeakViewHandle,
+    actions, elements::*, impl_actions, platform::MouseButton, AppContext, Entity, View,
+    ViewContext, WeakViewHandle,
 };
 
 pub struct Select {
@@ -116,10 +116,9 @@ impl View for Select {
                 .contained()
                 .with_style(style.header)
             })
-            .on_click(
-                MouseButton::Left,
-                move |_, _, cx: &mut EventContext<Self>| cx.dispatch_action(ToggleSelect),
-            ),
+            .on_click(MouseButton::Left, move |_, this, cx| {
+                this.toggle(&Default::default(), cx);
+            }),
         );
         if self.is_open {
             result.add_child(Overlay::new(
@@ -143,12 +142,9 @@ impl View for Select {
                                     cx,
                                 )
                             })
-                            .on_click(
-                                MouseButton::Left,
-                                move |_, _, cx: &mut EventContext<Self>| {
-                                    cx.dispatch_action(SelectItem(ix))
-                                },
-                            )
+                            .on_click(MouseButton::Left, move |_, this, cx| {
+                                this.select_item(&SelectItem(ix), cx);
+                            })
                             .into_any()
                         }))
                     },

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -2,27 +2,23 @@ use editor::Editor;
 use gpui::{
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Entity, Subscription, View, ViewContext, ViewHandle,
+    Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
 use std::sync::Arc;
-use workspace::{item::ItemHandle, StatusItemView};
+use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 pub struct ActiveBufferLanguage {
     active_language: Option<Option<Arc<str>>>,
+    workspace: WeakViewHandle<Workspace>,
     _observe_active_editor: Option<Subscription>,
 }
 
-impl Default for ActiveBufferLanguage {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 impl ActiveBufferLanguage {
-    pub fn new() -> Self {
+    pub fn new(workspace: &Workspace) -> Self {
         Self {
             active_language: None,
+            workspace: workspace.weak_handle(),
             _observe_active_editor: None,
         }
     }
@@ -66,8 +62,12 @@ impl View for ActiveBufferLanguage {
                     .with_style(style.container)
             })
             .with_cursor_style(CursorStyle::PointingHand)
-            .on_click(MouseButton::Left, |_, _, cx| {
-                cx.dispatch_action(crate::Toggle)
+            .on_click(MouseButton::Left, |_, this, cx| {
+                if let Some(workspace) = this.workspace.upgrade(cx) {
+                    workspace.update(cx, |workspace, cx| {
+                        crate::toggle(workspace, &Default::default(), cx)
+                    });
+                }
             })
             .into_any()
         } else {

crates/language_selector/src/language_selector.rs 🔗

@@ -11,21 +11,18 @@ use project::Project;
 use settings::Settings;
 use std::sync::Arc;
 use util::ResultExt;
-use workspace::{AppState, Workspace};
+use workspace::Workspace;
 
 actions!(language_selector, [Toggle]);
 
-pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
+pub fn init(cx: &mut AppContext) {
     Picker::<LanguageSelectorDelegate>::init(cx);
-    cx.add_action({
-        let language_registry = app_state.languages.clone();
-        move |workspace, _: &Toggle, cx| toggle(workspace, language_registry.clone(), cx)
-    });
+    cx.add_action(toggle);
 }
 
-fn toggle(
+pub fn toggle(
     workspace: &mut Workspace,
-    registry: Arc<LanguageRegistry>,
+    _: &Toggle,
     cx: &mut ViewContext<Workspace>,
 ) -> Option<()> {
     let (_, buffer, _) = workspace
@@ -34,6 +31,7 @@ fn toggle(
         .read(cx)
         .active_excerpt(cx)?;
     workspace.toggle_modal(cx, |workspace, cx| {
+        let registry = workspace.app_state().languages.clone();
         cx.add_view(|cx| {
             Picker::new(
                 LanguageSelectorDelegate::new(buffer, workspace.project().clone(), registry),

crates/outline/src/outline.rs 🔗

@@ -24,7 +24,7 @@ pub fn init(cx: &mut AppContext) {
     OutlineView::init(cx);
 }
 
-fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
     if let Some(editor) = workspace
         .active_item(cx)
         .and_then(|item| item.downcast::<Editor>())

crates/project_panel/src/project_panel.rs 🔗

@@ -13,7 +13,7 @@ use gpui::{
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, PromptLevel},
     AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext,
-    ViewHandle,
+    ViewHandle, WeakViewHandle,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@@ -44,6 +44,7 @@ pub struct ProjectPanel {
     clipboard_entry: Option<ClipboardEntry>,
     context_menu: ViewHandle<ContextMenu>,
     dragged_entry_destination: Option<Arc<Path>>,
+    workspace: WeakViewHandle<Workspace>,
 }
 
 #[derive(Copy, Clone)]
@@ -137,7 +138,8 @@ pub enum Event {
 }
 
 impl ProjectPanel {
-    pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+        let project = workspace.project().clone();
         let project_panel = cx.add_view(|cx: &mut ViewContext<Self>| {
             cx.observe(&project, |this, _, cx| {
                 this.update_visible_entries(None, cx);
@@ -206,6 +208,7 @@ impl ProjectPanel {
                 clipboard_entry: None,
                 context_menu: cx.add_view(ContextMenu::new),
                 dragged_entry_destination: None,
+                workspace: workspace.weak_handle(),
             };
             this.update_visible_entries(None, cx);
             this
@@ -1296,8 +1299,14 @@ impl View for ProjectPanel {
                             )
                         }
                     })
-                    .on_click(MouseButton::Left, move |_, _, cx| {
-                        cx.dispatch_action(workspace::Open)
+                    .on_click(MouseButton::Left, move |_, this, cx| {
+                        if let Some(workspace) = this.workspace.upgrade(cx) {
+                            workspace.update(cx, |workspace, cx| {
+                                if let Some(task) = workspace.open(&Default::default(), cx) {
+                                    task.detach_and_log_err(cx);
+                                }
+                            })
+                        }
                     })
                     .with_cursor_style(CursorStyle::PointingHand),
                 )
@@ -1400,7 +1409,7 @@ mod tests {
 
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
+        let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
         assert_eq!(
             visible_entries_as_strings(&panel, 0..50, cx),
             &[
@@ -1492,7 +1501,7 @@ mod tests {
 
         let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
         let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
+        let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         select_path(&panel, "root1", cx);
         assert_eq!(
@@ -1785,7 +1794,7 @@ mod tests {
 
         let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-        let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx));
+        let panel = workspace.update(cx, |workspace, cx| ProjectPanel::new(workspace, cx));
 
         panel.update(cx, |panel, cx| {
             panel.select_next(&Default::default(), cx);

crates/recent_projects/src/recent_projects.rs 🔗

@@ -11,24 +11,24 @@ use highlighted_workspace_location::HighlightedWorkspaceLocation;
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate, PickerEvent};
 use settings::Settings;
-use std::sync::{Arc, Weak};
+use std::sync::Arc;
 use workspace::{
-    notifications::simple_message_notification::MessageNotification, AppState, Workspace,
-    WorkspaceLocation, WORKSPACE_DB,
+    notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
+    WORKSPACE_DB,
 };
 
 actions!(projects, [OpenRecent]);
 
-pub fn init(cx: &mut AppContext, app_state: Weak<AppState>) {
-    cx.add_async_action(
-        move |_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext<Workspace>| {
-            toggle(app_state.clone(), cx)
-        },
-    );
+pub fn init(cx: &mut AppContext) {
+    cx.add_async_action(toggle);
     RecentProjects::init(cx);
 }
 
-fn toggle(app_state: Weak<AppState>, cx: &mut ViewContext<Workspace>) -> Option<Task<Result<()>>> {
+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()
@@ -49,11 +49,7 @@ fn toggle(app_state: Weak<AppState>, cx: &mut ViewContext<Workspace>) -> Option<
                     let workspace = cx.weak_handle();
                     cx.add_view(|cx| {
                         RecentProjects::new(
-                            RecentProjectsDelegate::new(
-                                workspace,
-                                workspace_locations,
-                                app_state.clone(),
-                            ),
+                            RecentProjectsDelegate::new(workspace, workspace_locations),
                             cx,
                         )
                         .with_max_size(800., 1200.)
@@ -74,7 +70,6 @@ type RecentProjects = Picker<RecentProjectsDelegate>;
 struct RecentProjectsDelegate {
     workspace: WeakViewHandle<Workspace>,
     workspace_locations: Vec<WorkspaceLocation>,
-    app_state: Weak<AppState>,
     selected_match_index: usize,
     matches: Vec<StringMatch>,
 }
@@ -83,12 +78,10 @@ impl RecentProjectsDelegate {
     fn new(
         workspace: WeakViewHandle<Workspace>,
         workspace_locations: Vec<WorkspaceLocation>,
-        app_state: Weak<AppState>,
     ) -> Self {
         Self {
             workspace,
             workspace_locations,
-            app_state,
             selected_match_index: 0,
             matches: Default::default(),
         }
@@ -155,20 +148,16 @@ impl PickerDelegate for RecentProjectsDelegate {
     }
 
     fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) {
-        if let Some(((selected_match, workspace), app_state)) = self
+        if let Some((selected_match, workspace)) = self
             .matches
             .get(self.selected_index())
             .zip(self.workspace.upgrade(cx))
-            .zip(self.app_state.upgrade())
         {
             let workspace_location = &self.workspace_locations[selected_match.candidate_id];
             workspace
                 .update(cx, |workspace, cx| {
-                    workspace.open_workspace_for_paths(
-                        workspace_location.paths().as_ref().clone(),
-                        app_state,
-                        cx,
-                    )
+                    workspace
+                        .open_workspace_for_paths(workspace_location.paths().as_ref().clone(), cx)
                 })
                 .detach_and_log_err(cx);
             cx.emit(PickerEvent::Dismiss);

crates/terminal_view/src/terminal_button.rs 🔗

@@ -7,7 +7,11 @@ use gpui::{
 };
 use settings::Settings;
 use std::any::TypeId;
-use workspace::{dock::FocusDock, item::ItemHandle, NewTerminal, StatusItemView, Workspace};
+use workspace::{
+    dock::{Dock, FocusDock},
+    item::ItemHandle,
+    NewTerminal, StatusItemView, Workspace,
+};
 
 pub struct TerminalButton {
     workspace: WeakViewHandle<Workspace>,
@@ -80,7 +84,11 @@ impl View for TerminalButton {
                         this.deploy_terminal_menu(cx);
                     } else {
                         if !active {
-                            cx.dispatch_action(FocusDock);
+                            if let Some(workspace) = this.workspace.upgrade(cx) {
+                                workspace.update(cx, |workspace, cx| {
+                                    Dock::focus_dock(workspace, &Default::default(), cx)
+                                })
+                            }
                         }
                     };
                 })

crates/theme/src/ui.rs 🔗

@@ -156,24 +156,7 @@ pub fn keystroke_label<V: View>(
 
 pub type ButtonStyle = Interactive<ContainedText>;
 
-pub fn cta_button<L, A, V>(
-    label: L,
-    action: A,
-    max_width: f32,
-    style: &ButtonStyle,
-    cx: &mut ViewContext<V>,
-) -> MouseEventHandler<A, V>
-where
-    L: Into<Cow<'static, str>>,
-    A: 'static + Action + Clone,
-    V: View,
-{
-    cta_button_with_click::<A, _, _, _>(label, max_width, style, cx, move |_, _, cx| {
-        cx.dispatch_action(action.clone())
-    })
-}
-
-pub fn cta_button_with_click<Tag, L, V, F>(
+pub fn cta_button<Tag, L, V, F>(
     label: L,
     max_width: f32,
     style: &ButtonStyle,

crates/theme_selector/src/theme_selector.rs 🔗

@@ -6,20 +6,18 @@ use staff_mode::StaffMode;
 use std::sync::Arc;
 use theme::{Theme, ThemeMeta, ThemeRegistry};
 use util::ResultExt;
-use workspace::{AppState, Workspace};
+use workspace::Workspace;
 
 actions!(theme_selector, [Toggle, Reload]);
 
-pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    cx.add_action({
-        let theme_registry = app_state.themes.clone();
-        move |workspace, _: &Toggle, cx| toggle(workspace, theme_registry.clone(), cx)
-    });
+pub fn init(cx: &mut AppContext) {
+    cx.add_action(toggle);
     ThemeSelector::init(cx);
 }
 
-fn toggle(workspace: &mut Workspace, themes: Arc<ThemeRegistry>, cx: &mut ViewContext<Workspace>) {
-    workspace.toggle_modal(cx, |_, cx| {
+pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
+    workspace.toggle_modal(cx, |workspace, cx| {
+        let themes = workspace.app_state().themes.clone();
         cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(themes, cx), cx))
     });
 }

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -18,7 +18,7 @@ pub fn init(cx: &mut AppContext) {
     BaseKeymapSelector::init(cx);
 }
 
-fn toggle(
+pub fn toggle(
     workspace: &mut Workspace,
     _: &ToggleBaseKeymapSelector,
     cx: &mut ViewContext<Workspace>,

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},
-    AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext,
+    AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle,
 };
 use settings::{settings_file::SettingsFile, Settings};
 
@@ -20,7 +20,7 @@ pub const FIRST_OPEN: &str = "first_open";
 
 pub fn init(cx: &mut AppContext) {
     cx.add_action(|workspace: &mut Workspace, _: &Welcome, cx| {
-        let welcome_page = cx.add_view(WelcomePage::new);
+        let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx));
         workspace.add_item(Box::new(welcome_page), cx)
     });
 
@@ -30,7 +30,7 @@ pub fn init(cx: &mut AppContext) {
 pub fn show_welcome_experience(app_state: &Arc<AppState>, cx: &mut AppContext) {
     open_new(&app_state, cx, |workspace, cx| {
         workspace.toggle_sidebar(SidebarSide::Left, cx);
-        let welcome_page = cx.add_view(|cx| WelcomePage::new(cx));
+        let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx));
         workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
         cx.focus(&welcome_page);
         cx.notify();
@@ -43,6 +43,7 @@ pub fn show_welcome_experience(app_state: &Arc<AppState>, cx: &mut AppContext) {
 }
 
 pub struct WelcomePage {
+    workspace: WeakViewHandle<Workspace>,
     _settings_subscription: Subscription,
 }
 
@@ -97,26 +98,46 @@ impl View for WelcomePage {
                 )
                 .with_child(
                     Flex::column()
-                        .with_child(theme::ui::cta_button(
+                        .with_child(theme::ui::cta_button::<theme_selector::Toggle, _, _, _>(
                             "Choose a theme",
-                            theme_selector::Toggle,
                             width,
                             &theme.welcome.button,
                             cx,
+                            |_, this, cx| {
+                                if let Some(workspace) = this.workspace.upgrade(cx) {
+                                    workspace.update(cx, |workspace, cx| {
+                                        theme_selector::toggle(workspace, &Default::default(), cx)
+                                    })
+                                }
+                            },
                         ))
-                        .with_child(theme::ui::cta_button(
+                        .with_child(theme::ui::cta_button::<ToggleBaseKeymapSelector, _, _, _>(
                             "Choose a keymap",
-                            ToggleBaseKeymapSelector,
                             width,
                             &theme.welcome.button,
                             cx,
+                            |_, this, cx| {
+                                if let Some(workspace) = this.workspace.upgrade(cx) {
+                                    workspace.update(cx, |workspace, cx| {
+                                        base_keymap_picker::toggle(
+                                            workspace,
+                                            &Default::default(),
+                                            cx,
+                                        )
+                                    })
+                                }
+                            },
                         ))
-                        .with_child(theme::ui::cta_button(
+                        .with_child(theme::ui::cta_button::<install_cli::Install, _, _, _>(
                             "Install the CLI",
-                            install_cli::Install,
                             width,
                             &theme.welcome.button,
                             cx,
+                            |_, _, cx| {
+                                cx.app_context()
+                                    .spawn(|cx| async move { install_cli::install_cli(&cx).await })
+                                    .detach_and_log_err(cx);
+                            },
                         ))
                         .contained()
                         .with_style(theme.welcome.button_group)
@@ -190,8 +211,9 @@ impl View for WelcomePage {
 }
 
 impl WelcomePage {
-    pub fn new(cx: &mut ViewContext<Self>) -> Self {
+    pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
         WelcomePage {
+            workspace: workspace.weak_handle(),
             _settings_subscription: cx.observe_global::<Settings, _>(move |_, cx| cx.notify()),
         }
     }
@@ -220,11 +242,15 @@ impl Item for WelcomePage {
     fn show_toolbar(&self) -> bool {
         false
     }
+
     fn clone_on_split(
         &self,
         _workspace_id: WorkspaceId,
         cx: &mut ViewContext<Self>,
     ) -> Option<Self> {
-        Some(WelcomePage::new(cx))
+        Some(WelcomePage {
+            workspace: self.workspace.clone(),
+            _settings_subscription: cx.observe_global::<Settings, _>(move |_, cx| cx.notify()),
+        })
     }
 }

crates/workspace/src/dock.rs 🔗

@@ -271,11 +271,11 @@ impl Dock {
         }
     }
 
-    fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext<Workspace>) {
+    pub fn focus_dock(workspace: &mut Workspace, _: &FocusDock, cx: &mut ViewContext<Workspace>) {
         Self::set_dock_position(workspace, workspace.dock.position.show(), true, cx);
     }
 
-    fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext<Workspace>) {
+    pub fn hide_dock(workspace: &mut Workspace, _: &HideDock, cx: &mut ViewContext<Workspace>) {
         Self::set_dock_position(workspace, workspace.dock.position.hide(), true, cx);
     }
 
@@ -374,8 +374,8 @@ impl Dock {
                                     .with_background_color(style.wash_color)
                             })
                             .capture_all()
-                            .on_down(MouseButton::Left, |_, _, cx| {
-                                cx.dispatch_action(HideDock);
+                            .on_down(MouseButton::Left, |_, workspace, cx| {
+                                Dock::hide_dock(workspace, &Default::default(), cx)
                             })
                             .with_cursor_style(CursorStyle::Arrow),
                         )

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

@@ -1,3 +1,5 @@
+use super::{icon_for_dock_anchor, Dock, FocusDock, HideDock};
+use crate::{handle_dropped_item, StatusItemView, Workspace};
 use gpui::{
     elements::{Empty, MouseEventHandler, Svg},
     platform::CursorStyle,
@@ -6,10 +8,6 @@ use gpui::{
 };
 use settings::Settings;
 
-use crate::{handle_dropped_item, StatusItemView, Workspace};
-
-use super::{icon_for_dock_anchor, FocusDock, HideDock};
-
 pub struct ToggleDockButton {
     workspace: WeakViewHandle<Workspace>,
 }
@@ -82,8 +80,12 @@ impl View for ToggleDockButton {
 
         if dock_position.is_visible() {
             button
-                .on_click(MouseButton::Left, |_, _, cx| {
-                    cx.dispatch_action(HideDock);
+                .on_click(MouseButton::Left, |_, this, cx| {
+                    if let Some(workspace) = this.workspace.upgrade(cx) {
+                        workspace.update(cx, |workspace, cx| {
+                            Dock::hide_dock(workspace, &Default::default(), cx)
+                        })
+                    }
                 })
                 .with_tooltip::<Self>(
                     0,
@@ -94,8 +96,12 @@ impl View for ToggleDockButton {
                 )
         } else {
             button
-                .on_click(MouseButton::Left, |_, _, cx| {
-                    cx.dispatch_action(FocusDock);
+                .on_click(MouseButton::Left, |_, this, cx| {
+                    if let Some(workspace) = this.workspace.upgrade(cx) {
+                        workspace.update(cx, |workspace, cx| {
+                            Dock::focus_dock(workspace, &Default::default(), cx)
+                        })
+                    }
                 })
                 .with_tooltip::<Self>(
                     0,

crates/workspace/src/notifications.rs 🔗

@@ -278,8 +278,8 @@ pub mod simple_message_notification {
                                         .with_height(style.button_width)
                                 })
                                 .with_padding(Padding::uniform(5.))
-                                .on_click(MouseButton::Left, move |_, _, cx| {
-                                    cx.dispatch_action(CancelMessageNotification)
+                                .on_click(MouseButton::Left, move |_, this, cx| {
+                                    this.dismiss(&Default::default(), cx);
                                 })
                                 .with_cursor_style(CursorStyle::PointingHand)
                                 .aligned()

crates/workspace/src/pane.rs 🔗

@@ -2,7 +2,7 @@ mod dragged_item_receiver;
 
 use super::{ItemHandle, SplitDirection};
 use crate::{
-    dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, ExpandDock, HideDock},
+    dock::{icon_for_dock_anchor, AnchorDockBottom, AnchorDockRight, Dock, ExpandDock},
     item::WeakItemHandle,
     toolbar::Toolbar,
     Item, NewFile, NewSearch, NewTerminal, Workspace,
@@ -259,6 +259,10 @@ impl Pane {
         }
     }
 
+    pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
+        &self.workspace
+    }
+
     pub fn is_active(&self) -> bool {
         self.is_active
     }
@@ -1340,8 +1344,8 @@ impl Pane {
                                         cx,
                                     )
                                 })
-                                .on_down(MouseButton::Left, move |_, _, cx| {
-                                    cx.dispatch_action(ActivateItem(ix));
+                                .on_down(MouseButton::Left, move |_, this, cx| {
+                                    this.activate_item(ix, true, true, cx);
                                 })
                                 .on_click(MouseButton::Middle, {
                                     let item_id = item.id();
@@ -1639,7 +1643,13 @@ impl Pane {
                     3,
                     "icons/x_mark_8.svg",
                     cx,
-                    |_, cx| cx.dispatch_action(HideDock),
+                    |this, cx| {
+                        if let Some(workspace) = this.workspace.upgrade(cx) {
+                            workspace.update(cx, |workspace, cx| {
+                                Dock::hide_dock(workspace, &Default::default(), cx)
+                            })
+                        }
+                    },
                     None,
                 )
             }))
@@ -1693,8 +1703,8 @@ impl View for Pane {
                             })
                             .on_down(
                                 MouseButton::Left,
-                                move |_, _, cx| {
-                                    cx.dispatch_action(ActivateItem(active_item_index));
+                                move |_, this, cx| {
+                                    this.activate_item(active_item_index, true, true, cx);
                                 },
                             ),
                         );
@@ -1759,15 +1769,27 @@ impl View for Pane {
         })
         .on_down(
             MouseButton::Navigate(NavigationDirection::Back),
-            move |_, _, cx| {
-                let pane = cx.weak_handle();
-                cx.dispatch_action(GoBack { pane: Some(pane) });
+            move |_, pane, cx| {
+                if let Some(workspace) = pane.workspace.upgrade(cx) {
+                    let pane = cx.weak_handle();
+                    cx.window_context().defer(move |cx| {
+                        workspace.update(cx, |workspace, cx| {
+                            Pane::go_back(workspace, Some(pane), cx).detach_and_log_err(cx)
+                        })
+                    })
+                }
             },
         )
         .on_down(MouseButton::Navigate(NavigationDirection::Forward), {
-            move |_, _, cx| {
-                let pane = cx.weak_handle();
-                cx.dispatch_action(GoForward { pane: Some(pane) })
+            move |_, pane, cx| {
+                if let Some(workspace) = pane.workspace.upgrade(cx) {
+                    let pane = cx.weak_handle();
+                    cx.window_context().defer(move |cx| {
+                        workspace.update(cx, |workspace, cx| {
+                            Pane::go_forward(workspace, Some(pane), cx).detach_and_log_err(cx)
+                        })
+                    })
+                }
             }
         })
         .into_any_named("pane")

crates/workspace/src/sidebar.rs 🔗

@@ -279,9 +279,9 @@ impl View for SidebarButtons {
                             .with_style(style.container)
                     })
                     .with_cursor_style(CursorStyle::PointingHand)
-                    .on_click(MouseButton::Left, {
-                        let action = action.clone();
-                        move |_, _, cx| cx.dispatch_action(action.clone())
+                    .on_click(MouseButton::Left, move |_, this, cx| {
+                        this.sidebar
+                            .update(cx, |sidebar, cx| sidebar.toggle_item(ix, cx));
                     })
                     .with_tooltip::<Self>(
                         ix,

crates/workspace/src/toolbar.rs 🔗

@@ -130,8 +130,20 @@ impl View for Toolbar {
                         tooltip_style.clone(),
                         enable_go_backward,
                         spacing,
-                        super::GoBack {
-                            pane: Some(pane.clone()),
+                        {
+                            let pane = pane.clone();
+                            move |toolbar, cx| {
+                                if let Some(workspace) = toolbar
+                                    .pane
+                                    .upgrade(cx)
+                                    .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
+                                {
+                                    workspace.update(cx, |workspace, cx| {
+                                        Pane::go_back(workspace, Some(pane.clone()), cx)
+                                            .detach_and_log_err(cx);
+                                    });
+                                }
+                            }
                         },
                         super::GoBack { pane: None },
                         "Go Back",
@@ -143,7 +155,21 @@ impl View for Toolbar {
                         tooltip_style,
                         enable_go_forward,
                         spacing,
-                        super::GoForward { pane: Some(pane) },
+                        {
+                            let pane = pane.clone();
+                            move |toolbar, cx| {
+                                if let Some(workspace) = toolbar
+                                    .pane
+                                    .upgrade(cx)
+                                    .and_then(|pane| pane.read(cx).workspace().upgrade(cx))
+                                {
+                                    workspace.update(cx, |workspace, cx| {
+                                        Pane::go_forward(workspace, Some(pane.clone()), cx)
+                                            .detach_and_log_err(cx);
+                                    });
+                                }
+                            }
+                        },
                         super::GoForward { pane: None },
                         "Go Forward",
                         cx,
@@ -161,13 +187,13 @@ impl View for Toolbar {
 }
 
 #[allow(clippy::too_many_arguments)]
-fn nav_button<A: Action + Clone>(
+fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
     svg_path: &'static str,
     style: theme::Interactive<theme::IconButton>,
     tooltip_style: TooltipStyle,
     enabled: bool,
     spacing: f32,
-    action: A,
+    on_click: F,
     tooltip_action: A,
     action_name: &str,
     cx: &mut ViewContext<Toolbar>,
@@ -195,8 +221,8 @@ fn nav_button<A: Action + Clone>(
     } else {
         CursorStyle::default()
     })
-    .on_click(MouseButton::Left, move |_, _, cx| {
-        cx.dispatch_action(action.clone())
+    .on_click(MouseButton::Left, move |_, toolbar, cx| {
+        on_click(toolbar, cx)
     })
     .with_tooltip::<A>(
         0,

crates/workspace/src/workspace.rs 🔗

@@ -208,48 +208,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
             }
         }
     });
-    cx.add_action({
-        let app_state = Arc::downgrade(&app_state);
-        move |_, _: &Open, cx: &mut ViewContext<Workspace>| {
-            let mut paths = cx.prompt_for_paths(PathPromptOptions {
-                files: true,
-                directories: true,
-                multiple: true,
-            });
-
-            if let Some(app_state) = app_state.upgrade() {
-                cx.spawn(|this, mut cx| async move {
-                    if let Some(paths) = paths.recv().await.flatten() {
-                        if let Some(task) = this
-                            .update(&mut cx, |this, cx| {
-                                this.open_workspace_for_paths(paths, app_state, cx)
-                            })
-                            .log_err()
-                        {
-                            task.await.log_err();
-                        }
-                    }
-                })
-                .detach();
-            }
-        }
-    });
-    cx.add_global_action({
-        let app_state = Arc::downgrade(&app_state);
-        move |_: &NewWindow, cx: &mut AppContext| {
-            if let Some(app_state) = app_state.upgrade() {
-                open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach();
-            }
-        }
-    });
-    cx.add_global_action({
-        let app_state = Arc::downgrade(&app_state);
-        move |_: &NewFile, cx: &mut AppContext| {
-            if let Some(app_state) = app_state.upgrade() {
-                open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach();
-            }
-        }
-    });
+    cx.add_async_action(Workspace::open);
 
     cx.add_async_action(Workspace::follow_next_collaborator);
     cx.add_async_action(Workspace::close);
@@ -913,7 +872,6 @@ impl Workspace {
     /// to the callback. Otherwise, a new empty window will be created.
     pub fn with_local_workspace<T, F>(
         &mut self,
-        app_state: &Arc<AppState>,
         cx: &mut ViewContext<Self>,
         callback: F,
     ) -> Task<Result<T>>
@@ -924,7 +882,7 @@ impl Workspace {
         if self.project.read(cx).is_local() {
             Task::Ready(Some(Ok(callback(self, cx))))
         } else {
-            let task = Self::new_local(Vec::new(), app_state.clone(), None, cx);
+            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
             cx.spawn(|_vh, mut cx| async move {
                 let (workspace, _) = task.await;
                 workspace.update(&mut cx, callback)
@@ -1093,10 +1051,29 @@ impl Workspace {
         })
     }
 
+    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
+        let mut paths = cx.prompt_for_paths(PathPromptOptions {
+            files: true,
+            directories: true,
+            multiple: true,
+        });
+
+        Some(cx.spawn(|this, mut cx| async move {
+            if let Some(paths) = paths.recv().await.flatten() {
+                if let Some(task) = this
+                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
+                    .log_err()
+                {
+                    task.await?
+                }
+            }
+            Ok(())
+        }))
+    }
+
     pub fn open_workspace_for_paths(
         &mut self,
         paths: Vec<PathBuf>,
-        app_state: Arc<AppState>,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
         let window_id = cx.window_id();
@@ -1108,6 +1085,7 @@ impl Workspace {
         } else {
             Some(self.prepare_to_close(false, cx))
         };
+        let app_state = self.app_state.clone();
 
         cx.spawn(|_, mut cx| async move {
             let window_id_to_replace = if let Some(close_task) = close_task {

crates/zed/src/main.rs 🔗

@@ -10,6 +10,7 @@ use cli::{
 };
 use client::{self, UserStore, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
 use db::kvp::KEY_VALUE_STORE;
+use editor::Editor;
 use futures::{
     channel::{mpsc, oneshot},
     FutureExt, SinkExt, StreamExt,
@@ -51,8 +52,7 @@ use staff_mode::StaffMode;
 use theme::ThemeRegistry;
 use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
 use workspace::{
-    self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile,
-    Workspace,
+    self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace,
 };
 use zed::{self, build_window_options, initialize_workspace, languages, menus, OpenSettings};
 
@@ -115,7 +115,10 @@ fn main() {
     .on_reopen(move |cx| {
         if cx.has_global::<Weak<AppState>>() {
             if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() {
-                workspace::open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach();
+                workspace::open_new(&app_state, cx, |workspace, cx| {
+                    Editor::new_file(workspace, &Default::default(), cx)
+                })
+                .detach();
             }
         }
     });
@@ -208,14 +211,14 @@ fn main() {
         auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
 
         workspace::init(app_state.clone(), cx);
-        recent_projects::init(cx, Arc::downgrade(&app_state));
+        recent_projects::init(cx);
 
         journal::init(app_state.clone(), cx);
-        language_selector::init(app_state.clone(), cx);
-        theme_selector::init(app_state.clone(), cx);
+        language_selector::init(cx);
+        theme_selector::init(cx);
         zed::init(&app_state, cx);
         collab_ui::init(&app_state, cx);
-        feedback::init(app_state.clone(), cx);
+        feedback::init(cx);
         welcome::init(cx);
 
         cx.set_menus(menus::menus());
@@ -289,7 +292,10 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
         cx.update(|cx| show_welcome_experience(app_state, cx));
     } else {
         cx.update(|cx| {
-            workspace::open_new(app_state, cx, |_, cx| cx.dispatch_action(NewFile)).detach();
+            workspace::open_new(app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+            .detach();
         });
     }
 }

crates/zed/src/zed.rs 🔗

@@ -20,7 +20,7 @@ use gpui::{
     geometry::vector::vec2f,
     impl_actions,
     platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions},
-    AssetSource, ViewContext,
+    AppContext, AssetSource, ViewContext,
 };
 use language::Rope;
 pub use lsp;
@@ -35,7 +35,7 @@ use terminal_view::terminal_button::TerminalButton;
 use util::{channel::ReleaseChannel, paths, ResultExt};
 use uuid::Uuid;
 pub use workspace;
-use workspace::{sidebar::SidebarSide, AppState, Workspace};
+use workspace::{open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, Workspace};
 
 #[derive(Deserialize, Clone, PartialEq)]
 pub struct OpenBrowser {
@@ -147,10 +147,9 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
         })
         .detach_and_log_err(cx);
     });
-    cx.add_action({
-        let app_state = app_state.clone();
-        move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
-            open_config_file(&paths::SETTINGS, app_state.clone(), cx, || {
+    cx.add_action(
+        move |workspace: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
+            open_config_file(workspace, &paths::SETTINGS, cx, || {
                 str::from_utf8(
                     Assets
                         .load("settings/initial_user_settings.json")
@@ -160,73 +159,68 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
                 .unwrap()
                 .into()
             });
-        }
-    });
-    cx.add_action({
-        let app_state = app_state.clone();
+        },
+    );
+    cx.add_action(
         move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext<Workspace>| {
-            open_log_file(workspace, app_state.clone(), cx);
-        }
-    });
-    cx.add_action({
-        let app_state = app_state.clone();
-        move |_: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
+            open_log_file(workspace, cx);
+        },
+    );
+    cx.add_action(
+        move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
             open_bundled_file(
-                app_state.clone(),
+                workspace,
                 "licenses.md",
                 "Open Source License Attribution",
                 "Markdown",
                 cx,
             );
-        }
-    });
-    cx.add_action({
-        let app_state = app_state.clone();
+        },
+    );
+    cx.add_action(
         move |workspace: &mut Workspace, _: &OpenTelemetryLog, cx: &mut ViewContext<Workspace>| {
-            open_telemetry_log_file(workspace, app_state.clone(), cx);
-        }
-    });
-    cx.add_action({
-        let app_state = app_state.clone();
-        move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
-            open_config_file(&paths::KEYMAP, app_state.clone(), cx, Default::default);
-        }
-    });
-    cx.add_action({
-        let app_state = app_state.clone();
-        move |_: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
+            open_telemetry_log_file(workspace, cx);
+        },
+    );
+    cx.add_action(
+        move |workspace: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
+            open_config_file(workspace, &paths::KEYMAP, cx, Default::default);
+        },
+    );
+    cx.add_action(
+        move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
             open_bundled_file(
-                app_state.clone(),
+                workspace,
                 "keymaps/default.json",
                 "Default Key Bindings",
                 "JSON",
                 cx,
             );
-        }
-    });
-    cx.add_action({
-        let app_state = app_state.clone();
-        move |_: &mut Workspace, _: &OpenDefaultSettings, cx: &mut ViewContext<Workspace>| {
+        },
+    );
+    cx.add_action(
+        move |workspace: &mut Workspace,
+              _: &OpenDefaultSettings,
+              cx: &mut ViewContext<Workspace>| {
             open_bundled_file(
-                app_state.clone(),
+                workspace,
                 "settings/default.json",
                 "Default Settings",
                 "JSON",
                 cx,
             );
-        }
-    });
+        },
+    );
     cx.add_action({
-        let app_state = app_state.clone();
-        move |_: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
-            let app_state = app_state.clone();
+        move |workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
+            let app_state = workspace.app_state().clone();
             let markdown = app_state.languages.language_for_name("JSON");
             let content = to_string_pretty(&cx.debug_elements()).unwrap();
             cx.spawn(|workspace, mut cx| async move {
                 let markdown = markdown.await.log_err();
                 workspace
                     .update(&mut cx, |workspace, cx| {
-                        workspace.with_local_workspace(&app_state, cx, move |workspace, cx| {
+                        workspace.with_local_workspace(cx, move |workspace, cx| {
                             let project = workspace.project().clone();
 
                             let buffer = project
@@ -258,6 +252,28 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
             workspace.toggle_sidebar_item_focus(SidebarSide::Left, 0, cx);
         },
     );
+    cx.add_global_action({
+        let app_state = Arc::downgrade(&app_state);
+        move |_: &NewWindow, cx: &mut AppContext| {
+            if let Some(app_state) = app_state.upgrade() {
+                open_new(&app_state, cx, |workspace, cx| {
+                    Editor::new_file(workspace, &Default::default(), cx)
+                })
+                .detach();
+            }
+        }
+    });
+    cx.add_global_action({
+        let app_state = Arc::downgrade(&app_state);
+        move |_: &NewFile, cx: &mut AppContext| {
+            if let Some(app_state) = app_state.upgrade() {
+                open_new(&app_state, cx, |workspace, cx| {
+                    Editor::new_file(workspace, &Default::default(), cx)
+                })
+                .detach();
+            }
+        }
+    });
     activity_indicator::init(cx);
     lsp_log::init(cx);
     call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
@@ -275,7 +291,7 @@ pub fn initialize_workspace(
             if let workspace::Event::PaneAdded(pane) = event {
                 pane.update(cx, |pane, cx| {
                     pane.toolbar().update(cx, |toolbar, cx| {
-                        let breadcrumbs = cx.add_view(|_| Breadcrumbs::new());
+                        let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
                         toolbar.add_item(breadcrumbs, cx);
                         let buffer_search_bar = cx.add_view(BufferSearchBar::new);
                         toolbar.add_item(buffer_search_bar, cx);
@@ -304,7 +320,7 @@ pub fn initialize_workspace(
     });
     workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
 
-    let project_panel = ProjectPanel::new(workspace.project().clone(), cx);
+    let project_panel = ProjectPanel::new(workspace, cx);
     workspace.left_sidebar().update(cx, |sidebar, cx| {
         sidebar.add_item(
             "icons/folder_tree_16.svg",
@@ -317,12 +333,13 @@ pub fn initialize_workspace(
     let toggle_terminal = cx.add_view(|cx| TerminalButton::new(workspace_handle.clone(), cx));
     let copilot = cx.add_view(|cx| copilot_button::CopilotButton::new(cx));
     let diagnostic_summary =
-        cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace.project(), cx));
+        cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
     let activity_indicator =
         activity_indicator::ActivityIndicator::new(workspace, app_state.languages.clone(), cx);
-    let active_buffer_language = cx.add_view(|_| language_selector::ActiveBufferLanguage::new());
+    let active_buffer_language =
+        cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
     let feedback_button =
-        cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new());
+        cx.add_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace));
     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
     workspace.status_bar().update(cx, |status_bar, cx| {
         status_bar.add_left_item(diagnostic_summary, cx);
@@ -428,13 +445,13 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
 }
 
 fn open_config_file(
+    workspace: &mut Workspace,
     path: &'static Path,
-    app_state: Arc<AppState>,
     cx: &mut ViewContext<Workspace>,
     default_content: impl 'static + Send + FnOnce() -> Rope,
 ) {
+    let fs = workspace.app_state().fs.clone();
     cx.spawn(|workspace, mut cx| async move {
-        let fs = &app_state.fs;
         if !fs.is_file(path).await {
             fs.create_file(path, Default::default()).await?;
             fs.save(path, &default_content(), Default::default())
@@ -443,7 +460,7 @@ fn open_config_file(
 
         workspace
             .update(&mut cx, |workspace, cx| {
-                workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
+                workspace.with_local_workspace(cx, |workspace, cx| {
                     workspace.open_paths(vec![path.to_path_buf()], false, cx)
                 })
             })?
@@ -454,20 +471,15 @@ fn open_config_file(
     .detach_and_log_err(cx)
 }
 
-fn open_log_file(
-    workspace: &mut Workspace,
-    app_state: Arc<AppState>,
-    cx: &mut ViewContext<Workspace>,
-) {
+fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
     const MAX_LINES: usize = 1000;
 
     workspace
-        .with_local_workspace(&app_state.clone(), cx, move |_, cx| {
+        .with_local_workspace(cx, move |workspace, cx| {
+            let fs = workspace.app_state().fs.clone();
             cx.spawn(|workspace, mut cx| async move {
-                let (old_log, new_log) = futures::join!(
-                    app_state.fs.load(&paths::OLD_LOG),
-                    app_state.fs.load(&paths::LOG)
-                );
+                let (old_log, new_log) =
+                    futures::join!(fs.load(&paths::OLD_LOG), fs.load(&paths::LOG));
 
                 let mut lines = VecDeque::with_capacity(MAX_LINES);
                 for line in old_log
@@ -512,12 +524,9 @@ fn open_log_file(
         .detach();
 }
 
-fn open_telemetry_log_file(
-    workspace: &mut Workspace,
-    app_state: Arc<AppState>,
-    cx: &mut ViewContext<Workspace>,
-) {
-    workspace.with_local_workspace(&app_state.clone(), cx, move |_, cx| {
+fn open_telemetry_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
+    workspace.with_local_workspace(cx, move |workspace, cx| {
+        let app_state = workspace.app_state().clone();
         cx.spawn(|workspace, mut cx| async move {
             async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
                 let path = app_state.client.telemetry().log_file_path()?;
@@ -573,18 +582,18 @@ fn open_telemetry_log_file(
 }
 
 fn open_bundled_file(
-    app_state: Arc<AppState>,
+    workspace: &mut Workspace,
     asset_path: &'static str,
     title: &'static str,
     language: &'static str,
     cx: &mut ViewContext<Workspace>,
 ) {
-    let language = app_state.languages.language_for_name(language);
+    let language = workspace.app_state().languages.language_for_name(language);
     cx.spawn(|workspace, mut cx| async move {
         let language = language.await.log_err();
         workspace
             .update(&mut cx, |workspace, cx| {
-                workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
+                workspace.with_local_workspace(cx, |workspace, cx| {
                     let project = workspace.project();
                     let buffer = project.update(cx, |project, cx| {
                         let text = Assets::get(asset_path)
@@ -815,8 +824,12 @@ mod tests {
     #[gpui::test]
     async fn test_new_empty_workspace(cx: &mut TestAppContext) {
         let app_state = init(cx);
-        cx.update(|cx| open_new(&app_state, cx, |_, cx| cx.dispatch_action(NewFile)))
-            .await;
+        cx.update(|cx| {
+            open_new(&app_state, cx, |workspace, cx| {
+                Editor::new_file(workspace, &Default::default(), cx)
+            })
+        })
+        .await;
 
         let window_id = *cx.window_ids().first().unwrap();
         let workspace = cx