Cargo.lock 🔗
@@ -18158,6 +18158,7 @@ dependencies = [
"itertools 0.14.0",
"language",
"log",
+ "menu",
"node_runtime",
"parking_lot",
"postage",
Kirill Bulatov created
Closes https://github.com/zed-industries/zed/issues/10140
* On `menu::Cancel` action (`ESC`), close notifications, one by one, if
`Workspace` gets to handle this action.
More specific, focused items contexts (e.g. `Editor`) take priority.
* Allows to temporarily suppress notifications of this kind either by
clicking a corresponding button in the UI, or using
`workspace::SuppressNotification` action.
This might not work well out of the box for all notifications and might
require further improvement.
https://github.com/user-attachments/assets/0ea49ee6-cd21-464f-ba74-fc40f7a8dedf
Release Notes:
- Added a way to dismiss workspace notifications
Cargo.lock | 1
crates/collab_ui/src/notification_panel.rs | 10 +++
crates/workspace/Cargo.toml | 1
crates/workspace/src/notifications.rs | 72 ++++++++++++++++++++---
crates/workspace/src/workspace.rs | 25 +++++++
5 files changed, 96 insertions(+), 13 deletions(-)
@@ -18158,6 +18158,7 @@ dependencies = [
"itertools 0.14.0",
"language",
"log",
+ "menu",
"node_runtime",
"parking_lot",
"postage",
@@ -22,7 +22,9 @@ use ui::{
Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex,
};
use util::{ResultExt, TryFutureExt};
-use workspace::notifications::{Notification as WorkspaceNotification, NotificationId};
+use workspace::notifications::{
+ Notification as WorkspaceNotification, NotificationId, SuppressEvent,
+};
use workspace::{
Workspace,
dock::{DockPosition, Panel, PanelEvent},
@@ -823,6 +825,11 @@ impl Render for NotificationToast {
IconButton::new("close", IconName::Close)
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
)
+ .child(
+ IconButton::new("suppress", IconName::XCircle)
+ .tooltip(Tooltip::text("Do not show until restart"))
+ .on_click(cx.listener(|_, _, _, cx| cx.emit(SuppressEvent))),
+ )
.on_click(cx.listener(|this, _, window, cx| {
this.focus_notification_panel(window, cx);
cx.emit(DismissEvent);
@@ -831,3 +838,4 @@ impl Render for NotificationToast {
}
impl EventEmitter<DismissEvent> for NotificationToast {}
+impl EventEmitter<SuppressEvent> for NotificationToast {}
@@ -43,6 +43,7 @@ http_client.workspace = true
itertools.workspace = true
language.workspace = true
log.workspace = true
+menu.workspace = true
node_runtime.workspace = true
parking_lot.workspace = true
postage.workspace = true
@@ -29,7 +29,7 @@ impl std::ops::DerefMut for Notifications {
}
}
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum NotificationId {
Unique(TypeId),
Composite(TypeId, ElementId),
@@ -54,7 +54,12 @@ impl NotificationId {
}
}
-pub trait Notification: EventEmitter<DismissEvent> + Focusable + Render {}
+pub trait Notification:
+ EventEmitter<DismissEvent> + EventEmitter<SuppressEvent> + Focusable + Render
+{
+}
+
+pub struct SuppressEvent;
impl Workspace {
#[cfg(any(test, feature = "test-support"))]
@@ -81,6 +86,13 @@ impl Workspace {
}
})
.detach();
+ cx.subscribe(¬ification, {
+ let id = id.clone();
+ move |workspace: &mut Workspace, _, _: &SuppressEvent, cx| {
+ workspace.suppress_notification(&id, cx);
+ }
+ })
+ .detach();
notification.into()
});
}
@@ -96,6 +108,9 @@ impl Workspace {
cx: &mut Context<Self>,
build_notification: impl FnOnce(&mut Context<Self>) -> AnyView,
) {
+ if self.suppressed_notifications.contains(id) {
+ return;
+ }
self.dismiss_notification(id, cx);
self.notifications
.push((id.clone(), build_notification(cx)));
@@ -172,6 +187,11 @@ impl Workspace {
cx.notify();
}
+ pub fn suppress_notification(&mut self, id: &NotificationId, cx: &mut Context<Self>) {
+ self.dismiss_notification(id, cx);
+ self.suppressed_notifications.insert(id.clone());
+ }
+
pub fn show_initial_notifications(&mut self, cx: &mut Context<Self>) {
// Allow absence of the global so that tests don't need to initialize it.
let app_notifications = GLOBAL_APP_NOTIFICATIONS
@@ -268,6 +288,14 @@ impl Render for LanguageServerPrompt {
)
.child(
h_flex()
+ .gap_2()
+ .child(
+ IconButton::new("suppress", IconName::XCircle)
+ .tooltip(Tooltip::text("Do not show until restart"))
+ .on_click(
+ cx.listener(|_, _, _, cx| cx.emit(SuppressEvent)),
+ ),
+ )
.child(
IconButton::new("copy", IconName::Copy)
.on_click({
@@ -305,6 +333,7 @@ impl Render for LanguageServerPrompt {
}
impl EventEmitter<DismissEvent> for LanguageServerPrompt {}
+impl EventEmitter<SuppressEvent> for LanguageServerPrompt {}
fn workspace_error_notification_id() -> NotificationId {
struct WorkspaceErrorNotification;
@@ -401,6 +430,7 @@ impl Focusable for ErrorMessagePrompt {
}
impl EventEmitter<DismissEvent> for ErrorMessagePrompt {}
+impl EventEmitter<SuppressEvent> for ErrorMessagePrompt {}
impl Notification for ErrorMessagePrompt {}
@@ -411,9 +441,9 @@ pub mod simple_message_notification {
AnyElement, DismissEvent, EventEmitter, FocusHandle, Focusable, ParentElement, Render,
SharedString, Styled, div,
};
- use ui::prelude::*;
+ use ui::{Tooltip, prelude::*};
- use super::Notification;
+ use super::{Notification, SuppressEvent};
pub struct MessageNotification {
focus_handle: FocusHandle,
@@ -429,6 +459,7 @@ pub mod simple_message_notification {
more_info_message: Option<SharedString>,
more_info_url: Option<Arc<str>>,
show_close_button: bool,
+ show_suppress_button: bool,
title: Option<SharedString>,
}
@@ -439,6 +470,7 @@ pub mod simple_message_notification {
}
impl EventEmitter<DismissEvent> for MessageNotification {}
+ impl EventEmitter<SuppressEvent> for MessageNotification {}
impl Notification for MessageNotification {}
@@ -470,6 +502,7 @@ pub mod simple_message_notification {
more_info_message: None,
more_info_url: None,
show_close_button: true,
+ show_suppress_button: true,
title: None,
focus_handle: cx.focus_handle(),
}
@@ -568,6 +601,11 @@ pub mod simple_message_notification {
self
}
+ pub fn show_suppress_button(mut self, show: bool) -> Self {
+ self.show_suppress_button = show;
+ self
+ }
+
pub fn with_title<S>(mut self, title: S) -> Self
where
S: Into<SharedString>,
@@ -597,12 +635,26 @@ pub mod simple_message_notification {
})
.child(div().max_w_96().child((self.build_content)(window, cx))),
)
- .when(self.show_close_button, |this| {
- this.child(
- IconButton::new("close", IconName::Close)
- .on_click(cx.listener(|this, _, _, cx| this.dismiss(cx))),
- )
- }),
+ .child(
+ h_flex()
+ .gap_2()
+ .when(self.show_suppress_button, |this| {
+ this.child(
+ IconButton::new("suppress", IconName::XCircle)
+ .tooltip(Tooltip::text("Do not show until restart"))
+ .on_click(cx.listener(|_, _, _, cx| {
+ cx.emit(SuppressEvent);
+ })),
+ )
+ })
+ .when(self.show_close_button, |this| {
+ this.child(
+ IconButton::new("close", IconName::Close).on_click(
+ cx.listener(|this, _, _, cx| this.dismiss(cx)),
+ ),
+ )
+ }),
+ ),
)
.child(
h_flex()
@@ -52,7 +52,8 @@ use language::{Buffer, LanguageRegistry, Rope};
pub use modal_layer::*;
use node_runtime::NodeRuntime;
use notifications::{
- DetachAndPromptErr, Notifications, simple_message_notification::MessageNotification,
+ DetachAndPromptErr, Notifications, dismiss_app_notification,
+ simple_message_notification::MessageNotification,
};
pub use pane::*;
pub use pane_group::*;
@@ -179,6 +180,7 @@ actions!(
SaveAs,
SaveWithoutFormat,
ShutdownDebugAdapters,
+ SuppressNotification,
ToggleBottomDock,
ToggleCenteredLayout,
ToggleLeftDock,
@@ -921,6 +923,7 @@ pub struct Workspace {
toast_layer: Entity<ToastLayer>,
titlebar_item: Option<AnyView>,
notifications: Notifications,
+ suppressed_notifications: HashSet<NotificationId>,
project: Entity<Project>,
follower_states: HashMap<CollaboratorId, FollowerState>,
last_leaders_by_pane: HashMap<WeakEntity<Pane>, CollaboratorId>,
@@ -1245,7 +1248,8 @@ impl Workspace {
modal_layer,
toast_layer,
titlebar_item: None,
- notifications: Default::default(),
+ notifications: Notifications::default(),
+ suppressed_notifications: HashSet::default(),
left_dock,
bottom_dock,
bottom_dock_layout,
@@ -5301,12 +5305,20 @@ impl Workspace {
workspace.clear_all_notifications(cx);
},
))
+ .on_action(cx.listener(
+ |workspace: &mut Workspace, _: &SuppressNotification, _, cx| {
+ if let Some((notification_id, _)) = workspace.notifications.pop() {
+ workspace.suppress_notification(¬ification_id, cx);
+ }
+ },
+ ))
.on_action(cx.listener(
|workspace: &mut Workspace, _: &ReopenClosedItem, window, cx| {
workspace.reopen_closed_item(window, cx).detach();
},
))
.on_action(cx.listener(Workspace::toggle_centered_layout))
+ .on_action(cx.listener(Workspace::cancel))
}
#[cfg(any(test, feature = "test-support"))]
@@ -5477,6 +5489,15 @@ impl Workspace {
.update(cx, |_, window, _| window.activate_window())
.ok();
}
+
+ pub fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
+ if let Some((notification_id, _)) = self.notifications.pop() {
+ dismiss_app_notification(¬ification_id, cx);
+ return;
+ }
+
+ cx.propagate();
+ }
}
fn leader_border_for_pane(