diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 4ea031985e..d5ee1364b3 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/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>, + on_click: Option)>>, } 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::::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() diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index f7ff163424..ea25355065 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -560,7 +560,7 @@ impl Copilot { } } - fn reinstall(&mut self, cx: &mut ModelContext) -> Task<()> { + pub fn reinstall(&mut self, cx: &mut ModelContext) -> Task<()> { let start_task = cx .spawn({ let http = self.http.clone(); diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index a597bb7e47..832bdaf3da 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/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, ); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b88e38850e..d8c2e81ce1 100644 --- a/crates/editor/src/editor.rs +++ b/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() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a949b64c04..7343a7245d 100644 --- a/crates/gpui/src/app.rs +++ b/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) { - 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)) { let handle = self.handle(); self.window_context diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 6429448f75..414b3e9323 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -61,7 +61,7 @@ fn toggle(app_state: Weak, cx: &mut ViewContext) -> 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.")) }) } })?; diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 91ca99c5c3..ee5a2e8332 100644 --- a/crates/search/src/buffer_search.rs +++ b/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::( @@ -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::( @@ -405,7 +407,6 @@ impl BufferSearchBar { theme: &theme::Search, cx: &mut ViewContext, ) -> AnyElement { - let action = Box::new(Dismiss); let tooltip = "Dismiss Buffer Search"; let tooltip_style = cx.global::().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::(0, tooltip.to_string(), Some(action), tooltip_style, cx) + .with_tooltip::( + 0, + tooltip.to_string(), + Some(Box::new(Dismiss)), + tooltip_style, + cx, + ) .into_any() } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index ac478a8a2c..ea29f9cfda 100644 --- a/crates/search/src/project_search.rs +++ b/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::( @@ -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::( diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 57749a5c2b..455ffb2bb0 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -114,17 +114,14 @@ impl Workspace { pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext) { self.dismiss_notification::(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>, + on_click: Option)>>, click_message: Option>, } @@ -207,36 +202,31 @@ pub mod simple_message_notification { } impl MessageNotification { - pub fn new_message>>(message: S) -> MessageNotification { + pub fn new(message: S) -> MessageNotification + where + S: Into>, + { Self { message: message.into(), - click_action: None, + on_click: None, click_message: None, } } - pub fn new_boxed_action>, S2: Into>>( - message: S1, - click_action: Box, - click_message: S2, - ) -> Self { - Self { - message: message.into(), - click_action: Some(click_action), - click_message: Some(click_message.into()), - } + pub fn with_click_message(mut self, message: S) -> Self + where + S: Into>, + { + self.click_message = Some(message.into()); + self } - pub fn new>, A: Action, S2: Into>>( - message: S1, - click_action: A, - click_message: S2, - ) -> Self { - Self { - message: message.into(), - click_action: Some(Box::new(click_action) as Box), - click_message: Some(click_message.into()), - } + pub fn on_click(mut self, on_click: F) -> Self + where + F: 'static + Fn(&mut ViewContext), + { + self.on_click = Some(Arc::new(on_click)); + self } pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext) { @@ -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::::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, )) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 1a622babb3..6a1f7aa8bb 100644 --- a/crates/workspace/src/workspace.rs +++ b/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)>, + on_click: Option<(Cow<'static, str>, Arc)>, } impl Toast { @@ -145,21 +146,17 @@ impl Toast { Toast { id, msg: msg.into(), - click: None, + on_click: None, } } - pub fn new_action>, I2: Into>>( - 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(mut self, message: M, on_click: F) -> Self + where + M: Into>, + 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, 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, 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, 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::()? + .downgrade(), + ) + }) + .collect::>(); + + // 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::().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 { let mut parts = value.split(','); let width: usize = parts.next()?.parse().ok()?; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 01a5decfd0..28b17c297d 100644 --- a/crates/zed/src/zed.rs +++ b/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, 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, 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::()? - .downgrade(), - ) - }) - .collect::>(); - - // 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::().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()