diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 7f92b3be80de896eb7510eebd0fe8c06752a5684..0ccb2a58a0e4ff13dc3febf697bdc08023fcd56d 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -1,11 +1,12 @@ use crate::{Toast, Workspace}; +use anyhow::Context; +use anyhow::{anyhow, Result}; use collections::HashMap; use gpui::{ svg, AnyView, AppContext, AsyncWindowContext, ClipboardItem, DismissEvent, Entity, EntityId, EventEmitter, Global, PromptLevel, Render, ScrollHandle, Task, View, ViewContext, VisualContext, WindowContext, }; - use std::{any::TypeId, ops::DerefMut, time::Duration}; use ui::{prelude::*, Tooltip}; use util::ResultExt; @@ -151,13 +152,9 @@ impl Workspace { where E: std::fmt::Debug + std::fmt::Display, { - struct WorkspaceErrorNotification; - - self.show_notification( - NotificationId::unique::(), - cx, - |cx| cx.new_view(|_cx| ErrorMessagePrompt::new(format!("Error: {err:#}"))), - ); + self.show_notification(workspace_error_notification_id(), cx, |cx| { + cx.new_view(|_cx| ErrorMessagePrompt::new(format!("Error: {err}"))) + }); } pub fn show_portal_error(&mut self, err: String, cx: &mut ViewContext) { @@ -331,6 +328,12 @@ impl Render for LanguageServerPrompt { impl EventEmitter for LanguageServerPrompt {} +fn workspace_error_notification_id() -> NotificationId { + struct WorkspaceErrorNotification; + NotificationId::unique::() +} + +#[derive(Debug, Clone)] pub struct ErrorMessagePrompt { message: SharedString, label_and_url_button: Option<(SharedString, SharedString)>, @@ -413,14 +416,16 @@ impl Render for ErrorMessagePrompt { impl EventEmitter for ErrorMessagePrompt {} pub mod simple_message_notification { + use std::sync::Arc; + use gpui::{ - div, DismissEvent, EventEmitter, ParentElement, Render, SharedString, Styled, ViewContext, + div, AnyElement, DismissEvent, EventEmitter, ParentElement, Render, SharedString, Styled, + ViewContext, }; - use std::sync::Arc; use ui::prelude::*; pub struct MessageNotification { - message: SharedString, + content: Box) -> AnyElement>, on_click: Option)>>, click_message: Option, secondary_click_message: Option, @@ -433,9 +438,17 @@ pub mod simple_message_notification { pub fn new(message: S) -> MessageNotification where S: Into, + { + let message = message.into(); + Self::new_from_builder(move |_| Label::new(message.clone()).into_any_element()) + } + + pub fn new_from_builder(content: F) -> MessageNotification + where + F: 'static + Fn(&mut ViewContext) -> AnyElement, { Self { - message: message.into(), + content: Box::new(content), on_click: None, click_message: None, secondary_on_click: None, @@ -490,7 +503,7 @@ pub mod simple_message_notification { h_flex() .gap_4() .justify_between() - .child(div().max_w_80().child(Label::new(self.message.clone()))) + .child(div().max_w_80().child((self.content)(cx))) .child( IconButton::new("close", IconName::Close) .on_click(cx.listener(|this, _, cx| this.dismiss(cx))), @@ -532,6 +545,70 @@ pub mod simple_message_notification { } } +/// Shows a notification in the active workspace if there is one, otherwise shows it in all workspaces. +pub fn show_app_notification( + id: NotificationId, + cx: &mut AppContext, + build_notification: impl Fn(&mut ViewContext) -> View, +) -> Result<()> { + let workspaces_to_notify = if let Some(active_workspace_window) = cx + .active_window() + .and_then(|window| window.downcast::()) + { + vec![active_workspace_window] + } else { + let mut workspaces_to_notify = Vec::new(); + for window in cx.windows() { + if let Some(workspace_window) = window.downcast::() { + workspaces_to_notify.push(workspace_window); + } + } + workspaces_to_notify + }; + + let mut notified = false; + let mut notify_errors = Vec::new(); + + for workspace_window in workspaces_to_notify { + let notify_result = workspace_window.update(cx, |workspace, cx| { + workspace.show_notification(id.clone(), cx, &build_notification); + }); + match notify_result { + Ok(()) => notified = true, + Err(notify_err) => notify_errors.push(notify_err), + } + } + + if notified { + Ok(()) + } else { + if notify_errors.is_empty() { + Err(anyhow!("Found no workspaces to show notification.")) + } else { + Err(anyhow!( + "No workspaces were able to show notification. Errors:\n\n{}", + notify_errors + .iter() + .map(|e| e.to_string()) + .collect::>() + .join("\n\n") + )) + } + } +} + +pub fn dismiss_app_notification(id: &NotificationId, cx: &mut AppContext) { + for window in cx.windows() { + if let Some(workspace_window) = window.downcast::() { + workspace_window + .update(cx, |workspace, cx| { + workspace.dismiss_notification(&id, cx); + }) + .ok(); + } + } +} + pub trait NotifyResultExt { type Ok; @@ -542,9 +619,12 @@ pub trait NotifyResultExt { ) -> Option; fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option; + + /// Notifies the active workspace if there is one, otherwise notifies all workspaces. + fn notify_app_err(self, cx: &mut AppContext) -> Option; } -impl NotifyResultExt for Result +impl NotifyResultExt for std::result::Result where E: std::fmt::Debug + std::fmt::Display, { @@ -576,13 +656,28 @@ where } } } + + fn notify_app_err(self, cx: &mut AppContext) -> Option { + match self { + Ok(value) => Some(value), + Err(err) => { + let message: SharedString = format!("Error: {err}").into(); + show_app_notification(workspace_error_notification_id(), cx, |cx| { + cx.new_view(|_cx| ErrorMessagePrompt::new(message.clone())) + }) + .with_context(|| format!("Showing error notification: {message}")) + .log_err(); + None + } + } + } } pub trait NotifyTaskExt { fn detach_and_notify_err(self, cx: &mut WindowContext); } -impl NotifyTaskExt for Task> +impl NotifyTaskExt for Task> where E: std::fmt::Debug + std::fmt::Display + Sized + 'static, R: 'static,