Detailed changes
@@ -57,7 +57,10 @@ use ui::{
};
use util::defer;
use util::{ResultExt, size::format_file_size, time::duration_alt_display};
-use workspace::{CollaboratorId, NewTerminal, Toast, Workspace, notifications::NotificationId};
+use workspace::{
+ CollaboratorId, NewTerminal, NotificationSource, Toast, Workspace,
+ notifications::NotificationId,
+};
use zed_actions::agent::{Chat, ToggleModelSelector};
use zed_actions::assistant::OpenRulesLibrary;
@@ -1439,6 +1439,7 @@ impl AcpThreadView {
));
},
),
+ NotificationSource::Agent,
cx,
);
});
@@ -1512,6 +1513,7 @@ impl AcpThreadView {
"Thread synced with latest version",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -66,7 +66,8 @@ use ui::{
};
use util::ResultExt as _;
use workspace::{
- CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
+ CollaboratorId, DraggedSelection, DraggedTab, NotificationSource, ToggleZoom, ToolbarItemView,
+ Workspace,
dock::{DockPosition, Panel, PanelEvent},
};
use zed_actions::{
@@ -1216,6 +1217,7 @@ impl AgentPanel {
"No active native thread to copy",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -1243,6 +1245,7 @@ impl AgentPanel {
"Thread copied to clipboard (base64 encoded)",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -1265,6 +1268,7 @@ impl AgentPanel {
"No clipboard content available",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -1282,6 +1286,7 @@ impl AgentPanel {
"Clipboard does not contain text",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -1302,6 +1307,7 @@ impl AgentPanel {
"Failed to decode clipboard content (expected base64)",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -1323,6 +1329,7 @@ impl AgentPanel {
"Failed to parse thread data from clipboard",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -1366,6 +1373,7 @@ impl AgentPanel {
"Thread loaded from clipboard",
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
});
@@ -52,7 +52,9 @@ use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
use text::{OffsetRangeExt, ToPoint as _};
use ui::prelude::*;
use util::{RangeExt, ResultExt, maybe};
-use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
+use workspace::{
+ ItemHandle, NotificationSource, Toast, Workspace, dock::Panel, notifications::NotificationId,
+};
use zed_actions::agent::OpenSettings;
pub fn init(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, cx: &mut App) {
@@ -1835,7 +1837,11 @@ impl InlineAssist {
assist_id.0,
);
- workspace.show_toast(Toast::new(id, error), cx);
+ workspace.show_toast(
+ Toast::new(id, error),
+ NotificationSource::Agent,
+ cx,
+ );
})
} else {
#[cfg(any(test, feature = "test-support"))]
@@ -29,7 +29,7 @@ use ui::utils::WithRemSize;
use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
use uuid::Uuid;
use workspace::notifications::NotificationId;
-use workspace::{Toast, Workspace};
+use workspace::{NotificationSource, Toast, Workspace};
use zed_actions::{
agent::ToggleModelSelector,
editor::{MoveDown, MoveUp},
@@ -725,6 +725,7 @@ impl<T: 'static> PromptEditor<T> {
toast
},
+ NotificationSource::Agent,
cx,
);
})
@@ -36,7 +36,10 @@ use std::{
use text::OffsetRangeExt;
use ui::{Disclosure, Toggleable, prelude::*};
use util::{ResultExt, debug_panic, rel_path::RelPath};
-use workspace::{Workspace, notifications::NotifyResultExt as _};
+use workspace::{
+ Workspace,
+ notifications::{NotificationSource, NotifyResultExt as _},
+};
use crate::ui::MentionCrease;
@@ -298,7 +301,7 @@ impl MentionSet {
// Notify the user if we failed to load the mentioned context
cx.spawn_in(window, async move |this, cx| {
- let result = task.await.notify_async_err(cx);
+ let result = task.await.notify_async_err(NotificationSource::Agent, cx);
drop(tx);
if result.is_none() {
this.update(cx, |this, cx| {
@@ -718,7 +721,11 @@ pub(crate) async fn insert_images_as_context(
mention_set.insert_mention(crease_id, MentionUri::PastedImage, task.clone())
});
- if task.await.notify_async_err(cx).is_none() {
+ if task
+ .await
+ .notify_async_err(NotificationSource::Agent, cx)
+ .is_none()
+ {
editor.update(cx, |editor, cx| {
editor.edit([(start_anchor..end_anchor, "")], cx);
});
@@ -27,7 +27,7 @@ use terminal_view::TerminalView;
use ui::prelude::*;
use util::ResultExt;
use uuid::Uuid;
-use workspace::{Toast, Workspace, notifications::NotificationId};
+use workspace::{NotificationSource, Toast, Workspace, notifications::NotificationId};
pub fn init(fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, cx: &mut App) {
cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder));
@@ -452,7 +452,11 @@ impl TerminalInlineAssist {
assist_id.0,
);
- workspace.show_toast(Toast::new(id, error), cx);
+ workspace.show_toast(
+ Toast::new(id, error),
+ NotificationSource::Agent,
+ cx,
+ );
})
}
@@ -60,7 +60,7 @@ use ui::{
};
use util::{ResultExt, maybe};
use workspace::{
- CollaboratorId,
+ CollaboratorId, NotificationSource,
searchable::{Direction, SearchToken, SearchableItemHandle},
};
@@ -1389,6 +1389,7 @@ impl TextThreadEditor {
),
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
}
@@ -9,7 +9,7 @@ use util::{ResultExt as _, maybe};
use workspace::Workspace;
use workspace::notifications::ErrorMessagePrompt;
use workspace::notifications::simple_message_notification::MessageNotification;
-use workspace::notifications::{NotificationId, show_app_notification};
+use workspace::notifications::{NotificationId, NotificationSource, show_app_notification};
actions!(
auto_update,
@@ -47,6 +47,7 @@ fn notify_release_notes_failed_to_show(
struct ViewReleaseNotesError;
workspace.show_notification(
NotificationId::unique::<ViewReleaseNotesError>(),
+ NotificationSource::Update,
cx,
|cx| {
cx.new(move |cx| {
@@ -183,6 +184,7 @@ pub fn notify_if_app_was_updated(cx: &mut App) {
let app_name = ReleaseChannel::global(cx).display_name();
show_app_notification(
NotificationId::unique::<UpdateNotification>(),
+ NotificationSource::Update,
cx,
move |cx| {
let workspace_handle = cx.entity().downgrade();
@@ -22,7 +22,7 @@ use std::{
};
use ui::prelude::*;
use util::ResultExt;
-use workspace::{CollaboratorId, item::TabContentParams};
+use workspace::{CollaboratorId, NotificationSource, item::TabContentParams};
use workspace::{
ItemNavHistory, Pane, SaveIntent, Toast, ViewId, Workspace, WorkspaceId,
item::{FollowableItem, Item, ItemEvent},
@@ -332,6 +332,7 @@ impl ChannelView {
NotificationId::unique::<CopyLinkForPositionToast>(),
"Link copied to clipboard",
),
+ NotificationSource::Collab,
cx,
);
})
@@ -36,7 +36,8 @@ use ui::{
};
use util::{ResultExt, TryFutureExt, maybe};
use workspace::{
- CopyRoomId, Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace,
+ CopyRoomId, Deafen, LeaveCall, Mute, NotificationSource, OpenChannelNotes, ScreenShare,
+ ShareProject, Workspace,
dock::{DockPosition, Panel, PanelEvent},
notifications::{DetachAndPromptErr, NotifyResultExt},
};
@@ -130,13 +131,18 @@ pub fn init(cx: &mut App) {
"Room ID copied to clipboard",
)
.autohide(),
+ NotificationSource::Collab,
cx,
);
})
})
- .detach_and_notify_err(window, cx);
+ .detach_and_notify_err(NotificationSource::Collab, window, cx);
} else {
- workspace.show_error(&"Thereβs no active call; join one first.", cx);
+ workspace.show_error(
+ &"There's no active call; join one first.",
+ NotificationSource::Collab,
+ cx,
+ );
}
});
workspace.register_action(|workspace, _: &ShareProject, window, cx| {
@@ -2194,7 +2200,7 @@ impl CollabPanel {
channel_store
.update(cx, |channels, _| channels.remove_channel(channel_id))
.await
- .notify_async_err(cx);
+ .notify_async_err(NotificationSource::Collab, cx);
this.update_in(cx, |_, window, cx| cx.focus_self(window))
.ok();
}
@@ -2228,7 +2234,7 @@ impl CollabPanel {
user_store
.update(cx, |store, cx| store.remove_contact(user_id, cx))
.await
- .notify_async_err(cx);
+ .notify_async_err(NotificationSource::Collab, cx);
}
anyhow::Ok(())
})
@@ -2333,7 +2339,7 @@ impl CollabPanel {
.connect(true, cx)
.await
.into_response()
- .notify_async_err(cx);
+ .notify_async_err(NotificationSource::Collab, cx);
})
.detach()
})),
@@ -26,7 +26,7 @@ use workspace::notifications::{
Notification as WorkspaceNotification, NotificationId, SuppressEvent,
};
use workspace::{
- Workspace,
+ NotificationSource, Workspace,
dock::{DockPosition, Panel, PanelEvent},
};
@@ -473,7 +473,7 @@ impl NotificationPanel {
let id = NotificationId::unique::<NotificationToast>();
workspace.dismiss_notification(&id, cx);
- workspace.show_notification(id, cx, |cx| {
+ workspace.show_notification(id, NotificationSource::Collab, cx, |cx| {
let workspace = cx.entity().downgrade();
cx.new(|cx| NotificationToast {
actor,
@@ -12,7 +12,7 @@ use project::project_settings::ProjectSettings;
use settings::Settings as _;
use ui::{ButtonLike, CommonAnimationExt, ConfiguredApiCard, Vector, VectorName, prelude::*};
use util::ResultExt as _;
-use workspace::{AppState, Toast, Workspace, notifications::NotificationId};
+use workspace::{AppState, NotificationSource, Toast, Workspace, notifications::NotificationId};
const COPILOT_SIGN_UP_URL: &str = "https://github.com/features/copilot";
const ERROR_LABEL: &str =
@@ -37,7 +37,7 @@ pub fn initiate_sign_out(copilot: Entity<Copilot>, window: &mut Window, cx: &mut
Err(err) => cx.update(|window, cx| {
if let Some(workspace) = window.root::<Workspace>().flatten() {
workspace.update(cx, |workspace, cx| {
- workspace.show_error(&err, cx);
+ workspace.show_error(&err, NotificationSource::Copilot, cx);
})
} else {
log::error!("{:?}", err);
@@ -88,7 +88,11 @@ fn copilot_toast(message: Option<&'static str>, window: &Window, cx: &mut App) {
cx.defer(move |cx| {
workspace.update(cx, |workspace, cx| match message {
- Some(message) => workspace.show_toast(Toast::new(NOTIFICATION_ID, message), cx),
+ Some(message) => workspace.show_toast(
+ Toast::new(NOTIFICATION_ID, message),
+ NotificationSource::Copilot,
+ cx,
+ ),
None => workspace.dismiss_toast(&NOTIFICATION_ID, cx),
});
})
@@ -50,7 +50,9 @@ use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use thiserror::Error;
use util::{RangeExt as _, ResultExt as _};
-use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification};
+use workspace::notifications::{
+ ErrorMessagePrompt, NotificationId, NotificationSource, show_app_notification,
+};
pub mod cursor_excerpt;
pub mod example_spec;
@@ -1991,6 +1993,7 @@ impl EditPredictionStore {
let error_message: SharedString = err.to_string().into();
show_app_notification(
NotificationId::unique::<ZedUpdateRequiredError>(),
+ NotificationSource::Copilot,
cx,
move |cx| {
cx.new(|cx| {
@@ -18,6 +18,7 @@ use language::{
use project::{Project, ProjectPath};
use release_channel::AppVersion;
use text::Bias;
+use workspace::NotificationSource;
use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification};
use zeta_prompt::{
Event, ZetaPromptInput,
@@ -169,6 +170,7 @@ pub(crate) fn request_prediction_with_zeta1(
let error_message: SharedString = err.to_string().into();
show_app_notification(
NotificationId::unique::<ZedUpdateRequiredError>(),
+ NotificationSource::Copilot,
cx,
move |cx| {
cx.new(|cx| {
@@ -38,8 +38,8 @@ use ui::{
use util::ResultExt as _;
use workspace::{
- StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle,
- notifications::NotificationId,
+ NotificationSource, StatusItemView, Toast, Workspace, create_and_open_local_file,
+ item::ItemHandle, notifications::NotificationId,
};
use zed_actions::{OpenBrowser, OpenSettingsAt};
@@ -137,6 +137,7 @@ impl Render for EditPredictionButton {
)
},
),
+ NotificationSource::Copilot,
cx,
);
});
@@ -211,9 +211,10 @@ use ui::{
use ui_input::ErasedEditor;
use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
use workspace::{
- CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, OpenInTerminal,
- OpenTerminal, Pane, RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection,
- TabBarSettings, Toast, ViewId, Workspace, WorkspaceId, WorkspaceSettings,
+ CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry,
+ NotificationSource, OpenInTerminal, OpenTerminal, Pane, RestoreOnStartupBehavior,
+ SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast, ViewId, Workspace,
+ WorkspaceId, WorkspaceSettings,
item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
searchable::{CollapseDirection, SearchEvent},
@@ -11459,8 +11460,11 @@ impl Editor {
let Some(project) = self.project.clone() else {
return;
};
- self.reload(project, window, cx)
- .detach_and_notify_err(window, cx);
+ self.reload(project, window, cx).detach_and_notify_err(
+ NotificationSource::Editor,
+ window,
+ cx,
+ );
}
pub fn restore_file(
@@ -22968,6 +22972,7 @@ impl Editor {
NotificationId::unique::<CopyPermalinkToLine>(),
message,
),
+ NotificationSource::Editor,
cx,
)
})
@@ -23028,6 +23033,7 @@ impl Editor {
workspace.show_toast(
Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
+ NotificationSource::Editor,
cx,
)
});
@@ -96,8 +96,8 @@ use unicode_segmentation::UnicodeSegmentation;
use util::post_inc;
use util::{RangeExt, ResultExt, debug_panic};
use workspace::{
- CollaboratorId, ItemHandle, ItemSettings, OpenInTerminal, OpenTerminal, RevealInProjectPanel,
- Workspace,
+ CollaboratorId, ItemHandle, ItemSettings, NotificationSource, OpenInTerminal, OpenTerminal,
+ RevealInProjectPanel, Workspace,
item::{BreadcrumbText, Item, ItemBufferKind},
notifications::NotifyTaskExt,
};
@@ -541,21 +541,21 @@ impl EditorElement {
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.format(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.format_selections(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.organize_imports(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
@@ -565,49 +565,49 @@ impl EditorElement {
register_action(editor, window, Editor::show_character_palette);
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_completion(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_completion_replace(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_completion_insert(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.compose_completion(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_code_action(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.rename(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
});
register_action(editor, window, |editor, action, window, cx| {
if let Some(task) = editor.confirm_rename(action, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::Editor, window, cx);
} else {
cx.propagate();
}
@@ -13,7 +13,10 @@ use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::{HighlightedLabel, ListItem, ListItemSpacing, Toggleable, v_flex};
use util::ResultExt;
-use workspace::{ModalView, Toast, Workspace, notifications::NotificationId};
+use workspace::{
+ ModalView, Toast, Workspace,
+ notifications::{NotificationId, NotificationSource},
+};
actions!(
encoding_selector,
@@ -62,6 +65,7 @@ impl EncodingSelector {
NotificationId::unique::<EncodingSelector>(),
"Save file to change encoding",
),
+ NotificationSource::Editor,
cx,
);
return Some(());
@@ -72,6 +76,7 @@ impl EncodingSelector {
NotificationId::unique::<EncodingSelector>(),
"Cannot change encoding during collaboration",
),
+ NotificationSource::Collab,
cx,
);
return Some(());
@@ -82,6 +87,7 @@ impl EncodingSelector {
NotificationId::unique::<EncodingSelector>(),
"Cannot change encoding of remote server file",
),
+ NotificationSource::Remote,
cx,
);
return Some(());
@@ -9,7 +9,10 @@ use language::Buffer;
use ui::prelude::*;
use util::rel_path::RelPath;
use workspace::notifications::simple_message_notification::MessageNotification;
-use workspace::{Workspace, notifications::NotificationId};
+use workspace::{
+ Workspace,
+ notifications::{NotificationId, NotificationSource},
+};
const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[
("astro", &["astro"]),
@@ -166,7 +169,7 @@ pub(crate) fn suggest(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Cont
SharedString::from(extension_id.clone()),
);
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::Extension, cx, |cx| {
cx.new(move |cx| {
MessageNotification::new(
format!(
@@ -33,6 +33,7 @@ use vim_mode_setting::VimModeSetting;
use workspace::{
Workspace,
item::{Item, ItemEvent},
+ notifications::NotificationSource,
};
use zed_actions::ExtensionCategoryFilter;
@@ -159,6 +160,7 @@ pub fn init(cx: &mut App) {
// NOTE: using `anyhow::context` here ends up not printing
// the error
&format!("Failed to install dev extension: {}", err),
+ NotificationSource::Extension,
cx,
);
})
@@ -44,8 +44,10 @@ use util::{
rel_path::RelPath,
};
use workspace::{
- ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace, item::PreviewTabsSettings,
- notifications::NotifyResultExt, pane,
+ ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace,
+ item::PreviewTabsSettings,
+ notifications::{NotificationSource, NotifyResultExt},
+ pane,
};
use zed_actions::search::ToggleIncludeIgnored;
@@ -1568,7 +1570,9 @@ impl PickerDelegate for FileFinderDelegate {
let finder = self.file_finder.clone();
cx.spawn_in(window, async move |_, cx| {
- let item = open_task.await.notify_async_err(cx)?;
+ let item = open_task
+ .await
+ .notify_async_err(NotificationSource::File, cx)?;
if let Some(row) = row
&& let Some(active_editor) = item.downcast::<Editor>()
{
@@ -34,7 +34,7 @@ use workspace::{
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
Workspace,
item::{ItemEvent, TabContentParams},
- notifications::NotifyTaskExt,
+ notifications::{NotificationSource, NotifyTaskExt},
pane::SaveIntent,
searchable::SearchableItemHandle,
};
@@ -774,7 +774,7 @@ impl CommitView {
callback(repo, &sha, stash, commit_view_entity, workspace_weak, cx).await?;
anyhow::Ok(())
})
- .detach_and_notify_err(window, cx);
+ .detach_and_notify_err(NotificationSource::Git, window, cx);
}
async fn close_commit_view(
@@ -76,7 +76,9 @@ use workspace::SERIALIZATION_THROTTLE_TIME;
use workspace::{
Workspace,
dock::{DockPosition, Panel, PanelEvent},
- notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotifyResultExt},
+ notifications::{
+ DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotificationSource, NotifyResultExt,
+ },
};
actions!(
git_panel,
@@ -739,7 +741,7 @@ impl GitPanel {
GitStoreEvent::IndexWriteError(error) => {
this.workspace
.update(cx, |workspace, cx| {
- workspace.show_error(error, cx);
+ workspace.show_error(error, NotificationSource::Git, cx);
})
.ok();
}
@@ -1277,7 +1279,7 @@ impl GitPanel {
cx.spawn_in(window, async move |_, mut cx| {
let item = open_task
.await
- .notify_async_err(&mut cx)
+ .notify_async_err(NotificationSource::Git, &mut cx)
.ok_or_else(|| anyhow::anyhow!("Failed to open file"))?;
if let Some(active_editor) = item.downcast::<Editor>() {
if let Some(diff_task) =
@@ -3752,7 +3754,7 @@ impl GitPanel {
let _ = workspace.update(cx, |workspace, cx| {
struct CommitMessageError;
let notification_id = NotificationId::unique::<CommitMessageError>();
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::Git, cx, |cx| {
cx.new(|cx| {
ErrorMessagePrompt::new(
format!("Failed to generate commit message: {err}"),
@@ -44,7 +44,7 @@ use workspace::{
CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation,
ToolbarItemView, Workspace,
item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams},
- notifications::NotifyTaskExt,
+ notifications::{NotificationSource, NotifyTaskExt},
searchable::SearchableItemHandle,
};
use ztracing::instrument;
@@ -138,7 +138,7 @@ impl ProjectDiff {
.ok();
anyhow::Ok(())
})
- .detach_and_notify_err(window, cx);
+ .detach_and_notify_err(NotificationSource::Git, window, cx);
}
pub fn deploy_at(
@@ -9,13 +9,13 @@ pub use inspector::init;
#[cfg(not(debug_assertions))]
pub fn init(_app_state: std::sync::Arc<workspace::AppState>, cx: &mut gpui::App) {
use std::any::TypeId;
- use workspace::notifications::NotifyResultExt as _;
+ use workspace::notifications::{NotificationSource, NotifyResultExt as _};
cx.on_action(|_: &zed_actions::dev::ToggleInspector, cx| {
Err::<(), anyhow::Error>(anyhow::anyhow!(
"dev::ToggleInspector is only available in debug builds"
))
- .notify_app_err(cx);
+ .notify_app_err(NotificationSource::System, cx);
});
command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
@@ -5,7 +5,7 @@ use release_channel::ReleaseChannel;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use util::ResultExt;
-use workspace::notifications::{DetachAndPromptErr, NotificationId};
+use workspace::notifications::{DetachAndPromptErr, NotificationId, NotificationSource};
use workspace::{Toast, Workspace};
actions!(
@@ -91,6 +91,7 @@ pub fn install_cli_binary(window: &mut Window, cx: &mut Context<Workspace>) {
ReleaseChannel::global(cx).display_name()
),
),
+ NotificationSource::Cli,
cx,
)
})?;
@@ -38,7 +38,8 @@ use ui::{
use ui_input::InputField;
use util::ResultExt;
use workspace::{
- Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _,
+ Item, ModalView, SerializableItem, Workspace,
+ notifications::{NotificationSource, NotifyTaskExt as _},
register_serializable_item,
};
@@ -1319,7 +1320,7 @@ impl KeymapEditor {
cx.spawn(async move |_, _| {
remove_keybinding(to_remove, &fs, keyboard_mapper.as_ref()).await
})
- .detach_and_notify_err(window, cx);
+ .detach_and_notify_err(NotificationSource::Settings, window, cx);
}
fn copy_context_to_clipboard(
@@ -19,7 +19,7 @@ use ui::{
pub use workspace::welcome::ShowWelcome;
use workspace::welcome::WelcomePage;
use workspace::{
- AppState, Workspace, WorkspaceId,
+ AppState, NotificationSource, Workspace, WorkspaceId,
dock::DockPosition,
item::{Item, ItemEvent},
notifications::NotifyResultExt as _,
@@ -246,7 +246,7 @@ impl Onboarding {
client
.sign_in_with_optional_connect(true, cx)
.await
- .notify_async_err(cx);
+ .notify_async_err(NotificationSource::System, cx);
})
.detach();
}
@@ -72,7 +72,7 @@ use workspace::{
DraggedSelection, OpenInTerminal, OpenOptions, OpenVisible, PreviewTabsSettings, SelectedEntry,
SplitDirection, Workspace,
dock::{DockPosition, Panel, PanelEvent},
- notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
+ notifications::{DetachAndPromptErr, NotificationSource, NotifyResultExt, NotifyTaskExt},
};
use worktree::CreatedEntry;
use zed_actions::{project_panel::ToggleFocus, workspace::OpenWithSystem};
@@ -772,7 +772,11 @@ impl ProjectPanel {
{
match project_panel.confirm_edit(false, window, cx) {
Some(task) => {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(
+ NotificationSource::File,
+ window,
+ cx,
+ );
}
None => {
project_panel.discard_edit_state(window, cx);
@@ -1648,7 +1652,7 @@ impl ProjectPanel {
fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
if let Some(task) = self.confirm_edit(true, window, cx) {
- task.detach_and_notify_err(window, cx);
+ task.detach_and_notify_err(NotificationSource::File, window, cx);
}
}
@@ -3040,13 +3044,15 @@ impl ProjectPanel {
match task {
PasteTask::Rename(task) => {
if let Some(CreatedEntry::Included(entry)) =
- task.await.notify_async_err(cx)
+ task.await.notify_async_err(NotificationSource::File, cx)
{
last_succeed = Some(entry);
}
}
PasteTask::Copy(task) => {
- if let Some(Some(entry)) = task.await.notify_async_err(cx) {
+ if let Some(Some(entry)) =
+ task.await.notify_async_err(NotificationSource::File, cx)
+ {
last_succeed = Some(entry);
}
}
@@ -3209,6 +3215,7 @@ impl ProjectPanel {
notification_id.clone(),
format!("Downloading 0/{} files...", total_files),
),
+ NotificationSource::File,
cx,
);
})
@@ -3229,6 +3236,7 @@ impl ProjectPanel {
total_files
),
),
+ NotificationSource::File,
cx,
);
})
@@ -3262,6 +3270,7 @@ impl ProjectPanel {
notification_id.clone(),
format!("Downloaded {} files", total_files),
),
+ NotificationSource::File,
cx,
);
})
@@ -6,8 +6,8 @@ use std::sync::LazyLock;
use ui::prelude::*;
use util::rel_path::RelPath;
use workspace::Workspace;
-use workspace::notifications::NotificationId;
use workspace::notifications::simple_message_notification::MessageNotification;
+use workspace::notifications::{NotificationId, NotificationSource};
use worktree::UpdatedEntriesSet;
const DEV_CONTAINER_SUGGEST_KEY: &str = "dev_container_suggest_dismissed";
@@ -78,7 +78,7 @@ pub fn suggest_on_worktree_updated(
SharedString::from(project_path.clone()),
);
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::DevContainer, cx, |cx| {
cx.new(move |cx| {
MessageNotification::new(
"This project contains a Dev Container configuration file. Would you like to re-open it in a container?",
@@ -20,7 +20,8 @@ pub use settings::SshConnection;
use settings::{DevContainerConnection, ExtendingVec, RegisterSetting, Settings, WslConnection};
use util::paths::PathWithPosition;
use workspace::{
- AppState, OpenOptions, SerializedWorkspaceLocation, Workspace, find_existing_workspace,
+ AppState, NotificationSource, OpenOptions, SerializedWorkspaceLocation, Workspace,
+ find_existing_workspace,
};
pub use remote_connection::{
@@ -175,7 +176,7 @@ pub async fn open_remote_project(
_ = existing.update(cx, |workspace, _, cx| {
for item in open_results.iter().flatten() {
if let Err(e) = item {
- workspace.show_error(&e, cx);
+ workspace.show_error(&e, NotificationSource::Remote, cx);
}
}
});
@@ -9,6 +9,7 @@ use dev_container::{
DevContainerConfig, find_devcontainer_configs, start_dev_container_with_config,
};
use editor::Editor;
+use workspace::notifications::NotificationSource;
use futures::{FutureExt, channel::oneshot, future::Shared};
use gpui::{
@@ -2375,6 +2376,7 @@ impl RemoteServerProjects {
notification,
)
.autohide(),
+ NotificationSource::Remote,
cx,
);
})
@@ -220,6 +220,7 @@ impl NativeRunningKernel {
);
},
),
+ workspace::notifications::NotificationSource::Repl,
cx,
);
})
@@ -7,7 +7,7 @@ use project::search::SearchQuery;
pub use project_search::ProjectSearchView;
use ui::{ButtonStyle, IconButton, IconButtonShape};
use ui::{Tooltip, prelude::*};
-use workspace::notifications::NotificationId;
+use workspace::notifications::{NotificationId, NotificationSource};
use workspace::{Toast, Workspace};
pub use zed_actions::search::ToggleIncludeIgnored;
@@ -197,6 +197,7 @@ pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) {
workspace.update(cx, |workspace, cx| {
workspace.show_toast(
Toast::new(notification_id.clone(), "No more matches").autohide(),
+ NotificationSource::Editor,
cx,
);
})
@@ -18,7 +18,10 @@ use std::{
};
use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
use util::ResultExt;
-use workspace::{ModalView, OpenOptions, OpenVisible, Workspace, notifications::NotifyResultExt};
+use workspace::{
+ ModalView, OpenOptions, OpenVisible, Workspace,
+ notifications::{NotificationSource, NotifyResultExt},
+};
#[derive(Eq, Hash, PartialEq)]
struct ScopeName(Cow<'static, str>);
@@ -93,7 +96,7 @@ fn open_folder(
_: &mut Window,
cx: &mut Context<Workspace>,
) {
- fs::create_dir_all(snippets_dir()).notify_err(workspace, cx);
+ fs::create_dir_all(snippets_dir()).notify_err(workspace, NotificationSource::Editor, cx);
cx.open_with_system(snippets_dir().borrow());
}
@@ -43,7 +43,10 @@ use ui::{
};
use update_version::UpdateVersion;
use util::ResultExt;
-use workspace::{SwitchProject, ToggleWorktreeSecurity, Workspace, notifications::NotifyResultExt};
+use workspace::{
+ SwitchProject, ToggleWorktreeSecurity, Workspace,
+ notifications::{NotificationSource, NotifyResultExt},
+};
use zed_actions::OpenRemote;
pub use onboarding_banner::restore_banner;
@@ -945,7 +948,7 @@ impl TitleBar {
client
.sign_in_with_optional_connect(true, cx)
.await
- .notify_async_err(cx);
+ .notify_async_err(NotificationSource::Collab, cx);
})
.detach();
})
@@ -36,7 +36,10 @@ use util::{
rel_path::{RelPath, RelPathBuf},
};
use workspace::{Item, SaveIntent, Workspace, notifications::NotifyResultExt};
-use workspace::{SplitDirection, notifications::DetachAndPromptErr};
+use workspace::{
+ SplitDirection,
+ notifications::{DetachAndPromptErr, NotificationSource},
+};
use zed_actions::{OpenDocs, RevealTarget};
use crate::{
@@ -892,7 +895,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
return;
};
workspace.update(cx, |workspace, cx| {
- e.notify_err(workspace, cx);
+ e.notify_err(workspace, NotificationSource::Editor, cx);
});
}
});
@@ -936,7 +939,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
return;
};
workspace.update(cx, |workspace, cx| {
- e.notify_err(workspace, cx);
+ e.notify_err(workspace, NotificationSource::Editor, cx);
});
return;
}
@@ -2136,7 +2139,7 @@ impl OnMatchingLines {
return;
};
workspace.update(cx, |workspace, cx| {
- e.notify_err(workspace, cx);
+ e.notify_err(workspace, NotificationSource::Editor, cx);
});
return;
}
@@ -2153,7 +2156,7 @@ impl OnMatchingLines {
return;
};
workspace.update(cx, |workspace, cx| {
- e.notify_err(workspace, cx);
+ e.notify_err(workspace, NotificationSource::Editor, cx);
});
return;
}
@@ -7,7 +7,10 @@ use serde::Deserialize;
use settings::Settings;
use std::{iter::Peekable, str::Chars};
use util::serde::default_true;
-use workspace::{notifications::NotifyResultExt, searchable::Direction};
+use workspace::{
+ notifications::{NotificationSource, NotifyResultExt},
+ searchable::Direction,
+};
use crate::{
Vim, VimSettings,
@@ -571,7 +574,7 @@ impl Vim {
anyhow::Ok(())
}) {
workspace.update(cx, |workspace, cx| {
- result.notify_err(workspace, cx);
+ result.notify_err(workspace, NotificationSource::Editor, cx);
})
}
let Some(search_bar) = pane.update(cx, |pane, cx| {
@@ -9,11 +9,13 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle};
use parking_lot::Mutex;
use project::project_settings::ProjectSettings;
use settings::Settings;
+use telemetry;
use theme::ThemeSettings;
+use std::any::TypeId;
use std::ops::Deref;
use std::sync::{Arc, LazyLock};
-use std::{any::TypeId, time::Duration};
+use std::time::Duration;
use ui::{CopyButton, Tooltip, prelude::*};
use util::ResultExt;
@@ -68,6 +70,153 @@ pub trait Notification:
pub struct SuppressEvent;
+/// Source categories for notification telemetry.
+/// These help identify which part of Zed generated a notification.
+#[derive(Clone, Copy, Debug, Default)]
+pub enum NotificationSource {
+ /// Language server notifications (errors, warnings, info from LSP)
+ Lsp,
+ /// Settings and keymap parse/migration errors
+ Settings,
+ /// App update notifications, release notes
+ Update,
+ /// Extension suggestions, dev extension errors
+ Extension,
+ /// Git blame errors, commit message generation failures
+ Git,
+ /// Local settings/tasks/debug errors, project-level issues
+ Project,
+ /// Collaboration notifications (contact requests, channel invites)
+ Collab,
+ /// WSL filesystem warnings, SSH/remote project errors
+ Remote,
+ /// Database load failures
+ Database,
+ /// File access errors, file drop errors
+ File,
+ /// Dev container suggestions
+ DevContainer,
+ /// Agent/assistant related notifications
+ Agent,
+ /// Copilot related notifications
+ Copilot,
+ /// Editor operations (permalinks, encoding, search)
+ Editor,
+ /// Task execution notifications
+ Task,
+ /// CLI installation notifications
+ Cli,
+ /// REPL/Jupyter kernel notifications
+ Repl,
+ /// Generic system notifications (fallback)
+ #[default]
+ System,
+}
+
+impl NotificationSource {
+ fn as_str(&self) -> &'static str {
+ match self {
+ NotificationSource::Lsp => "lsp",
+ NotificationSource::Settings => "settings",
+ NotificationSource::Update => "update",
+ NotificationSource::Extension => "extension",
+ NotificationSource::Git => "git",
+ NotificationSource::Project => "project",
+ NotificationSource::Collab => "collab",
+ NotificationSource::Remote => "remote",
+ NotificationSource::Database => "database",
+ NotificationSource::File => "file",
+ NotificationSource::DevContainer => "dev_container",
+ NotificationSource::Agent => "agent",
+ NotificationSource::Copilot => "copilot",
+ NotificationSource::Editor => "editor",
+ NotificationSource::Task => "task",
+ NotificationSource::Cli => "cli",
+ NotificationSource::Repl => "repl",
+ NotificationSource::System => "system",
+ }
+ }
+}
+
+#[derive(Clone)]
+struct NotificationTelemetry {
+ notification_type: &'static str,
+ source: &'static str,
+ lsp_name: Option<String>,
+ level: Option<&'static str>,
+ has_actions: bool,
+ notification_id: String,
+ is_auto_dismissing: bool,
+}
+
+impl NotificationTelemetry {
+ fn for_language_server_prompt(prompt: &LanguageServerPrompt, id: &NotificationId) -> Self {
+ let (level, has_actions, lsp_name) = prompt
+ .request
+ .as_ref()
+ .map(|req| {
+ let level = match req.level {
+ PromptLevel::Critical => "critical",
+ PromptLevel::Warning => "warning",
+ PromptLevel::Info => "info",
+ };
+ (
+ Some(level),
+ !req.actions.is_empty(),
+ Some(req.lsp_name.clone()),
+ )
+ })
+ .unwrap_or((None, false, None));
+
+ Self {
+ notification_type: "lsp",
+ source: "lsp",
+ lsp_name,
+ level,
+ has_actions,
+ notification_id: format!("{:?}", id),
+ is_auto_dismissing: !has_actions,
+ }
+ }
+
+ fn for_error_message_prompt(source: NotificationSource, id: &NotificationId) -> Self {
+ Self {
+ notification_type: "error",
+ source: source.as_str(),
+ lsp_name: None,
+ level: Some("critical"),
+ has_actions: false,
+ notification_id: format!("{:?}", id),
+ is_auto_dismissing: false,
+ }
+ }
+
+ fn for_message_notification(source: NotificationSource, id: &NotificationId) -> Self {
+ Self {
+ notification_type: "notification",
+ source: source.as_str(),
+ lsp_name: None,
+ level: None,
+ has_actions: false,
+ notification_id: format!("{:?}", id),
+ is_auto_dismissing: false,
+ }
+ }
+
+ fn report(self) {
+ telemetry::event!(
+ "Notification Shown",
+ notification_type = self.notification_type,
+ source = self.source,
+ lsp_name = self.lsp_name,
+ level = self.level,
+ has_actions = self.has_actions,
+ notification_id = self.notification_id,
+ is_auto_dismissing = self.is_auto_dismissing,
+ );
+ }
+}
+
impl Workspace {
#[cfg(any(test, feature = "test-support"))]
pub fn notification_ids(&self) -> Vec<NotificationId> {
@@ -81,9 +230,12 @@ impl Workspace {
pub fn show_notification<V: Notification>(
&mut self,
id: NotificationId,
+ source: NotificationSource,
cx: &mut Context<Self>,
build_notification: impl FnOnce(&mut Context<Self>) -> Entity<V>,
) {
+ let mut telemetry_data: Option<NotificationTelemetry> = None;
+
self.show_notification_without_handling_dismiss_events(&id, cx, |cx| {
let notification = build_notification(cx);
cx.subscribe(¬ification, {
@@ -104,6 +256,11 @@ impl Workspace {
if let Ok(prompt) =
AnyEntity::from(notification.clone()).downcast::<LanguageServerPrompt>()
{
+ telemetry_data = Some(NotificationTelemetry::for_language_server_prompt(
+ prompt.read(cx),
+ &id,
+ ));
+
let is_prompt_without_actions = prompt
.read(cx)
.request
@@ -133,9 +290,24 @@ impl Workspace {
});
}
}
+ } else if AnyEntity::from(notification.clone())
+ .downcast::<ErrorMessagePrompt>()
+ .is_ok()
+ {
+ telemetry_data = Some(NotificationTelemetry::for_error_message_prompt(source, &id));
+ } else if AnyEntity::from(notification.clone())
+ .downcast::<simple_message_notification::MessageNotification>()
+ .is_ok()
+ {
+ telemetry_data = Some(NotificationTelemetry::for_message_notification(source, &id));
}
+
notification.into()
});
+
+ if let Some(telemetry) = telemetry_data {
+ telemetry.report();
+ }
}
/// Shows a notification in this workspace's window. Caller must handle dismiss.
@@ -158,11 +330,11 @@ impl Workspace {
cx.notify();
}
- pub fn show_error<E>(&mut self, err: &E, cx: &mut Context<Self>)
+ pub fn show_error<E>(&mut self, err: &E, source: NotificationSource, cx: &mut Context<Self>)
where
E: std::fmt::Debug + std::fmt::Display,
{
- self.show_notification(workspace_error_notification_id(), cx, |cx| {
+ self.show_notification(workspace_error_notification_id(), source, cx, |cx| {
cx.new(|cx| ErrorMessagePrompt::new(format!("Error: {err}"), cx))
});
}
@@ -170,14 +342,19 @@ impl Workspace {
pub fn show_portal_error(&mut self, err: String, cx: &mut Context<Self>) {
struct PortalError;
- self.show_notification(NotificationId::unique::<PortalError>(), cx, |cx| {
- cx.new(|cx| {
- ErrorMessagePrompt::new(err.to_string(), cx).with_link_button(
- "See docs",
- "https://zed.dev/docs/linux#i-cant-open-any-files",
- )
- })
- });
+ self.show_notification(
+ NotificationId::unique::<PortalError>(),
+ NotificationSource::System,
+ cx,
+ |cx| {
+ cx.new(|cx| {
+ ErrorMessagePrompt::new(err.to_string(), cx).with_link_button(
+ "See docs",
+ "https://zed.dev/docs/linux#i-cant-open-any-files",
+ )
+ })
+ },
+ );
}
pub fn dismiss_notification(&mut self, id: &NotificationId, cx: &mut Context<Self>) {
@@ -191,9 +368,9 @@ impl Workspace {
});
}
- pub fn show_toast(&mut self, toast: Toast, cx: &mut Context<Self>) {
+ pub fn show_toast(&mut self, toast: Toast, source: NotificationSource, cx: &mut Context<Self>) {
self.dismiss_notification(&toast.id, cx);
- self.show_notification(toast.id.clone(), cx, |cx| {
+ self.show_notification(toast.id.clone(), source, cx, |cx| {
cx.new(|cx| match toast.on_click.as_ref() {
Some((click_msg, on_click)) => {
let on_click = on_click.clone();
@@ -1002,9 +1179,23 @@ impl AppNotifications {
/// exist. If the notification is dismissed within any workspace, it will be removed from all.
pub fn show_app_notification<V: Notification + 'static>(
id: NotificationId,
+ source: NotificationSource,
cx: &mut App,
build_notification: impl Fn(&mut Context<Workspace>) -> Entity<V> + 'static + Send + Sync,
) {
+ let telemetry_data = if TypeId::of::<V>() == TypeId::of::<ErrorMessagePrompt>() {
+ Some(NotificationTelemetry::for_error_message_prompt(source, &id))
+ } else if TypeId::of::<V>() == TypeId::of::<simple_message_notification::MessageNotification>()
+ {
+ Some(NotificationTelemetry::for_message_notification(source, &id))
+ } else {
+ None
+ };
+
+ if let Some(telemetry) = telemetry_data {
+ telemetry.report();
+ }
+
// Defer notification creation so that windows on the stack can be returned to GPUI
cx.defer(move |cx| {
// Handle dismiss events by removing the notification from all workspaces.
@@ -1073,13 +1264,21 @@ pub fn dismiss_app_notification(id: &NotificationId, cx: &mut App) {
pub trait NotifyResultExt {
type Ok;
- fn notify_err(self, workspace: &mut Workspace, cx: &mut Context<Workspace>)
- -> Option<Self::Ok>;
+ fn notify_err(
+ self,
+ workspace: &mut Workspace,
+ source: NotificationSource,
+ cx: &mut Context<Workspace>,
+ ) -> Option<Self::Ok>;
- fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
+ fn notify_async_err(
+ self,
+ source: NotificationSource,
+ 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 App) -> Option<Self::Ok>;
+ fn notify_app_err(self, source: NotificationSource, cx: &mut App) -> Option<Self::Ok>;
}
impl<T, E> NotifyResultExt for std::result::Result<T, E>
@@ -1088,25 +1287,34 @@ where
{
type Ok = T;
- fn notify_err(self, workspace: &mut Workspace, cx: &mut Context<Workspace>) -> Option<T> {
+ fn notify_err(
+ self,
+ workspace: &mut Workspace,
+ source: NotificationSource,
+ cx: &mut Context<Workspace>,
+ ) -> Option<T> {
match self {
Ok(value) => Some(value),
Err(err) => {
log::error!("Showing error notification in workspace: {err:?}");
- workspace.show_error(&err, cx);
+ workspace.show_error(&err, source, cx);
None
}
}
}
- fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
+ fn notify_async_err(
+ self,
+ source: NotificationSource,
+ cx: &mut AsyncWindowContext,
+ ) -> Option<T> {
match self {
Ok(value) => Some(value),
Err(err) => {
log::error!("{err:?}");
cx.update_root(|view, _, cx| {
if let Ok(workspace) = view.downcast::<Workspace>() {
- workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
+ workspace.update(cx, |workspace, cx| workspace.show_error(&err, source, cx))
}
})
.ok();
@@ -1115,13 +1323,13 @@ where
}
}
- fn notify_app_err(self, cx: &mut App) -> Option<T> {
+ fn notify_app_err(self, source: NotificationSource, cx: &mut App) -> Option<T> {
match self {
Ok(value) => Some(value),
Err(err) => {
let message: SharedString = format!("Error: {err}").into();
log::error!("Showing error notification in app: {message}");
- show_app_notification(workspace_error_notification_id(), cx, {
+ show_app_notification(workspace_error_notification_id(), source, cx, {
move |cx| {
cx.new({
let message = message.clone();
@@ -1137,7 +1345,7 @@ where
}
pub trait NotifyTaskExt {
- fn detach_and_notify_err(self, window: &mut Window, cx: &mut App);
+ fn detach_and_notify_err(self, source: NotificationSource, window: &mut Window, cx: &mut App);
}
impl<R, E> NotifyTaskExt for Task<std::result::Result<R, E>>
@@ -1145,9 +1353,9 @@ where
E: std::fmt::Debug + std::fmt::Display + Sized + 'static,
R: 'static,
{
- fn detach_and_notify_err(self, window: &mut Window, cx: &mut App) {
+ fn detach_and_notify_err(self, source: NotificationSource, window: &mut Window, cx: &mut App) {
window
- .spawn(cx, async move |cx| self.await.notify_async_err(cx))
+ .spawn(cx, async move |cx| self.await.notify_async_err(source, cx))
.detach();
}
}
@@ -1252,7 +1460,7 @@ mod tests {
lsp_name.to_string(),
);
let notification_id = NotificationId::composite::<LanguageServerPrompt>(request.id);
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| {
cx.new(|cx| LanguageServerPrompt::new(request, cx))
});
})
@@ -1311,7 +1519,7 @@ mod tests {
);
let notification_id = NotificationId::composite::<LanguageServerPrompt>(request.id);
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| {
cx.new(|cx| LanguageServerPrompt::new(request, cx))
});
})
@@ -1362,7 +1570,7 @@ mod tests {
"test_server".to_string(),
);
let notification_id = NotificationId::composite::<LanguageServerPrompt>(request.id);
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| {
cx.new(|cx| LanguageServerPrompt::new(request, cx))
});
});
@@ -1405,7 +1613,7 @@ mod tests {
"test_server".to_string(),
);
let notification_id = NotificationId::composite::<LanguageServerPrompt>(request.id);
- workspace.show_notification(notification_id, cx, |cx| {
+ workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| {
cx.new(|cx| LanguageServerPrompt::new(request, cx))
});
});
@@ -9,7 +9,7 @@ use crate::{
TabContentParams, TabTooltipContent, WeakItemHandle,
},
move_item,
- notifications::NotifyResultExt,
+ notifications::{NotificationSource, NotifyResultExt},
toolbar::Toolbar,
utility_pane::UtilityPaneSlot,
workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings},
@@ -3890,8 +3890,9 @@ impl Pane {
{
let load_path_task = workspace.load_path(project_path.clone(), window, cx);
cx.spawn_in(window, async move |workspace, cx| {
- if let Some((project_entry_id, build_item)) =
- load_path_task.await.notify_async_err(cx)
+ if let Some((project_entry_id, build_item)) = load_path_task
+ .await
+ .notify_async_err(NotificationSource::File, cx)
{
let (to_pane, new_item_handle) = workspace
.update_in(cx, |workspace, window, cx| {
@@ -3961,6 +3962,7 @@ impl Pane {
if workspace.project().read(cx).is_via_collab() {
workspace.show_error(
&anyhow::anyhow!("Cannot drop files on a remote project"),
+ NotificationSource::File,
cx,
);
true
@@ -4018,7 +4020,7 @@ impl Pane {
_ = workspace.update_in(cx, |workspace, window, cx| {
for item in opened_items.into_iter().flatten() {
if let Err(e) = item {
- workspace.show_error(&e, cx);
+ workspace.show_error(&e, NotificationSource::File, cx);
}
}
if to_pane.read(cx).items_len() == 0 {
@@ -10,7 +10,10 @@ use task::{
};
use ui::Window;
-use crate::{Toast, Workspace, notifications::NotificationId};
+use crate::{
+ Toast, Workspace,
+ notifications::{NotificationId, NotificationSource},
+};
impl Workspace {
pub fn schedule_task(
@@ -90,7 +93,11 @@ impl Workspace {
log::error!("Task spawn failed: {e:#}");
_ = w.update(cx, |w, cx| {
let id = NotificationId::unique::<ResolvedTask>();
- w.show_toast(Toast::new(id, format!("Task spawn failed: {e}")), cx);
+ w.show_toast(
+ Toast::new(id, format!("Task spawn failed: {e}")),
+ NotificationSource::Task,
+ cx,
+ );
})
}
None => log::debug!("Task spawn got cancelled"),
@@ -59,6 +59,7 @@ use itertools::Itertools;
use language::{Buffer, LanguageRegistry, Rope, language_settings::all_language_settings};
pub use modal_layer::*;
use node_runtime::NodeRuntime;
+pub use notifications::NotificationSource;
use notifications::{
DetachAndPromptErr, Notifications, dismiss_app_notification,
simple_message_notification::MessageNotification,
@@ -1358,6 +1359,7 @@ impl Workspace {
link,
} => this.show_notification(
NotificationId::named(notification_id.clone()),
+ NotificationSource::Project,
cx,
|cx| {
let mut notification = MessageNotification::new(message.clone(), cx);
@@ -1380,6 +1382,7 @@ impl Workspace {
this.show_notification(
NotificationId::composite::<LanguageServerPrompt>(request.id),
+ NotificationSource::Lsp,
cx,
|cx| {
cx.new(|cx| {
@@ -3132,6 +3135,7 @@ impl Workspace {
if project.is_via_collab() {
self.show_error(
&anyhow!("You cannot add folders to someone else's project"),
+ NotificationSource::Collab,
cx,
);
return;
@@ -7061,6 +7065,7 @@ fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncA
workspace.show_notification(
NotificationId::unique::<DatabaseFailedNotification>(),
+ NotificationSource::Database,
cx,
|cx| {
cx.new(|cx| {
@@ -8460,7 +8465,7 @@ pub fn open_paths(
_ = existing.update(cx, |workspace, _, cx| {
for item in open_task.iter().flatten() {
if let Err(e) = item {
- workspace.show_error(&e, cx);
+ workspace.show_error(&e, NotificationSource::File, cx);
}
}
});
@@ -8487,7 +8492,7 @@ pub fn open_paths(
workspace
.update(cx, move |workspace, _window, cx| {
struct OpenInWsl;
- workspace.show_notification(NotificationId::unique::<OpenInWsl>(), cx, move |cx| {
+ workspace.show_notification(NotificationId::unique::<OpenInWsl>(), NotificationSource::Remote, cx, move |cx| {
let display_path = util::markdown::MarkdownInlineCode(&path.to_string_lossy());
let msg = format!("{display_path} is inside a WSL filesystem, some features may not work unless you open it with WSL remote");
cx.new(move |cx| {
@@ -8749,10 +8754,14 @@ async fn open_remote_project_inner(
for error in project_path_errors {
if error.error_code() == proto::ErrorCode::DevServerProjectPathDoesNotExist {
if let Some(path) = error.error_tag("path") {
- workspace.show_error(&anyhow!("'{path}' does not exist"), cx)
+ workspace.show_error(
+ &anyhow!("'{path}' does not exist"),
+ NotificationSource::Remote,
+ cx,
+ )
}
} else {
- workspace.show_error(&error, cx)
+ workspace.show_error(&error, NotificationSource::Remote, cx)
}
}
})?;
@@ -55,7 +55,8 @@ use util::{ResultExt, TryFutureExt, maybe};
use uuid::Uuid;
use workspace::{
AppState, PathList, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceId,
- WorkspaceSettings, WorkspaceStore, notifications::NotificationId,
+ WorkspaceSettings, WorkspaceStore,
+ notifications::{NotificationId, NotificationSource},
};
use zed::{
OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options,
@@ -938,6 +939,7 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
format!("Imported shared thread from {}", response.sharer_username),
)
.autohide(),
+ NotificationSource::Agent,
cx,
);
})?;
@@ -1360,8 +1362,11 @@ async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: &mut AsyncApp
{
workspace
.update(cx, |workspace, _, cx| {
- workspace
- .show_toast(Toast::new(NotificationId::unique::<()>(), message), cx)
+ workspace.show_toast(
+ Toast::new(NotificationId::unique::<()>(), message),
+ NotificationSource::System,
+ cx,
+ )
})
.ok();
return true;
@@ -84,7 +84,8 @@ use util::{ResultExt, asset_str, maybe};
use uuid::Uuid;
use vim_mode_setting::VimModeSetting;
use workspace::notifications::{
- NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification,
+ NotificationId, NotificationSource, SuppressEvent, dismiss_app_notification,
+ show_app_notification,
};
use workspace::utility_pane::utility_slot_for_dock_position;
use workspace::{
@@ -803,6 +804,7 @@ fn register_actions(
"Opening this URL in a browser failed because the URL is invalid: {}\n\nError was: {e}",
action.url
),
+ NotificationSource::System,
cx,
);
}
@@ -999,6 +1001,7 @@ fn register_actions(
ReleaseChannel::global(cx).display_name()
),
),
+ NotificationSource::Cli,
cx,
)
})?;
@@ -1410,6 +1413,7 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex
.update(cx, |workspace, cx| {
workspace.show_notification(
NotificationId::unique::<OpenLogError>(),
+ NotificationSource::System,
cx,
|cx| {
cx.new(|cx| {
@@ -1494,7 +1498,7 @@ fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool,
false
// Local settings errors are displayed by the projects
} else {
- show_app_notification(id, cx, move |cx| {
+ show_app_notification(id, NotificationSource::Settings, cx, move |cx| {
cx.new(|cx| {
MessageNotification::new(format!("Invalid user settings file\n{error}"), cx)
.primary_message("Open Settings File")
@@ -1524,7 +1528,7 @@ fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool,
}
settings::MigrationStatus::Failed { error: err } => {
if !showed_parse_error {
- show_app_notification(id, cx, move |cx| {
+ show_app_notification(id, NotificationSource::Settings, cx, move |cx| {
cx.new(|cx| {
MessageNotification::new(
format!(
@@ -1730,17 +1734,22 @@ fn show_keymap_file_json_error(
) {
let message: SharedString =
format!("JSON parse error in keymap file. Bindings not reloaded.\n\n{error}").into();
- show_app_notification(notification_id, cx, move |cx| {
- cx.new(|cx| {
- MessageNotification::new(message.clone(), cx)
- .primary_message("Open Keymap File")
- .primary_icon(IconName::Settings)
- .primary_on_click(|window, cx| {
- window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx);
- cx.emit(DismissEvent);
- })
- })
- });
+ show_app_notification(
+ notification_id,
+ NotificationSource::Settings,
+ cx,
+ move |cx| {
+ cx.new(|cx| {
+ MessageNotification::new(message.clone(), cx)
+ .primary_message("Open Keymap File")
+ .primary_icon(IconName::Settings)
+ .primary_on_click(|window, cx| {
+ window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx);
+ cx.emit(DismissEvent);
+ })
+ })
+ },
+ );
}
fn show_keymap_file_load_error(
@@ -1785,29 +1794,34 @@ fn show_markdown_app_notification<F>(
let primary_button_message = primary_button_message.clone();
let primary_button_on_click = Arc::new(primary_button_on_click);
cx.update(|cx| {
- show_app_notification(notification_id, cx, move |cx| {
- let workspace_handle = cx.entity().downgrade();
- let parsed_markdown = parsed_markdown.clone();
- let primary_button_message = primary_button_message.clone();
- let primary_button_on_click = primary_button_on_click.clone();
- cx.new(move |cx| {
- MessageNotification::new_from_builder(cx, move |window, cx| {
- image_cache(retain_all("notification-cache"))
- .child(div().text_ui(cx).child(
- markdown_preview::markdown_renderer::render_parsed_markdown(
- &parsed_markdown.clone(),
- Some(workspace_handle.clone()),
- window,
- cx,
- ),
- ))
- .into_any()
+ show_app_notification(
+ notification_id,
+ NotificationSource::Settings,
+ cx,
+ move |cx| {
+ let workspace_handle = cx.entity().downgrade();
+ let parsed_markdown = parsed_markdown.clone();
+ let primary_button_message = primary_button_message.clone();
+ let primary_button_on_click = primary_button_on_click.clone();
+ cx.new(move |cx| {
+ MessageNotification::new_from_builder(cx, move |window, cx| {
+ image_cache(retain_all("notification-cache"))
+ .child(div().text_ui(cx).child(
+ markdown_preview::markdown_renderer::render_parsed_markdown(
+ &parsed_markdown.clone(),
+ Some(workspace_handle.clone()),
+ window,
+ cx,
+ ),
+ ))
+ .into_any()
+ })
+ .primary_message(primary_button_message)
+ .primary_icon(IconName::Settings)
+ .primary_on_click_arc(primary_button_on_click)
})
- .primary_message(primary_button_message)
- .primary_icon(IconName::Settings)
- .primary_on_click_arc(primary_button_on_click)
- })
- })
+ },
+ )
});
})
.detach();
@@ -2007,9 +2021,12 @@ fn open_local_file(
} else {
struct NoOpenFolders;
- workspace.show_notification(NotificationId::unique::<NoOpenFolders>(), cx, |cx| {
- cx.new(|cx| MessageNotification::new("This project has no folders open.", cx))
- })
+ workspace.show_notification(
+ NotificationId::unique::<NoOpenFolders>(),
+ NotificationSource::Project,
+ cx,
+ |cx| cx.new(|cx| MessageNotification::new("This project has no folders open.", cx)),
+ )
}
}
@@ -2184,6 +2201,7 @@ fn capture_recent_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Cont
workspace.show_notification(
NotificationId::unique::<CaptureRecentAudioNotification>(),
+ NotificationSource::System,
cx,
|cx| cx.new(CaptureRecentAudioNotification::new),
);
@@ -4,7 +4,7 @@ use fs::Fs;
use migrator::{migrate_keymap, migrate_settings};
use settings::{KeymapFile, Settings, SettingsStore};
use util::ResultExt;
-use workspace::notifications::NotifyTaskExt;
+use workspace::notifications::{NotificationSource, NotifyTaskExt};
use std::sync::Arc;
@@ -241,11 +241,19 @@ impl Render for MigrationBanner {
match migration_type {
Some(MigrationType::Keymap) => {
cx.background_spawn(write_keymap_migration(fs.clone()))
- .detach_and_notify_err(window, cx);
+ .detach_and_notify_err(
+ NotificationSource::Settings,
+ window,
+ cx,
+ );
}
Some(MigrationType::Settings) => {
cx.background_spawn(write_settings_migration(fs.clone()))
- .detach_and_notify_err(window, cx);
+ .detach_and_notify_err(
+ NotificationSource::Settings,
+ window,
+ cx,
+ );
}
None => unreachable!(),
}
@@ -22,7 +22,7 @@ use ui::{
};
use workspace::{
Item, ItemHandle, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
- notifications::NotificationId,
+ notifications::{NotificationId, NotificationSource},
};
const MAX_EVENTS: usize = 10_000;
@@ -37,7 +37,7 @@ pub fn init(cx: &mut App) {
cx.subscribe(&telemetry_log, |workspace, _, event, cx| {
let TelemetryLogEvent::ShowToast(toast) = event;
- workspace.show_toast(toast.clone(), cx);
+ workspace.show_toast(toast.clone(), NotificationSource::System, cx);
})
.detach();