Detailed changes
@@ -23,7 +23,7 @@ use settings::Settings;
use std::{ops::Range, sync::Arc};
use theme::{AvatarStyle, Theme};
use util::ResultExt;
-use workspace::{FollowNextCollaborator, JoinProject, Workspace};
+use workspace::{FollowNextCollaborator, Workspace};
actions!(
collab,
@@ -134,21 +134,18 @@ impl View for CollabTitlebarItem {
}
impl CollabTitlebarItem {
- pub fn new(
- workspace: &ViewHandle<Workspace>,
- user_store: &ModelHandle<UserStore>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
+ pub fn new(workspace: &ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
let active_call = ActiveCall::global(cx);
+ let user_store = workspace.read(cx).user_store().clone();
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify()));
subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
subscriptions.push(cx.observe_window_activation(|this, active, cx| {
this.window_activation_changed(active, cx)
}));
- subscriptions.push(cx.observe(user_store, |_, _, cx| cx.notify()));
+ subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
subscriptions.push(
- cx.subscribe(user_store, move |this, user_store, event, cx| {
+ cx.subscribe(&user_store, move |this, user_store, event, cx| {
if let Some(workspace) = this.workspace.upgrade(cx) {
workspace.update(cx, |workspace, cx| {
if let client::Event::Contact { user, kind } = event {
@@ -257,9 +254,7 @@ impl CollabTitlebarItem {
pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext<Self>) {
if self.contacts_popover.take().is_none() {
if let Some(workspace) = self.workspace.upgrade(cx) {
- let project = workspace.read(cx).project().clone();
- let user_store = workspace.read(cx).user_store().clone();
- let view = cx.add_view(|cx| ContactsPopover::new(project, user_store, cx));
+ let view = cx.add_view(|cx| ContactsPopover::new(&workspace, cx));
cx.subscribe(&view, |this, _, event, cx| {
match event {
contacts_popover::Event::Dismissed => {
@@ -776,6 +771,8 @@ impl CollabTitlebarItem {
)
.into_any();
} else if let ParticipantLocation::SharedProject { project_id } = location {
+ enum JoinProject {}
+
let user_id = user.id;
content = MouseEventHandler::<JoinProject, Self>::new(
peer_id.as_u64() as usize,
@@ -783,11 +780,12 @@ impl CollabTitlebarItem {
move |_, _| content,
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(JoinProject {
- project_id,
- follow_user_id: user_id,
- })
+ .on_click(MouseButton::Left, move |_, this, cx| {
+ if let Some(workspace) = this.workspace.upgrade(cx) {
+ let app_state = workspace.read(cx).app_state().clone();
+ workspace::join_remote_project(project_id, user_id, app_state, cx)
+ .detach_and_log_err(cx);
+ }
})
.with_tooltip::<JoinProject>(
peer_id.as_u64() as usize,
@@ -10,29 +10,25 @@ mod notifications;
mod project_shared_notification;
mod sharing_status_indicator;
-use anyhow::anyhow;
use call::ActiveCall;
pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
use gpui::{actions, AppContext, Task};
use std::sync::Arc;
-use workspace::{AppState, JoinProject, Workspace};
+use workspace::AppState;
actions!(collab, [ToggleScreenSharing]);
-pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
collab_titlebar_item::init(cx);
contact_notification::init(cx);
contact_list::init(cx);
contact_finder::init(cx);
contacts_popover::init(cx);
- incoming_call_notification::init(cx);
- project_shared_notification::init(cx);
+ incoming_call_notification::init(&app_state, cx);
+ project_shared_notification::init(&app_state, cx);
sharing_status_indicator::init(cx);
cx.add_global_action(toggle_screen_sharing);
- cx.add_global_action(move |action: &JoinProject, cx| {
- join_project(action, app_state.clone(), cx);
- });
}
pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
@@ -47,88 +43,3 @@ pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
toggle_screen_sharing.detach_and_log_err(cx);
}
}
-
-fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppContext) {
- let project_id = action.project_id;
- let follow_user_id = action.follow_user_id;
- cx.spawn(|mut cx| async move {
- let existing_workspace = cx.update(|cx| {
- cx.window_ids()
- .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
- .find(|workspace| {
- workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
- })
- });
-
- let workspace = if let Some(existing_workspace) = existing_workspace {
- existing_workspace.downgrade()
- } else {
- let active_call = cx.read(ActiveCall::global);
- let room = active_call
- .read_with(&cx, |call, _| call.room().cloned())
- .ok_or_else(|| anyhow!("not in a call"))?;
- let project = room
- .update(&mut cx, |room, cx| {
- room.join_project(
- project_id,
- app_state.languages.clone(),
- app_state.fs.clone(),
- cx,
- )
- })
- .await?;
-
- let (_, workspace) = cx.add_window(
- (app_state.build_window_options)(None, None, cx.platform().as_ref()),
- |cx| {
- let mut workspace = Workspace::new(
- Default::default(),
- 0,
- project,
- app_state.dock_default_item_factory,
- app_state.background_actions,
- cx,
- );
- (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
- workspace
- },
- );
- workspace.downgrade()
- };
-
- cx.activate_window(workspace.window_id());
- cx.platform().activate(true);
-
- workspace.update(&mut cx, |workspace, cx| {
- if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
- let follow_peer_id = room
- .read(cx)
- .remote_participants()
- .iter()
- .find(|(_, participant)| participant.user.id == follow_user_id)
- .map(|(_, p)| p.peer_id)
- .or_else(|| {
- // If we couldn't follow the given user, follow the host instead.
- let collaborator = workspace
- .project()
- .read(cx)
- .collaborators()
- .values()
- .find(|collaborator| collaborator.replica_id == 0)?;
- Some(collaborator.peer_id)
- });
-
- if let Some(follow_peer_id) = follow_peer_id {
- if !workspace.is_being_followed(follow_peer_id) {
- workspace
- .toggle_follow(follow_peer_id, cx)
- .map(|follow| follow.detach_and_log_err(cx));
- }
- }
- }
- })?;
-
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
-}
@@ -11,7 +11,7 @@ use gpui::{
impl_actions, impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel},
- AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle,
+ AppContext, Entity, ModelHandle, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::Project;
@@ -19,7 +19,7 @@ use serde::Deserialize;
use settings::Settings;
use std::{mem, sync::Arc};
use theme::IconButton;
-use workspace::{JoinProject, OpenSharedScreen};
+use workspace::{OpenSharedScreen, Workspace};
impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
impl_internal_actions!(contact_list, [ToggleExpanded, Call]);
@@ -161,6 +161,7 @@ pub struct ContactList {
match_candidates: Vec<StringMatchCandidate>,
list_state: ListState<Self>,
project: ModelHandle<Project>,
+ workspace: WeakViewHandle<Workspace>,
user_store: ModelHandle<UserStore>,
filter_editor: ViewHandle<Editor>,
collapsed_sections: Vec<Section>,
@@ -169,11 +170,7 @@ pub struct ContactList {
}
impl ContactList {
- pub fn new(
- project: ModelHandle<Project>,
- user_store: ModelHandle<UserStore>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
+ pub fn new(workspace: &ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
let filter_editor = cx.add_view(|cx| {
let mut editor = Editor::single_line(
Some(Arc::new(|theme| {
@@ -278,6 +275,7 @@ impl ContactList {
});
let active_call = ActiveCall::global(cx);
+ let user_store = workspace.read(cx).user_store().clone();
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(&user_store, |this, _, cx| this.update_entries(cx)));
subscriptions.push(cx.observe(&active_call, |this, _, cx| this.update_entries(cx)));
@@ -290,7 +288,8 @@ impl ContactList {
match_candidates: Default::default(),
filter_editor,
_subscriptions: subscriptions,
- project,
+ project: workspace.read(cx).project().clone(),
+ workspace: workspace.downgrade(),
user_store,
};
this.update_entries(cx);
@@ -422,10 +421,16 @@ impl ContactList {
host_user_id,
..
} => {
- cx.dispatch_global_action(JoinProject {
- project_id: *project_id,
- follow_user_id: *host_user_id,
- });
+ if let Some(workspace) = self.workspace.upgrade(cx) {
+ let app_state = workspace.read(cx).app_state().clone();
+ workspace::join_remote_project(
+ *project_id,
+ *host_user_id,
+ app_state,
+ cx,
+ )
+ .detach_and_log_err(cx);
+ }
}
ContactEntry::ParticipantScreen { peer_id, .. } => {
cx.dispatch_action(OpenSharedScreen { peer_id: *peer_id });
@@ -798,6 +803,8 @@ impl ContactList {
theme: &theme::ContactList,
cx: &mut ViewContext<Self>,
) -> AnyElement<Self> {
+ enum JoinProject {}
+
let font_cache = cx.font_cache();
let host_avatar_height = theme
.contact_avatar
@@ -873,12 +880,13 @@ impl ContactList {
} else {
CursorStyle::Arrow
})
- .on_click(MouseButton::Left, move |_, _, cx| {
+ .on_click(MouseButton::Left, move |_, this, cx| {
if !is_current {
- cx.dispatch_global_action(JoinProject {
- project_id,
- follow_user_id: host_user_id,
- });
+ if let Some(workspace) = this.workspace.upgrade(cx) {
+ let app_state = workspace.read(cx).app_state().clone();
+ workspace::join_remote_project(project_id, host_user_id, app_state, cx)
+ .detach_and_log_err(cx);
+ }
}
})
.into_any()
@@ -6,11 +6,11 @@ use crate::{
use client::UserStore;
use gpui::{
actions, elements::*, platform::MouseButton, AppContext, Entity, ModelHandle, View,
- ViewContext, ViewHandle,
+ ViewContext, ViewHandle, WeakViewHandle,
};
use picker::PickerEvent;
-use project::Project;
use settings::Settings;
+use workspace::Workspace;
actions!(contacts_popover, [ToggleContactFinder]);
@@ -29,23 +29,17 @@ enum Child {
pub struct ContactsPopover {
child: Child,
- project: ModelHandle<Project>,
user_store: ModelHandle<UserStore>,
+ workspace: WeakViewHandle<Workspace>,
_subscription: Option<gpui::Subscription>,
}
impl ContactsPopover {
- pub fn new(
- project: ModelHandle<Project>,
- user_store: ModelHandle<UserStore>,
- cx: &mut ViewContext<Self>,
- ) -> Self {
+ pub fn new(workspace: &ViewHandle<Workspace>, cx: &mut ViewContext<Self>) -> Self {
let mut this = Self {
- child: Child::ContactList(
- cx.add_view(|cx| ContactList::new(project.clone(), user_store.clone(), cx)),
- ),
- project,
- user_store,
+ child: Child::ContactList(cx.add_view(|cx| ContactList::new(workspace, cx))),
+ user_store: workspace.read(cx).user_store().clone(),
+ workspace: workspace.downgrade(),
_subscription: None,
};
this.show_contact_list(String::new(), cx);
@@ -74,16 +68,16 @@ impl ContactsPopover {
}
fn show_contact_list(&mut self, editor_text: String, cx: &mut ViewContext<ContactsPopover>) {
- let child = cx.add_view(|cx| {
- ContactList::new(self.project.clone(), self.user_store.clone(), cx)
- .with_editor_text(editor_text, cx)
- });
- cx.focus(&child);
- self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
- crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed),
- }));
- self.child = Child::ContactList(child);
- cx.notify();
+ if let Some(workspace) = self.workspace.upgrade(cx) {
+ let child = cx
+ .add_view(|cx| ContactList::new(&workspace, cx).with_editor_text(editor_text, cx));
+ cx.focus(&child);
+ self._subscription = Some(cx.subscribe(&child, |_, _, event, cx| match event {
+ crate::contact_list::Event::Dismissed => cx.emit(Event::Dismissed),
+ }));
+ self.child = Child::ContactList(child);
+ cx.notify();
+ }
}
}
@@ -1,3 +1,5 @@
+use std::sync::{Arc, Weak};
+
use call::{ActiveCall, IncomingCall};
use client::proto;
use futures::StreamExt;
@@ -10,13 +12,14 @@ use gpui::{
};
use settings::Settings;
use util::ResultExt;
-use workspace::JoinProject;
+use workspace::AppState;
impl_internal_actions!(incoming_call_notification, [RespondToCall]);
-pub fn init(cx: &mut AppContext) {
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
cx.add_action(IncomingCallNotification::respond_to_call);
+ let app_state = Arc::downgrade(app_state);
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
cx.spawn(|mut cx| async move {
let mut notification_windows = Vec::new();
@@ -48,7 +51,7 @@ pub fn init(cx: &mut AppContext) {
is_movable: false,
screen: Some(screen),
},
- |_| IncomingCallNotification::new(incoming_call.clone()),
+ |_| IncomingCallNotification::new(incoming_call.clone(), app_state.clone()),
);
notification_windows.push(window_id);
@@ -66,11 +69,12 @@ struct RespondToCall {
pub struct IncomingCallNotification {
call: IncomingCall,
+ app_state: Weak<AppState>,
}
impl IncomingCallNotification {
- pub fn new(call: IncomingCall) -> Self {
- Self { call }
+ pub fn new(call: IncomingCall, app_state: Weak<AppState>) -> Self {
+ Self { call, app_state }
}
fn respond_to_call(&mut self, action: &RespondToCall, cx: &mut ViewContext<Self>) {
@@ -79,15 +83,20 @@ impl IncomingCallNotification {
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
let caller_user_id = self.call.calling_user.id;
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
- cx.spawn(|_, mut cx| async move {
+ cx.spawn(|this, mut cx| async move {
join.await?;
if let Some(project_id) = initial_project_id {
- cx.update(|cx| {
- cx.dispatch_global_action(JoinProject {
- project_id,
- follow_user_id: caller_user_id,
- })
- });
+ this.update(&mut cx, |this, cx| {
+ if let Some(app_state) = this.app_state.upgrade() {
+ workspace::join_remote_project(
+ project_id,
+ caller_user_id,
+ app_state,
+ cx,
+ )
+ .detach_and_log_err(cx);
+ }
+ })?;
}
anyhow::Ok(())
})
@@ -2,22 +2,17 @@ use call::{room, ActiveCall};
use client::User;
use collections::HashMap;
use gpui::{
- actions,
elements::*,
geometry::{rect::RectF, vector::vec2f},
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AppContext, Entity, View, ViewContext,
};
use settings::Settings;
-use std::sync::Arc;
-use workspace::JoinProject;
-
-actions!(project_shared_notification, [DismissProject]);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(ProjectSharedNotification::join);
- cx.add_action(ProjectSharedNotification::dismiss);
+use std::sync::{Arc, Weak};
+use workspace::AppState;
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
+ let app_state = Arc::downgrade(app_state);
let active_call = ActiveCall::global(cx);
let mut notification_windows = HashMap::default();
cx.subscribe(&active_call, move |_, event, cx| match event {
@@ -50,6 +45,7 @@ pub fn init(cx: &mut AppContext) {
owner.clone(),
*project_id,
worktree_root_names.clone(),
+ app_state.clone(),
)
},
);
@@ -82,23 +78,33 @@ pub struct ProjectSharedNotification {
project_id: u64,
worktree_root_names: Vec<String>,
owner: Arc<User>,
+ app_state: Weak<AppState>,
}
impl ProjectSharedNotification {
- fn new(owner: Arc<User>, project_id: u64, worktree_root_names: Vec<String>) -> Self {
+ fn new(
+ owner: Arc<User>,
+ project_id: u64,
+ worktree_root_names: Vec<String>,
+ app_state: Weak<AppState>,
+ ) -> Self {
Self {
project_id,
worktree_root_names,
owner,
+ app_state,
}
}
- fn join(&mut self, _: &JoinProject, cx: &mut ViewContext<Self>) {
+ fn join(&mut self, cx: &mut ViewContext<Self>) {
cx.remove_window();
- cx.propagate_action();
+ if let Some(app_state) = self.app_state.upgrade() {
+ workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
+ .detach_and_log_err(cx);
+ }
}
- fn dismiss(&mut self, _: &DismissProject, cx: &mut ViewContext<Self>) {
+ fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
cx.remove_window();
}
@@ -161,9 +167,6 @@ impl ProjectSharedNotification {
enum Open {}
enum Dismiss {}
- let project_id = self.project_id;
- let owner_user_id = self.owner.id;
-
Flex::column()
.with_child(
MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
@@ -174,12 +177,7 @@ impl ProjectSharedNotification {
.with_style(theme.open_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(JoinProject {
- project_id,
- follow_user_id: owner_user_id,
- });
- })
+ .on_click(MouseButton::Left, move |_, this, cx| this.join(cx))
.flex(1., true),
)
.with_child(
@@ -191,8 +189,8 @@ impl ProjectSharedNotification {
.with_style(theme.dismiss_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, _, cx| {
- cx.dispatch_action(DismissProject);
+ .on_click(MouseButton::Left, |_, this, cx| {
+ this.dismiss(cx);
})
.flex(1., true),
)
@@ -404,11 +404,13 @@ mod tests {
use std::{
ops::{Deref, DerefMut},
path::PathBuf,
+ sync::Arc,
};
use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext};
use project::{FakeFs, Project};
use settings::Settings;
+ use theme::ThemeRegistry;
use super::*;
use crate::{
@@ -419,7 +421,7 @@ mod tests {
},
register_deserializable_item,
sidebar::Sidebar,
- ItemHandle, Workspace,
+ AppState, ItemHandle, Workspace,
};
pub fn default_item_factory(
@@ -467,8 +469,17 @@ mod tests {
Some(serialized_workspace),
0,
project.clone(),
- default_item_factory,
- || &[],
+ Arc::new(AppState {
+ languages: project.read(cx).languages().clone(),
+ themes: ThemeRegistry::new((), cx.font_cache().clone()),
+ client: project.read(cx).client(),
+ user_store: project.read(cx).user_store(),
+ fs: project.read(cx).fs().clone(),
+ build_window_options: |_, _, _| Default::default(),
+ initialize_workspace: |_, _, _| {},
+ dock_default_item_factory: default_item_factory,
+ background_actions: || &[],
+ }),
cx,
)
});
@@ -598,11 +609,20 @@ mod tests {
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| {
Workspace::new(
- Default::default(),
+ None,
0,
- project,
- default_item_factory,
- || &[],
+ project.clone(),
+ Arc::new(AppState {
+ languages: project.read(cx).languages().clone(),
+ themes: ThemeRegistry::new((), cx.font_cache().clone()),
+ client: project.read(cx).client(),
+ user_store: project.read(cx).user_store(),
+ fs: project.read(cx).fs().clone(),
+ build_window_options: |_, _, _| Default::default(),
+ initialize_workspace: |_, _, _| {},
+ dock_default_item_factory: default_item_factory,
+ background_actions: || &[],
+ }),
cx,
)
});
@@ -365,7 +365,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
workspace.update_followers(
proto::update_followers::Variant::CreateView(proto::View {
id: followed_item
- .remote_id(&workspace.client, cx)
+ .remote_id(&workspace.app_state.client, cx)
.map(|id| id.to_proto()),
variant: Some(message),
leader_id: workspace.leader_for_pane(&pane),
@@ -421,7 +421,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
proto::update_followers::Variant::UpdateView(
proto::UpdateView {
id: item
- .remote_id(&this.client, cx)
+ .remote_id(&this.app_state.client, cx)
.map(|id| id.to_proto()),
variant: pending_update.borrow_mut().take(),
leader_id,
@@ -1,4 +1,6 @@
-use crate::{FollowerStatesByLeader, JoinProject, Pane, Workspace};
+use std::sync::Arc;
+
+use crate::{AppState, FollowerStatesByLeader, Pane, Workspace};
use anyhow::{anyhow, Result};
use call::{ActiveCall, ParticipantLocation};
use gpui::{
@@ -70,6 +72,7 @@ impl PaneGroup {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
+ app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
self.root.render(
@@ -78,6 +81,7 @@ impl PaneGroup {
follower_states,
active_call,
active_pane,
+ app_state,
cx,
)
}
@@ -131,6 +135,7 @@ impl Member {
follower_states: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
+ app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
enum FollowIntoExternalProject {}
@@ -175,6 +180,7 @@ impl Member {
} else {
let leader_user = leader.user.clone();
let leader_user_id = leader.user.id;
+ let app_state = Arc::downgrade(app_state);
Some(
MouseEventHandler::<FollowIntoExternalProject, _>::new(
pane.id(),
@@ -199,10 +205,15 @@ impl Member {
)
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(JoinProject {
- project_id: leader_project_id,
- follow_user_id: leader_user_id,
- })
+ if let Some(app_state) = app_state.upgrade() {
+ crate::join_remote_project(
+ leader_project_id,
+ leader_user_id,
+ app_state,
+ cx,
+ )
+ .detach_and_log_err(cx);
+ }
})
.aligned()
.bottom()
@@ -257,6 +268,7 @@ impl Member {
follower_states,
active_call,
active_pane,
+ app_state,
cx,
),
}
@@ -360,6 +372,7 @@ impl PaneAxis {
follower_state: &FollowerStatesByLeader,
active_call: Option<&ModelHandle<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
+ app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
let last_member_ix = self.members.len() - 1;
@@ -370,8 +383,15 @@ impl PaneAxis {
flex = cx.global::<Settings>().active_pane_magnification;
}
- let mut member =
- member.render(project, theme, follower_state, active_call, active_pane, cx);
+ let mut member = member.render(
+ project,
+ theme,
+ follower_state,
+ active_call,
+ active_pane,
+ app_state,
+ cx,
+ );
if ix < last_member_ix {
let mut border = theme.workspace.pane_divider;
border.left = false;
@@ -25,7 +25,6 @@ use client::{
use collections::{hash_map, HashMap, HashSet};
use dock::{Dock, DockDefaultItemFactory, ToggleDockButton};
use drag_and_drop::DragAndDrop;
-use fs::{self, Fs};
use futures::{
channel::{mpsc, oneshot},
future::try_join_all,
@@ -135,12 +134,6 @@ pub struct OpenPaths {
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePane(pub usize);
-#[derive(Clone, PartialEq)]
-pub struct JoinProject {
- pub project_id: u64,
- pub follow_user_id: u64,
-}
-
#[derive(Clone, PartialEq)]
pub struct OpenSharedScreen {
pub peer_id: PeerId,
@@ -216,7 +209,6 @@ pub type WorkspaceId = i64;
impl_internal_actions!(
workspace,
[
- JoinProject,
OpenSharedScreen,
RemoveWorktreeFromProject,
SplitWithItem,
@@ -524,10 +516,7 @@ pub enum Event {
pub struct Workspace {
weak_self: WeakViewHandle<Self>,
- client: Arc<Client>,
- user_store: ModelHandle<client::UserStore>,
remote_entity_subscription: Option<client::Subscription>,
- fs: Arc<dyn Fs>,
modal: Option<AnyViewHandle>,
center: PaneGroup,
left_sidebar: ViewHandle<Sidebar>,
@@ -548,7 +537,7 @@ pub struct Workspace {
active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
database_id: WorkspaceId,
- background_actions: BackgroundActions,
+ app_state: Arc<AppState>,
_window_subscriptions: [Subscription; 3],
_apply_leader_updates: Task<Result<()>>,
_observe_current_user: Task<Result<()>>,
@@ -578,8 +567,7 @@ impl Workspace {
serialized_workspace: Option<SerializedWorkspace>,
workspace_id: WorkspaceId,
project: ModelHandle<Project>,
- dock_default_factory: DockDefaultItemFactory,
- background_actions: BackgroundActions,
+ app_state: Arc<AppState>,
cx: &mut ViewContext<Self>,
) -> Self {
cx.observe(&project, |_, _, cx| cx.notify()).detach();
@@ -616,8 +604,8 @@ impl Workspace {
let weak_handle = cx.weak_handle();
- let center_pane =
- cx.add_view(|cx| Pane::new(weak_handle.clone(), None, background_actions, cx));
+ let center_pane = cx
+ .add_view(|cx| Pane::new(weak_handle.clone(), None, app_state.background_actions, cx));
let pane_id = center_pane.id();
cx.subscribe(¢er_pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@@ -625,14 +613,15 @@ impl Workspace {
.detach();
cx.focus(¢er_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
- let dock = Dock::new(dock_default_factory, background_actions, cx);
+ let dock = Dock::new(
+ app_state.dock_default_item_factory,
+ app_state.background_actions,
+ cx,
+ );
let dock_pane = dock.pane().clone();
- let fs = project.read(cx).fs().clone();
- let user_store = project.read(cx).user_store();
- let client = project.read(cx).client();
- let mut current_user = user_store.read(cx).watch_current_user();
- let mut connection_status = client.status();
+ let mut current_user = app_state.user_store.read(cx).watch_current_user();
+ let mut connection_status = app_state.client.status();
let _observe_current_user = cx.spawn(|this, mut cx| async move {
current_user.recv().await;
connection_status.recv().await;
@@ -725,10 +714,7 @@ impl Workspace {
status_bar,
titlebar_item: None,
notifications: Default::default(),
- client,
remote_entity_subscription: None,
- user_store,
- fs,
left_sidebar,
right_sidebar,
project: project.clone(),
@@ -738,7 +724,7 @@ impl Workspace {
window_edited: false,
active_call,
database_id: workspace_id,
- background_actions,
+ app_state,
_observe_current_user,
_apply_leader_updates,
leader_updates_tx,
@@ -827,8 +813,7 @@ impl Workspace {
serialized_workspace,
workspace_id,
project_handle.clone(),
- app_state.dock_default_item_factory,
- app_state.background_actions,
+ app_state.clone(),
cx,
);
(app_state.initialize_workspace)(&mut workspace, &app_state, cx);
@@ -938,8 +923,12 @@ impl Workspace {
&self.status_bar
}
+ pub fn app_state(&self) -> &Arc<AppState> {
+ &self.app_state
+ }
+
pub fn user_store(&self) -> &ModelHandle<UserStore> {
- &self.user_store
+ &self.app_state.user_store
}
pub fn project(&self) -> &ModelHandle<Project> {
@@ -947,7 +936,7 @@ impl Workspace {
}
pub fn client(&self) -> &Client {
- &self.client
+ &self.app_state.client
}
pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
@@ -1183,7 +1172,7 @@ impl Workspace {
visible: bool,
cx: &mut ViewContext<Self>,
) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
- let fs = self.fs.clone();
+ let fs = self.app_state.fs.clone();
// Sort the paths to ensure we add worktrees for parents before their children.
abs_paths.sort_unstable();
@@ -1494,8 +1483,14 @@ impl Workspace {
}
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
- let pane =
- cx.add_view(|cx| Pane::new(self.weak_handle(), None, self.background_actions, cx));
+ let pane = cx.add_view(|cx| {
+ Pane::new(
+ self.weak_handle(),
+ None,
+ self.app_state.background_actions,
+ cx,
+ )
+ });
let pane_id = pane.id();
cx.subscribe(&pane, move |this, _, event, cx| {
this.handle_pane_event(pane_id, event, cx)
@@ -1688,7 +1683,7 @@ impl Workspace {
proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
id: self.active_item(cx).and_then(|item| {
item.to_followable_item_handle(cx)?
- .remote_id(&self.client, cx)
+ .remote_id(&self.app_state.client, cx)
.map(|id| id.to_proto())
}),
leader_id: self.leader_for_pane(&pane),
@@ -1857,8 +1852,11 @@ impl Workspace {
fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
if let Some(remote_id) = remote_id {
- self.remote_entity_subscription =
- Some(self.client.add_view_for_remote_entity(remote_id, cx));
+ self.remote_entity_subscription = Some(
+ self.app_state
+ .client
+ .add_view_for_remote_entity(remote_id, cx),
+ );
} else {
self.remote_entity_subscription.take();
}
@@ -1898,7 +1896,7 @@ impl Workspace {
cx.notify();
let project_id = self.project.read(cx).remote_id()?;
- let request = self.client.request(proto::Follow {
+ let request = self.app_state.client.request(proto::Follow {
project_id,
leader_id: Some(leader_id),
});
@@ -1977,7 +1975,8 @@ impl Workspace {
if states_by_pane.is_empty() {
self.follower_states_by_leader.remove(&leader_id);
if let Some(project_id) = self.project.read(cx).remote_id() {
- self.client
+ self.app_state
+ .client
.send(proto::Unfollow {
project_id,
leader_id: Some(leader_id),
@@ -2158,7 +2157,7 @@ impl Workspace {
mut cx: AsyncAppContext,
) -> Result<proto::FollowResponse> {
this.update(&mut cx, |this, cx| {
- let client = &this.client;
+ let client = &this.app_state.client;
this.leader_state
.followers
.insert(envelope.original_sender_id()?);
@@ -2366,7 +2365,8 @@ impl Workspace {
) -> Option<()> {
let project_id = self.project.read(cx).remote_id()?;
if !self.leader_state.followers.is_empty() {
- self.client
+ self.app_state
+ .client
.send(proto::UpdateFollowers {
project_id,
follower_ids: self.leader_state.followers.iter().copied().collect(),
@@ -2698,7 +2698,18 @@ impl Workspace {
#[cfg(any(test, feature = "test-support"))]
pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
- Self::new(None, 0, project, |_, _| None, || &[], cx)
+ let app_state = Arc::new(AppState {
+ languages: project.read(cx).languages().clone(),
+ themes: ThemeRegistry::new((), cx.font_cache().clone()),
+ client: project.read(cx).client(),
+ user_store: project.read(cx).user_store(),
+ fs: project.read(cx).fs().clone(),
+ build_window_options: |_, _, _| Default::default(),
+ initialize_workspace: |_, _, _| {},
+ dock_default_item_factory: |_, _| None,
+ background_actions: || &[],
+ });
+ Self::new(None, 0, project, app_state, cx)
}
}
@@ -2784,6 +2795,7 @@ impl View for Workspace {
&self.follower_states_by_leader,
self.active_call(),
self.active_pane(),
+ &self.app_state,
cx,
))
.flex(1., true),
@@ -3015,6 +3027,87 @@ pub fn open_new(
})
}
+pub fn join_remote_project(
+ project_id: u64,
+ follow_user_id: u64,
+ app_state: Arc<AppState>,
+ cx: &mut AppContext,
+) -> Task<Result<()>> {
+ cx.spawn(|mut cx| async move {
+ let existing_workspace = cx.update(|cx| {
+ cx.window_ids()
+ .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+ .find(|workspace| {
+ workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
+ })
+ });
+
+ let workspace = if let Some(existing_workspace) = existing_workspace {
+ existing_workspace.downgrade()
+ } else {
+ let active_call = cx.read(ActiveCall::global);
+ let room = active_call
+ .read_with(&cx, |call, _| call.room().cloned())
+ .ok_or_else(|| anyhow!("not in a call"))?;
+ let project = room
+ .update(&mut cx, |room, cx| {
+ room.join_project(
+ project_id,
+ app_state.languages.clone(),
+ app_state.fs.clone(),
+ cx,
+ )
+ })
+ .await?;
+
+ let (_, workspace) = cx.add_window(
+ (app_state.build_window_options)(None, None, cx.platform().as_ref()),
+ |cx| {
+ let mut workspace =
+ Workspace::new(Default::default(), 0, project, app_state.clone(), cx);
+ (app_state.initialize_workspace)(&mut workspace, &app_state, cx);
+ workspace
+ },
+ );
+ workspace.downgrade()
+ };
+
+ cx.activate_window(workspace.window_id());
+ cx.platform().activate(true);
+
+ workspace.update(&mut cx, |workspace, cx| {
+ if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
+ let follow_peer_id = room
+ .read(cx)
+ .remote_participants()
+ .iter()
+ .find(|(_, participant)| participant.user.id == follow_user_id)
+ .map(|(_, p)| p.peer_id)
+ .or_else(|| {
+ // If we couldn't follow the given user, follow the host instead.
+ let collaborator = workspace
+ .project()
+ .read(cx)
+ .collaborators()
+ .values()
+ .find(|collaborator| collaborator.replica_id == 0)?;
+ Some(collaborator.peer_id)
+ });
+
+ if let Some(follow_peer_id) = follow_peer_id {
+ if !workspace.is_being_followed(follow_peer_id) {
+ workspace
+ .toggle_follow(follow_peer_id, cx)
+ .map(|follow| follow.detach_and_log_err(cx));
+ }
+ }
+ }
+ })?;
+
+ anyhow::Ok(())
+ })
+}
+
fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
let mut parts = value.split(',');
let width: usize = parts.next()?.parse().ok()?;
@@ -3041,16 +3134,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| None,
- || &[],
- cx,
- )
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// Adding an item with no ambiguity renders the tab without detail.
let item1 = cx.add_view(&workspace, |_| {
@@ -3114,16 +3198,7 @@ mod tests {
.await;
let project = Project::test(fs, ["root1".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| None,
- || &[],
- cx,
- )
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
let worktree_id = project.read_with(cx, |project, cx| {
project.worktrees(cx).next().unwrap().read(cx).id()
});
@@ -3213,16 +3288,7 @@ mod tests {
fs.insert_tree("/root", json!({ "one": "" })).await;
let project = Project::test(fs, ["root".as_ref()], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(
- Default::default(),
- 0,
- project.clone(),
- |_, _| None,
- || &[],
- cx,
- )
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
// When there are no dirty items, there's nothing to do.
let item1 = cx.add_view(&workspace, |_| TestItem::new());
@@ -3257,9 +3323,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, None, cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let item1 = cx.add_view(&workspace, |cx| {
TestItem::new()
@@ -3366,9 +3430,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
// Create several workspace items with single project entries, and two
// workspace items with multiple project entries.
@@ -3475,9 +3537,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (window_id, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
- });
+ let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let item = cx.add_view(&workspace, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -3594,9 +3654,7 @@ mod tests {
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
- let (_, workspace) = cx.add_window(|cx| {
- Workspace::new(Default::default(), 0, project, |_, _| None, || &[], cx)
- });
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
let item = cx.add_view(&workspace, |cx| {
TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
@@ -199,7 +199,7 @@ fn main() {
language_selector::init(app_state.clone(), cx);
theme_selector::init(app_state.clone(), cx);
zed::init(&app_state, cx);
- collab_ui::init(app_state.clone(), cx);
+ collab_ui::init(&app_state, cx);
feedback::init(app_state.clone(), cx);
welcome::init(cx);
@@ -302,8 +302,7 @@ pub fn initialize_workspace(
cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone()));
- let collab_titlebar_item =
- cx.add_view(|cx| CollabTitlebarItem::new(&workspace_handle, &app_state.user_store, cx));
+ let collab_titlebar_item = cx.add_view(|cx| CollabTitlebarItem::new(&workspace_handle, cx));
workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);