Remove `ViewContext::dispatch_any_action`

Antonio Scandurra created

Change summary

crates/activity_indicator/src/activity_indicator.rs |  40 ++-
crates/copilot/src/copilot.rs                       |   2 
crates/copilot_button/src/copilot_button.rs         |  16 +
crates/editor/src/editor.rs                         |  12 
crates/gpui/src/app.rs                              |   9 
crates/recent_projects/src/recent_projects.rs       |   2 
crates/search/src/buffer_search.rs                  |  24 +
crates/search/src/project_search.rs                 |  11 
crates/workspace/src/notifications.rs               |  84 +++-----
crates/workspace/src/workspace.rs                   | 144 +++++++++-----
crates/zed/src/zed.rs                               |  55 -----
11 files changed, 197 insertions(+), 202 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs πŸ”—

@@ -5,7 +5,7 @@ use gpui::{
     actions, anyhow,
     elements::*,
     platform::{CursorStyle, MouseButton},
-    Action, AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
+    AppContext, Entity, ModelHandle, View, ViewContext, ViewHandle,
 };
 use language::{LanguageRegistry, LanguageServerBinaryStatus};
 use project::{LanguageServerProgress, Project};
@@ -45,7 +45,7 @@ struct PendingWork<'a> {
 struct Content {
     icon: Option<&'static str>,
     message: String,
-    action: Option<Box<dyn Action>>,
+    on_click: Option<Arc<dyn Fn(&mut ActivityIndicator, &mut ViewContext<ActivityIndicator>)>>,
 }
 
 pub fn init(cx: &mut AppContext) {
@@ -199,7 +199,7 @@ impl ActivityIndicator {
             return Content {
                 icon: None,
                 message,
-                action: None,
+                on_click: None,
             };
         }
 
@@ -230,7 +230,7 @@ impl ActivityIndicator {
                     downloading.join(", "),
                     if downloading.len() > 1 { "s" } else { "" }
                 ),
-                action: None,
+                on_click: None,
             };
         } else if !checking_for_update.is_empty() {
             return Content {
@@ -244,7 +244,7 @@ impl ActivityIndicator {
                         ""
                     }
                 ),
-                action: None,
+                on_click: None,
             };
         } else if !failed.is_empty() {
             return Content {
@@ -254,7 +254,9 @@ impl ActivityIndicator {
                     failed.join(", "),
                     if failed.len() > 1 { "s" } else { "" }
                 ),
-                action: Some(Box::new(ShowErrorMessage)),
+                on_click: Some(Arc::new(|this, cx| {
+                    this.show_error_message(&Default::default(), cx)
+                })),
             };
         }
 
@@ -264,27 +266,31 @@ impl ActivityIndicator {
                 AutoUpdateStatus::Checking => Content {
                     icon: Some(DOWNLOAD_ICON),
                     message: "Checking for Zed updates…".to_string(),
-                    action: None,
+                    on_click: None,
                 },
                 AutoUpdateStatus::Downloading => Content {
                     icon: Some(DOWNLOAD_ICON),
                     message: "Downloading Zed update…".to_string(),
-                    action: None,
+                    on_click: None,
                 },
                 AutoUpdateStatus::Installing => Content {
                     icon: Some(DOWNLOAD_ICON),
                     message: "Installing Zed update…".to_string(),
-                    action: None,
+                    on_click: None,
                 },
                 AutoUpdateStatus::Updated => Content {
                     icon: None,
                     message: "Click to restart and update Zed".to_string(),
-                    action: Some(Box::new(workspace::Restart)),
+                    on_click: Some(Arc::new(|_, cx| {
+                        workspace::restart(&Default::default(), cx)
+                    })),
                 },
                 AutoUpdateStatus::Errored => Content {
                     icon: Some(WARNING_ICON),
                     message: "Auto update failed".to_string(),
-                    action: Some(Box::new(DismissErrorMessage)),
+                    on_click: Some(Arc::new(|this, cx| {
+                        this.dismiss_error_message(&Default::default(), cx)
+                    })),
                 },
                 AutoUpdateStatus::Idle => Default::default(),
             };
@@ -294,7 +300,7 @@ impl ActivityIndicator {
             return Content {
                 icon: None,
                 message: most_recent_active_task.to_string(),
-                action: None,
+                on_click: None,
             };
         }
 
@@ -315,7 +321,7 @@ impl View for ActivityIndicator {
         let Content {
             icon,
             message,
-            action,
+            on_click,
         } = self.content_to_render(cx);
 
         let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
@@ -325,7 +331,7 @@ impl View for ActivityIndicator {
                 .workspace
                 .status_bar
                 .lsp_status;
-            let style = if state.hovered() && action.is_some() {
+            let style = if state.hovered() && on_click.is_some() {
                 theme.hover.as_ref().unwrap_or(&theme.default)
             } else {
                 &theme.default
@@ -353,12 +359,10 @@ impl View for ActivityIndicator {
                 .aligned()
         });
 
-        if let Some(action) = action {
+        if let Some(on_click) = on_click.clone() {
             element = element
                 .with_cursor_style(CursorStyle::PointingHand)
-                .on_click(MouseButton::Left, move |_, _, cx| {
-                    cx.dispatch_any_action(action.boxed_clone())
-                });
+                .on_click(MouseButton::Left, move |_, this, cx| on_click(this, cx));
         }
 
         element.into_any()

crates/copilot/src/copilot.rs πŸ”—

@@ -560,7 +560,7 @@ impl Copilot {
         }
     }
 
-    fn reinstall(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
+    pub fn reinstall(&mut self, cx: &mut ModelContext<Self>) -> Task<()> {
         let start_task = cx
             .spawn({
                 let http = self.http.clone();

crates/copilot_button/src/copilot_button.rs πŸ”—

@@ -1,5 +1,5 @@
 use context_menu::{ContextMenu, ContextMenuItem};
-use copilot::{Copilot, Reinstall, SignOut, Status};
+use copilot::{Copilot, SignOut, Status};
 use editor::Editor;
 use gpui::{
     elements::*,
@@ -103,11 +103,21 @@ impl View for CopilotButton {
                             {
                                 workspace.update(cx, |workspace, cx| {
                                     workspace.show_toast(
-                                        Toast::new_action(
+                                        Toast::new(
                                             COPILOT_ERROR_TOAST_ID,
                                             format!("Copilot can't be started: {}", e),
+                                        )
+                                        .on_click(
                                             "Reinstall Copilot",
-                                            Reinstall,
+                                            |cx| {
+                                                if let Some(copilot) = Copilot::global(cx) {
+                                                    copilot
+                                                        .update(cx, |copilot, cx| {
+                                                            copilot.reinstall(cx)
+                                                        })
+                                                        .detach();
+                                                }
+                                            },
                                         ),
                                         cx,
                                     );

crates/editor/src/editor.rs πŸ”—

@@ -3199,11 +3199,13 @@ impl Editor {
                             .with_cursor_style(CursorStyle::PointingHand)
                             .with_padding(Padding::uniform(3.))
                             .on_click(MouseButton::Left, {
-                                move |_, _, cx| {
-                                    cx.dispatch_any_action(match fold_status {
-                                        FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }),
-                                        FoldStatus::Foldable => Box::new(FoldAt { buffer_row }),
-                                    });
+                                move |_, editor, cx| match fold_status {
+                                    FoldStatus::Folded => {
+                                        editor.unfold_at(&UnfoldAt { buffer_row }, cx);
+                                    }
+                                    FoldStatus::Foldable => {
+                                        editor.fold_at(&FoldAt { buffer_row }, cx);
+                                    }
                                 }
                             })
                             .into_any()

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

@@ -1745,7 +1745,7 @@ impl AppContext {
         self.pending_effects.push_back(Effect::RefreshWindows);
     }
 
-    pub fn dispatch_action_at(&mut self, window_id: usize, view_id: usize, action: impl Action) {
+    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));
     }
 
@@ -3196,13 +3196,6 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
             .dispatch_action_at(window_id, view_id, action)
     }
 
-    pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
-        let window_id = self.window_id;
-        let view_id = self.view_id;
-        self.window_context
-            .dispatch_any_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/recent_projects/src/recent_projects.rs πŸ”—

@@ -61,7 +61,7 @@ fn toggle(app_state: Weak<AppState>, cx: &mut ViewContext<Workspace>) -> Option<
                 });
             } else {
                 workspace.show_notification(0, cx, |cx| {
-                    cx.add_view(|_| MessageNotification::new_message("No recent projects to open."))
+                    cx.add_view(|_| MessageNotification::new("No recent projects to open."))
                 })
             }
         })?;

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

@@ -338,8 +338,8 @@ impl BufferSearchBar {
                     .contained()
                     .with_style(style.container)
             })
-            .on_click(MouseButton::Left, move |_, _, cx| {
-                cx.dispatch_any_action(option.to_toggle_action())
+            .on_click(MouseButton::Left, move |_, this, cx| {
+                this.toggle_search_option(option, cx);
             })
             .with_cursor_style(CursorStyle::PointingHand)
             .with_tooltip::<Self>(
@@ -386,8 +386,10 @@ impl BufferSearchBar {
                 .with_style(style.container)
         })
         .on_click(MouseButton::Left, {
-            let action = action.boxed_clone();
-            move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
+            move |_, this, cx| match direction {
+                Direction::Prev => this.select_prev_match(&Default::default(), cx),
+                Direction::Next => this.select_next_match(&Default::default(), cx),
+            }
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .with_tooltip::<NavButton>(
@@ -405,7 +407,6 @@ impl BufferSearchBar {
         theme: &theme::Search,
         cx: &mut ViewContext<Self>,
     ) -> AnyElement<Self> {
-        let action = Box::new(Dismiss);
         let tooltip = "Dismiss Buffer Search";
         let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
 
@@ -422,12 +423,17 @@ impl BufferSearchBar {
                 .contained()
                 .with_style(style.container)
         })
-        .on_click(MouseButton::Left, {
-            let action = action.boxed_clone();
-            move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
+        .on_click(MouseButton::Left, move |_, this, cx| {
+            this.dismiss(&Default::default(), cx)
         })
         .with_cursor_style(CursorStyle::PointingHand)
-        .with_tooltip::<CloseButton>(0, tooltip.to_string(), Some(action), tooltip_style, cx)
+        .with_tooltip::<CloseButton>(
+            0,
+            tooltip.to_string(),
+            Some(Box::new(Dismiss)),
+            tooltip_style,
+            cx,
+        )
         .into_any()
     }
 

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

@@ -788,9 +788,10 @@ impl ProjectSearchBar {
                 .contained()
                 .with_style(style.container)
         })
-        .on_click(MouseButton::Left, {
-            let action = action.boxed_clone();
-            move |_, _, cx| cx.dispatch_any_action(action.boxed_clone())
+        .on_click(MouseButton::Left, move |_, this, cx| {
+            if let Some(search) = this.active_project_search.as_ref() {
+                search.update(cx, |search, cx| search.select_match(direction, cx));
+            }
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .with_tooltip::<NavButton>(
@@ -822,8 +823,8 @@ impl ProjectSearchBar {
                 .contained()
                 .with_style(style.container)
         })
-        .on_click(MouseButton::Left, move |_, _, cx| {
-            cx.dispatch_any_action(option.to_toggle_action())
+        .on_click(MouseButton::Left, move |_, this, cx| {
+            this.toggle_search_option(option, cx);
         })
         .with_cursor_style(CursorStyle::PointingHand)
         .with_tooltip::<Self>(

crates/workspace/src/notifications.rs πŸ”—

@@ -114,17 +114,14 @@ impl Workspace {
     pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
         self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
         self.show_notification(toast.id, cx, |cx| {
-            cx.add_view(|_cx| match &toast.click {
-                Some((click_msg, action)) => {
-                    simple_message_notification::MessageNotification::new_boxed_action(
-                        toast.msg.clone(),
-                        action.boxed_clone(),
-                        click_msg.clone(),
-                    )
-                }
-                None => {
-                    simple_message_notification::MessageNotification::new_message(toast.msg.clone())
+            cx.add_view(|_cx| match toast.on_click.as_ref() {
+                Some((click_msg, on_click)) => {
+                    let on_click = on_click.clone();
+                    simple_message_notification::MessageNotification::new(toast.msg.clone())
+                        .with_click_message(click_msg.clone())
+                        .on_click(move |cx| on_click(cx))
                 }
+                None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
             })
         })
     }
@@ -152,19 +149,17 @@ impl Workspace {
 }
 
 pub mod simple_message_notification {
-
-    use std::borrow::Cow;
-
     use gpui::{
         actions,
         elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
         impl_actions,
         platform::{CursorStyle, MouseButton},
-        Action, AppContext, Element, Entity, View, ViewContext,
+        AppContext, Element, Entity, View, ViewContext,
     };
     use menu::Cancel;
     use serde::Deserialize;
     use settings::Settings;
+    use std::{borrow::Cow, sync::Arc};
 
     use crate::Workspace;
 
@@ -194,7 +189,7 @@ pub mod simple_message_notification {
 
     pub struct MessageNotification {
         message: Cow<'static, str>,
-        click_action: Option<Box<dyn Action>>,
+        on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
         click_message: Option<Cow<'static, str>>,
     }
 
@@ -207,36 +202,31 @@ pub mod simple_message_notification {
     }
 
     impl MessageNotification {
-        pub fn new_message<S: Into<Cow<'static, str>>>(message: S) -> MessageNotification {
+        pub fn new<S>(message: S) -> MessageNotification
+        where
+            S: Into<Cow<'static, str>>,
+        {
             Self {
                 message: message.into(),
-                click_action: None,
+                on_click: None,
                 click_message: None,
             }
         }
 
-        pub fn new_boxed_action<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
-            message: S1,
-            click_action: Box<dyn Action>,
-            click_message: S2,
-        ) -> Self {
-            Self {
-                message: message.into(),
-                click_action: Some(click_action),
-                click_message: Some(click_message.into()),
-            }
+        pub fn with_click_message<S>(mut self, message: S) -> Self
+        where
+            S: Into<Cow<'static, str>>,
+        {
+            self.click_message = Some(message.into());
+            self
         }
 
-        pub fn new<S1: Into<Cow<'static, str>>, A: Action, S2: Into<Cow<'static, str>>>(
-            message: S1,
-            click_action: A,
-            click_message: S2,
-        ) -> Self {
-            Self {
-                message: message.into(),
-                click_action: Some(Box::new(click_action) as Box<dyn Action>),
-                click_message: Some(click_message.into()),
-            }
+        pub fn on_click<F>(mut self, on_click: F) -> Self
+        where
+            F: 'static + Fn(&mut ViewContext<Self>),
+        {
+            self.on_click = Some(Arc::new(on_click));
+            self
         }
 
         pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
@@ -255,14 +245,10 @@ pub mod simple_message_notification {
 
             enum MessageNotificationTag {}
 
-            let click_action = self
-                .click_action
-                .as_ref()
-                .map(|action| action.boxed_clone());
-            let click_message = self.click_message.as_ref().map(|message| message.clone());
+            let click_message = self.click_message.clone();
             let message = self.message.clone();
-
-            let has_click_action = click_action.is_some();
+            let on_click = self.on_click.clone();
+            let has_click_action = on_click.is_some();
 
             MouseEventHandler::<MessageNotificationTag, _>::new(0, cx, |state, cx| {
                 Flex::column()
@@ -326,10 +312,10 @@ pub mod simple_message_notification {
             // Since we're not using a proper overlay, we have to capture these extra events
             .on_down(MouseButton::Left, |_, _, _| {})
             .on_up(MouseButton::Left, |_, _, _| {})
-            .on_click(MouseButton::Left, move |_, _, cx| {
-                if let Some(click_action) = click_action.as_ref() {
-                    cx.dispatch_any_action(click_action.boxed_clone());
-                    cx.dispatch_action(CancelMessageNotification)
+            .on_click(MouseButton::Left, move |_, this, cx| {
+                if let Some(on_click) = on_click.as_ref() {
+                    on_click(cx);
+                    this.dismiss(&Default::default(), cx);
                 }
             })
             .with_cursor_style(if has_click_action {
@@ -372,7 +358,7 @@ where
             Err(err) => {
                 workspace.show_notification(0, cx, |cx| {
                     cx.add_view(|_cx| {
-                        simple_message_notification::MessageNotification::new_message(format!(
+                        simple_message_notification::MessageNotification::new(format!(
                             "Error: {:?}",
                             err,
                         ))

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

@@ -43,8 +43,9 @@ use gpui::{
         CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
         WindowOptions,
     },
-    Action, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext,
-    ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle,
+    SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    WindowContext,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
 use language::LanguageRegistry;
@@ -59,7 +60,7 @@ use std::{
 };
 
 use crate::{
-    notifications::simple_message_notification::{MessageNotification, OsOpen},
+    notifications::simple_message_notification::MessageNotification,
     persistence::model::{SerializedPane, SerializedPaneGroup, SerializedWorkspace},
 };
 use lazy_static::lazy_static;
@@ -137,7 +138,7 @@ pub struct ActivatePane(pub usize);
 pub struct Toast {
     id: usize,
     msg: Cow<'static, str>,
-    click: Option<(Cow<'static, str>, Box<dyn Action>)>,
+    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 }
 
 impl Toast {
@@ -145,21 +146,17 @@ impl Toast {
         Toast {
             id,
             msg: msg.into(),
-            click: None,
+            on_click: None,
         }
     }
 
-    pub fn new_action<I1: Into<Cow<'static, str>>, I2: Into<Cow<'static, str>>>(
-        id: usize,
-        msg: I1,
-        click_msg: I2,
-        action: impl Action,
-    ) -> Self {
-        Toast {
-            id,
-            msg: msg.into(),
-            click: Some((click_msg.into(), Box::new(action))),
-        }
+    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
+    where
+        M: Into<Cow<'static, str>>,
+        F: Fn(&mut WindowContext) + 'static,
+    {
+        self.on_click = Some((message.into(), Arc::new(on_click)));
+        self
     }
 }
 
@@ -167,7 +164,7 @@ impl PartialEq for Toast {
     fn eq(&self, other: &Self) -> bool {
         self.id == other.id
             && self.msg == other.msg
-            && self.click.is_some() == other.click.is_some()
+            && self.on_click.is_some() == other.on_click.is_some()
     }
 }
 
@@ -176,10 +173,7 @@ impl Clone for Toast {
         Toast {
             id: self.id,
             msg: self.msg.to_owned(),
-            click: self
-                .click
-                .as_ref()
-                .map(|(msg, click)| (msg.to_owned(), click.boxed_clone())),
+            on_click: self.on_click.clone(),
         }
     }
 }
@@ -260,6 +254,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.add_async_action(Workspace::follow_next_collaborator);
     cx.add_async_action(Workspace::close);
     cx.add_global_action(Workspace::close_global);
+    cx.add_global_action(restart);
     cx.add_async_action(Workspace::save_all);
     cx.add_action(Workspace::add_folder_to_project);
     cx.add_action(
@@ -303,9 +298,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
                 } else {
                     workspace.show_notification(1, cx, |cx| {
                         cx.add_view(|_| {
-                            MessageNotification::new_message(
-                                "Successfully installed the `zed` binary",
-                            )
+                            MessageNotification::new("Successfully installed the `zed` binary")
                         })
                     });
                 }
@@ -2668,36 +2661,37 @@ impl Workspace {
 }
 
 fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
-    workspace.update(cx, |workspace, cx| {
-        if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
-            workspace.show_notification_once(0, cx, |cx| {
-                cx.add_view(|_| {
-                    MessageNotification::new(
-                        "Failed to load any database file.",
-                        OsOpen::new("https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml".to_string()),
-                        "Click to let us know about this error"
-                    )
-                })
-            });
-        } else {
-            let backup_path = (*db::BACKUP_DB_PATH).read();
-            if let Some(backup_path) = &*backup_path {
+    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
+
+    workspace
+        .update(cx, |workspace, cx| {
+            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
                 workspace.show_notification_once(0, cx, |cx| {
                     cx.add_view(|_| {
-                        let backup_path = backup_path.to_string_lossy();
-                        MessageNotification::new(
-                            format!(
-                                "Database file was corrupted. Old database backed up to {}",
-                                backup_path
-                            ),
-                            OsOpen::new(backup_path.to_string()),
-                            "Click to show old database in finder",
-                        )
+                        MessageNotification::new("Failed to load any database file.")
+                            .with_click_message("Click to let us know about this error")
+                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
                     })
                 });
+            } else {
+                let backup_path = (*db::BACKUP_DB_PATH).read();
+                if let Some(backup_path) = backup_path.clone() {
+                    workspace.show_notification_once(0, cx, move |cx| {
+                        cx.add_view(move |_| {
+                            MessageNotification::new(format!(
+                                "Database file was corrupted. Old database backed up to {}",
+                                backup_path.display()
+                            ))
+                            .with_click_message("Click to show old database in finder")
+                            .on_click(move |cx| {
+                                cx.platform().open_url(&backup_path.to_string_lossy())
+                            })
+                        })
+                    });
+                }
             }
-        }
-    }).log_err();
+        })
+        .log_err();
 }
 
 impl Entity for Workspace {
@@ -3062,6 +3056,58 @@ pub fn join_remote_project(
     })
 }
 
+pub fn restart(_: &Restart, cx: &mut AppContext) {
+    let mut workspaces = cx
+        .window_ids()
+        .filter_map(|window_id| {
+            Some(
+                cx.root_view(window_id)?
+                    .clone()
+                    .downcast::<Workspace>()?
+                    .downgrade(),
+            )
+        })
+        .collect::<Vec<_>>();
+
+    // If multiple windows have unsaved changes, and need a save prompt,
+    // prompt in the active window before switching to a different window.
+    workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
+
+    let should_confirm = cx.global::<Settings>().confirm_quit;
+    cx.spawn(|mut cx| async move {
+        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
+            let answer = cx.prompt(
+                workspace.window_id(),
+                PromptLevel::Info,
+                "Are you sure you want to restart?",
+                &["Restart", "Cancel"],
+            );
+
+            if let Some(mut answer) = answer {
+                let answer = answer.next().await;
+                if answer != Some(0) {
+                    return Ok(());
+                }
+            }
+        }
+
+        // If the user cancels any save prompt, then keep the app open.
+        for workspace in workspaces {
+            if !workspace
+                .update(&mut cx, |workspace, cx| {
+                    workspace.prepare_to_close(true, cx)
+                })?
+                .await?
+            {
+                return Ok(());
+            }
+        }
+        cx.platform().restart();
+        anyhow::Ok(())
+    })
+    .detach_and_log_err(cx);
+}
+
 fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
     let mut parts = value.split(',');
     let width: usize = parts.next()?.parse().ok()?;

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

@@ -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, Restart, Workspace};
+use workspace::{sidebar::SidebarSide, AppState, Workspace};
 
 #[derive(Deserialize, Clone, PartialEq)]
 pub struct OpenBrowser {
@@ -113,7 +113,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
         },
     );
     cx.add_global_action(quit);
-    cx.add_global_action(restart);
     cx.add_global_action(move |action: &OpenBrowser, cx| cx.platform().open_url(&action.url));
     cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
         cx.update_global::<Settings, _, _>(|settings, cx| {
@@ -370,58 +369,6 @@ pub fn build_window_options(
     }
 }
 
-fn restart(_: &Restart, cx: &mut gpui::AppContext) {
-    let mut workspaces = cx
-        .window_ids()
-        .filter_map(|window_id| {
-            Some(
-                cx.root_view(window_id)?
-                    .clone()
-                    .downcast::<Workspace>()?
-                    .downgrade(),
-            )
-        })
-        .collect::<Vec<_>>();
-
-    // If multiple windows have unsaved changes, and need a save prompt,
-    // prompt in the active window before switching to a different window.
-    workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
-
-    let should_confirm = cx.global::<Settings>().confirm_quit;
-    cx.spawn(|mut cx| async move {
-        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
-            let answer = cx.prompt(
-                workspace.window_id(),
-                PromptLevel::Info,
-                "Are you sure you want to restart?",
-                &["Restart", "Cancel"],
-            );
-
-            if let Some(mut answer) = answer {
-                let answer = answer.next().await;
-                if answer != Some(0) {
-                    return Ok(());
-                }
-            }
-        }
-
-        // If the user cancels any save prompt, then keep the app open.
-        for workspace in workspaces {
-            if !workspace
-                .update(&mut cx, |workspace, cx| {
-                    workspace.prepare_to_close(true, cx)
-                })?
-                .await?
-            {
-                return Ok(());
-            }
-        }
-        cx.platform().restart();
-        anyhow::Ok(())
-    })
-    .detach_and_log_err(cx);
-}
-
 fn quit(_: &Quit, cx: &mut gpui::AppContext) {
     let mut workspaces = cx
         .window_ids()