Revert PRs for landing in main (#48969)

Mikayla Maki created

We're going to re-apply these after landing the multiworkspace branch.

Release Notes:

- N/A

Change summary

crates/agent_ui/src/acp/thread_view.rs                  | 166 ++----
crates/agent_ui/src/acp/thread_view/active_thread.rs    |   6 
crates/agent_ui/src/agent_panel.rs                      |  14 
crates/agent_ui/src/inline_assistant.rs                 |  10 
crates/agent_ui/src/inline_prompt_editor.rs             |   3 
crates/agent_ui/src/mention_set.rs                      |  13 
crates/agent_ui/src/terminal_inline_assistant.rs        |   8 
crates/agent_ui/src/text_thread_editor.rs               |   3 
crates/auto_update_ui/src/auto_update_ui.rs             |   4 
crates/collab_ui/src/channel_view.rs                    |   3 
crates/collab_ui/src/collab_panel.rs                    |  18 
crates/collab_ui/src/notification_panel.rs              |   4 
crates/copilot_ui/src/sign_in.rs                        |  10 
crates/edit_prediction/src/edit_prediction.rs           |   5 
crates/edit_prediction/src/zeta1.rs                     |   2 
crates/edit_prediction_ui/src/edit_prediction_button.rs |   5 
crates/editor/src/editor.rs                             |  16 
crates/editor/src/element.rs                            |  24 
crates/encoding_selector/src/encoding_selector.rs       |   8 
crates/extensions_ui/src/extension_suggest.rs           |   7 
crates/extensions_ui/src/extensions_ui.rs               |   2 
crates/file_finder/src/file_finder.rs                   |  10 
crates/git_ui/src/commit_view.rs                        |   4 
crates/git_ui/src/git_panel.rs                          |  10 
crates/git_ui/src/project_diff.rs                       |   4 
crates/inspector_ui/src/inspector_ui.rs                 |   4 
crates/install_cli/src/install_cli_binary.rs            |   3 
crates/keymap_editor/src/keymap_editor.rs               |   5 
crates/onboarding/src/onboarding.rs                     |   4 
crates/project/src/project.rs                           |  21 
crates/project/src/worktree_store.rs                    |   4 
crates/project_panel/src/project_panel.rs               |  19 
crates/recent_projects/src/dev_container_suggest.rs     |   4 
crates/recent_projects/src/recent_projects.rs           |   2 
crates/recent_projects/src/remote_connections.rs        | 114 +---
crates/recent_projects/src/remote_servers.rs            |   2 
crates/remote/src/remote_client.rs                      |   2 
crates/repl/src/kernels/native_kernel.rs                |   1 
crates/search/src/search.rs                             |   3 
crates/snippets_ui/src/snippets_ui.rs                   |   7 
crates/title_bar/src/title_bar.rs                       |   7 
crates/util/src/paths.rs                                |  12 
crates/vim/src/command.rs                               |  13 
crates/vim/src/normal/search.rs                         |   7 
crates/workspace/src/notifications.rs                   | 268 +---------
crates/workspace/src/pane.rs                            |  10 
crates/workspace/src/tasks.rs                           |  11 
crates/workspace/src/workspace.rs                       | 217 ++------
crates/zed/Cargo.toml                                   |   2 
crates/zed/src/main.rs                                  |  15 
crates/zed/src/zed.rs                                   | 116 +--
crates/zed/src/zed/migrate.rs                           |  14 
crates/zed/src/zed/open_listener.rs                     | 268 +++++-----
crates/zed/src/zed/telemetry_log.rs                     |   4 
54 files changed, 474 insertions(+), 1,044 deletions(-)

Detailed changes

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<AcpThreadView>> {
+    pub fn active_thread(&self) -> Option<Entity<AcpThreadView>> {
         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<Entity<AcpThreadView>> {
         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<acp::SessionId>,
+    current: Entity<AcpThreadView>,
     threads: HashMap<acp::SessionId, Entity<AcpThreadView>>,
     connection: Rc<dyn AgentConnection>,
 }
@@ -280,18 +277,13 @@ struct LoadingView {
 }
 
 impl ConnectedServerState {
-    pub fn active_view(&self) -> Option<&Entity<AcpThreadView>> {
-        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::<acp_thread::AuthRequired>() {
                     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<Self>,
         err: AuthRequired,
         agent_name: SharedString,
-        connection: Rc<dyn AgentConnection>,
         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<Self>,
     ) {
-        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::<LoadError>() {
             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<AcpServerView>,
         cx: &TestAppContext,
     ) -> Entity<AcpThreadView> {
-        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(

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,
                         );

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,
                         );
                     });

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<dyn Fs>, prompt_builder: Arc<PromptBuilder>, 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"))]

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<T: 'static> PromptEditor<T> {
 
                         toast
                     },
-                    NotificationSource::Agent,
                     cx,
                 );
             })

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);
             });

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<dyn Fs>, prompt_builder: Arc<PromptBuilder>, 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);
                                 })
                             }
 

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,
         );
     }

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::<ViewReleaseNotesError>(),
-        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::<UpdateNotification>(),
-                    NotificationSource::Update,
                     cx,
                     move |cx| {
                         let workspace_handle = cx.entity().downgrade();

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::<CopyLinkForPositionToast>(),
                         "Link copied to clipboard",
                     ),
-                    NotificationSource::Collab,
                     cx,
                 );
             })

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()
                             })),

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::<NotificationToast>();
 
                 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,

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<Copilot>, window: &mut Window, cx: &mut
             Err(err) => cx.update(|window, cx| {
                 if let Some(workspace) = window.root::<Workspace>().flatten() {
                     workspace.update(cx, |workspace, cx| {
-                        workspace.show_error(&err, 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),
         });
     })

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::<ZedUpdateRequiredError>(),
-                            NotificationSource::Copilot,
                             cx,
                             move |cx| {
                                 cx.new(|cx| {

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::<ZedUpdateRequiredError>(),
-                            NotificationSource::Copilot,
                             cx,
                             move |cx| {
                                 cx.new(|cx| {

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,
                                         );
                                     });

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::<CopyPermalinkToLine>(),
                                     message,
                                 ),
-                                NotificationSource::Editor,
                                 cx,
                             )
                         })
@@ -23055,7 +23050,6 @@ impl Editor {
 
                         workspace.show_toast(
                             Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
-                            NotificationSource::Editor,
                             cx,
                         )
                     });

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();
             }

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::<EncodingSelector>(),
                     "Save file to change encoding",
                 ),
-                NotificationSource::Editor,
                 cx,
             );
             return Some(());
@@ -76,7 +72,6 @@ impl EncodingSelector {
                     NotificationId::unique::<EncodingSelector>(),
                     "Cannot change encoding during collaboration",
                 ),
-                NotificationSource::Collab,
                 cx,
             );
             return Some(());
@@ -87,7 +82,6 @@ impl EncodingSelector {
                     NotificationId::unique::<EncodingSelector>(),
                     "Cannot change encoding of remote server file",
                 ),
-                NotificationSource::Remote,
                 cx,
             );
             return Some(());

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<Buffer>, 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!(

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,
                                         );
                                     })

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::<Editor>()
                 {

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(

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::<Editor>() {
                     if let Some(diff_task) =
@@ -3754,7 +3752,7 @@ impl GitPanel {
             let _ = workspace.update(cx, |workspace, cx| {
                 struct CommitMessageError;
                 let notification_id = NotificationId::unique::<CommitMessageError>();
-                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}"),

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(

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<workspace::AppState>, 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| {

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<Workspace>) {
                         ReleaseChannel::global(cx).display_name()
                     ),
                 ),
-                NotificationSource::Cli,

                 cx,
             )
         })?;

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(

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();
     }

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<bool> {
         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<bool> {
         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()

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()));
             }

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<Self>) {
         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,
                             );
                         })

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?",

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};

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::<Vec<_>>();
-        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::<Editor>() {
+                        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<Workspace>,
-    items: impl IntoIterator<Item = Option<Box<dyn workspace::item::ItemHandle>>>,
-    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::<Editor>() {
-            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<dyn RemoteConnection>,
     mut paths: Vec<PathBuf>,

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,
                             );
                         })

crates/remote/src/remote_client.rs πŸ”—

@@ -1132,7 +1132,7 @@ impl RemoteClient {
             .unwrap()
     }
 
-    pub fn remote_connection(&self) -> Option<Arc<dyn RemoteConnection>> {
+    fn remote_connection(&self) -> Option<Arc<dyn RemoteConnection>> {
         self.state
             .as_ref()
             .and_then(|state| state.remote_connection())

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,
             );
         })

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<Workspace>,
 ) {
-    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());
 }
 

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();
             })

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()

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<Vim>) {
                 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<Vim>) {
                     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;
             }

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| {

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<String>,
-    level: Option<&'static str>,
-    has_actions: bool,
-    notification_id: String,
-    is_auto_dismissing: bool,
-}
-
-impl NotificationTelemetry {
-    fn for_language_server_prompt(prompt: &LanguageServerPrompt, id: &NotificationId) -> Self {
-        let (level, has_actions, lsp_name) = prompt
-            .request
-            .as_ref()
-            .map(|req| {
-                let level = match req.level {
-                    PromptLevel::Critical => "critical",
-                    PromptLevel::Warning => "warning",
-                    PromptLevel::Info => "info",
-                };
-                (
-                    Some(level),
-                    !req.actions.is_empty(),
-                    Some(req.lsp_name.clone()),
-                )
-            })
-            .unwrap_or((None, false, None));
-
-        Self {
-            notification_type: "lsp",
-            source: "lsp",
-            lsp_name,
-            level,
-            has_actions,
-            notification_id: format!("{:?}", id),
-            is_auto_dismissing: !has_actions,
-        }
-    }
-
-    fn for_error_message_prompt(source: NotificationSource, id: &NotificationId) -> Self {
-        Self {
-            notification_type: "error",
-            source: source.as_str(),
-            lsp_name: None,
-            level: Some("critical"),
-            has_actions: false,
-            notification_id: format!("{:?}", id),
-            is_auto_dismissing: false,
-        }
-    }
-
-    fn for_message_notification(source: NotificationSource, id: &NotificationId) -> Self {
-        Self {
-            notification_type: "notification",
-            source: source.as_str(),
-            lsp_name: None,
-            level: None,
-            has_actions: false,
-            notification_id: format!("{:?}", id),
-            is_auto_dismissing: false,
-        }
-    }
-
-    fn report(self) {
-        telemetry::event!(
-            "Notification Shown",
-            notification_type = self.notification_type,
-            source = self.source,
-            lsp_name = self.lsp_name,
-            level = self.level,
-            has_actions = self.has_actions,
-            notification_id = self.notification_id,
-            is_auto_dismissing = self.is_auto_dismissing,
-        );
-    }
-}
-
 impl Workspace {
     #[cfg(any(test, feature = "test-support"))]
     pub fn notification_ids(&self) -> Vec<NotificationId> {
@@ -230,12 +81,9 @@ impl Workspace {
     pub fn show_notification<V: Notification>(
         &mut self,
         id: NotificationId,
-        source: NotificationSource,
         cx: &mut Context<Self>,
         build_notification: impl FnOnce(&mut Context<Self>) -> Entity<V>,
     ) {
-        let mut telemetry_data: Option<NotificationTelemetry> = None;
-
         self.show_notification_without_handling_dismiss_events(&id, cx, |cx| {
             let notification = build_notification(cx);
             cx.subscribe(&notification, {
@@ -256,11 +104,6 @@ impl Workspace {
             if let Ok(prompt) =
                 AnyEntity::from(notification.clone()).downcast::<LanguageServerPrompt>()
             {
-                telemetry_data = Some(NotificationTelemetry::for_language_server_prompt(
-                    prompt.read(cx),
-                    &id,
-                ));
-
                 let is_prompt_without_actions = prompt
                     .read(cx)
                     .request
@@ -290,24 +133,9 @@ impl Workspace {
                         });
                     }
                 }
-            } else if AnyEntity::from(notification.clone())
-                .downcast::<ErrorMessagePrompt>()
-                .is_ok()
-            {
-                telemetry_data = Some(NotificationTelemetry::for_error_message_prompt(source, &id));
-            } else if AnyEntity::from(notification.clone())
-                .downcast::<simple_message_notification::MessageNotification>()
-                .is_ok()
-            {
-                telemetry_data = Some(NotificationTelemetry::for_message_notification(source, &id));
             }
-
             notification.into()
         });
-
-        if let Some(telemetry) = telemetry_data {
-            telemetry.report();
-        }
     }
 
     /// Shows a notification in this workspace's window. Caller must handle dismiss.
@@ -330,11 +158,11 @@ impl Workspace {
         cx.notify();
     }
 
-    pub fn show_error<E>(&mut self, err: &E, source: NotificationSource, cx: &mut Context<Self>)
+    pub fn show_error<E>(&mut self, err: &E, cx: &mut Context<Self>)
     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<Self>) {
         struct PortalError;
 
-        self.show_notification(
-            NotificationId::unique::<PortalError>(),
-            NotificationSource::System,
-            cx,
-            |cx| {
-                cx.new(|cx| {
-                    ErrorMessagePrompt::new(err.to_string(), cx).with_link_button(
-                        "See docs",
-                        "https://zed.dev/docs/linux#i-cant-open-any-files",
-                    )
-                })
-            },
-        );
+        self.show_notification(NotificationId::unique::<PortalError>(), cx, |cx| {
+            cx.new(|cx| {
+                ErrorMessagePrompt::new(err.to_string(), cx).with_link_button(
+                    "See docs",
+                    "https://zed.dev/docs/linux#i-cant-open-any-files",
+                )
+            })
+        });
     }
 
     pub fn dismiss_notification(&mut self, id: &NotificationId, cx: &mut Context<Self>) {
@@ -368,9 +191,9 @@ impl Workspace {
         });
     }
 
-    pub fn show_toast(&mut self, toast: Toast, source: NotificationSource, cx: &mut Context<Self>) {
+    pub fn show_toast(&mut self, toast: Toast, cx: &mut Context<Self>) {
         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<V: Notification + 'static>(
     id: NotificationId,
-    source: NotificationSource,
     cx: &mut App,
     build_notification: impl Fn(&mut Context<Workspace>) -> Entity<V> + 'static + Send + Sync,
 ) {
-    let telemetry_data = if TypeId::of::<V>() == TypeId::of::<ErrorMessagePrompt>() {
-        Some(NotificationTelemetry::for_error_message_prompt(source, &id))
-    } else if TypeId::of::<V>() == TypeId::of::<simple_message_notification::MessageNotification>()
-    {
-        Some(NotificationTelemetry::for_message_notification(source, &id))
-    } else {
-        None
-    };
-
-    if let Some(telemetry) = telemetry_data {
-        telemetry.report();
-    }
-
     // Defer notification creation so that windows on the stack can be returned to GPUI
     cx.defer(move |cx| {
         // Handle dismiss events by removing the notification from all workspaces.
@@ -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<Workspace>,
-    ) -> Option<Self::Ok>;
+    fn notify_err(self, workspace: &mut Workspace, cx: &mut Context<Workspace>)
+    -> Option<Self::Ok>;
 
-    fn notify_async_err(
-        self,
-        source: NotificationSource,
-        cx: &mut AsyncWindowContext,
-    ) -> Option<Self::Ok>;
+    fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
 
     /// Notifies the active workspace if there is one, otherwise notifies all workspaces.
-    fn notify_app_err(self, source: NotificationSource, cx: &mut App) -> Option<Self::Ok>;
+    fn notify_app_err(self, cx: &mut App) -> Option<Self::Ok>;
 }
 
 impl<T, E> NotifyResultExt for std::result::Result<T, E>
@@ -1287,34 +1088,25 @@ where
 {
     type Ok = T;
 
-    fn notify_err(
-        self,
-        workspace: &mut Workspace,
-        source: NotificationSource,
-        cx: &mut Context<Workspace>,
-    ) -> Option<T> {
+    fn notify_err(self, workspace: &mut Workspace, cx: &mut Context<Workspace>) -> Option<T> {
         match self {
             Ok(value) => Some(value),
             Err(err) => {
                 log::error!("Showing error notification in workspace: {err:?}");
-                workspace.show_error(&err, source, cx);
+                workspace.show_error(&err, cx);
                 None
             }
         }
     }
 
-    fn notify_async_err(
-        self,
-        source: NotificationSource,
-        cx: &mut AsyncWindowContext,
-    ) -> Option<T> {
+    fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
         match self {
             Ok(value) => Some(value),
             Err(err) => {
                 log::error!("{err:?}");
                 cx.update_root(|view, _, cx| {
                     if let Ok(workspace) = view.downcast::<Workspace>() {
-                        workspace.update(cx, |workspace, cx| workspace.show_error(&err, 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<T> {
+    fn notify_app_err(self, cx: &mut App) -> Option<T> {
         match self {
             Ok(value) => Some(value),
             Err(err) => {
                 let message: SharedString = format!("Error: {err}").into();
                 log::error!("Showing error notification in app: {message}");
-                show_app_notification(workspace_error_notification_id(), 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<R, E> NotifyTaskExt for Task<std::result::Result<R, E>>
@@ -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::<LanguageServerPrompt>(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::<LanguageServerPrompt>(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::<LanguageServerPrompt>(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::<LanguageServerPrompt>(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))
             });
         });

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 {

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::<ResolvedTask>();
-                            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"),

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::<LanguageServerPrompt>(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<SerializedWorkspaceLocation> {
-        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<Workspace>, cx: &mut AsyncA
 
                 workspace.show_notification(
                     NotificationId::unique::<DatabaseFailedNotification>(),
-                    NotificationSource::Database,
                     cx,
                     |cx| {
                         cx.new(|cx| {
@@ -8185,60 +8170,24 @@ fn activate_any_workspace_window(cx: &mut AsyncApp) -> Option<WindowHandle<Works
     })
 }
 
-pub fn workspace_windows_for_location(
-    serialized_location: &SerializedWorkspaceLocation,
-    cx: &App,
-) -> Vec<WindowHandle<Workspace>> {
+pub fn local_workspace_windows(cx: &App) -> Vec<WindowHandle<Workspace>> {
     cx.windows()
         .into_iter()
         .filter_map(|window| window.downcast::<Workspace>())
         .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<OpenVisible>,
     pub focus: Option<bool>,
     pub open_new_workspace: Option<bool>,
-    pub wait: bool,
+    pub prefer_focused_window: bool,
     pub replace_window: Option<WindowHandle<Workspace>>,
     pub env: Option<HashMap<String, String>>,
 }
@@ -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<WindowHandle<Workspace>>, 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::<Workspace>())
-                    .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::<Vec<_>>();
 
-            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::<Workspace>())
-                        .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::<OpenInWsl>(), NotificationSource::Remote, cx, move |cx| {
+                    workspace.show_notification(NotificationId::unique::<OpenInWsl>(), 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)
             }
         }
     })?;

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"] }

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<AppState>, 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<AppState>, 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;

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::<OpenLogError>(),
-                            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<F>(
         let primary_button_message = primary_button_message.clone();
         let primary_button_on_click = Arc::new(primary_button_on_click);
         cx.update(|cx| {
-            show_app_notification(
-                notification_id,
-                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::<NoOpenFolders>(),
-            NotificationSource::Project,
-            cx,
-            |cx| cx.new(|cx| MessageNotification::new("This project has no folders open.", cx)),
-        )
+        workspace.show_notification(NotificationId::unique::<NoOpenFolders>(), 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::<CaptureRecentAudioNotification>(),
-        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::<Vec<_>>(),
-                    &[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()
                 };
 

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!(),
                         }

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<Workspace>,
     Vec<Option<Result<Box<dyn ItemHandle>>>>,
 )> {
+    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::<Vec<_>>();
 
     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::<Editor>() {
+            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::<Vec<_>>();
-    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<String>,
     diff_paths: Vec<[String; 2]>,
     diff_all: bool,
-    open_options: workspace::OpenOptions,
+    open_new_workspace: Option<bool>,
+    reuse: bool,
+    wait: bool,
     responses: &IpcSender<CliResponse>,
+    env: Option<&HashMap<String, String>>,
     app_state: &Arc<AppState>,
     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<Item = impl AsRef<str>>,
 ) -> Vec<PathWithPosition> {
-    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<PathBuf> = 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,
                     )

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();