diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 88532954d498d66b0f31a8924bc6d83deda5b295..94fbff72f780ab5f4a1fa00d53a1b068c8505247 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -55,12 +55,9 @@ use ui::{ PopoverMenu, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, prelude::*, right_click_menu, }; +use util::defer; use util::{ResultExt, size::format_file_size, time::duration_alt_display}; -use util::{debug_panic, defer}; -use workspace::{ - CollaboratorId, NewTerminal, NotificationSource, Toast, Workspace, - notifications::NotificationId, -}; +use workspace::{CollaboratorId, NewTerminal, Toast, Workspace, notifications::NotificationId}; use zed_actions::agent::{Chat, ToggleModelSelector}; use zed_actions::assistant::OpenRulesLibrary; @@ -181,9 +178,9 @@ pub struct AcpServerView { } impl AcpServerView { - pub fn active_thread(&self) -> Option<&Entity> { + pub fn active_thread(&self) -> Option> { match &self.server_state { - ServerState::Connected(connected) => connected.active_view(), + ServerState::Connected(connected) => Some(connected.current.clone()), _ => None, } } @@ -191,15 +188,15 @@ impl AcpServerView { pub fn parent_thread(&self, cx: &App) -> Option> { match &self.server_state { ServerState::Connected(connected) => { - let mut current = connected.active_view()?; + let mut current = connected.current.clone(); while let Some(parent_id) = current.read(cx).parent_id.clone() { if let Some(parent) = connected.threads.get(&parent_id) { - current = parent; + current = parent.clone(); } else { break; } } - Some(current.clone()) + Some(current) } _ => None, } @@ -252,7 +249,7 @@ enum ServerState { // hashmap of threads, current becomes session_id pub struct ConnectedServerState { auth_state: AuthState, - active_id: Option, + current: Entity, threads: HashMap>, connection: Rc, } @@ -280,18 +277,13 @@ struct LoadingView { } impl ConnectedServerState { - pub fn active_view(&self) -> Option<&Entity> { - self.active_id.as_ref().and_then(|id| self.threads.get(id)) - } - pub fn has_thread_error(&self, cx: &App) -> bool { - self.active_view() - .map_or(false, |view| view.read(cx).thread_error.is_some()) + self.current.read(cx).thread_error.is_some() } pub fn navigate_to_session(&mut self, session_id: acp::SessionId) { - if self.threads.contains_key(&session_id) { - self.active_id = Some(session_id); + if let Some(session) = self.threads.get(&session_id) { + self.current = session.clone(); } } @@ -394,8 +386,8 @@ impl AcpServerView { ); self.set_server_state(state, cx); - if let Some(view) = self.active_thread() { - view.update(cx, |this, cx| { + if let Some(connected) = self.as_connected() { + connected.current.update(cx, |this, cx| { this.message_editor.update(cx, |editor, cx| { editor.set_command_state( this.prompt_capabilities.clone(), @@ -528,14 +520,7 @@ impl AcpServerView { Err(e) => match e.downcast::() { Ok(err) => { cx.update(|window, cx| { - Self::handle_auth_required( - this, - err, - agent.name(), - connection, - window, - cx, - ) + Self::handle_auth_required(this, err, agent.name(), window, cx) }) .log_err(); return; @@ -566,13 +551,15 @@ impl AcpServerView { .focus(window, cx); } - let id = current.read(cx).thread.read(cx).session_id().clone(); this.set_server_state( ServerState::Connected(ConnectedServerState { connection, auth_state: AuthState::Ok, - active_id: Some(id.clone()), - threads: HashMap::from_iter([(id, current)]), + current: current.clone(), + threads: HashMap::from_iter([( + current.read(cx).thread.read(cx).session_id().clone(), + current, + )]), }), cx, ); @@ -829,7 +816,6 @@ impl AcpServerView { this: WeakEntity, err: AuthRequired, agent_name: SharedString, - connection: Rc, window: &mut Window, cx: &mut App, ) { @@ -869,36 +855,26 @@ impl AcpServerView { }; this.update(cx, |this, cx| { - let description = err - .description - .map(|desc| cx.new(|cx| Markdown::new(desc.into(), None, None, cx))); - let auth_state = AuthState::Unauthenticated { - pending_auth_method: None, - configuration_view, - description, - _subscription: subscription, - }; if let Some(connected) = this.as_connected_mut() { - connected.auth_state = auth_state; - if let Some(view) = connected.active_view() - && view - .read(cx) - .message_editor - .focus_handle(cx) - .is_focused(window) + let description = err + .description + .map(|desc| cx.new(|cx| Markdown::new(desc.into(), None, None, cx))); + + connected.auth_state = AuthState::Unauthenticated { + pending_auth_method: None, + configuration_view, + description, + _subscription: subscription, + }; + if connected + .current + .read(cx) + .message_editor + .focus_handle(cx) + .is_focused(window) { this.focus_handle.focus(window, cx) } - } else { - this.set_server_state( - ServerState::Connected(ConnectedServerState { - auth_state, - active_id: None, - threads: HashMap::default(), - connection, - }), - cx, - ); } cx.notify(); }) @@ -911,15 +887,19 @@ impl AcpServerView { window: &mut Window, cx: &mut Context, ) { - if let Some(view) = self.active_thread() { - if view - .read(cx) - .message_editor - .focus_handle(cx) - .is_focused(window) - { - self.focus_handle.focus(window, cx) + match &self.server_state { + ServerState::Connected(connected) => { + if connected + .current + .read(cx) + .message_editor + .focus_handle(cx) + .is_focused(window) + { + self.focus_handle.focus(window, cx) + } } + _ => {} } let load_error = if let Some(load_err) = err.downcast_ref::() { load_err.clone() @@ -1168,15 +1148,19 @@ impl AcpServerView { } } AcpThreadEvent::LoadError(error) => { - if let Some(view) = self.active_thread() { - if view - .read(cx) - .message_editor - .focus_handle(cx) - .is_focused(window) - { - self.focus_handle.focus(window, cx) + match &self.server_state { + ServerState::Connected(connected) => { + if connected + .current + .read(cx) + .message_editor + .focus_handle(cx) + .is_focused(window) + { + self.focus_handle.focus(window, cx) + } } + _ => {} } self.set_server_state(ServerState::LoadError(error.clone()), cx); } @@ -1413,7 +1397,6 @@ impl AcpServerView { provider_id: Some(language_model::GOOGLE_PROVIDER_ID), }, agent_name, - connection, window, cx, ); @@ -1439,7 +1422,6 @@ impl AcpServerView { provider_id: None, }, agent_name, - connection, window, cx, ) @@ -2415,19 +2397,8 @@ impl AcpServerView { active.update(cx, |active, cx| active.clear_thread_error(cx)); } let this = cx.weak_entity(); - let Some(connection) = self.as_connected().map(|c| c.connection.clone()) else { - debug_panic!("This should not be possible"); - return; - }; window.defer(cx, |window, cx| { - Self::handle_auth_required( - this, - AuthRequired::new(), - agent_name, - connection, - window, - cx, - ); + Self::handle_auth_required(this, AuthRequired::new(), agent_name, window, cx); }) } @@ -2537,14 +2508,7 @@ impl Render for AcpServerView { cx, )) .into_any_element(), - ServerState::Connected(connected) => { - if let Some(view) = connected.active_view() { - view.clone().into_any_element() - } else { - debug_panic!("This state should never be reached"); - div().into_any_element() - } - } + ServerState::Connected(connected) => connected.current.clone().into_any_element(), }) } } @@ -3625,13 +3589,7 @@ pub(crate) mod tests { thread_view: &Entity, cx: &TestAppContext, ) -> Entity { - cx.read(|cx| { - thread_view - .read(cx) - .active_thread() - .expect("No active thread") - .clone() - }) + cx.read(|cx| thread_view.read(cx).as_connected().unwrap().current.clone()) } fn message_editor( diff --git a/crates/agent_ui/src/acp/thread_view/active_thread.rs b/crates/agent_ui/src/acp/thread_view/active_thread.rs index 842cb76f6626110fe37136c767d0680e65b6eeca..bde91fbec9a77ca9554ca31c3e3b4d97b7a21c04 100644 --- a/crates/agent_ui/src/acp/thread_view/active_thread.rs +++ b/crates/agent_ui/src/acp/thread_view/active_thread.rs @@ -630,7 +630,6 @@ impl AcpThreadView { if can_login && !logout_supported { message_editor.update(cx, |editor, cx| editor.clear(window, cx)); - let connection = self.thread.read(cx).connection().clone(); window.defer(cx, { let agent_name = self.agent_name.clone(); let server_view = self.server_view.clone(); @@ -639,7 +638,6 @@ impl AcpThreadView { server_view.clone(), AuthRequired::new(), agent_name, - connection, window, cx, ); @@ -1441,7 +1439,6 @@ impl AcpThreadView { )); }, ), - NotificationSource::Agent, cx, ); }); @@ -1515,7 +1512,6 @@ impl AcpThreadView { "Thread synced with latest version", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -6720,13 +6716,11 @@ impl AcpThreadView { editor.set_message(message, window, cx); }); } - let connection = this.thread.read(cx).connection().clone(); window.defer(cx, |window, cx| { AcpServerView::handle_auth_required( server_view, AuthRequired::new(), agent_name, - connection, window, cx, ); diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 4ceae255be1fa0aa36ec4230cb38cfd3818c576a..ccfc0cd7073b08249a9bdc07cf3525f92e689e9a 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -66,8 +66,7 @@ use ui::{ }; use util::ResultExt as _; use workspace::{ - CollaboratorId, DraggedSelection, DraggedTab, NotificationSource, ToggleZoom, ToolbarItemView, - Workspace, + CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace, dock::{DockPosition, Panel, PanelEvent}, }; use zed_actions::{ @@ -923,7 +922,7 @@ impl AgentPanel { return; }; - let Some(active_thread) = thread_view.read(cx).active_thread().cloned() else { + let Some(active_thread) = thread_view.read(cx).active_thread() else { return; }; @@ -1196,7 +1195,7 @@ impl AgentPanel { ) { if let Some(workspace) = self.workspace.upgrade() && let Some(thread_view) = self.active_thread_view() - && let Some(active_thread) = thread_view.read(cx).active_thread().cloned() + && let Some(active_thread) = thread_view.read(cx).active_thread() { active_thread.update(cx, |thread, cx| { thread @@ -1217,7 +1216,6 @@ impl AgentPanel { "No active native thread to copy", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -1245,7 +1243,6 @@ impl AgentPanel { "Thread copied to clipboard (base64 encoded)", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -1268,7 +1265,6 @@ impl AgentPanel { "No clipboard content available", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -1286,7 +1282,6 @@ impl AgentPanel { "Clipboard does not contain text", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -1307,7 +1302,6 @@ impl AgentPanel { "Failed to decode clipboard content (expected base64)", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -1329,7 +1323,6 @@ impl AgentPanel { "Failed to parse thread data from clipboard", ) .autohide(), - NotificationSource::Agent, cx, ); }); @@ -1373,7 +1366,6 @@ impl AgentPanel { "Thread loaded from clipboard", ) .autohide(), - NotificationSource::Agent, cx, ); }); diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 24aec9ac6b7b0d57068c3a5b821443dd544eead4..3662f5a9f50f57fe29cc9c11344a2c3955b0a22d 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -52,9 +52,7 @@ use terminal_view::{TerminalView, terminal_panel::TerminalPanel}; use text::{OffsetRangeExt, ToPoint as _}; use ui::prelude::*; use util::{RangeExt, ResultExt, maybe}; -use workspace::{ - ItemHandle, NotificationSource, Toast, Workspace, dock::Panel, notifications::NotificationId, -}; +use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId}; use zed_actions::agent::OpenSettings; pub fn init(fs: Arc, prompt_builder: Arc, cx: &mut App) { @@ -1837,11 +1835,7 @@ impl InlineAssist { assist_id.0, ); - workspace.show_toast( - Toast::new(id, error), - NotificationSource::Agent, - cx, - ); + workspace.show_toast(Toast::new(id, error), cx); }) } else { #[cfg(any(test, feature = "test-support"))] diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs index 3bab8e0ec6999a272d2bf495a86e09687f1ee562..48c597f0431c480ade5810db99c36a890ec65093 100644 --- a/crates/agent_ui/src/inline_prompt_editor.rs +++ b/crates/agent_ui/src/inline_prompt_editor.rs @@ -29,7 +29,7 @@ use ui::utils::WithRemSize; use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*}; use uuid::Uuid; use workspace::notifications::NotificationId; -use workspace::{NotificationSource, Toast, Workspace}; +use workspace::{Toast, Workspace}; use zed_actions::{ agent::ToggleModelSelector, editor::{MoveDown, MoveUp}, @@ -725,7 +725,6 @@ impl PromptEditor { toast }, - NotificationSource::Agent, cx, ); }) diff --git a/crates/agent_ui/src/mention_set.rs b/crates/agent_ui/src/mention_set.rs index d86425487a970189b35d8a51eeea5d1659b4ce02..ee796323e28c64fb4162bbb05f6f6f9555a12d38 100644 --- a/crates/agent_ui/src/mention_set.rs +++ b/crates/agent_ui/src/mention_set.rs @@ -36,10 +36,7 @@ use std::{ use text::OffsetRangeExt; use ui::{Disclosure, Toggleable, prelude::*}; use util::{ResultExt, debug_panic, rel_path::RelPath}; -use workspace::{ - Workspace, - notifications::{NotificationSource, NotifyResultExt as _}, -}; +use workspace::{Workspace, notifications::NotifyResultExt as _}; use crate::ui::MentionCrease; @@ -301,7 +298,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(NotificationSource::Agent, cx); + let result = task.await.notify_async_err(cx); drop(tx); if result.is_none() { this.update(cx, |this, cx| { @@ -721,11 +718,7 @@ pub(crate) async fn insert_images_as_context( mention_set.insert_mention(crease_id, MentionUri::PastedImage, task.clone()) }); - if task - .await - .notify_async_err(NotificationSource::Agent, cx) - .is_none() - { + if task.await.notify_async_err(cx).is_none() { editor.update(cx, |editor, cx| { editor.edit([(start_anchor..end_anchor, "")], cx); }); diff --git a/crates/agent_ui/src/terminal_inline_assistant.rs b/crates/agent_ui/src/terminal_inline_assistant.rs index c96837164dc1e75d73dc7b0976b0c67a89ad423e..8ee59a0a096172ad9a81983f3517226e824c43e7 100644 --- a/crates/agent_ui/src/terminal_inline_assistant.rs +++ b/crates/agent_ui/src/terminal_inline_assistant.rs @@ -27,7 +27,7 @@ use terminal_view::TerminalView; use ui::prelude::*; use util::ResultExt; use uuid::Uuid; -use workspace::{NotificationSource, Toast, Workspace, notifications::NotificationId}; +use workspace::{Toast, Workspace, notifications::NotificationId}; pub fn init(fs: Arc, prompt_builder: Arc, cx: &mut App) { cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder)); @@ -452,11 +452,7 @@ impl TerminalInlineAssist { assist_id.0, ); - workspace.show_toast( - Toast::new(id, error), - NotificationSource::Agent, - cx, - ); + workspace.show_toast(Toast::new(id, error), cx); }) } diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 19204949ab6795b0e9748c3edbbdda309e96a1b2..447449fe72fee89b0c6775bbbcf8836141efb2b9 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -60,7 +60,7 @@ use ui::{ }; use util::{ResultExt, maybe}; use workspace::{ - CollaboratorId, NotificationSource, + CollaboratorId, searchable::{Direction, SearchToken, SearchableItemHandle}, }; @@ -1389,7 +1389,6 @@ impl TextThreadEditor { ), ) .autohide(), - NotificationSource::Agent, cx, ); } diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index f4a1fdbe61927258b2f5d7b386e09cd42675b0a3..bb300efac102172e8a36e32f06aea76f84aff0e3 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -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, NotificationSource, show_app_notification}; +use workspace::notifications::{NotificationId, show_app_notification}; actions!( auto_update, @@ -47,7 +47,6 @@ fn notify_release_notes_failed_to_show( struct ViewReleaseNotesError; workspace.show_notification( NotificationId::unique::(), - NotificationSource::Update, cx, |cx| { cx.new(move |cx| { @@ -184,7 +183,6 @@ pub fn notify_if_app_was_updated(cx: &mut App) { let app_name = ReleaseChannel::global(cx).display_name(); show_app_notification( NotificationId::unique::(), - NotificationSource::Update, cx, move |cx| { let workspace_handle = cx.entity().downgrade(); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 97ce2460b986cf63d601d6e06589d39c4329d9ac..cbfa06142e2e8a520653d031dfe8c4b69c08667a 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -22,7 +22,7 @@ use std::{ }; use ui::prelude::*; use util::ResultExt; -use workspace::{CollaboratorId, NotificationSource, item::TabContentParams}; +use workspace::{CollaboratorId, item::TabContentParams}; use workspace::{ ItemNavHistory, Pane, SaveIntent, Toast, ViewId, Workspace, WorkspaceId, item::{FollowableItem, Item, ItemEvent}, @@ -332,7 +332,6 @@ impl ChannelView { NotificationId::unique::(), "Link copied to clipboard", ), - NotificationSource::Collab, cx, ); }) diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index cfb306d488c39cf48d41ef2e64d1b7232b33323c..60262951ef916183bdaf72df90ab39f2edd83f27 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -36,8 +36,7 @@ use ui::{ }; use util::{ResultExt, TryFutureExt, maybe}; use workspace::{ - CopyRoomId, Deafen, LeaveCall, Mute, NotificationSource, OpenChannelNotes, ScreenShare, - ShareProject, Workspace, + CopyRoomId, Deafen, LeaveCall, Mute, OpenChannelNotes, ScreenShare, ShareProject, Workspace, dock::{DockPosition, Panel, PanelEvent}, notifications::{DetachAndPromptErr, NotifyResultExt}, }; @@ -131,18 +130,13 @@ pub fn init(cx: &mut App) { "Room ID copied to clipboard", ) .autohide(), - NotificationSource::Collab, cx, ); }) }) - .detach_and_notify_err(NotificationSource::Collab, window, cx); + .detach_and_notify_err(window, cx); } else { - workspace.show_error( - &"There's no active call; join one first.", - NotificationSource::Collab, - cx, - ); + workspace.show_error(&"There’s no active call; join one first.", cx); } }); workspace.register_action(|workspace, _: &ShareProject, window, cx| { @@ -2200,7 +2194,7 @@ impl CollabPanel { channel_store .update(cx, |channels, _| channels.remove_channel(channel_id)) .await - .notify_async_err(NotificationSource::Collab, cx); + .notify_async_err(cx); this.update_in(cx, |_, window, cx| cx.focus_self(window)) .ok(); } @@ -2234,7 +2228,7 @@ impl CollabPanel { user_store .update(cx, |store, cx| store.remove_contact(user_id, cx)) .await - .notify_async_err(NotificationSource::Collab, cx); + .notify_async_err(cx); } anyhow::Ok(()) }) @@ -2339,7 +2333,7 @@ impl CollabPanel { .connect(true, cx) .await .into_response() - .notify_async_err(NotificationSource::Collab, cx); + .notify_async_err(cx); }) .detach() })), diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 575e1746a9d5da543ba7f226c3fbc7d1e29c3d01..99203bc867ff7da9e140bc4a886e291252a5153d 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -26,7 +26,7 @@ use workspace::notifications::{ Notification as WorkspaceNotification, NotificationId, SuppressEvent, }; use workspace::{ - NotificationSource, Workspace, + Workspace, dock::{DockPosition, Panel, PanelEvent}, }; @@ -473,7 +473,7 @@ impl NotificationPanel { let id = NotificationId::unique::(); workspace.dismiss_notification(&id, cx); - workspace.show_notification(id, NotificationSource::Collab, cx, |cx| { + workspace.show_notification(id, cx, |cx| { let workspace = cx.entity().downgrade(); cx.new(|cx| NotificationToast { actor, diff --git a/crates/copilot_ui/src/sign_in.rs b/crates/copilot_ui/src/sign_in.rs index c9231c524b7106f51359c2f985f1e0f549d77729..dd48f95e0af6daeaf2a0a15b7b9595cb4c08aba2 100644 --- a/crates/copilot_ui/src/sign_in.rs +++ b/crates/copilot_ui/src/sign_in.rs @@ -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, NotificationSource, Toast, Workspace, notifications::NotificationId}; +use workspace::{AppState, 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, window: &mut Window, cx: &mut Err(err) => cx.update(|window, cx| { if let Some(workspace) = window.root::().flatten() { workspace.update(cx, |workspace, cx| { - workspace.show_error(&err, NotificationSource::Copilot, cx); + workspace.show_error(&err, cx); }) } else { log::error!("{:?}", err); @@ -88,11 +88,7 @@ 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), - NotificationSource::Copilot, - cx, - ), + Some(message) => workspace.show_toast(Toast::new(NOTIFICATION_ID, message), cx), None => workspace.dismiss_toast(&NOTIFICATION_ID, cx), }); }) diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index 98693749ff7165117c8d7e994d17f5906e76f2ad..1ec3c7ac44fc8f592fa094f668b3bfd84245eb5a 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -50,9 +50,7 @@ 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, NotificationSource, show_app_notification, -}; +use workspace::notifications::{ErrorMessagePrompt, NotificationId, show_app_notification}; pub mod cursor_excerpt; pub mod example_spec; @@ -1993,7 +1991,6 @@ impl EditPredictionStore { let error_message: SharedString = err.to_string().into(); show_app_notification( NotificationId::unique::(), - NotificationSource::Copilot, cx, move |cx| { cx.new(|cx| { diff --git a/crates/edit_prediction/src/zeta1.rs b/crates/edit_prediction/src/zeta1.rs index 0f9fcc4aa366b38d45109e9b9d7b0905f59e7ef5..43d467950fd388fb5a771e8c101a005df57c6897 100644 --- a/crates/edit_prediction/src/zeta1.rs +++ b/crates/edit_prediction/src/zeta1.rs @@ -18,7 +18,6 @@ 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, @@ -170,7 +169,6 @@ pub(crate) fn request_prediction_with_zeta1( let error_message: SharedString = err.to_string().into(); show_app_notification( NotificationId::unique::(), - NotificationSource::Copilot, cx, move |cx| { cx.new(|cx| { diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index adc7538c8b8ede3ae7e0a416cb13c7afc7a7ac6b..8835dd5507dc9deccb57ad4f4ba15d8af017bfd3 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -38,8 +38,8 @@ use ui::{ use util::ResultExt as _; use workspace::{ - NotificationSource, StatusItemView, Toast, Workspace, create_and_open_local_file, - item::ItemHandle, notifications::NotificationId, + StatusItemView, Toast, Workspace, create_and_open_local_file, item::ItemHandle, + notifications::NotificationId, }; use zed_actions::{OpenBrowser, OpenSettingsAt}; @@ -137,7 +137,6 @@ impl Render for EditPredictionButton { ) }, ), - NotificationSource::Copilot, cx, ); }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6b2cd48aa60bab641423cdcb005f4e2ab001bd49..2d8f30c6ee28071ec248f571b5045641e0f526a9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -212,10 +212,9 @@ use ui::{ use ui_input::ErasedEditor; use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc}; use workspace::{ - CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, - NotificationSource, OpenInTerminal, OpenTerminal, Pane, RestoreOnStartupBehavior, - SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast, ViewId, Workspace, - WorkspaceId, WorkspaceSettings, + CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, NavigationEntry, 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}, @@ -11482,11 +11481,8 @@ impl Editor { let Some(project) = self.project.clone() else { return; }; - self.reload(project, window, cx).detach_and_notify_err( - NotificationSource::Editor, - window, - cx, - ); + self.reload(project, window, cx) + .detach_and_notify_err(window, cx); } pub fn restore_file( @@ -22994,7 +22990,6 @@ impl Editor { NotificationId::unique::(), message, ), - NotificationSource::Editor, cx, ) }) @@ -23055,7 +23050,6 @@ impl Editor { workspace.show_toast( Toast::new(NotificationId::unique::(), message), - NotificationSource::Editor, cx, ) }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d9dcc30acdfd1f9f616ff09d04ced569e45244d2..d1fd99f09217dc736c12c3f8902fc2bdb777e03d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -96,8 +96,8 @@ use unicode_segmentation::UnicodeSegmentation; use util::post_inc; use util::{RangeExt, ResultExt, debug_panic}; use workspace::{ - CollaboratorId, ItemHandle, ItemSettings, NotificationSource, OpenInTerminal, OpenTerminal, - RevealInProjectPanel, Workspace, + CollaboratorId, ItemHandle, ItemSettings, 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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(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(NotificationSource::Editor, window, cx); + task.detach_and_notify_err(window, cx); } else { cx.propagate(); } diff --git a/crates/encoding_selector/src/encoding_selector.rs b/crates/encoding_selector/src/encoding_selector.rs index efe91774c2d242ab13e8b944d7e8127112046a32..3954bf29a30a0981c25bee3eb88829a7002881ad 100644 --- a/crates/encoding_selector/src/encoding_selector.rs +++ b/crates/encoding_selector/src/encoding_selector.rs @@ -13,10 +13,7 @@ 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, NotificationSource}, -}; +use workspace::{ModalView, Toast, Workspace, notifications::NotificationId}; actions!( encoding_selector, @@ -65,7 +62,6 @@ impl EncodingSelector { NotificationId::unique::(), "Save file to change encoding", ), - NotificationSource::Editor, cx, ); return Some(()); @@ -76,7 +72,6 @@ impl EncodingSelector { NotificationId::unique::(), "Cannot change encoding during collaboration", ), - NotificationSource::Collab, cx, ); return Some(()); @@ -87,7 +82,6 @@ impl EncodingSelector { NotificationId::unique::(), "Cannot change encoding of remote server file", ), - NotificationSource::Remote, cx, ); return Some(()); diff --git a/crates/extensions_ui/src/extension_suggest.rs b/crates/extensions_ui/src/extension_suggest.rs index 13efd1012f3e104cdc82facde92a62e4b9739d06..7ad4c1540a419f0cdeedb2aeff7661aafac5ef4c 100644 --- a/crates/extensions_ui/src/extension_suggest.rs +++ b/crates/extensions_ui/src/extension_suggest.rs @@ -9,10 +9,7 @@ use language::Buffer; use ui::prelude::*; use util::rel_path::RelPath; use workspace::notifications::simple_message_notification::MessageNotification; -use workspace::{ - Workspace, - notifications::{NotificationId, NotificationSource}, -}; +use workspace::{Workspace, notifications::NotificationId}; const SUGGESTIONS_BY_EXTENSION_ID: &[(&str, &[&str])] = &[ ("astro", &["astro"]), @@ -169,7 +166,7 @@ pub(crate) fn suggest(buffer: Entity, window: &mut Window, cx: &mut Cont SharedString::from(extension_id.clone()), ); - workspace.show_notification(notification_id, NotificationSource::Extension, cx, |cx| { + workspace.show_notification(notification_id, cx, |cx| { cx.new(move |cx| { MessageNotification::new( format!( diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 6e6cb2b5b57e0d64fa5b549e9a6df9d938b43f46..7c77579c9172e24680e8398645544cd1099dfaff 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -33,7 +33,6 @@ use vim_mode_setting::VimModeSetting; use workspace::{ Workspace, item::{Item, ItemEvent}, - notifications::NotificationSource, }; use zed_actions::ExtensionCategoryFilter; @@ -160,7 +159,6 @@ 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, ); }) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 58483d68195de5785d48ad0c0fc7226dae9e95e2..73533c57f156cdfba04ca736eeed5b0d23d2ee8f 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -44,10 +44,8 @@ use util::{ rel_path::RelPath, }; use workspace::{ - ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace, - item::PreviewTabsSettings, - notifications::{NotificationSource, NotifyResultExt}, - pane, + ModalView, OpenOptions, OpenVisible, SplitDirection, Workspace, item::PreviewTabsSettings, + notifications::NotifyResultExt, pane, }; use zed_actions::search::ToggleIncludeIgnored; @@ -1570,9 +1568,7 @@ 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(NotificationSource::File, cx)?; + let item = open_task.await.notify_async_err(cx)?; if let Some(row) = row && let Some(active_editor) = item.downcast::() { diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 5c05eaf1c471658b0757d99c387de15da10c804a..79f581777485b08952b95f2097f2e7083de35c98 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -34,7 +34,7 @@ use workspace::{ Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{ItemEvent, TabContentParams}, - notifications::{NotificationSource, NotifyTaskExt}, + notifications::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(NotificationSource::Git, window, cx); + .detach_and_notify_err(window, cx); } async fn close_commit_view( diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index 762c8f268cca85266f0cc13b2cfc7b4995171173..a4d26bc5a5a995f7bbc7af7df1cfef18dfe0b3d8 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -76,9 +76,7 @@ use workspace::SERIALIZATION_THROTTLE_TIME; use workspace::{ Workspace, dock::{DockPosition, Panel, PanelEvent}, - notifications::{ - DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotificationSource, NotifyResultExt, - }, + notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotifyResultExt}, }; actions!( git_panel, @@ -741,7 +739,7 @@ impl GitPanel { GitStoreEvent::IndexWriteError(error) => { this.workspace .update(cx, |workspace, cx| { - workspace.show_error(error, NotificationSource::Git, cx); + workspace.show_error(error, cx); }) .ok(); } @@ -1279,7 +1277,7 @@ impl GitPanel { cx.spawn_in(window, async move |_, mut cx| { let item = open_task .await - .notify_async_err(NotificationSource::Git, &mut cx) + .notify_async_err(&mut cx) .ok_or_else(|| anyhow::anyhow!("Failed to open file"))?; if let Some(active_editor) = item.downcast::() { if let Some(diff_task) = @@ -3754,7 +3752,7 @@ impl GitPanel { let _ = workspace.update(cx, |workspace, cx| { struct CommitMessageError; let notification_id = NotificationId::unique::(); - workspace.show_notification(notification_id, NotificationSource::Git, cx, |cx| { + workspace.show_notification(notification_id, cx, |cx| { cx.new(|cx| { ErrorMessagePrompt::new( format!("Failed to generate commit message: {err}"), diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index c8257e89c316e67204866449d4944a1dcbc780bc..21a0d41fe099d60436bd80b7f4ee06982735c847 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -44,7 +44,7 @@ use workspace::{ CloseActiveItem, ItemNavHistory, SerializableItem, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, item::{Item, ItemEvent, ItemHandle, SaveOptions, TabContentParams}, - notifications::{NotificationSource, NotifyTaskExt}, + notifications::NotifyTaskExt, searchable::SearchableItemHandle, }; use ztracing::instrument; @@ -138,7 +138,7 @@ impl ProjectDiff { .ok(); anyhow::Ok(()) }) - .detach_and_notify_err(NotificationSource::Git, window, cx); + .detach_and_notify_err(window, cx); } pub fn deploy_at( diff --git a/crates/inspector_ui/src/inspector_ui.rs b/crates/inspector_ui/src/inspector_ui.rs index e4cac26e44145a1d29fd70506347f94b577f57ac..1342007005586847db6966bcf7f5cdbcbe6444ac 100644 --- a/crates/inspector_ui/src/inspector_ui.rs +++ b/crates/inspector_ui/src/inspector_ui.rs @@ -9,13 +9,13 @@ pub use inspector::init; #[cfg(not(debug_assertions))] pub fn init(_app_state: std::sync::Arc, cx: &mut gpui::App) { use std::any::TypeId; - use workspace::notifications::{NotificationSource, NotifyResultExt as _}; + use workspace::notifications::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(NotificationSource::System, cx); + .notify_app_err(cx); }); command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| { diff --git a/crates/install_cli/src/install_cli_binary.rs b/crates/install_cli/src/install_cli_binary.rs index c89c64f050cb99f39b966e330532532847c4314c..095ed3cd315c49d38909a12608cd5607723abb3a 100644 --- a/crates/install_cli/src/install_cli_binary.rs +++ b/crates/install_cli/src/install_cli_binary.rs @@ -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, NotificationSource}; +use workspace::notifications::{DetachAndPromptErr, NotificationId}; use workspace::{Toast, Workspace}; actions!( @@ -91,7 +91,6 @@ pub fn install_cli_binary(window: &mut Window, cx: &mut Context) { ReleaseChannel::global(cx).display_name() ), ), - NotificationSource::Cli, cx, ) })?; diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index e7bef554ebeee7a0a0c054b5311608f82f729163..de22ae01b503fde9aabdd99be5253d7c4e3f1b71 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -38,8 +38,7 @@ use ui::{ use ui_input::InputField; use util::ResultExt; use workspace::{ - Item, ModalView, SerializableItem, Workspace, - notifications::{NotificationSource, NotifyTaskExt as _}, + Item, ModalView, SerializableItem, Workspace, notifications::NotifyTaskExt as _, register_serializable_item, }; @@ -1320,7 +1319,7 @@ impl KeymapEditor { cx.spawn(async move |_, _| { remove_keybinding(to_remove, &fs, keyboard_mapper.as_ref()).await }) - .detach_and_notify_err(NotificationSource::Settings, window, cx); + .detach_and_notify_err(window, cx); } fn copy_context_to_clipboard( diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index 43125353d5721c7e1174a1585c16b66c7cb99fe7..495a55411fc936d476dfa0d443e155d1fa7faecd 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -19,7 +19,7 @@ use ui::{ pub use workspace::welcome::ShowWelcome; use workspace::welcome::WelcomePage; use workspace::{ - AppState, NotificationSource, Workspace, WorkspaceId, + AppState, 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(NotificationSource::System, cx); + .notify_async_err(cx); }) .detach(); } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 4e0b0b382fd3da67fb8c29b09f7526bb48ae8ee1..265c8147da40ee1b1832c3e6d72def0b56188794 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -2325,12 +2325,14 @@ impl Project { pub fn visibility_for_paths( &self, paths: &[PathBuf], + metadatas: &[Metadata], exclude_sub_dirs: bool, cx: &App, ) -> Option { paths .iter() - .map(|path| self.visibility_for_path(path, exclude_sub_dirs, cx)) + .zip(metadatas) + .map(|(path, metadata)| self.visibility_for_path(path, metadata, exclude_sub_dirs, cx)) .max() .flatten() } @@ -2338,26 +2340,17 @@ impl Project { pub fn visibility_for_path( &self, path: &Path, + metadata: &Metadata, exclude_sub_dirs: bool, cx: &App, ) -> Option { let path = SanitizedPath::new(path).as_path(); - let path_style = self.path_style(cx); self.worktrees(cx) .filter_map(|worktree| { let worktree = worktree.read(cx); - let abs_path = worktree.abs_path(); - let relative_path = path_style.strip_prefix(path, abs_path.as_ref()); - let is_dir = relative_path - .as_ref() - .and_then(|p| worktree.entry_for_path(p)) - .is_some_and(|e| e.is_dir()); - // Don't exclude the worktree root itself, only actual subdirectories - let is_subdir = relative_path - .as_ref() - .is_some_and(|p| !p.as_ref().as_unix_str().is_empty()); - let contains = - relative_path.is_some() && (!exclude_sub_dirs || !is_dir || !is_subdir); + let abs_path = worktree.as_local()?.abs_path(); + let contains = path == abs_path.as_ref() + || (path.starts_with(abs_path) && (!exclude_sub_dirs || !metadata.is_dir)); contains.then(|| worktree.is_visible()) }) .max() diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index 31a6cc041eda875f3c7ee5b33b77519d7ee2b142..0f004b8653cbecd33e6aecc66ae20d6187d67ee4 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -223,8 +223,8 @@ impl WorktreeStore { let abs_path = SanitizedPath::new(abs_path.as_ref()); for tree in self.worktrees() { let path_style = tree.read(cx).path_style(); - if let Some(relative_path) = - path_style.strip_prefix(abs_path.as_ref(), tree.read(cx).abs_path().as_ref()) + if let Ok(relative_path) = abs_path.as_ref().strip_prefix(tree.read(cx).abs_path()) + && let Ok(relative_path) = RelPath::new(relative_path, path_style) { return Some((tree.clone(), relative_path.into_arc())); } diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 524c2eedc51112033da8306d26c8453ecbc4eaed..76dd4abe2f90e285e85ea8778c5ad785e1bbfab5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -72,7 +72,7 @@ use workspace::{ DraggedSelection, OpenInTerminal, OpenOptions, OpenVisible, PreviewTabsSettings, SelectedEntry, SplitDirection, Workspace, dock::{DockPosition, Panel, PanelEvent}, - notifications::{DetachAndPromptErr, NotificationSource, NotifyResultExt, NotifyTaskExt}, + notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt}, }; use worktree::CreatedEntry; use zed_actions::{project_panel::ToggleFocus, workspace::OpenWithSystem}; @@ -772,11 +772,7 @@ impl ProjectPanel { { match project_panel.confirm_edit(false, window, cx) { Some(task) => { - task.detach_and_notify_err( - NotificationSource::File, - window, - cx, - ); + task.detach_and_notify_err(window, cx); } None => { project_panel.discard_edit_state(window, cx); @@ -1652,7 +1648,7 @@ impl ProjectPanel { fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { if let Some(task) = self.confirm_edit(true, window, cx) { - task.detach_and_notify_err(NotificationSource::File, window, cx); + task.detach_and_notify_err(window, cx); } } @@ -3044,15 +3040,13 @@ impl ProjectPanel { match task { PasteTask::Rename(task) => { if let Some(CreatedEntry::Included(entry)) = - task.await.notify_async_err(NotificationSource::File, cx) + task.await.notify_async_err(cx) { last_succeed = Some(entry); } } PasteTask::Copy(task) => { - if let Some(Some(entry)) = - task.await.notify_async_err(NotificationSource::File, cx) - { + if let Some(Some(entry)) = task.await.notify_async_err(cx) { last_succeed = Some(entry); } } @@ -3215,7 +3209,6 @@ impl ProjectPanel { notification_id.clone(), format!("Downloading 0/{} files...", total_files), ), - NotificationSource::File, cx, ); }) @@ -3236,7 +3229,6 @@ impl ProjectPanel { total_files ), ), - NotificationSource::File, cx, ); }) @@ -3270,7 +3262,6 @@ impl ProjectPanel { notification_id.clone(), format!("Downloaded {} files", total_files), ), - NotificationSource::File, cx, ); }) diff --git a/crates/recent_projects/src/dev_container_suggest.rs b/crates/recent_projects/src/dev_container_suggest.rs index 9df04e789881aa955f512a0adaf5650c01ce1491..fd7fe4757a0f629579c5a5fdae7b16f12f1bba7a 100644 --- a/crates/recent_projects/src/dev_container_suggest.rs +++ b/crates/recent_projects/src/dev_container_suggest.rs @@ -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, NotificationSource::DevContainer, cx, |cx| { + workspace.show_notification(notification_id, 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?", diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 8d003ead0e578bc0638291e14148c657a702c942..7f67627ad4d6841419292e58b5b41a5fd5ef7d5b 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -11,7 +11,7 @@ mod wsl_picker; use remote::RemoteConnectionOptions; pub use remote_connection::{RemoteConnectionModal, connect}; -pub use remote_connections::{navigate_to_positions, open_remote_project}; +pub use remote_connections::open_remote_project; use disconnected_overlay::DisconnectedOverlay; use fuzzy::{StringMatch, StringMatchCandidate}; diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 0ac34097c353e82d679d2b28abeb810f97c8c9c8..9d6199786cb6f251a792fa32e8caccd9351d00d3 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -8,7 +8,7 @@ use askpass::EncryptedPassword; use editor::Editor; use extension_host::ExtensionStore; use futures::{FutureExt as _, channel::oneshot, select}; -use gpui::{AppContext, AsyncApp, PromptLevel, WindowHandle}; +use gpui::{AppContext, AsyncApp, PromptLevel}; use language::Point; use project::trusted_worktrees; @@ -19,10 +19,7 @@ use remote::{ pub use settings::SshConnection; use settings::{DevContainerConnection, ExtendingVec, RegisterSetting, Settings, WslConnection}; use util::paths::PathWithPosition; -use workspace::{ - AppState, NotificationSource, OpenOptions, SerializedWorkspaceLocation, Workspace, - find_existing_workspace, -}; +use workspace::{AppState, Workspace}; pub use remote_connection::{ RemoteClientDelegate, RemoteConnectionModal, RemoteConnectionPrompt, SshConnectionHeader, @@ -134,62 +131,6 @@ pub async fn open_remote_project( cx: &mut AsyncApp, ) -> Result<()> { let created_new_window = open_options.replace_window.is_none(); - - let (existing, open_visible) = find_existing_workspace( - &paths, - &open_options, - &SerializedWorkspaceLocation::Remote(connection_options.clone()), - cx, - ) - .await; - - if let Some(existing) = existing { - let remote_connection = existing - .update(cx, |workspace, _, cx| { - workspace - .project() - .read(cx) - .remote_client() - .and_then(|client| client.read(cx).remote_connection()) - })? - .ok_or_else(|| anyhow::anyhow!("no remote connection for existing remote workspace"))?; - - let (resolved_paths, paths_with_positions) = - determine_paths_with_positions(&remote_connection, paths).await; - - let open_results = existing - .update(cx, |workspace, window, cx| { - window.activate_window(); - workspace.open_paths( - resolved_paths, - OpenOptions { - visible: Some(open_visible), - ..Default::default() - }, - None, - window, - cx, - ) - })? - .await; - - _ = existing.update(cx, |workspace, _, cx| { - for item in open_results.iter().flatten() { - if let Err(e) = item { - workspace.show_error(&e, NotificationSource::Remote, cx); - } - } - }); - - let items = open_results - .into_iter() - .map(|r| r.and_then(|r| r.ok())) - .collect::>(); - navigate_to_positions(&existing, items, &paths_with_positions, cx); - - return Ok(()); - } - let window = if let Some(window) = open_options.replace_window { window } else { @@ -396,7 +337,29 @@ pub async fn open_remote_project( } Ok(items) => { - navigate_to_positions(&window, items, &paths_with_positions, cx); + for (item, path) in items.into_iter().zip(paths_with_positions) { + let Some(item) = item else { + continue; + }; + let Some(row) = path.row else { + continue; + }; + if let Some(active_editor) = item.downcast::() { + window + .update(cx, |_, window, cx| { + active_editor.update(cx, |editor, cx| { + let row = row.saturating_sub(1); + let col = path.column.unwrap_or(0).saturating_sub(1); + editor.go_to_singleton_buffer_point( + Point::new(row, col), + window, + cx, + ); + }); + }) + .ok(); + } + } } } @@ -416,33 +379,6 @@ pub async fn open_remote_project( Ok(()) } -pub fn navigate_to_positions( - window: &WindowHandle, - items: impl IntoIterator>>, - positions: &[PathWithPosition], - cx: &mut AsyncApp, -) { - for (item, path) in items.into_iter().zip(positions) { - let Some(item) = item else { - continue; - }; - let Some(row) = path.row else { - continue; - }; - if let Some(active_editor) = item.downcast::() { - window - .update(cx, |_, window, cx| { - active_editor.update(cx, |editor, cx| { - let row = row.saturating_sub(1); - let col = path.column.unwrap_or(0).saturating_sub(1); - editor.go_to_singleton_buffer_point(Point::new(row, col), window, cx); - }); - }) - .ok(); - } - } -} - pub(crate) async fn determine_paths_with_positions( remote_connection: &Arc, mut paths: Vec, diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index da8c469a8e6128c93efd88cd4312ee074116e1c0..5b719940f958ac0e4ecb6e186052e3e09987f80e 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -9,7 +9,6 @@ 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::{ @@ -2376,7 +2375,6 @@ impl RemoteServerProjects { notification, ) .autohide(), - NotificationSource::Remote, cx, ); }) diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index 479cfeae6523b6a45ab0a5157d2573a1326f5fab..0b4eed025e6c1e47e979af108514485c32aaccae 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -1132,7 +1132,7 @@ impl RemoteClient { .unwrap() } - pub fn remote_connection(&self) -> Option> { + fn remote_connection(&self) -> Option> { self.state .as_ref() .and_then(|state| state.remote_connection()) diff --git a/crates/repl/src/kernels/native_kernel.rs b/crates/repl/src/kernels/native_kernel.rs index 1fbda97d762179d194bb9e0b66b170868e17c06f..30e2740fb92c85f9b52e48b6e41593c639350344 100644 --- a/crates/repl/src/kernels/native_kernel.rs +++ b/crates/repl/src/kernels/native_kernel.rs @@ -220,7 +220,6 @@ impl NativeRunningKernel { ); }, ), - workspace::notifications::NotificationSource::Repl, cx, ); }) diff --git a/crates/search/src/search.rs b/crates/search/src/search.rs index ea0b1bb90409426b59d5562d54b763916d53b9d0..ac337fb4c8f53e407178d9ccf1be7e91d89fadcb 100644 --- a/crates/search/src/search.rs +++ b/crates/search/src/search.rs @@ -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, NotificationSource}; +use workspace::notifications::NotificationId; use workspace::{Toast, Workspace}; pub use zed_actions::search::ToggleIncludeIgnored; @@ -197,7 +197,6 @@ 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, ); }) diff --git a/crates/snippets_ui/src/snippets_ui.rs b/crates/snippets_ui/src/snippets_ui.rs index c0a5a10b3e9da006cadb9299cababc337465e9a1..c881d5276e6f96c5fc9b4db802aa3731e843852f 100644 --- a/crates/snippets_ui/src/snippets_ui.rs +++ b/crates/snippets_ui/src/snippets_ui.rs @@ -18,10 +18,7 @@ use std::{ }; use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*}; use util::ResultExt; -use workspace::{ - ModalView, OpenOptions, OpenVisible, Workspace, - notifications::{NotificationSource, NotifyResultExt}, -}; +use workspace::{ModalView, OpenOptions, OpenVisible, Workspace, notifications::NotifyResultExt}; #[derive(Eq, Hash, PartialEq)] struct ScopeName(Cow<'static, str>); @@ -96,7 +93,7 @@ fn open_folder( _: &mut Window, cx: &mut Context, ) { - fs::create_dir_all(snippets_dir()).notify_err(workspace, NotificationSource::Editor, cx); + fs::create_dir_all(snippets_dir()).notify_err(workspace, cx); cx.open_with_system(snippets_dir().borrow()); } diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index d16190a335f5364b3096c56e2bbe2faf48893251..ff14c6fa25bfa3b52bfdd34433548431a042bc2b 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -43,10 +43,7 @@ use ui::{ }; use update_version::UpdateVersion; use util::ResultExt; -use workspace::{ - SwitchProject, ToggleWorktreeSecurity, Workspace, - notifications::{NotificationSource, NotifyResultExt}, -}; +use workspace::{SwitchProject, ToggleWorktreeSecurity, Workspace, notifications::NotifyResultExt}; use zed_actions::OpenRemote; pub use onboarding_banner::restore_banner; @@ -948,7 +945,7 @@ impl TitleBar { client .sign_in_with_optional_connect(true, cx) .await - .notify_async_err(NotificationSource::Collab, cx); + .notify_async_err(cx); }) .detach(); }) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index f96e37b348ddf487821f69ba00fea1f1c838d4c8..f010ff57c636f40de50a06baefd99c9ee43728f9 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -428,17 +428,7 @@ impl PathStyle { .find_map(|sep| parent.strip_suffix(sep)) .unwrap_or(parent); let child = child.to_str()?; - - // Match behavior of std::path::Path, which is case-insensitive for drive letters (e.g., "C:" == "c:") - let stripped = if self.is_windows() - && child.as_bytes().get(1) == Some(&b':') - && parent.as_bytes().get(1) == Some(&b':') - && child.as_bytes()[0].eq_ignore_ascii_case(&parent.as_bytes()[0]) - { - child[2..].strip_prefix(&parent[2..])? - } else { - child.strip_prefix(parent)? - }; + let stripped = child.strip_prefix(parent)?; if let Some(relative) = self .separators() .iter() diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 48d6566e5883ff1763774cc98ef2470a240e14da..423c3b387b197edd2d8e86398b09157fdcb7711a 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -36,10 +36,7 @@ use util::{ rel_path::{RelPath, RelPathBuf}, }; use workspace::{Item, SaveIntent, Workspace, notifications::NotifyResultExt}; -use workspace::{ - SplitDirection, - notifications::{DetachAndPromptErr, NotificationSource}, -}; +use workspace::{SplitDirection, notifications::DetachAndPromptErr}; use zed_actions::{OpenDocs, RevealTarget}; use crate::{ @@ -895,7 +892,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { return; }; workspace.update(cx, |workspace, cx| { - e.notify_err(workspace, NotificationSource::Editor, cx); + e.notify_err(workspace, cx); }); } }); @@ -939,7 +936,7 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { return; }; workspace.update(cx, |workspace, cx| { - e.notify_err(workspace, NotificationSource::Editor, cx); + e.notify_err(workspace, cx); }); return; } @@ -2139,7 +2136,7 @@ impl OnMatchingLines { return; }; workspace.update(cx, |workspace, cx| { - e.notify_err(workspace, NotificationSource::Editor, cx); + e.notify_err(workspace, cx); }); return; } @@ -2156,7 +2153,7 @@ impl OnMatchingLines { return; }; workspace.update(cx, |workspace, cx| { - e.notify_err(workspace, NotificationSource::Editor, cx); + e.notify_err(workspace, cx); }); return; } diff --git a/crates/vim/src/normal/search.rs b/crates/vim/src/normal/search.rs index 8ea6f0a3b35073c368b5c0d005cd924eea692e8c..c11784d163e18451129656aa92d23dba568bd723 100644 --- a/crates/vim/src/normal/search.rs +++ b/crates/vim/src/normal/search.rs @@ -7,10 +7,7 @@ use serde::Deserialize; use settings::Settings; use std::{iter::Peekable, str::Chars}; use util::serde::default_true; -use workspace::{ - notifications::{NotificationSource, NotifyResultExt}, - searchable::Direction, -}; +use workspace::{notifications::NotifyResultExt, searchable::Direction}; use crate::{ Vim, VimSettings, @@ -574,7 +571,7 @@ impl Vim { anyhow::Ok(()) }) { workspace.update(cx, |workspace, cx| { - result.notify_err(workspace, NotificationSource::Editor, cx); + result.notify_err(workspace, cx); }) } let Some(search_bar) = pane.update(cx, |pane, cx| { diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 425132ad2d42199a2cba1857c9f6e0a5a5ac775a..10437743df39b22722638357976d5a8d6224eaf8 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -9,13 +9,11 @@ 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::time::Duration; +use std::{any::TypeId, time::Duration}; use ui::{CopyButton, Tooltip, prelude::*}; use util::ResultExt; @@ -70,153 +68,6 @@ 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, - 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 { @@ -230,12 +81,9 @@ impl Workspace { pub fn show_notification( &mut self, id: NotificationId, - source: NotificationSource, cx: &mut Context, build_notification: impl FnOnce(&mut Context) -> Entity, ) { - let mut telemetry_data: Option = None; - self.show_notification_without_handling_dismiss_events(&id, cx, |cx| { let notification = build_notification(cx); cx.subscribe(¬ification, { @@ -256,11 +104,6 @@ impl Workspace { if let Ok(prompt) = AnyEntity::from(notification.clone()).downcast::() { - telemetry_data = Some(NotificationTelemetry::for_language_server_prompt( - prompt.read(cx), - &id, - )); - let is_prompt_without_actions = prompt .read(cx) .request @@ -290,24 +133,9 @@ impl Workspace { }); } } - } else if AnyEntity::from(notification.clone()) - .downcast::() - .is_ok() - { - telemetry_data = Some(NotificationTelemetry::for_error_message_prompt(source, &id)); - } else if AnyEntity::from(notification.clone()) - .downcast::() - .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. @@ -330,11 +158,11 @@ impl Workspace { cx.notify(); } - pub fn show_error(&mut self, err: &E, source: NotificationSource, cx: &mut Context) + pub fn show_error(&mut self, err: &E, cx: &mut Context) where E: std::fmt::Debug + std::fmt::Display, { - self.show_notification(workspace_error_notification_id(), source, cx, |cx| { + self.show_notification(workspace_error_notification_id(), cx, |cx| { cx.new(|cx| ErrorMessagePrompt::new(format!("Error: {err}"), cx)) }); } @@ -342,19 +170,14 @@ impl Workspace { pub fn show_portal_error(&mut self, err: String, cx: &mut Context) { struct PortalError; - self.show_notification( - NotificationId::unique::(), - 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", - ) - }) - }, - ); + self.show_notification(NotificationId::unique::(), 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) { @@ -368,9 +191,9 @@ impl Workspace { }); } - pub fn show_toast(&mut self, toast: Toast, source: NotificationSource, cx: &mut Context) { + pub fn show_toast(&mut self, toast: Toast, cx: &mut Context) { self.dismiss_notification(&toast.id, cx); - self.show_notification(toast.id.clone(), source, cx, |cx| { + self.show_notification(toast.id.clone(), cx, |cx| { cx.new(|cx| match toast.on_click.as_ref() { Some((click_msg, on_click)) => { let on_click = on_click.clone(); @@ -1179,23 +1002,9 @@ impl AppNotifications { /// exist. If the notification is dismissed within any workspace, it will be removed from all. pub fn show_app_notification( id: NotificationId, - source: NotificationSource, cx: &mut App, build_notification: impl Fn(&mut Context) -> Entity + 'static + Send + Sync, ) { - let telemetry_data = if TypeId::of::() == TypeId::of::() { - Some(NotificationTelemetry::for_error_message_prompt(source, &id)) - } else if TypeId::of::() == TypeId::of::() - { - 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. @@ -1264,21 +1073,13 @@ pub fn dismiss_app_notification(id: &NotificationId, cx: &mut App) { pub trait NotifyResultExt { type Ok; - fn notify_err( - self, - workspace: &mut Workspace, - source: NotificationSource, - cx: &mut Context, - ) -> Option; + fn notify_err(self, workspace: &mut Workspace, cx: &mut Context) + -> Option; - fn notify_async_err( - self, - source: NotificationSource, - cx: &mut AsyncWindowContext, - ) -> 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, source: NotificationSource, cx: &mut App) -> Option; + fn notify_app_err(self, cx: &mut App) -> Option; } impl NotifyResultExt for std::result::Result @@ -1287,34 +1088,25 @@ where { type Ok = T; - fn notify_err( - self, - workspace: &mut Workspace, - source: NotificationSource, - cx: &mut Context, - ) -> Option { + fn notify_err(self, workspace: &mut Workspace, cx: &mut Context) -> Option { match self { Ok(value) => Some(value), Err(err) => { log::error!("Showing error notification in workspace: {err:?}"); - workspace.show_error(&err, source, cx); + workspace.show_error(&err, cx); None } } } - fn notify_async_err( - self, - source: NotificationSource, - cx: &mut AsyncWindowContext, - ) -> Option { + fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option { match self { Ok(value) => Some(value), Err(err) => { log::error!("{err:?}"); cx.update_root(|view, _, cx| { if let Ok(workspace) = view.downcast::() { - workspace.update(cx, |workspace, cx| workspace.show_error(&err, source, cx)) + workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx)) } }) .ok(); @@ -1323,13 +1115,13 @@ where } } - fn notify_app_err(self, source: NotificationSource, cx: &mut App) -> Option { + fn notify_app_err(self, cx: &mut App) -> Option { 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(), source, cx, { + show_app_notification(workspace_error_notification_id(), cx, { move |cx| { cx.new({ let message = message.clone(); @@ -1345,7 +1137,7 @@ where } pub trait NotifyTaskExt { - fn detach_and_notify_err(self, source: NotificationSource, window: &mut Window, cx: &mut App); + fn detach_and_notify_err(self, window: &mut Window, cx: &mut App); } impl NotifyTaskExt for Task> @@ -1353,9 +1145,9 @@ where E: std::fmt::Debug + std::fmt::Display + Sized + 'static, R: 'static, { - fn detach_and_notify_err(self, source: NotificationSource, window: &mut Window, cx: &mut App) { + fn detach_and_notify_err(self, window: &mut Window, cx: &mut App) { window - .spawn(cx, async move |cx| self.await.notify_async_err(source, cx)) + .spawn(cx, async move |cx| self.await.notify_async_err(cx)) .detach(); } } @@ -1460,7 +1252,7 @@ mod tests { lsp_name.to_string(), ); let notification_id = NotificationId::composite::(request.id); - workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| { + workspace.show_notification(notification_id, cx, |cx| { cx.new(|cx| LanguageServerPrompt::new(request, cx)) }); }) @@ -1519,7 +1311,7 @@ mod tests { ); let notification_id = NotificationId::composite::(request.id); - workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| { + workspace.show_notification(notification_id, cx, |cx| { cx.new(|cx| LanguageServerPrompt::new(request, cx)) }); }) @@ -1570,7 +1362,7 @@ mod tests { "test_server".to_string(), ); let notification_id = NotificationId::composite::(request.id); - workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| { + workspace.show_notification(notification_id, cx, |cx| { cx.new(|cx| LanguageServerPrompt::new(request, cx)) }); }); @@ -1613,7 +1405,7 @@ mod tests { "test_server".to_string(), ); let notification_id = NotificationId::composite::(request.id); - workspace.show_notification(notification_id, NotificationSource::Lsp, cx, |cx| { + workspace.show_notification(notification_id, cx, |cx| { cx.new(|cx| LanguageServerPrompt::new(request, cx)) }); }); diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fce6b4ebd13b703aabf3a912261ccc2df6829ef4..be5c1efbdf7b158e3b8f80828fb75eb4a2307d61 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -9,7 +9,7 @@ use crate::{ TabContentParams, TabTooltipContent, WeakItemHandle, }, move_item, - notifications::{NotificationSource, NotifyResultExt}, + notifications::NotifyResultExt, toolbar::Toolbar, utility_pane::UtilityPaneSlot, workspace_settings::{AutosaveSetting, TabBarSettings, WorkspaceSettings}, @@ -3890,9 +3890,8 @@ 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(NotificationSource::File, cx) + if let Some((project_entry_id, build_item)) = + load_path_task.await.notify_async_err(cx) { let (to_pane, new_item_handle) = workspace .update_in(cx, |workspace, window, cx| { @@ -3962,7 +3961,6 @@ 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 @@ -4020,7 +4018,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, NotificationSource::File, cx); + workspace.show_error(&e, cx); } } if to_pane.read(cx).items_len() == 0 { diff --git a/crates/workspace/src/tasks.rs b/crates/workspace/src/tasks.rs index 403b27d59527f44523b3880a00ccf3f052402cdb..f85e1488f97491a73314297d91c597bd7d3bb841 100644 --- a/crates/workspace/src/tasks.rs +++ b/crates/workspace/src/tasks.rs @@ -10,10 +10,7 @@ use task::{ }; use ui::Window; -use crate::{ - Toast, Workspace, - notifications::{NotificationId, NotificationSource}, -}; +use crate::{Toast, Workspace, notifications::NotificationId}; impl Workspace { pub fn schedule_task( @@ -93,11 +90,7 @@ impl Workspace { log::error!("Task spawn failed: {e:#}"); _ = w.update(cx, |w, cx| { let id = NotificationId::unique::(); - w.show_toast( - Toast::new(id, format!("Task spawn failed: {e}")), - NotificationSource::Task, - cx, - ); + w.show_toast(Toast::new(id, format!("Task spawn failed: {e}")), cx); }) } None => log::debug!("Task spawn got cancelled"), diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index e24c367b4a3a2c0de97cba2c95afbf3230af7e47..5c85061740b444dcb4082ff4d89d11a9d90cbe44 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -59,7 +59,6 @@ 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, @@ -1129,7 +1128,7 @@ pub enum Event { ModalOpened, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum OpenVisible { All, None, @@ -1359,7 +1358,6 @@ impl Workspace { link, } => this.show_notification( NotificationId::named(notification_id.clone()), - NotificationSource::Project, cx, |cx| { let mut notification = MessageNotification::new(message.clone(), cx); @@ -1382,7 +1380,6 @@ impl Workspace { this.show_notification( NotificationId::composite::(request.id), - NotificationSource::Lsp, cx, |cx| { cx.new(|cx| { @@ -3135,7 +3132,6 @@ impl Workspace { if project.is_via_collab() { self.show_error( &anyhow!("You cannot add folders to someone else's project"), - NotificationSource::Collab, cx, ); return; @@ -5877,7 +5873,7 @@ impl Workspace { } } - match self.workspace_location(cx) { + match self.serialize_workspace_location(cx) { WorkspaceLocation::Location(location, paths) => { let breakpoints = self.project.update(cx, |project, cx| { project @@ -5949,7 +5945,7 @@ impl Workspace { self.panes.iter().any(|pane| pane.read(cx).items_len() > 0) } - fn workspace_location(&self, cx: &App) -> WorkspaceLocation { + fn serialize_workspace_location(&self, cx: &App) -> WorkspaceLocation { let paths = PathList::new(&self.root_paths(cx)); if let Some(connection) = self.project.read(cx).remote_connection_options(cx) { WorkspaceLocation::Location(SerializedWorkspaceLocation::Remote(connection), paths) @@ -5964,16 +5960,6 @@ impl Workspace { } } - pub fn serialized_workspace_location(&self, cx: &App) -> Option { - if let Some(connection) = self.project.read(cx).remote_connection_options(cx) { - Some(SerializedWorkspaceLocation::Remote(connection)) - } else if self.project.read(cx).is_local() && self.has_any_items_open(cx) { - Some(SerializedWorkspaceLocation::Local) - } else { - None - } - } - fn update_history(&self, cx: &mut App) { let Some(id) = self.database_id() else { return; @@ -7065,7 +7051,6 @@ fn notify_if_database_failed(workspace: WindowHandle, cx: &mut AsyncA workspace.show_notification( NotificationId::unique::(), - NotificationSource::Database, cx, |cx| { cx.new(|cx| { @@ -8185,60 +8170,24 @@ fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option Vec> { +pub fn local_workspace_windows(cx: &App) -> Vec> { cx.windows() .into_iter() .filter_map(|window| window.downcast::()) .filter(|workspace| { - let same_host = |left: &RemoteConnectionOptions, right: &RemoteConnectionOptions| match (left, right) { - (RemoteConnectionOptions::Ssh(a), RemoteConnectionOptions::Ssh(b)) => { - (&a.host, &a.username, &a.port) == (&b.host, &b.username, &b.port) - } - (RemoteConnectionOptions::Wsl(a), RemoteConnectionOptions::Wsl(b)) => { - // The WSL username is not consistently populated in the workspace location, so ignore it for now. - a.distro_name == b.distro_name - } - (RemoteConnectionOptions::Docker(a), RemoteConnectionOptions::Docker(b)) => { - a.container_id == b.container_id - } - #[cfg(any(test, feature = "test-support"))] - (RemoteConnectionOptions::Mock(a), RemoteConnectionOptions::Mock(b)) => { - a.id == b.id - } - _ => false, - }; - workspace .read(cx) - .is_ok_and(|workspace| match workspace.workspace_location(cx) { - WorkspaceLocation::Location(location, _) => { - match (&location, serialized_location) { - ( - SerializedWorkspaceLocation::Local, - SerializedWorkspaceLocation::Local, - ) => true, - ( - SerializedWorkspaceLocation::Remote(a), - SerializedWorkspaceLocation::Remote(b), - ) => same_host(a, b), - _ => false, - } - } - _ => false, - }) + .is_ok_and(|workspace| workspace.project.read(cx).is_local()) }) .collect() } -#[derive(Default, Clone)] +#[derive(Default)] pub struct OpenOptions { pub visible: Option, pub focus: Option, pub open_new_workspace: Option, - pub wait: bool, + pub prefer_focused_window: bool, pub replace_window: Option>, pub env: Option>, } @@ -8320,84 +8269,6 @@ pub fn open_workspace_by_id( }) } -pub async fn find_existing_workspace( - abs_paths: &[PathBuf], - open_options: &OpenOptions, - location: &SerializedWorkspaceLocation, - cx: &mut AsyncApp, -) -> (Option>, OpenVisible) { - let mut existing = None; - let mut open_visible = OpenVisible::All; - let mut best_match = None; - - if open_options.open_new_workspace != Some(true) { - cx.update(|cx| { - for window in workspace_windows_for_location(location, cx) { - if let Ok(workspace) = window.read(cx) { - let project = workspace.project.read(cx); - let m = project.visibility_for_paths( - abs_paths, - open_options.open_new_workspace == None, - cx, - ); - if m > best_match { - existing = Some(window); - best_match = m; - } else if best_match.is_none() && open_options.open_new_workspace == Some(false) - { - existing = Some(window) - } - } - } - }); - - let all_paths_are_files = existing - .and_then(|workspace| { - cx.update(|cx| { - workspace - .read(cx) - .map(|workspace| { - let project = workspace.project.read(cx); - let path_style = workspace.path_style(cx); - !abs_paths.iter().any(|path| { - let path = util::paths::SanitizedPath::new(path); - project.worktrees(cx).any(|worktree| { - let worktree = worktree.read(cx); - let abs_path = worktree.abs_path(); - path_style - .strip_prefix(path.as_ref(), abs_path.as_ref()) - .and_then(|rel| worktree.entry_for_path(&rel)) - .is_some_and(|e| e.is_dir()) - }) - }) - }) - .ok() - }) - }) - .unwrap_or(false); - - if open_options.open_new_workspace.is_none() - && existing.is_some() - && open_options.wait - && all_paths_are_files - { - cx.update(|cx| { - let windows = workspace_windows_for_location(location, cx); - let window = cx - .active_window() - .and_then(|window| window.downcast::()) - .filter(|window| windows.contains(window)) - .or_else(|| windows.into_iter().next()); - if let Some(window) = window { - existing = Some(window); - open_visible = OpenVisible::None; - } - }); - } - } - return (existing, open_visible); -} - #[allow(clippy::type_complexity)] pub fn open_paths( abs_paths: &[PathBuf], @@ -8411,17 +8282,16 @@ pub fn open_paths( )>, > { let abs_paths = abs_paths.to_vec(); + let mut existing = None; + let mut best_match = None; + let mut open_visible = OpenVisible::All; #[cfg(target_os = "windows")] let wsl_path = abs_paths .iter() .find_map(|p| util::paths::WslPath::from_path(p)); cx.spawn(async move |cx| { - let (mut existing, mut open_visible) = find_existing_workspace(&abs_paths, &open_options, &SerializedWorkspaceLocation::Local, cx).await; - - // Fallback: if no workspace contains the paths and all paths are files, - // prefer an existing local workspace window (active window first). - if open_options.open_new_workspace.is_none() && existing.is_none() { + if open_options.open_new_workspace != Some(true) { let all_paths = abs_paths.iter().map(|path| app_state.fs.metadata(path)); let all_metadatas = futures::future::join_all(all_paths) .await @@ -8429,17 +8299,54 @@ pub fn open_paths( .filter_map(|result| result.ok().flatten()) .collect::>(); - if all_metadatas.iter().all(|file| !file.is_dir) { + cx.update(|cx| { + for window in local_workspace_windows(cx) { + if let Ok(workspace) = window.read(cx) { + let m = workspace.project.read(cx).visibility_for_paths( + &abs_paths, + &all_metadatas, + open_options.open_new_workspace == None, + cx, + ); + if m > best_match { + existing = Some(window); + best_match = m; + } else if best_match.is_none() + && open_options.open_new_workspace == Some(false) + { + existing = Some(window) + } + } + } + }); + + if open_options.open_new_workspace.is_none() + && (existing.is_none() || open_options.prefer_focused_window) + && all_metadatas.iter().all(|file| !file.is_dir) + { cx.update(|cx| { - let windows = workspace_windows_for_location(&SerializedWorkspaceLocation::Local, cx); - let window = cx + if let Some(window) = cx .active_window() .and_then(|window| window.downcast::()) - .filter(|window| windows.contains(window)) - .or_else(|| windows.into_iter().next()); - if let Some(window) = window { - existing = Some(window); - open_visible = OpenVisible::None; + && let Ok(workspace) = window.read(cx) + { + let project = workspace.project().read(cx); + if project.is_local() && !project.is_via_collab() { + existing = Some(window); + open_visible = OpenVisible::None; + return; + } + } + for window in local_workspace_windows(cx) { + if let Ok(workspace) = window.read(cx) { + let project = workspace.project().read(cx); + if project.is_via_collab() { + continue; + } + existing = Some(window); + open_visible = OpenVisible::None; + break; + } } }); } @@ -8465,7 +8372,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, NotificationSource::File, cx); + workspace.show_error(&e, cx); } } }); @@ -8492,7 +8399,7 @@ pub fn open_paths( workspace .update(cx, move |workspace, _window, cx| { struct OpenInWsl; - workspace.show_notification(NotificationId::unique::(), NotificationSource::Remote, cx, move |cx| { + workspace.show_notification(NotificationId::unique::(), 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| { @@ -8754,14 +8661,10 @@ 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"), - NotificationSource::Remote, - cx, - ) + workspace.show_error(&anyhow!("'{path}' does not exist"), cx) } } else { - workspace.show_error(&error, NotificationSource::Remote, cx) + workspace.show_error(&error, cx) } } })?; diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index a29ba7798c209818ac953c144215dfd36ee3be53..2f9c5b32c60478fc00a668083cf0814258372cca 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -249,9 +249,9 @@ pretty_assertions.workspace = true project = { workspace = true, features = ["test-support"] } semver.workspace = true terminal_view = { workspace = true, features = ["test-support"] } -title_bar = { workspace = true, features = ["test-support"] } tree-sitter-md.workspace = true tree-sitter-rust.workspace = true +title_bar = { workspace = true, features = ["test-support"] } workspace = { workspace = true, features = ["test-support"] } image.workspace = true agent_ui = { workspace = true, features = ["test-support"] } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 249c67d6663383fa99fee04be52066c6f66e3300..9a2cea33882b1a9d8434bf0f3e2cb1dba8471007 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -37,7 +37,7 @@ use parking_lot::Mutex; use project::{project_settings::ProjectSettings, trusted_worktrees}; use proto; use recent_projects::{RemoteSettings, open_remote_project}; -use release_channel::{AppCommitSha, AppVersion}; +use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use session::{AppSession, Session}; use settings::{BaseKeymap, Settings, SettingsStore, watch_config_file}; use std::{ @@ -55,8 +55,7 @@ use util::{ResultExt, TryFutureExt, maybe}; use uuid::Uuid; use workspace::{ AppState, PathList, SerializedWorkspaceLocation, Toast, Workspace, WorkspaceId, - WorkspaceSettings, WorkspaceStore, - notifications::{NotificationId, NotificationSource}, + WorkspaceSettings, WorkspaceStore, notifications::NotificationId, }; use zed::{ OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options, @@ -323,7 +322,7 @@ fn main() { let (open_listener, mut open_rx) = OpenListener::new(); let failed_single_instance_check = if *zed_env_vars::ZED_STATELESS - // || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev + || *release_channel::RELEASE_CHANNEL == ReleaseChannel::Dev { false } else { @@ -939,7 +938,6 @@ fn handle_open_request(request: OpenRequest, app_state: Arc, cx: &mut format!("Imported shared thread from {}", response.sharer_username), ) .autohide(), - NotificationSource::Agent, cx, ); })?; @@ -1362,11 +1360,8 @@ async fn restore_or_create_workspace(app_state: Arc, cx: &mut AsyncApp { workspace .update(cx, |workspace, _, cx| { - workspace.show_toast( - Toast::new(NotificationId::unique::<()>(), message), - NotificationSource::System, - cx, - ) + workspace + .show_toast(Toast::new(NotificationId::unique::<()>(), message), cx) }) .ok(); return true; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 28fbf3a0ddad1bade2233c1e7bf3bcf6938737c1..ef74f355e44088bed0f23d87d1b54468a06a7c30 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -84,8 +84,7 @@ use util::{ResultExt, asset_str, maybe}; use uuid::Uuid; use vim_mode_setting::VimModeSetting; use workspace::notifications::{ - NotificationId, NotificationSource, SuppressEvent, dismiss_app_notification, - show_app_notification, + NotificationId, SuppressEvent, dismiss_app_notification, show_app_notification, }; use workspace::utility_pane::utility_slot_for_dock_position; use workspace::{ @@ -804,7 +803,6 @@ fn register_actions( "Opening this URL in a browser failed because the URL is invalid: {}\n\nError was: {e}", action.url ), - NotificationSource::System, cx, ); } @@ -1001,7 +999,6 @@ fn register_actions( ReleaseChannel::global(cx).display_name() ), ), - NotificationSource::Cli, cx, ) })?; @@ -1413,7 +1410,6 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex .update(cx, |workspace, cx| { workspace.show_notification( NotificationId::unique::(), - NotificationSource::System, cx, |cx| { cx.new(|cx| { @@ -1498,7 +1494,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, NotificationSource::Settings, cx, move |cx| { + show_app_notification(id, cx, move |cx| { cx.new(|cx| { MessageNotification::new(format!("Invalid user settings file\n{error}"), cx) .primary_message("Open Settings File") @@ -1528,7 +1524,7 @@ fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool, } settings::MigrationStatus::Failed { error: err } => { if !showed_parse_error { - show_app_notification(id, NotificationSource::Settings, cx, move |cx| { + show_app_notification(id, cx, move |cx| { cx.new(|cx| { MessageNotification::new( format!( @@ -1734,22 +1730,17 @@ 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, - 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); - }) - }) - }, - ); + 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); + }) + }) + }); } fn show_keymap_file_load_error( @@ -1794,34 +1785,29 @@ fn show_markdown_app_notification( 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, - 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) + 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() }) - }, - ) + .primary_message(primary_button_message) + .primary_icon(IconName::Settings) + .primary_on_click_arc(primary_button_on_click) + }) + }) }); }) .detach(); @@ -2021,12 +2007,9 @@ fn open_local_file( } else { struct NoOpenFolders; - workspace.show_notification( - NotificationId::unique::(), - NotificationSource::Project, - cx, - |cx| cx.new(|cx| MessageNotification::new("This project has no folders open.", cx)), - ) + workspace.show_notification(NotificationId::unique::(), cx, |cx| { + cx.new(|cx| MessageNotification::new("This project has no folders open.", cx)) + }) } } @@ -2201,7 +2184,6 @@ fn capture_recent_audio(workspace: &mut Workspace, _: &mut Window, cx: &mut Cont workspace.show_notification( NotificationId::unique::(), - NotificationSource::System, cx, |cx| cx.new(CaptureRecentAudioNotification::new), ); @@ -2381,7 +2363,7 @@ mod tests { .fs .as_fake() .insert_tree( - path!("/root"), + "/root", json!({ "a": { "aa": null, @@ -2409,10 +2391,7 @@ mod tests { cx.update(|cx| { open_paths( - &[ - PathBuf::from(path!("/root/a")), - PathBuf::from(path!("/root/b")), - ], + &[PathBuf::from("/root/a"), PathBuf::from("/root/b")], app_state.clone(), workspace::OpenOptions::default(), cx, @@ -2424,7 +2403,7 @@ mod tests { cx.update(|cx| { open_paths( - &[PathBuf::from(path!("/root/a"))], + &[PathBuf::from("/root/a")], app_state.clone(), workspace::OpenOptions::default(), cx, @@ -2453,10 +2432,7 @@ mod tests { cx.update(|cx| { open_paths( - &[ - PathBuf::from(path!("/root/c")), - PathBuf::from(path!("/root/d")), - ], + &[PathBuf::from("/root/c"), PathBuf::from("/root/d")], app_state.clone(), workspace::OpenOptions::default(), cx, @@ -2472,7 +2448,7 @@ mod tests { .unwrap(); cx.update(|cx| { open_paths( - &[PathBuf::from(path!("/root/e"))], + &[PathBuf::from("/root/e")], app_state, workspace::OpenOptions { replace_window: Some(window), @@ -2495,7 +2471,7 @@ mod tests { .worktrees(cx) .map(|w| w.read(cx).abs_path()) .collect::>(), - &[Path::new(path!("/root/e")).into()] + &[Path::new("/root/e").into()] ); assert!(workspace.left_dock().read(cx).is_open()); assert!(workspace.active_pane().focus_handle(cx).is_focused(window)); @@ -5259,7 +5235,7 @@ mod tests { cx.update(|cx| { let open_options = OpenOptions { - wait: true, + prefer_focused_window: true, ..Default::default() }; diff --git a/crates/zed/src/zed/migrate.rs b/crates/zed/src/zed/migrate.rs index 481529d52c2c372d43e7755cbf85d6f82876cb85..2452f17d04007364861e9a262b492155daec0c55 100644 --- a/crates/zed/src/zed/migrate.rs +++ b/crates/zed/src/zed/migrate.rs @@ -4,7 +4,7 @@ use fs::Fs; use migrator::{migrate_keymap, migrate_settings}; use settings::{KeymapFile, Settings, SettingsStore}; use util::ResultExt; -use workspace::notifications::{NotificationSource, NotifyTaskExt}; +use workspace::notifications::NotifyTaskExt; use std::sync::Arc; @@ -241,19 +241,11 @@ impl Render for MigrationBanner { match migration_type { Some(MigrationType::Keymap) => { cx.background_spawn(write_keymap_migration(fs.clone())) - .detach_and_notify_err( - NotificationSource::Settings, - window, - cx, - ); + .detach_and_notify_err(window, cx); } Some(MigrationType::Settings) => { cx.background_spawn(write_settings_migration(fs.clone())) - .detach_and_notify_err( - NotificationSource::Settings, - window, - cx, - ); + .detach_and_notify_err(window, cx); } None => unreachable!(), } diff --git a/crates/zed/src/zed/open_listener.rs b/crates/zed/src/zed/open_listener.rs index 985f26b0217b6a4ba7f4436a48c2f4a81f61e0c4..86d35d558dc024931a901c479f26e502de381ca7 100644 --- a/crates/zed/src/zed/open_listener.rs +++ b/crates/zed/src/zed/open_listener.rs @@ -4,19 +4,21 @@ use anyhow::{Context as _, Result, anyhow}; use cli::{CliRequest, CliResponse, ipc::IpcSender}; use cli::{IpcHandshake, ipc}; use client::{ZedLink, parse_zed_link}; +use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; use fs::Fs; use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender}; use futures::channel::{mpsc, oneshot}; use futures::future; - +use futures::future::join_all; use futures::{FutureExt, SinkExt, StreamExt}; use git_ui::{file_diff_view::FileDiffView, multi_diff_view::MultiDiffView}; use gpui::{App, AsyncApp, Global, WindowHandle}; +use language::Point; use onboarding::FIRST_OPEN; use onboarding::show_onboarding_view; -use recent_projects::{RemoteSettings, navigate_to_positions, open_remote_project}; +use recent_projects::{RemoteSettings, open_remote_project}; use remote::{RemoteConnectionOptions, WslConnectionOptions}; use settings::Settings; use std::path::{Path, PathBuf}; @@ -338,9 +340,21 @@ pub async fn open_paths_with_positions( WindowHandle, Vec>>>, )> { + let mut caret_positions = HashMap::default(); + let paths = path_positions .iter() - .map(|path_with_position| path_with_position.path.clone()) + .map(|path_with_position| { + let path = path_with_position.path.clone(); + if let Some(row) = path_with_position.row + && path.is_file() + { + let row = row.saturating_sub(1); + let col = path_with_position.column.unwrap_or(0).saturating_sub(1); + caret_positions.insert(path.clone(), Point::new(row, col)); + } + path + }) .collect::>(); let (workspace, mut items) = cx @@ -372,15 +386,25 @@ pub async fn open_paths_with_positions( for (item, path) in items.iter_mut().zip(&paths) { if let Some(Err(error)) = item { *error = anyhow!("error opening {path:?}: {error}"); + continue; + } + let Some(Ok(item)) = item else { + continue; + }; + let Some(point) = caret_positions.remove(path) else { + continue; + }; + if let Some(active_editor) = item.downcast::() { + workspace + .update(cx, |_, window, cx| { + active_editor.update(cx, |editor, cx| { + editor.go_to_singleton_buffer_point(point, window, cx); + }); + }) + .log_err(); } } - let items_for_navigation = items - .iter() - .map(|item| item.as_ref().and_then(|r| r.as_ref().ok()).cloned()) - .collect::>(); - navigate_to_positions(&workspace, items_for_navigation, path_positions, cx); - Ok((workspace, items)) } @@ -503,81 +527,63 @@ async fn open_workspaces( .detach(); }); } - return Ok(()); - } - // If there are paths to open, open a workspace for each grouping of paths - let mut errored = false; - - for (location, workspace_paths) in grouped_locations { - // If reuse flag is passed, open a new workspace in an existing window. - let (open_new_workspace, replace_window) = if reuse { - ( - Some(true), - cx.update(|cx| { - workspace::workspace_windows_for_location(&location, cx) - .into_iter() - .next() - }), - ) - } else { - (open_new_workspace, None) - }; - let open_options = workspace::OpenOptions { - open_new_workspace, - replace_window, - wait, - env: env.clone(), - ..Default::default() - }; - - match location { - SerializedWorkspaceLocation::Local => { - let workspace_paths = workspace_paths - .paths() - .iter() - .map(|path| path.to_string_lossy().into_owned()) - .collect(); - - let workspace_failed_to_open = open_local_workspace( - workspace_paths, - diff_paths.clone(), - diff_all, - open_options, - responses, - &app_state, - cx, - ) - .await; + } else { + // If there are paths to open, open a workspace for each grouping of paths + let mut errored = false; + + for (location, workspace_paths) in grouped_locations { + match location { + SerializedWorkspaceLocation::Local => { + let workspace_paths = workspace_paths + .paths() + .iter() + .map(|path| path.to_string_lossy().into_owned()) + .collect(); + + let workspace_failed_to_open = open_local_workspace( + workspace_paths, + diff_paths.clone(), + diff_all, + open_new_workspace, + reuse, + wait, + responses, + env.as_ref(), + &app_state, + cx, + ) + .await; - if workspace_failed_to_open { - errored = true + if workspace_failed_to_open { + errored = true + } } - } - SerializedWorkspaceLocation::Remote(mut connection) => { - let app_state = app_state.clone(); - if let RemoteConnectionOptions::Ssh(options) = &mut connection { - cx.update(|cx| { - RemoteSettings::get_global(cx) - .fill_connection_options_from_settings(options) - }); + SerializedWorkspaceLocation::Remote(mut connection) => { + let app_state = app_state.clone(); + if let RemoteConnectionOptions::Ssh(options) = &mut connection { + cx.update(|cx| { + RemoteSettings::get_global(cx) + .fill_connection_options_from_settings(options) + }); + } + cx.spawn(async move |cx| { + open_remote_project( + connection, + workspace_paths.paths().to_vec(), + app_state, + OpenOptions::default(), + cx, + ) + .await + .log_err(); + }) + .detach(); } - cx.spawn(async move |cx| { - open_remote_project( - connection, - workspace_paths.paths().to_vec(), - app_state, - open_options, - cx, - ) - .await - .log_err(); - }) - .detach(); } } - } - anyhow::ensure!(!errored, "failed to open a workspace"); + anyhow::ensure!(!errored, "failed to open a workspace"); + } Ok(()) } @@ -586,20 +592,39 @@ async fn open_local_workspace( workspace_paths: Vec, diff_paths: Vec<[String; 2]>, diff_all: bool, - open_options: workspace::OpenOptions, + open_new_workspace: Option, + reuse: bool, + wait: bool, responses: &IpcSender, + env: Option<&HashMap>, app_state: &Arc, cx: &mut AsyncApp, ) -> bool { let paths_with_position = derive_paths_with_position(app_state.fs.as_ref(), workspace_paths).await; + // If reuse flag is passed, open a new workspace in an existing window. + let (open_new_workspace, replace_window) = if reuse { + ( + Some(true), + cx.update(|cx| workspace::local_workspace_windows(cx).into_iter().next()), + ) + } else { + (open_new_workspace, None) + }; + let (workspace, items) = match open_paths_with_positions( &paths_with_position, &diff_paths, diff_all, app_state.clone(), - open_options.clone(), + workspace::OpenOptions { + open_new_workspace, + replace_window, + prefer_focused_window: wait, + env: env.cloned(), + ..Default::default() + }, cx, ) .await @@ -618,9 +643,10 @@ async fn open_local_workspace( let mut errored = false; let mut item_release_futures = Vec::new(); let mut subscriptions = Vec::new(); + // If --wait flag is used with no paths, or a directory, then wait until // the entire workspace is closed. - if open_options.wait { + if wait { let mut wait_for_window_close = paths_with_position.is_empty() && diff_paths.is_empty(); for path_with_position in &paths_with_position { if app_state.fs.is_dir(&path_with_position.path).await { @@ -643,7 +669,7 @@ async fn open_local_workspace( for item in items { match item { Some(Ok(item)) => { - if open_options.wait { + if wait { let (release_tx, release_rx) = oneshot::channel(); item_release_futures.push(release_rx); subscriptions.push(Ok(cx.update(|cx| { @@ -668,7 +694,7 @@ async fn open_local_workspace( } } - if open_options.wait { + if wait { let wait = async move { let _subscriptions = subscriptions; let _ = future::try_join_all(item_release_futures).await; @@ -699,30 +725,17 @@ pub async fn derive_paths_with_position( fs: &dyn Fs, path_strings: impl IntoIterator>, ) -> Vec { - let path_strings: Vec<_> = path_strings.into_iter().collect(); - let mut result = Vec::with_capacity(path_strings.len()); - for path_str in path_strings { - let original_path = Path::new(path_str.as_ref()); - let mut parsed = PathWithPosition::parse_str(path_str.as_ref()); - - // If a the unparsed path string actually points to a file, use that file instead of parsing out the line/col number. - // Note: The colon syntax is also used to open NTFS alternate data streams (e.g., `file.txt:stream`), which would cause issues. - // However, the colon is not valid in NTFS file names, so we can just skip this logic. - if !cfg!(windows) - && parsed.row.is_some() - && parsed.path != original_path - && fs.is_file(original_path).await - { - parsed = PathWithPosition::from_path(original_path.to_path_buf()); - } - - if let Ok(canonicalized) = fs.canonicalize(&parsed.path).await { - parsed.path = canonicalized; - } - - result.push(parsed); - } - result + join_all(path_strings.into_iter().map(|path_str| async move { + let canonicalized = fs.canonicalize(Path::new(path_str.as_ref())).await; + (path_str, canonicalized) + })) + .await + .into_iter() + .map(|(original, canonicalized)| match canonicalized { + Ok(canonicalized) => PathWithPosition::from_path(canonicalized), + Err(_) => PathWithPosition::parse_str(original.as_ref()), + }) + .collect() } #[cfg(test)] @@ -742,7 +755,7 @@ mod tests { use serde_json::json; use std::{sync::Arc, task::Poll}; use util::path; - use workspace::{AppState, Workspace, find_existing_workspace}; + use workspace::{AppState, Workspace}; #[gpui::test] fn test_parse_ssh_url(cx: &mut TestAppContext) { @@ -944,11 +957,11 @@ mod tests { workspace_paths, vec![], false, - workspace::OpenOptions { - wait: true, - ..Default::default() - }, + None, + false, + true, &response_tx, + None, &app_state, &mut cx, ) @@ -1036,11 +1049,11 @@ mod tests { workspace_paths, vec![], false, - OpenOptions { - open_new_workspace, - ..Default::default() - }, + open_new_workspace, + false, + false, &response_tx, + None, &app_state, &mut cx, ) @@ -1110,8 +1123,11 @@ mod tests { workspace_paths, vec![], false, - workspace::OpenOptions::default(), + None, + false, + false, &response_tx, + None, &app_state, &mut cx, ) @@ -1122,16 +1138,6 @@ mod tests { // Now test the reuse functionality - should replace the existing workspace let workspace_paths_reuse = vec![file1_path.to_string()]; - let paths: Vec = workspace_paths_reuse.iter().map(PathBuf::from).collect(); - let window_to_replace = find_existing_workspace( - &paths, - &workspace::OpenOptions::default(), - &workspace::SerializedWorkspaceLocation::Local, - &mut cx.to_async(), - ) - .await - .0 - .unwrap(); let errored_reuse = cx .spawn({ @@ -1142,11 +1148,11 @@ mod tests { workspace_paths_reuse, vec![], false, - workspace::OpenOptions { - replace_window: Some(window_to_replace), - ..Default::default() - }, + None, // open_new_workspace will be overridden by reuse logic + true, // reuse = true + false, &response_tx, + None, &app_state, &mut cx, ) diff --git a/crates/zed/src/zed/telemetry_log.rs b/crates/zed/src/zed/telemetry_log.rs index 7e0b95e3d831f9b77650ef77fc5d24c9ab6688af..06e13ef5d86fb665151b13ce01de5a60def9ba15 100644 --- a/crates/zed/src/zed/telemetry_log.rs +++ b/crates/zed/src/zed/telemetry_log.rs @@ -22,7 +22,7 @@ use ui::{ }; use workspace::{ Item, ItemHandle, Toast, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, - notifications::{NotificationId, NotificationSource}, + notifications::NotificationId, }; 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(), NotificationSource::System, cx); + workspace.show_toast(toast.clone(), cx); }) .detach();