Add support for showing notification to active workspace from AppContext (#23226)
Falls back on notifying all workspaces if there isn't an active one. This is to support notifying the user about keymap file errors in #23113. It will also be useful for notifying about settings file errors. Release Notes: - N/A
This commit is contained in:
parent
55e1e831a1
commit
5fdd7edb90
1 changed files with 111 additions and 16 deletions
|
@ -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::<WorkspaceErrorNotification>(),
|
||||
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<Self>) {
|
||||
|
@ -331,6 +328,12 @@ impl Render for LanguageServerPrompt {
|
|||
|
||||
impl EventEmitter<DismissEvent> for LanguageServerPrompt {}
|
||||
|
||||
fn workspace_error_notification_id() -> NotificationId {
|
||||
struct WorkspaceErrorNotification;
|
||||
NotificationId::unique::<WorkspaceErrorNotification>()
|
||||
}
|
||||
|
||||
#[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<DismissEvent> for ErrorMessagePrompt {}
|
||||
|
||||
pub mod simple_message_notification {
|
||||
use gpui::{
|
||||
div, DismissEvent, EventEmitter, ParentElement, Render, SharedString, Styled, ViewContext,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{
|
||||
div, AnyElement, DismissEvent, EventEmitter, ParentElement, Render, SharedString, Styled,
|
||||
ViewContext,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct MessageNotification {
|
||||
message: SharedString,
|
||||
content: Box<dyn Fn(&mut ViewContext<Self>) -> AnyElement>,
|
||||
on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
|
||||
click_message: Option<SharedString>,
|
||||
secondary_click_message: Option<SharedString>,
|
||||
|
@ -433,9 +438,17 @@ pub mod simple_message_notification {
|
|||
pub fn new<S>(message: S) -> MessageNotification
|
||||
where
|
||||
S: Into<SharedString>,
|
||||
{
|
||||
let message = message.into();
|
||||
Self::new_from_builder(move |_| Label::new(message.clone()).into_any_element())
|
||||
}
|
||||
|
||||
pub fn new_from_builder<F>(content: F) -> MessageNotification
|
||||
where
|
||||
F: 'static + Fn(&mut ViewContext<Self>) -> 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<V: Notification>(
|
||||
id: NotificationId,
|
||||
cx: &mut AppContext,
|
||||
build_notification: impl Fn(&mut ViewContext<Workspace>) -> View<V>,
|
||||
) -> Result<()> {
|
||||
let workspaces_to_notify = if let Some(active_workspace_window) = cx
|
||||
.active_window()
|
||||
.and_then(|window| window.downcast::<Workspace>())
|
||||
{
|
||||
vec![active_workspace_window]
|
||||
} else {
|
||||
let mut workspaces_to_notify = Vec::new();
|
||||
for window in cx.windows() {
|
||||
if let Some(workspace_window) = window.downcast::<Workspace>() {
|
||||
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::<Vec<_>>()
|
||||
.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>() {
|
||||
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<Self::Ok>;
|
||||
|
||||
fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
|
||||
|
||||
/// Notifies the active workspace if there is one, otherwise notifies all workspaces.
|
||||
fn notify_app_err(self, cx: &mut AppContext) -> Option<Self::Ok>;
|
||||
}
|
||||
|
||||
impl<T, E> NotifyResultExt for Result<T, E>
|
||||
impl<T, E> NotifyResultExt for std::result::Result<T, E>
|
||||
where
|
||||
E: std::fmt::Debug + std::fmt::Display,
|
||||
{
|
||||
|
@ -576,13 +656,28 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn notify_app_err(self, cx: &mut AppContext) -> Option<T> {
|
||||
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<R, E> NotifyTaskExt for Task<Result<R, E>>
|
||||
impl<R, E> NotifyTaskExt for Task<std::result::Result<R, E>>
|
||||
where
|
||||
E: std::fmt::Debug + std::fmt::Display + Sized + 'static,
|
||||
R: 'static,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue