Add a way to clear activity indicator (#30156)

Follow-up of https://github.com/zed-industries/zed/pull/30015

* Restyles the dismiss and close buttons a bit: change the dismiss icon
and add tooltips with the bindings to both
* Allows ESC to clear any status that's in the activity indicator now,
if all notifications are cleared: this won't suppress any further status
additions though, so statuses may resurface later

Release Notes:

- Added a way to clear activity indicator
This commit is contained in:
Kirill Bulatov 2025-05-07 20:50:52 +03:00 committed by GitHub
parent 77ac82587a
commit 6e19c9b141
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 88 additions and 19 deletions

View file

@ -43,6 +43,7 @@ pub struct ActivityIndicator {
context_menu_handle: PopoverMenuHandle<ContextMenu>, context_menu_handle: PopoverMenuHandle<ContextMenu>,
} }
#[derive(Debug)]
struct ServerStatus { struct ServerStatus {
name: SharedString, name: SharedString,
status: BinaryStatus, status: BinaryStatus,
@ -70,6 +71,7 @@ impl ActivityIndicator {
) -> Entity<ActivityIndicator> { ) -> Entity<ActivityIndicator> {
let project = workspace.project().clone(); let project = workspace.project().clone();
let auto_updater = AutoUpdater::get(cx); let auto_updater = AutoUpdater::get(cx);
let workspace_handle = cx.entity();
let this = cx.new(|cx| { let this = cx.new(|cx| {
let mut status_events = languages.language_server_binary_statuses(); let mut status_events = languages.language_server_binary_statuses();
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
@ -84,6 +86,25 @@ impl ActivityIndicator {
}) })
.detach(); .detach();
cx.subscribe_in(
&workspace_handle,
window,
|activity_indicator, _, event, window, cx| match event {
workspace::Event::ClearActivityIndicator { .. } => {
if activity_indicator.statuses.pop().is_some() {
activity_indicator.dismiss_error_message(
&DismissErrorMessage,
window,
cx,
);
cx.notify();
}
}
_ => {}
},
)
.detach();
cx.subscribe( cx.subscribe(
&project.read(cx).lsp_store(), &project.read(cx).lsp_store(),
|_, _, event, cx| match event { |_, _, event, cx| match event {
@ -115,7 +136,7 @@ impl ActivityIndicator {
} }
Self { Self {
statuses: Default::default(), statuses: Vec::new(),
project: project.clone(), project: project.clone(),
auto_updater, auto_updater,
context_menu_handle: Default::default(), context_menu_handle: Default::default(),
@ -185,11 +206,8 @@ impl ActivityIndicator {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if let Some(updater) = &self.auto_updater { if let Some(updater) = &self.auto_updater {
updater.update(cx, |updater, cx| { updater.update(cx, |updater, cx| updater.dismiss_error(cx));
updater.dismiss_error(cx);
});
} }
cx.notify();
} }
fn pending_language_server_work<'a>( fn pending_language_server_work<'a>(

View file

@ -335,9 +335,13 @@ impl AutoUpdater {
self.status.clone() self.status.clone()
} }
pub fn dismiss_error(&mut self, cx: &mut Context<Self>) { pub fn dismiss_error(&mut self, cx: &mut Context<Self>) -> bool {
if self.status == AutoUpdateStatus::Idle {
return false;
}
self.status = AutoUpdateStatus::Idle; self.status = AutoUpdateStatus::Idle;
cx.notify(); cx.notify();
true
} }
// If you are packaging Zed and need to override the place it downloads SSH remotes from, // If you are packaging Zed and need to override the place it downloads SSH remotes from,

View file

@ -22,6 +22,7 @@ use ui::{
Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex, Avatar, Button, Icon, IconButton, IconName, Label, Tab, Tooltip, h_flex, prelude::*, v_flex,
}; };
use util::{ResultExt, TryFutureExt}; use util::{ResultExt, TryFutureExt};
use workspace::SuppressNotification;
use workspace::notifications::{ use workspace::notifications::{
Notification as WorkspaceNotification, NotificationId, SuppressEvent, Notification as WorkspaceNotification, NotificationId, SuppressEvent,
}; };
@ -823,11 +824,19 @@ impl Render for NotificationToast {
.child(Label::new(self.text.clone())) .child(Label::new(self.text.clone()))
.child( .child(
IconButton::new("close", IconName::Close) IconButton::new("close", IconName::Close)
.tooltip(|window, cx| Tooltip::for_action("Close", &menu::Cancel, window, cx))
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
) )
.child( .child(
IconButton::new("suppress", IconName::XCircle) IconButton::new("suppress", IconName::SquareMinus)
.tooltip(Tooltip::text("Do not show until restart")) .tooltip(|window, cx| {
Tooltip::for_action(
"Do not show until restart",
&SuppressNotification,
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| cx.emit(SuppressEvent))), .on_click(cx.listener(|_, _, _, cx| cx.emit(SuppressEvent))),
) )
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {

View file

@ -1,4 +1,4 @@
use crate::{Toast, Workspace}; use crate::{SuppressNotification, Toast, Workspace};
use gpui::{ use gpui::{
AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent, AnyView, App, AppContext as _, AsyncWindowContext, ClipboardItem, Context, DismissEvent,
Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle, Task, svg, Entity, EventEmitter, FocusHandle, Focusable, PromptLevel, Render, ScrollHandle, Task, svg,
@ -290,8 +290,15 @@ impl Render for LanguageServerPrompt {
h_flex() h_flex()
.gap_2() .gap_2()
.child( .child(
IconButton::new("suppress", IconName::XCircle) IconButton::new("suppress", IconName::SquareMinus)
.tooltip(Tooltip::text("Do not show until restart")) .tooltip(|window, cx| {
Tooltip::for_action(
"Do not show until restart",
&SuppressNotification,
window,
cx,
)
})
.on_click( .on_click(
cx.listener(|_, _, _, cx| cx.emit(SuppressEvent)), cx.listener(|_, _, _, cx| cx.emit(SuppressEvent)),
), ),
@ -308,9 +315,20 @@ impl Render for LanguageServerPrompt {
}) })
.tooltip(Tooltip::text("Copy Description")), .tooltip(Tooltip::text("Copy Description")),
) )
.child(IconButton::new("close", IconName::Close).on_click( .child(
cx.listener(|_, _, _, cx| cx.emit(gpui::DismissEvent)), IconButton::new("close", IconName::Close)
)), .tooltip(|window, cx| {
Tooltip::for_action(
"Close",
&menu::Cancel,
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| {
cx.emit(gpui::DismissEvent)
})),
),
), ),
) )
.child(Label::new(request.message.to_string()).size(LabelSize::Small)) .child(Label::new(request.message.to_string()).size(LabelSize::Small))
@ -443,6 +461,8 @@ pub mod simple_message_notification {
}; };
use ui::{Tooltip, prelude::*}; use ui::{Tooltip, prelude::*};
use crate::SuppressNotification;
use super::{Notification, SuppressEvent}; use super::{Notification, SuppressEvent};
pub struct MessageNotification { pub struct MessageNotification {
@ -640,8 +660,15 @@ pub mod simple_message_notification {
.gap_2() .gap_2()
.when(self.show_suppress_button, |this| { .when(self.show_suppress_button, |this| {
this.child( this.child(
IconButton::new("suppress", IconName::XCircle) IconButton::new("suppress", IconName::SquareMinus)
.tooltip(Tooltip::text("Do not show until restart")) .tooltip(|window, cx| {
Tooltip::for_action(
"Do not show until restart",
&SuppressNotification,
window,
cx,
)
})
.on_click(cx.listener(|_, _, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(SuppressEvent); cx.emit(SuppressEvent);
})), })),
@ -649,9 +676,18 @@ pub mod simple_message_notification {
}) })
.when(self.show_close_button, |this| { .when(self.show_close_button, |this| {
this.child( this.child(
IconButton::new("close", IconName::Close).on_click( IconButton::new("close", IconName::Close)
cx.listener(|this, _, _, cx| this.dismiss(cx)), .tooltip(|window, cx| {
), Tooltip::for_action(
"Close",
&menu::Cancel,
window,
cx,
)
})
.on_click(
cx.listener(|this, _, _, cx| this.dismiss(cx)),
),
) )
}), }),
), ),

View file

@ -869,6 +869,7 @@ pub enum Event {
}, },
ZoomChanged, ZoomChanged,
ModalOpened, ModalOpened,
ClearActivityIndicator,
} }
#[derive(Debug)] #[derive(Debug)]
@ -5496,6 +5497,7 @@ impl Workspace {
return; return;
} }
cx.emit(Event::ClearActivityIndicator);
cx.propagate(); cx.propagate();
} }
} }