Allow opening of remote projects via the contacts panel

This commit is contained in:
Antonio Scandurra 2021-12-21 11:58:01 +01:00
parent 5d2c4807db
commit 17094ec542
5 changed files with 156 additions and 86 deletions

View file

@ -1,21 +1,15 @@
use std::sync::Arc;
use client::{Contact, UserStore}; use client::{Contact, UserStore};
use gpui::{ use gpui::{
action,
elements::*, elements::*,
geometry::{rect::RectF, vector::vec2f}, geometry::{rect::RectF, vector::vec2f},
platform::CursorStyle, platform::CursorStyle,
Element, ElementBox, Entity, LayoutContext, ModelHandle, MutableAppContext, RenderContext, Element, ElementBox, Entity, LayoutContext, ModelHandle, RenderContext, Subscription, View,
Subscription, View, ViewContext, ViewContext,
}; };
use postage::watch; use postage::watch;
use theme::Theme; use workspace::{AppState, JoinProject, JoinProjectParams, Settings};
use workspace::{Settings, Workspace};
action!(JoinProject, u64);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ContactsPanel::join_project);
}
pub struct ContactsPanel { pub struct ContactsPanel {
contacts: ListState, contacts: ListState,
@ -25,42 +19,33 @@ pub struct ContactsPanel {
} }
impl ContactsPanel { impl ContactsPanel {
pub fn new( pub fn new(app_state: Arc<AppState>, cx: &mut ViewContext<Self>) -> Self {
user_store: ModelHandle<UserStore>,
settings: watch::Receiver<Settings>,
cx: &mut ViewContext<Self>,
) -> Self {
Self { Self {
contacts: ListState::new( contacts: ListState::new(
user_store.read(cx).contacts().len(), app_state.user_store.read(cx).contacts().len(),
Orientation::Top, Orientation::Top,
1000., 1000.,
{ {
let user_store = user_store.clone(); let app_state = app_state.clone();
let settings = settings.clone();
move |ix, cx| { 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 contacts = user_store.contacts().clone();
let current_user_id = user_store.current_user().map(|user| user.id); let current_user_id = user_store.current_user().map(|user| user.id);
Self::render_collaborator( Self::render_collaborator(
&contacts[ix], &contacts[ix],
current_user_id, current_user_id,
&settings.borrow().theme, app_state.clone(),
cx, cx,
) )
} }
}, },
), ),
_maintain_contacts: cx.observe(&user_store, Self::update_contacts), _maintain_contacts: cx.observe(&app_state.user_store, Self::update_contacts),
user_store, user_store: app_state.user_store.clone(),
settings, settings: app_state.settings.clone(),
} }
} }
fn join_project(_: &mut Workspace, _: &JoinProject, _: &mut ViewContext<Workspace>) {
todo!();
}
fn update_contacts(&mut self, _: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) { fn update_contacts(&mut self, _: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) {
self.contacts self.contacts
.reset(self.user_store.read(cx).contacts().len()); .reset(self.user_store.read(cx).contacts().len());
@ -70,10 +55,10 @@ impl ContactsPanel {
fn render_collaborator( fn render_collaborator(
collaborator: &Contact, collaborator: &Contact,
current_user_id: Option<u64>, current_user_id: Option<u64>,
theme: &Theme, app_state: Arc<AppState>,
cx: &mut LayoutContext, cx: &mut LayoutContext,
) -> ElementBox { ) -> ElementBox {
let theme = &theme.contacts_panel; let theme = &app_state.settings.borrow().theme.contacts_panel;
let project_count = collaborator.projects.len(); let project_count = collaborator.projects.len();
let font_cache = cx.font_cache(); let font_cache = cx.font_cache();
let line_height = theme.unshared_project.name.text.line_height(font_cache); let line_height = theme.unshared_project.name.text.line_height(font_cache);
@ -169,6 +154,7 @@ impl ContactsPanel {
.iter() .iter()
.any(|guest| Some(guest.id) == current_user_id); .any(|guest| Some(guest.id) == current_user_id);
let is_shared = project.is_shared; let is_shared = project.is_shared;
let app_state = app_state.clone();
MouseEventHandler::new::<ContactsPanel, _, _, _>( MouseEventHandler::new::<ContactsPanel, _, _, _>(
project_id as usize, project_id as usize,
@ -222,7 +208,10 @@ impl ContactsPanel {
}) })
.on_click(move |cx| { .on_click(move |cx| {
if !is_host && !is_guest { 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) .expanded(1.0)

View file

@ -1165,7 +1165,6 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_unshare_project(mut cx_a: TestAppContext, mut cx_b: TestAppContext) { 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 lang_registry = Arc::new(LanguageRegistry::new());
let fs = Arc::new(FakeFs::new()); let fs = Arc::new(FakeFs::new());
cx_a.foreground().forbid_parking(); cx_a.foreground().forbid_parking();

View file

@ -40,17 +40,21 @@ use theme::{Theme, ThemeRegistry};
action!(Open, Arc<AppState>); action!(Open, Arc<AppState>);
action!(OpenNew, Arc<AppState>); action!(OpenNew, Arc<AppState>);
action!(OpenPaths, OpenParams); action!(OpenPaths, OpenParams);
action!(JoinProject, JoinProjectParams);
action!(Save); action!(Save);
action!(DebugElements); action!(DebugElements);
pub fn init(cx: &mut MutableAppContext) { pub fn init(cx: &mut MutableAppContext) {
cx.add_global_action(open); cx.add_global_action(open);
cx.add_global_action(move |action: &OpenPaths, cx: &mut MutableAppContext| { 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| { cx.add_global_action(move |action: &OpenNew, cx: &mut MutableAppContext| {
open_new(&action.0, cx) 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::save_active_item);
cx.add_action(Workspace::debug_elements); cx.add_action(Workspace::debug_elements);
@ -90,8 +94,11 @@ pub struct AppState {
pub channel_list: ModelHandle<client::ChannelList>, pub channel_list: ModelHandle<client::ChannelList>,
pub entry_openers: Arc<[Box<dyn EntryOpener>]>, pub entry_openers: Arc<[Box<dyn EntryOpener>]>,
pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>, pub build_window_options: &'static dyn Fn() -> WindowOptions<'static>,
pub build_workspace: pub build_workspace: &'static dyn Fn(
&'static dyn Fn(&WorkspaceParams, &mut ViewContext<Workspace>) -> Workspace, ModelHandle<Project>,
&Arc<AppState>,
&mut ViewContext<Workspace>,
) -> Workspace,
} }
#[derive(Clone)] #[derive(Clone)]
@ -100,6 +107,12 @@ pub struct OpenParams {
pub app_state: Arc<AppState>, pub app_state: Arc<AppState>,
} }
#[derive(Clone)]
pub struct JoinProjectParams {
pub project_id: u64,
pub app_state: Arc<AppState>,
}
pub trait EntryOpener { pub trait EntryOpener {
fn open( fn open(
&self, &self,
@ -338,6 +351,7 @@ impl Clone for Box<dyn ItemHandle> {
#[derive(Clone)] #[derive(Clone)]
pub struct WorkspaceParams { pub struct WorkspaceParams {
pub project: ModelHandle<Project>,
pub client: Arc<Client>, pub client: Arc<Client>,
pub fs: Arc<dyn Fs>, pub fs: Arc<dyn Fs>,
pub languages: Arc<LanguageRegistry>, pub languages: Arc<LanguageRegistry>,
@ -350,7 +364,8 @@ pub struct WorkspaceParams {
impl WorkspaceParams { impl WorkspaceParams {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut MutableAppContext) -> Self { 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 client = Client::new();
let http_client = client::test::FakeHttpClient::new(|_| async move { let http_client = client::test::FakeHttpClient::new(|_| async move {
Ok(client::http::ServerResponse::new(404)) Ok(client::http::ServerResponse::new(404))
@ -359,17 +374,45 @@ impl WorkspaceParams {
gpui::fonts::with_font_cache(cx.font_cache().clone(), || theme::Theme::default()); 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 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 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 { Self {
project,
channel_list: cx channel_list: cx
.add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)), .add_model(|cx| ChannelList::new(user_store.clone(), client.clone(), cx)),
client, client,
fs: Arc::new(project::FakeFs::new()), fs,
languages: Arc::new(languages), languages,
settings: watch::channel_with(settings).1, settings: watch::channel_with(settings).1,
user_store, user_store,
entry_openers: Arc::from([]), entry_openers: Arc::from([]),
} }
} }
#[cfg(any(test, feature = "test-support"))]
pub fn local(app_state: &Arc<AppState>, 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 { pub struct Workspace {
@ -392,14 +435,7 @@ pub struct Workspace {
impl Workspace { impl Workspace {
pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self { pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
let project = Project::local( cx.observe(&params.project, |_, _, cx| cx.notify()).detach();
params.client.clone(),
params.user_store.clone(),
params.languages.clone(),
params.fs.clone(),
cx,
);
cx.observe(&project, |_, _, cx| cx.notify()).detach();
let pane = cx.add_view(|_| Pane::new(params.settings.clone())); let pane = cx.add_view(|_| Pane::new(params.settings.clone()));
let pane_id = pane.id(); let pane_id = pane.id();
@ -445,7 +481,7 @@ impl Workspace {
fs: params.fs.clone(), fs: params.fs.clone(),
left_sidebar: Sidebar::new(Side::Left), left_sidebar: Sidebar::new(Side::Left),
right_sidebar: Sidebar::new(Side::Right), right_sidebar: Sidebar::new(Side::Right),
project, project: params.project.clone(),
entry_openers: params.entry_openers.clone(), entry_openers: params.entry_openers.clone(),
items: Default::default(), items: Default::default(),
_observe_current_user, _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) { fn open(action: &Open, cx: &mut MutableAppContext) {
let app_state = action.0.clone(); let app_state = action.0.clone();
cx.prompt_for_paths( cx.prompt_for_paths(
@ -1314,7 +1336,14 @@ pub fn open_paths(
let workspace = existing.unwrap_or_else(|| { let workspace = existing.unwrap_or_else(|| {
cx.add_window((app_state.build_window_options)(), |cx| { 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 .1
}); });
@ -1326,9 +1355,49 @@ pub fn open_paths(
}) })
} }
pub fn join_project(
project_id: u64,
app_state: &Arc<AppState>,
cx: &mut MutableAppContext,
) -> Task<Result<ViewHandle<Workspace>>> {
for window_id in cx.window_ids().collect::<Vec<_>>() {
if let Some(workspace) = cx.root_view::<Workspace>(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<AppState>, cx: &mut MutableAppContext) { fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
let (window_id, workspace) = cx.add_window((app_state.build_window_options)(), |cx| { 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())); cx.dispatch_action(window_id, vec![workspace.id()], &OpenNew(app_state.clone()));
} }

View file

@ -58,7 +58,6 @@ fn main() {
editor::init(cx, &mut entry_openers); editor::init(cx, &mut entry_openers);
go_to_line::init(cx); go_to_line::init(cx);
file_finder::init(cx); file_finder::init(cx);
contacts_panel::init(cx);
chat_panel::init(cx); chat_panel::init(cx);
project_panel::init(cx); project_panel::init(cx);
diagnostics::init(cx); diagnostics::init(cx);

View file

@ -14,9 +14,10 @@ use gpui::{
geometry::vector::vec2f, geometry::vector::vec2f,
keymap::Binding, keymap::Binding,
platform::{WindowBounds, WindowOptions}, platform::{WindowBounds, WindowOptions},
ViewContext, ModelHandle, ViewContext,
}; };
pub use lsp; pub use lsp;
use project::Project;
pub use project::{self, fs}; pub use project::{self, fs};
use project_panel::ProjectPanel; use project_panel::ProjectPanel;
use std::sync::Arc; use std::sync::Arc;
@ -48,27 +49,39 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
]) ])
} }
pub fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext<Workspace>) -> Workspace { pub fn build_workspace(
let mut workspace = Workspace::new(params, cx); project: ModelHandle<Project>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> 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(); let project = workspace.project().clone();
workspace.left_sidebar_mut().add_item( workspace.left_sidebar_mut().add_item(
"icons/folder-tree-16.svg", "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( workspace.right_sidebar_mut().add_item(
"icons/user-16.svg", "icons/user-16.svg",
cx.add_view(|cx| { cx.add_view(|cx| ContactsPanel::new(app_state.clone(), cx))
ContactsPanel::new(params.user_store.clone(), params.settings.clone(), cx) .into(),
})
.into(),
); );
workspace.right_sidebar_mut().add_item( workspace.right_sidebar_mut().add_item(
"icons/comment-16.svg", "icons/comment-16.svg",
cx.add_view(|cx| { cx.add_view(|cx| {
ChatPanel::new( ChatPanel::new(
params.client.clone(), app_state.client.clone(),
params.channel_list.clone(), app_state.channel_list.clone(),
params.settings.clone(), app_state.settings.clone(),
cx, cx,
) )
}) })
@ -76,9 +89,9 @@ pub fn build_workspace(params: &WorkspaceParams, cx: &mut ViewContext<Workspace>
); );
let diagnostic = 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 = 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| { workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic, cx); status_bar.add_left_item(diagnostic, cx);
status_bar.add_right_item(cursor_position, cx); status_bar.add_right_item(cursor_position, cx);
@ -225,8 +238,8 @@ mod tests {
}), }),
) )
.await; .await;
let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
let (_, workspace) = cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); let (_, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree(Path::new("/root"), 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("/dir1/a.txt", "".into()).await.unwrap();
fs.insert_file("/dir2/b.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(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree("/dir1".as_ref(), cx) workspace.add_worktree("/dir1".as_ref(), cx)
@ -406,8 +420,8 @@ mod tests {
let fs = app_state.fs.as_fake(); let fs = app_state.fs.as_fake();
fs.insert_tree("/root", json!({ "a.txt": "" })).await; fs.insert_tree("/root", json!({ "a.txt": "" })).await;
let (window_id, workspace) = let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree(Path::new("/root"), 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) { async fn test_open_and_save_new_file(mut cx: gpui::TestAppContext) {
let app_state = cx.update(test_app_state); let app_state = cx.update(test_app_state);
app_state.fs.as_fake().insert_dir("/root").await.unwrap(); 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(&params, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
@ -570,7 +584,7 @@ mod tests {
) { ) {
let app_state = cx.update(test_app_state); let app_state = cx.update(test_app_state);
app_state.fs.as_fake().insert_dir("/root").await.unwrap(); 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(&params, cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
// Create a new untitled buffer // Create a new untitled buffer
@ -628,8 +642,8 @@ mod tests {
) )
.await; .await;
let (window_id, workspace) = let params = cx.update(|cx| WorkspaceParams::local(&app_state, cx));
cx.add_window(|cx| Workspace::new(&app_state.as_ref().into(), cx)); let (window_id, workspace) = cx.add_window(|cx| Workspace::new(&params, cx));
workspace workspace
.update(&mut cx, |workspace, cx| { .update(&mut cx, |workspace, cx| {
workspace.add_worktree(Path::new("/root"), cx) workspace.add_worktree(Path::new("/root"), cx)