Detailed changes
@@ -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(
@@ -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,
);
@@ -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,
);
});
@@ -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"))]
@@ -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,
);
})
@@ -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);
});
@@ -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);
})
}
@@ -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,
);
}
@@ -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();
@@ -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,
);
})
@@ -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()
})),
@@ -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,
@@ -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),
});
})
@@ -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| {
@@ -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| {
@@ -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,
);
});
@@ -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,
)
});
@@ -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();
}
@@ -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(());
@@ -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!(
@@ -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,
);
})
@@ -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>()
{
@@ -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(
@@ -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}"),
@@ -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(
@@ -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| {
@@ -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,
)
})?;
@@ -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(
@@ -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();
}
@@ -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()
@@ -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()));
}
@@ -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,
);
})
@@ -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?",
@@ -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};
@@ -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>,
@@ -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,
);
})
@@ -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())
@@ -220,7 +220,6 @@ impl NativeRunningKernel {
);
},
),
- workspace::notifications::NotificationSource::Repl,
cx,
);
})
@@ -7,7 +7,7 @@ use project::search::SearchQuery;
pub use project_search::ProjectSearchView;
use ui::{ButtonStyle, IconButton, IconButtonShape};
use ui::{Tooltip, prelude::*};
-use workspace::notifications::{NotificationId, 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,
);
})
@@ -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());
}
@@ -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();
})
@@ -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()
@@ -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;
}
@@ -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| {
@@ -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(¬ification, {
@@ -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))
});
});
@@ -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 {
@@ -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"),
@@ -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)
}
}
})?;
@@ -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"] }
@@ -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;
@@ -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()
};
@@ -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!(),
}
@@ -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,
)
@@ -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();