diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index e3db1931af..2fcdf79bce 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -1,21 +1,15 @@ +use std::sync::Arc; + use client::{Contact, UserStore}; use gpui::{ - action, elements::*, geometry::{rect::RectF, vector::vec2f}, platform::CursorStyle, - Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext, RenderContext, - Subscription, View, ViewContext, + Element, ElementBox, Entity, LayoutContext, ModelHandle, RenderContext, Subscription, View, + ViewContext, }; use postage::watch; -use theme::Theme; -use workspace::{Settings, Workspace}; - -action!(JoinProject, u64); - -pub fn init(cx: &mut MutableAppContext) { - cx.add_action(ContactsPanel::join_project); -} +use workspace::{AppState, JoinProject, JoinProjectParams, Settings}; pub struct ContactsPanel { contacts: ListState, @@ -25,42 +19,33 @@ pub struct ContactsPanel { } impl ContactsPanel { - pub fn new( - user_store: ModelHandle, - settings: watch::Receiver, - cx: &mut ViewContext, - ) -> Self { + pub fn new(app_state: Arc, cx: &mut ViewContext) -> Self { Self { contacts: ListState::new( - user_store.read(cx).contacts().len(), + app_state.user_store.read(cx).contacts().len(), Orientation::Top, 1000., { - let user_store = user_store.clone(); - let settings = settings.clone(); + let app_state = app_state.clone(); move |ix, cx| { - let user_store = user_store.read(cx); + let user_store = app_state.user_store.read(cx); let contacts = user_store.contacts().clone(); let current_user_id = user_store.current_user().map(|user| user.id); Self::render_collaborator( &contacts[ix], current_user_id, - &settings.borrow().theme, + app_state.clone(), cx, ) } }, ), - _maintain_contacts: cx.observe(&user_store, Self::update_contacts), - user_store, - settings, + _maintain_contacts: cx.observe(&app_state.user_store, Self::update_contacts), + user_store: app_state.user_store.clone(), + settings: app_state.settings.clone(), } } - fn join_project(_: &mut Workspace, _: &JoinProject, _: &mut ViewContext) { - todo!(); - } - fn update_contacts(&mut self, _: ModelHandle, cx: &mut ViewContext) { self.contacts .reset(self.user_store.read(cx).contacts().len()); @@ -70,10 +55,10 @@ impl ContactsPanel { fn render_collaborator( collaborator: &Contact, current_user_id: Option, - theme: &Theme, + app_state: Arc, cx: &mut LayoutContext, ) -> ElementBox { - let theme = &theme.contacts_panel; + let theme = &app_state.settings.borrow().theme.contacts_panel; let project_count = collaborator.projects.len(); let font_cache = cx.font_cache(); let line_height = theme.unshared_project.name.text.line_height(font_cache); @@ -169,6 +154,7 @@ impl ContactsPanel { .iter() .any(|guest| Some(guest.id) == current_user_id); let is_shared = project.is_shared; + let app_state = app_state.clone(); MouseEventHandler::new::( project_id as usize, @@ -222,7 +208,10 @@ impl ContactsPanel { }) .on_click(move |cx| { if !is_host && !is_guest { - cx.dispatch_action(JoinProject(project_id)) + cx.dispatch_global_action(JoinProject(JoinProjectParams { + project_id, + app_state: app_state.clone(), + })); } }) .expanded(1.0) diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index b33d76bfc6..0aa269e28a 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1165,7 +1165,6 @@ mod tests { #[gpui::test] async fn test_unshare_project(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { - cx_b.update(zed::contacts_panel::init); let lang_registry = Arc::new(LanguageRegistry::new()); let fs = Arc::new(FakeFs::new()); cx_a.foreground().forbid_parking(); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7c98eda812..7dfe81f84e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -40,17 +40,21 @@ use theme::{Theme, ThemeRegistry}; action!(Open, Arc); action!(OpenNew, Arc); action!(OpenPaths, OpenParams); +action!(JoinProject, JoinProjectParams); action!(Save); action!(DebugElements); pub fn init(cx: &mut MutableAppContext) { cx.add_global_action(open); cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| { - open_paths(&action.0.paths, &action.0.app_state, cx).detach() + open_paths(&action.0.paths, &action.0.app_state, cx).detach(); }); cx.add_global_action(move |action: &OpenNew, cx: &mut MutableAppContext| { open_new(&action.0, cx) }); + cx.add_global_action(move |action: &JoinProject, cx: &mut MutableAppContext| { + join_project(action.0.project_id, &action.0.app_state, cx).detach(); + }); cx.add_action(Workspace::save_active_item); cx.add_action(Workspace::debug_elements); @@ -90,8 +94,11 @@ pub struct AppState { pub channel_list: ModelHandle, pub entry_openers: Arc<[Box]>, pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>, - pub build_workspace: - &'static dyn Fn(&WorkspaceParams, &mut ViewContext) -> Workspace, + pub build_workspace: &'static dyn Fn( + ModelHandle, + &Arc, + &mut ViewContext, + ) -> Workspace, } #[derive(Clone)] @@ -100,6 +107,12 @@ pub struct OpenParams { pub app_state: Arc, } +#[derive(Clone)] +pub struct JoinProjectParams { + pub project_id: u64, + pub app_state: Arc, +} + pub trait EntryOpener { fn open( &self, @@ -338,6 +351,7 @@ impl Clone for Box { #[derive(Clone)] pub struct WorkspaceParams { + pub project: ModelHandle, pub client: Arc, pub fs: Arc, pub languages: Arc, @@ -350,7 +364,8 @@ pub struct WorkspaceParams { impl WorkspaceParams { #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut MutableAppContext) -> Self { - let languages = LanguageRegistry::new(); + let fs = Arc::new(project::FakeFs::new()); + let languages = Arc::new(LanguageRegistry::new()); let client = Client::new(); let http_client = client::test::FakeHttpClient::new(|_| async move { Ok(client::http::ServerResponse::new(404)) @@ -359,17 +374,45 @@ impl WorkspaceParams { gpui::fonts::with_font_cache(cx.font_cache().clone(), || theme::Theme::default()); let settings = Settings::new("Courier", cx.font_cache(), Arc::new(theme)).unwrap(); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx)); + let project = Project::local( + client.clone(), + user_store.clone(), + languages.clone(), + fs.clone(), + cx, + ); Self { + project, channel_list: cx .add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)), client, - fs: Arc::new(project::FakeFs::new()), - languages: Arc::new(languages), + fs, + languages, settings: watch::channel_with(settings).1, user_store, entry_openers: Arc::from([]), } } + + #[cfg(any(test, feature = "test-support"))] + pub fn local(app_state: &Arc, cx: &mut MutableAppContext) -> Self { + Self { + project: Project::local( + app_state.client.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx, + ), + client: app_state.client.clone(), + fs: app_state.fs.clone(), + languages: app_state.languages.clone(), + settings: app_state.settings.clone(), + user_store: app_state.user_store.clone(), + channel_list: app_state.channel_list.clone(), + entry_openers: app_state.entry_openers.clone(), + } + } } pub struct Workspace { @@ -392,14 +435,7 @@ pub struct Workspace { impl Workspace { pub fn new(params: &WorkspaceParams, cx: &mut ViewContext) -> Self { - let project = Project::local( - params.client.clone(), - params.user_store.clone(), - params.languages.clone(), - params.fs.clone(), - cx, - ); - cx.observe(&project, |_, _, cx| cx.notify()).detach(); + cx.observe(¶ms.project, |_, _, cx| cx.notify()).detach(); let pane = cx.add_view(|_| Pane::new(params.settings.clone())); let pane_id = pane.id(); @@ -445,7 +481,7 @@ impl Workspace { fs: params.fs.clone(), left_sidebar: Sidebar::new(Side::Left), right_sidebar: Sidebar::new(Side::Right), - project, + project: params.project.clone(), entry_openers: params.entry_openers.clone(), items: Default::default(), _observe_current_user, @@ -1258,20 +1294,6 @@ impl std::fmt::Debug for OpenParams { } } -impl<'a> From<&'a AppState> for WorkspaceParams { - fn from(state: &'a AppState) -> Self { - Self { - client: state.client.clone(), - fs: state.fs.clone(), - languages: state.languages.clone(), - settings: state.settings.clone(), - user_store: state.user_store.clone(), - channel_list: state.channel_list.clone(), - entry_openers: state.entry_openers.clone(), - } - } -} - fn open(action: &Open, cx: &mut MutableAppContext) { let app_state = action.0.clone(); cx.prompt_for_paths( @@ -1314,7 +1336,14 @@ pub fn open_paths( let workspace = existing.unwrap_or_else(|| { cx.add_window((app_state.build_window_options)(), |cx| { - (app_state.build_workspace)(&WorkspaceParams::from(app_state.as_ref()), cx) + let project = Project::local( + app_state.client.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx, + ); + (app_state.build_workspace)(project, &app_state, cx) }) .1 }); @@ -1326,9 +1355,49 @@ pub fn open_paths( }) } +pub fn join_project( + project_id: u64, + app_state: &Arc, + cx: &mut MutableAppContext, +) -> Task>> { + for window_id in cx.window_ids().collect::>() { + if let Some(workspace) = cx.root_view::(window_id) { + if workspace.read(cx).project().read(cx).remote_id() == Some(project_id) { + return Task::ready(Ok(workspace)); + } + } + } + + let app_state = app_state.clone(); + cx.spawn(|mut cx| async move { + let project = Project::remote( + project_id, + app_state.client.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + &mut cx, + ) + .await?; + let (_, workspace) = cx.update(|cx| { + cx.add_window((app_state.build_window_options)(), |cx| { + (app_state.build_workspace)(project, &app_state, cx) + }) + }); + Ok(workspace) + }) +} + fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { - (app_state.build_workspace)(&app_state.as_ref().into(), cx) + let project = Project::local( + app_state.client.clone(), + app_state.user_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx, + ); + (app_state.build_workspace)(project, &app_state, cx) }); cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone())); } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 1b578598d1..c676523554 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -58,7 +58,6 @@ fn main() { editor::init(cx, &mut entry_openers); go_to_line::init(cx); file_finder::init(cx); - contacts_panel::init(cx); chat_panel::init(cx); project_panel::init(cx); diagnostics::init(cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index ad543ee166..713a9a3cac 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -14,9 +14,10 @@ use gpui::{ geometry::vector::vec2f, keymap::Binding, platform::{WindowBounds, WindowOptions}, - ViewContext, + ModelHandle, ViewContext, }; pub use lsp; +use project::Project; pub use project::{self, fs}; use project_panel::ProjectPanel; use std::sync::Arc; @@ -48,27 +49,39 @@ pub fn init(app_state: &Arc, cx: &mut gpui::MutableAppContext) { ]) } -pub fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext) -> Workspace { - let mut workspace = Workspace::new(params, cx); +pub fn build_workspace( + project: ModelHandle, + app_state: &Arc, + cx: &mut ViewContext, +) -> Workspace { + let workspace_params = WorkspaceParams { + project, + client: app_state.client.clone(), + fs: app_state.fs.clone(), + languages: app_state.languages.clone(), + settings: app_state.settings.clone(), + user_store: app_state.user_store.clone(), + channel_list: app_state.channel_list.clone(), + entry_openers: app_state.entry_openers.clone(), + }; + let mut workspace = Workspace::new(&workspace_params, cx); let project = workspace.project().clone(); workspace.left_sidebar_mut().add_item( "icons/folder-tree-16.svg", - ProjectPanel::new(project, params.settings.clone(), cx).into(), + ProjectPanel::new(project, app_state.settings.clone(), cx).into(), ); workspace.right_sidebar_mut().add_item( "icons/user-16.svg", - cx.add_view(|cx| { - ContactsPanel::new(params.user_store.clone(), params.settings.clone(), cx) - }) - .into(), + cx.add_view(|cx| ContactsPanel::new(app_state.clone(), cx)) + .into(), ); workspace.right_sidebar_mut().add_item( "icons/comment-16.svg", cx.add_view(|cx| { ChatPanel::new( - params.client.clone(), - params.channel_list.clone(), - params.settings.clone(), + app_state.client.clone(), + app_state.channel_list.clone(), + app_state.settings.clone(), cx, ) }) @@ -76,9 +89,9 @@ pub fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext ); let diagnostic = - cx.add_view(|_| editor::items::DiagnosticMessage::new(params.settings.clone())); + cx.add_view(|_| editor::items::DiagnosticMessage::new(app_state.settings.clone())); let cursor_position = - cx.add_view(|_| editor::items::CursorPosition::new(params.settings.clone())); + cx.add_view(|_| editor::items::CursorPosition::new(app_state.settings.clone())); workspace.status_bar().update(cx, |status_bar, cx| { status_bar.add_left_item(diagnostic, cx); status_bar.add_right_item(cursor_position, cx); @@ -225,8 +238,8 @@ mod tests { }), ) .await; - - let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); workspace .update(&mut cx, |workspace, cx| { workspace.add_worktree(Path::new("/root"), cx) @@ -340,7 +353,8 @@ mod tests { fs.insert_file("/dir1/a.txt", "".into()).await.unwrap(); fs.insert_file("/dir2/b.txt", "".into()).await.unwrap(); - let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); workspace .update(&mut cx, |workspace, cx| { workspace.add_worktree("/dir1".as_ref(), cx) @@ -406,8 +420,8 @@ mod tests { let fs = app_state.fs.as_fake(); fs.insert_tree("/root", json!({ "a.txt": "" })).await; - let (window_id, workspace) = - cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); + let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); workspace .update(&mut cx, |workspace, cx| { workspace.add_worktree(Path::new("/root"), cx) @@ -453,7 +467,7 @@ mod tests { async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) { let app_state = cx.update(test_app_state); app_state.fs.as_fake().insert_dir("/root").await.unwrap(); - let params = app_state.as_ref().into(); + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); workspace .update(&mut cx, |workspace, cx| { @@ -570,7 +584,7 @@ mod tests { ) { let app_state = cx.update(test_app_state); app_state.fs.as_fake().insert_dir("/root").await.unwrap(); - let params = app_state.as_ref().into(); + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); // Create a new untitled buffer @@ -628,8 +642,8 @@ mod tests { ) .await; - let (window_id, workspace) = - cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); + let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx)); + let (window_id, workspace) = cx.add_window(|cx| Workspace::new(¶ms, cx)); workspace .update(&mut cx, |workspace, cx| { workspace.add_worktree(Path::new("/root"), cx)