Detailed changes
@@ -38,9 +38,7 @@ use std::{
},
};
use unindent::Unindent as _;
-use workspace::{
- item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, ToggleFollow, Workspace,
-};
+use workspace::{item::ItemHandle as _, shared_screen::SharedScreen, SplitDirection, Workspace};
#[ctor::ctor]
fn init_logger() {
@@ -6119,9 +6117,7 @@ async fn test_basic_following(
// When client B starts following client A, all visible view states are replicated to client B.
workspace_b
.update(cx_b, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(peer_id_a), cx)
- .unwrap()
+ workspace.toggle_follow(peer_id_a, cx).unwrap()
})
.await
.unwrap();
@@ -6160,9 +6156,7 @@ async fn test_basic_following(
// Client C also follows client A.
workspace_c
.update(cx_c, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(peer_id_a), cx)
- .unwrap()
+ workspace.toggle_follow(peer_id_a, cx).unwrap()
})
.await
.unwrap();
@@ -6197,7 +6191,7 @@ async fn test_basic_following(
// Client C unfollows client A.
workspace_c.update(cx_c, |workspace, cx| {
- workspace.toggle_follow(&ToggleFollow(peer_id_a), cx);
+ workspace.toggle_follow(peer_id_a, cx);
});
// All clients see that clients B is following client A.
@@ -6220,7 +6214,7 @@ async fn test_basic_following(
// Client C re-follows client A.
workspace_c.update(cx_c, |workspace, cx| {
- workspace.toggle_follow(&ToggleFollow(peer_id_a), cx);
+ workspace.toggle_follow(peer_id_a, cx);
});
// All clients see that clients B and C are following client A.
@@ -6244,9 +6238,7 @@ async fn test_basic_following(
// Client D follows client C.
workspace_d
.update(cx_d, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(peer_id_c), cx)
- .unwrap()
+ workspace.toggle_follow(peer_id_c, cx).unwrap()
})
.await
.unwrap();
@@ -6436,9 +6428,7 @@ async fn test_basic_following(
// Client A starts following client B.
workspace_a
.update(cx_a, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(peer_id_b), cx)
- .unwrap()
+ workspace.toggle_follow(peer_id_b, cx).unwrap()
})
.await
.unwrap();
@@ -6707,9 +6697,7 @@ async fn test_following_tab_order(
//Follow client B as client A
workspace_a
.update(cx_a, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(client_b_id), cx)
- .unwrap()
+ workspace.toggle_follow(client_b_id, cx).unwrap()
})
.await
.unwrap();
@@ -6824,9 +6812,7 @@ async fn test_peers_following_each_other(
workspace_a
.update(cx_a, |workspace, cx| {
let leader_id = *project_a.read(cx).collaborators().keys().next().unwrap();
- workspace
- .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
- .unwrap()
+ workspace.toggle_follow(leader_id, cx).unwrap()
})
.await
.unwrap();
@@ -6840,9 +6826,7 @@ async fn test_peers_following_each_other(
workspace_b
.update(cx_b, |workspace, cx| {
let leader_id = *project_b.read(cx).collaborators().keys().next().unwrap();
- workspace
- .toggle_follow(&workspace::ToggleFollow(leader_id), cx)
- .unwrap()
+ workspace.toggle_follow(leader_id, cx).unwrap()
})
.await
.unwrap();
@@ -6988,9 +6972,7 @@ async fn test_auto_unfollowing(
});
workspace_b
.update(cx_b, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(leader_id), cx)
- .unwrap()
+ workspace.toggle_follow(leader_id, cx).unwrap()
})
.await
.unwrap();
@@ -7015,9 +6997,7 @@ async fn test_auto_unfollowing(
workspace_b
.update(cx_b, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(leader_id), cx)
- .unwrap()
+ workspace.toggle_follow(leader_id, cx).unwrap()
})
.await
.unwrap();
@@ -7035,9 +7015,7 @@ async fn test_auto_unfollowing(
workspace_b
.update(cx_b, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(leader_id), cx)
- .unwrap()
+ workspace.toggle_follow(leader_id, cx).unwrap()
})
.await
.unwrap();
@@ -7057,9 +7035,7 @@ async fn test_auto_unfollowing(
workspace_b
.update(cx_b, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(leader_id), cx)
- .unwrap()
+ workspace.toggle_follow(leader_id, cx).unwrap()
})
.await
.unwrap();
@@ -7134,14 +7110,10 @@ async fn test_peers_simultaneously_following_each_other(
});
let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(client_b_id), cx)
- .unwrap()
+ workspace.toggle_follow(client_b_id, cx).unwrap()
});
let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
- workspace
- .toggle_follow(&ToggleFollow(client_a_id), cx)
- .unwrap()
+ workspace.toggle_follow(client_a_id, cx).unwrap()
});
futures::try_join!(a_follow_b, b_follow_a).unwrap();
@@ -13,7 +13,6 @@ use gpui::{
color::Color,
elements::*,
geometry::{rect::RectF, vector::vec2f, PathBuilder},
- impl_internal_actions,
json::{self, ToJson},
platform::{CursorStyle, MouseButton},
AppContext, Entity, ImageData, ModelHandle, SceneBuilder, Subscription, View, ViewContext,
@@ -23,7 +22,7 @@ use settings::Settings;
use std::{ops::Range, sync::Arc};
use theme::{AvatarStyle, Theme};
use util::ResultExt;
-use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace};
+use workspace::{FollowNextCollaborator, Workspace};
actions!(
collab,
@@ -36,17 +35,11 @@ actions!(
]
);
-impl_internal_actions!(collab, [LeaveCall]);
-
-#[derive(Copy, Clone, PartialEq)]
-pub(crate) struct LeaveCall;
-
pub fn init(cx: &mut AppContext) {
cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover);
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
cx.add_action(CollabTitlebarItem::share_project);
cx.add_action(CollabTitlebarItem::unshare_project);
- cx.add_action(CollabTitlebarItem::leave_call);
cx.add_action(CollabTitlebarItem::toggle_user_menu);
}
@@ -136,7 +129,7 @@ impl View for CollabTitlebarItem {
impl CollabTitlebarItem {
pub fn new(
workspace: &ViewHandle<Workspace>,
- user_store: &ModelHandle<UserStore>,
+ user_store: ModelHandle<UserStore>,
cx: &mut ViewContext<Self>,
) -> Self {
let active_call = ActiveCall::global(cx);
@@ -146,9 +139,9 @@ impl CollabTitlebarItem {
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 +250,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 => {
@@ -301,13 +292,19 @@ impl CollabTitlebarItem {
.with_style(item_style.container)
.into_any()
})),
- ContextMenuItem::item("Sign out", SignOut),
- ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
+ ContextMenuItem::action("Sign out", SignOut),
+ ContextMenuItem::action(
+ "Send Feedback",
+ feedback::feedback_editor::GiveFeedback,
+ ),
]
} else {
vec![
- ContextMenuItem::item("Sign in", SignIn),
- ContextMenuItem::item("Send Feedback", feedback::feedback_editor::GiveFeedback),
+ ContextMenuItem::action("Sign in", SignIn),
+ ContextMenuItem::action(
+ "Send Feedback",
+ feedback::feedback_editor::GiveFeedback,
+ ),
]
};
@@ -315,12 +312,6 @@ impl CollabTitlebarItem {
});
}
- fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
- ActiveCall::global(cx)
- .update(cx, |call, cx| call.hang_up(cx))
- .detach_and_log_err(cx);
- }
-
fn render_toggle_contacts_button(
&self,
theme: &Theme,
@@ -740,14 +731,22 @@ impl CollabTitlebarItem {
if let Some(location) = location {
if let Some(replica_id) = replica_id {
+ enum ToggleFollow {}
+
content = MouseEventHandler::<ToggleFollow, Self>::new(
replica_id.into(),
cx,
move |_, _| content,
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(ToggleFollow(peer_id))
+ .on_click(MouseButton::Left, move |_, item, cx| {
+ if let Some(workspace) = item.workspace.upgrade(cx) {
+ if let Some(task) = workspace
+ .update(cx, |workspace, cx| workspace.toggle_follow(peer_id, cx))
+ {
+ task.detach_and_log_err(cx);
+ }
+ }
})
.with_tooltip::<ToggleFollow>(
peer_id.as_u64() as usize,
@@ -762,6 +761,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,
@@ -769,11 +770,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,24 @@ 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, ToggleFollow, 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 +42,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(&ToggleFollow(follow_peer_id), cx)
- .map(|follow| follow.detach_and_log_err(cx));
- }
- }
- }
- })?;
-
- anyhow::Ok(())
- })
- .detach_and_log_err(cx);
-}
@@ -1,4 +1,3 @@
-use super::collab_titlebar_item::LeaveCall;
use crate::contacts_popover;
use call::ActiveCall;
use client::{proto::PeerId, Contact, User, UserStore};
@@ -8,10 +7,10 @@ use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
- impl_actions, impl_internal_actions,
+ impl_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,10 +18,9 @@ use serde::Deserialize;
use settings::Settings;
use std::{mem, sync::Arc};
use theme::IconButton;
-use workspace::{JoinProject, OpenSharedScreen};
+use workspace::Workspace;
impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
-impl_internal_actions!(contact_list, [ToggleExpanded, Call]);
pub fn init(cx: &mut AppContext) {
cx.add_action(ContactList::remove_contact);
@@ -31,17 +29,6 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(ContactList::select_next);
cx.add_action(ContactList::select_prev);
cx.add_action(ContactList::confirm);
- cx.add_action(ContactList::toggle_expanded);
- cx.add_action(ContactList::call);
-}
-
-#[derive(Clone, PartialEq)]
-struct ToggleExpanded(Section);
-
-#[derive(Clone, PartialEq)]
-struct Call {
- recipient_user_id: u64,
- initial_project: Option<ModelHandle<Project>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
@@ -161,6 +148,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 +157,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 +262,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 +275,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);
@@ -403,18 +389,11 @@ impl ContactList {
if let Some(entry) = self.entries.get(selection) {
match entry {
ContactEntry::Header(section) => {
- let section = *section;
- self.toggle_expanded(&ToggleExpanded(section), cx);
+ self.toggle_expanded(*section, cx);
}
ContactEntry::Contact { contact, calling } => {
if contact.online && !contact.busy && !calling {
- self.call(
- &Call {
- recipient_user_id: contact.user.id,
- initial_project: Some(self.project.clone()),
- },
- cx,
- );
+ self.call(contact.user.id, Some(self.project.clone()), cx);
}
}
ContactEntry::ParticipantProject {
@@ -422,13 +401,23 @@ 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 });
+ if let Some(workspace) = self.workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ workspace.open_shared_screen(*peer_id, cx)
+ });
+ }
}
_ => {}
}
@@ -436,8 +425,7 @@ impl ContactList {
}
}
- fn toggle_expanded(&mut self, action: &ToggleExpanded, cx: &mut ViewContext<Self>) {
- let section = action.0;
+ fn toggle_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
self.collapsed_sections.remove(ix);
} else {
@@ -798,6 +786,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 +863,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()
@@ -891,6 +882,8 @@ impl ContactList {
theme: &theme::ContactList,
cx: &mut ViewContext<Self>,
) -> AnyElement<Self> {
+ enum OpenSharedScreen {}
+
let font_cache = cx.font_cache();
let host_avatar_height = theme
.contact_avatar
@@ -971,8 +964,12 @@ impl ContactList {
},
)
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(OpenSharedScreen { peer_id });
+ .on_click(MouseButton::Left, move |_, this, cx| {
+ if let Some(workspace) = this.workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ workspace.open_shared_screen(peer_id, cx)
+ });
+ }
})
.into_any()
}
@@ -1004,7 +1001,11 @@ impl ContactList {
.contained()
.with_style(style.container)
})
- .on_click(MouseButton::Left, |_, _, cx| cx.dispatch_action(LeaveCall))
+ .on_click(MouseButton::Left, |_, _, cx| {
+ ActiveCall::global(cx)
+ .update(cx, |call, cx| call.hang_up(cx))
+ .detach_and_log_err(cx);
+ })
.aligned(),
)
} else {
@@ -1043,8 +1044,8 @@ impl ContactList {
.with_style(header_style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(ToggleExpanded(section))
+ .on_click(MouseButton::Left, move |_, this, cx| {
+ this.toggle_expanded(section, cx);
})
.into_any()
}
@@ -1142,12 +1143,9 @@ impl ContactList {
.style_for(&mut Default::default(), is_selected),
)
})
- .on_click(MouseButton::Left, move |_, _, cx| {
+ .on_click(MouseButton::Left, move |_, this, cx| {
if online && !busy {
- cx.dispatch_action(Call {
- recipient_user_id: user_id,
- initial_project: Some(initial_project.clone()),
- });
+ this.call(user_id, Some(initial_project.clone()), cx);
}
});
@@ -1269,9 +1267,12 @@ impl ContactList {
.into_any()
}
- fn call(&mut self, action: &Call, cx: &mut ViewContext<Self>) {
- let recipient_user_id = action.recipient_user_id;
- let initial_project = action.initial_project.clone();
+ fn call(
+ &mut self,
+ recipient_user_id: u64,
+ initial_project: Option<ModelHandle<Project>>,
+ cx: &mut ViewContext<Self>,
+ ) {
ActiveCall::global(cx)
.update(cx, |call, cx| {
call.invite(recipient_user_id, initial_project, cx)
@@ -2,18 +2,9 @@ use std::sync::Arc;
use crate::notifications::render_user_notification;
use client::{ContactEventKind, User, UserStore};
-use gpui::{
- elements::*, impl_internal_actions, AppContext, Entity, ModelHandle, View, ViewContext,
-};
+use gpui::{elements::*, Entity, ModelHandle, View, ViewContext};
use workspace::notifications::Notification;
-impl_internal_actions!(contact_notifications, [Dismiss, RespondToContactRequest]);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(ContactNotification::dismiss);
- cx.add_action(ContactNotification::respond_to_contact_request);
-}
-
pub struct ContactNotification {
user_store: ModelHandle<UserStore>,
user: Arc<User>,
@@ -48,20 +39,18 @@ impl View for ContactNotification {
self.user.clone(),
"wants to add you as a contact",
Some("They won't be alerted if you decline."),
- Dismiss(self.user.id),
+ |notification, cx| notification.dismiss(cx),
vec![
(
"Decline",
- Box::new(RespondToContactRequest {
- user_id: self.user.id,
- accept: false,
+ Box::new(|notification, cx| {
+ notification.respond_to_contact_request(false, cx)
}),
),
(
"Accept",
- Box::new(RespondToContactRequest {
- user_id: self.user.id,
- accept: true,
+ Box::new(|notification, cx| {
+ notification.respond_to_contact_request(true, cx)
}),
),
],
@@ -71,7 +60,7 @@ impl View for ContactNotification {
self.user.clone(),
"accepted your contact request",
None,
- Dismiss(self.user.id),
+ |notification, cx| notification.dismiss(cx),
vec![],
cx,
),
@@ -113,7 +102,7 @@ impl ContactNotification {
}
}
- fn dismiss(&mut self, _: &Dismiss, cx: &mut ViewContext<Self>) {
+ fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
self.user_store.update(cx, |store, cx| {
store
.dismiss_contact_request(self.user.id, cx)
@@ -122,14 +111,10 @@ impl ContactNotification {
cx.emit(Event::Dismiss);
}
- fn respond_to_contact_request(
- &mut self,
- action: &RespondToContactRequest,
- cx: &mut ViewContext<Self>,
- ) {
+ fn respond_to_contact_request(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
self.user_store
.update(cx, |store, cx| {
- store.respond_to_contact_request(action.user_id, action.accept, cx)
+ store.respond_to_contact_request(self.user.id, accept, cx)
})
.detach();
}
@@ -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,22 +1,20 @@
+use std::sync::{Arc, Weak};
+
use call::{ActiveCall, IncomingCall};
use client::proto;
use futures::StreamExt;
use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
- impl_internal_actions,
platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
AnyElement, AppContext, Entity, View, ViewContext,
};
use settings::Settings;
use util::ResultExt;
-use workspace::JoinProject;
-
-impl_internal_actions!(incoming_call_notification, [RespondToCall]);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(IncomingCallNotification::respond_to_call);
+use workspace::AppState;
+pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
+ 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 +46,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,28 +64,34 @@ 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>) {
+ fn respond(&mut self, accept: bool, cx: &mut ViewContext<Self>) {
let active_call = ActiveCall::global(cx);
- if action.accept {
+ if accept {
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(())
})
@@ -174,8 +178,8 @@ impl IncomingCallNotification {
.with_style(theme.accept_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, _, cx| {
- cx.dispatch_action(RespondToCall { accept: true });
+ .on_click(MouseButton::Left, |_, this, cx| {
+ this.respond(true, cx);
})
.flex(1., true),
)
@@ -188,8 +192,8 @@ impl IncomingCallNotification {
.with_style(theme.decline_button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, _, cx| {
- cx.dispatch_action(RespondToCall { accept: false });
+ .on_click(MouseButton::Left, |_, this, cx| {
+ this.respond(false, cx);
})
.flex(1., true),
)
@@ -2,7 +2,7 @@ use client::User;
use gpui::{
elements::*,
platform::{CursorStyle, MouseButton},
- Action, AnyElement, Element, View, ViewContext,
+ AnyElement, Element, View, ViewContext,
};
use settings::Settings;
use std::sync::Arc;
@@ -10,14 +10,18 @@ use std::sync::Arc;
enum Dismiss {}
enum Button {}
-pub fn render_user_notification<V: View, A: Action + Clone>(
+pub fn render_user_notification<F, V>(
user: Arc<User>,
title: &'static str,
body: Option<&'static str>,
- dismiss_action: A,
- buttons: Vec<(&'static str, Box<dyn Action>)>,
+ on_dismiss: F,
+ buttons: Vec<(&'static str, Box<dyn Fn(&mut V, &mut ViewContext<V>)>)>,
cx: &mut ViewContext<V>,
-) -> AnyElement<V> {
+) -> AnyElement<V>
+where
+ F: 'static + Fn(&mut V, &mut ViewContext<V>),
+ V: View,
+{
let theme = cx.global::<Settings>().theme.clone();
let theme = &theme.contact_notification;
@@ -64,9 +68,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
})
.with_cursor_style(CursorStyle::PointingHand)
.with_padding(Padding::uniform(5.))
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_any_action(dismiss_action.boxed_clone())
- })
+ .on_click(MouseButton::Left, move |_, view, cx| on_dismiss(view, cx))
.aligned()
.constrained()
.with_height(
@@ -90,7 +92,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
Some(
Flex::row()
.with_children(buttons.into_iter().enumerate().map(
- |(ix, (message, action))| {
+ |(ix, (message, handler))| {
MouseEventHandler::<Button, V>::new(ix, cx, |state, _| {
let button = theme.button.style_for(state, false);
Label::new(message, button.text.clone())
@@ -98,9 +100,7 @@ pub fn render_user_notification<V: View, A: Action + Clone>(
.with_style(button.container)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_any_action(action.boxed_clone())
- })
+ .on_click(MouseButton::Left, move |_, view, cx| handler(view, cx))
},
))
.aligned()
@@ -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),
)
@@ -2,7 +2,6 @@ use gpui::{
anyhow,
elements::*,
geometry::vector::Vector2F,
- impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton},
Action, AnyViewHandle, AppContext, Axis, Entity, MouseState, SizeConstraint, Subscription,
@@ -10,19 +9,13 @@ use gpui::{
};
use menu::*;
use settings::Settings;
-use std::{any::TypeId, borrow::Cow, time::Duration};
-
-#[derive(Copy, Clone, PartialEq)]
-struct Clicked;
-
-impl_internal_actions!(context_menu, [Clicked]);
+use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
pub fn init(cx: &mut AppContext) {
cx.add_action(ContextMenu::select_first);
cx.add_action(ContextMenu::select_last);
cx.add_action(ContextMenu::select_next);
cx.add_action(ContextMenu::select_prev);
- cx.add_action(ContextMenu::clicked);
cx.add_action(ContextMenu::confirm);
cx.add_action(ContextMenu::cancel);
}
@@ -37,21 +30,43 @@ pub enum ContextMenuItemLabel {
Element(ContextMenuItemBuilder),
}
-pub enum ContextMenuAction {
- ParentAction {
- action: Box<dyn Action>,
- },
- ViewAction {
- action: Box<dyn Action>,
- for_view: usize,
- },
+impl From<Cow<'static, str>> for ContextMenuItemLabel {
+ fn from(s: Cow<'static, str>) -> Self {
+ Self::String(s)
+ }
+}
+
+impl From<&'static str> for ContextMenuItemLabel {
+ fn from(s: &'static str) -> Self {
+ Self::String(s.into())
+ }
+}
+
+impl From<String> for ContextMenuItemLabel {
+ fn from(s: String) -> Self {
+ Self::String(s.into())
+ }
+}
+
+impl<T> From<T> for ContextMenuItemLabel
+where
+ T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement<ContextMenu>,
+{
+ fn from(f: T) -> Self {
+ Self::Element(Box::new(f))
+ }
}
-impl ContextMenuAction {
- fn id(&self) -> TypeId {
+pub enum ContextMenuItemAction {
+ Action(Box<dyn Action>),
+ Handler(Arc<dyn Fn(&mut ViewContext<ContextMenu>)>),
+}
+
+impl Clone for ContextMenuItemAction {
+ fn clone(&self) -> Self {
match self {
- ContextMenuAction::ParentAction { action } => action.id(),
- ContextMenuAction::ViewAction { action, .. } => action.id(),
+ Self::Action(action) => Self::Action(action.boxed_clone()),
+ Self::Handler(handler) => Self::Handler(handler.clone()),
}
}
}
@@ -59,42 +74,27 @@ impl ContextMenuAction {
pub enum ContextMenuItem {
Item {
label: ContextMenuItemLabel,
- action: ContextMenuAction,
+ action: ContextMenuItemAction,
},
Static(StaticItem),
Separator,
}
impl ContextMenuItem {
- pub fn element_item(label: ContextMenuItemBuilder, action: impl 'static + Action) -> Self {
+ pub fn action(label: impl Into<ContextMenuItemLabel>, action: impl 'static + Action) -> Self {
Self::Item {
- label: ContextMenuItemLabel::Element(label),
- action: ContextMenuAction::ParentAction {
- action: Box::new(action),
- },
+ label: label.into(),
+ action: ContextMenuItemAction::Action(Box::new(action)),
}
}
- pub fn item(label: impl Into<Cow<'static, str>>, action: impl 'static + Action) -> Self {
- Self::Item {
- label: ContextMenuItemLabel::String(label.into()),
- action: ContextMenuAction::ParentAction {
- action: Box::new(action),
- },
- }
- }
-
- pub fn item_for_view(
- label: impl Into<Cow<'static, str>>,
- view_id: usize,
- action: impl 'static + Action,
+ pub fn handler(
+ label: impl Into<ContextMenuItemLabel>,
+ handler: impl 'static + Fn(&mut ViewContext<ContextMenu>),
) -> Self {
Self::Item {
- label: ContextMenuItemLabel::String(label.into()),
- action: ContextMenuAction::ViewAction {
- action: Box::new(action),
- for_view: view_id,
- },
+ label: label.into(),
+ action: ContextMenuItemAction::Handler(Arc::new(handler)),
}
}
@@ -108,7 +108,10 @@ impl ContextMenuItem {
fn action_id(&self) -> Option<TypeId> {
match self {
- ContextMenuItem::Item { action, .. } => Some(action.id()),
+ ContextMenuItem::Item { action, .. } => match action {
+ ContextMenuItemAction::Action(action) => Some(action.id()),
+ ContextMenuItemAction::Handler(_) => None,
+ },
ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
}
}
@@ -218,22 +221,20 @@ impl ContextMenu {
}
}
- fn clicked(&mut self, _: &Clicked, _: &mut ViewContext<Self>) {
- self.clicked = true;
- }
-
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.selected_index {
if let Some(ContextMenuItem::Item { action, .. }) = self.items.get(ix) {
match action {
- ContextMenuAction::ParentAction { action } => {
- cx.dispatch_any_action(action.boxed_clone())
- }
- ContextMenuAction::ViewAction { action, for_view } => {
+ ContextMenuItemAction::Action(action) => {
let window_id = cx.window_id();
- cx.dispatch_any_action_at(window_id, *for_view, action.boxed_clone())
+ cx.dispatch_any_action_at(
+ window_id,
+ self.parent_view_id,
+ action.boxed_clone(),
+ );
}
- };
+ ContextMenuItemAction::Handler(handler) => handler(cx),
+ }
self.reset(cx);
}
}
@@ -375,22 +376,17 @@ impl ContextMenu {
&mut Default::default(),
Some(ix) == self.selected_index,
);
- let (action, view_id) = match action {
- ContextMenuAction::ParentAction { action } => {
- (action.boxed_clone(), self.parent_view_id)
- }
- ContextMenuAction::ViewAction { action, for_view } => {
- (action.boxed_clone(), *for_view)
- }
- };
- KeystrokeLabel::new(
- view_id,
- action.boxed_clone(),
- style.keystroke.container,
- style.keystroke.text.clone(),
- )
- .into_any()
+ match action {
+ ContextMenuItemAction::Action(action) => KeystrokeLabel::new(
+ self.parent_view_id,
+ action.boxed_clone(),
+ style.keystroke.container,
+ style.keystroke.text.clone(),
+ )
+ .into_any(),
+ ContextMenuItemAction::Handler(_) => Empty::new().into_any(),
+ }
}
ContextMenuItem::Static(_) => Empty::new().into_any(),
@@ -422,18 +418,23 @@ impl ContextMenu {
.with_children(self.items.iter().enumerate().map(|(ix, item)| {
match item {
ContextMenuItem::Item { label, action } => {
- let (action, view_id) = match action {
- ContextMenuAction::ParentAction { action } => {
- (action.boxed_clone(), self.parent_view_id)
- }
- ContextMenuAction::ViewAction { action, for_view } => {
- (action.boxed_clone(), *for_view)
- }
- };
-
+ let action = action.clone();
+ let view_id = self.parent_view_id;
MouseEventHandler::<MenuItem, ContextMenu>::new(ix, cx, |state, _| {
let style =
style.item.style_for(state, Some(ix) == self.selected_index);
+ let keystroke = match &action {
+ ContextMenuItemAction::Action(action) => Some(
+ KeystrokeLabel::new(
+ view_id,
+ action.boxed_clone(),
+ style.keystroke.container,
+ style.keystroke.text.clone(),
+ )
+ .flex_float(),
+ ),
+ ContextMenuItemAction::Handler(_) => None,
+ };
Flex::row()
.with_child(match label {
@@ -446,25 +447,26 @@ impl ContextMenu {
element(state, style)
}
})
- .with_child({
- KeystrokeLabel::new(
- view_id,
- action.boxed_clone(),
- style.keystroke.container,
- style.keystroke.text.clone(),
- )
- .flex_float()
- })
+ .with_children(keystroke)
.contained()
.with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_up(MouseButton::Left, |_, _, _| {}) // Capture these events
.on_down(MouseButton::Left, |_, _, _| {}) // Capture these events
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(Clicked);
+ .on_click(MouseButton::Left, move |_, menu, cx| {
+ menu.clicked = true;
let window_id = cx.window_id();
- cx.dispatch_any_action_at(window_id, view_id, action.boxed_clone());
+ match &action {
+ ContextMenuItemAction::Action(action) => {
+ cx.dispatch_any_action_at(
+ window_id,
+ view_id,
+ action.boxed_clone(),
+ );
+ }
+ ContextMenuItemAction::Handler(handler) => handler(cx),
+ }
})
.on_drag(MouseButton::Left, |_, _, _| {})
.into_any()
@@ -458,7 +458,7 @@ impl Copilot {
}
}
- fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+ pub fn sign_in(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
if let CopilotServer::Running(server) = &mut self.server {
let task = match &server.sign_in_status {
SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => {
@@ -2,7 +2,6 @@ use crate::{request::PromptUserDeviceFlow, Copilot, Status};
use gpui::{
elements::*,
geometry::rect::RectF,
- impl_internal_actions,
platform::{WindowBounds, WindowKind, WindowOptions},
AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
ViewHandle,
@@ -10,11 +9,6 @@ use gpui::{
use settings::Settings;
use theme::ui::modal;
-#[derive(PartialEq, Eq, Debug, Clone)]
-struct ClickedConnect;
-
-impl_internal_actions!(copilot_verification, [ClickedConnect]);
-
#[derive(PartialEq, Eq, Debug, Clone)]
struct CopyUserCode;
@@ -68,12 +62,6 @@ pub fn init(cx: &mut AppContext) {
}
})
.detach();
-
- cx.add_action(
- |code_verification: &mut CopilotCodeVerification, _: &ClickedConnect, _| {
- code_verification.connect_clicked = true;
- },
- );
}
}
@@ -219,9 +207,9 @@ impl CopilotCodeVerification {
cx,
{
let verification_uri = data.verification_uri.clone();
- move |_, _, cx| {
+ move |_, verification, cx| {
cx.platform().open_url(&verification_uri);
- cx.dispatch_action(ClickedConnect)
+ verification.connect_clicked = true;
}
},
))
@@ -1,145 +1,24 @@
-use std::sync::Arc;
-
use context_menu::{ContextMenu, ContextMenuItem};
+use copilot::{Copilot, Reinstall, SignOut, Status};
use editor::Editor;
use gpui::{
elements::*,
- impl_internal_actions,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Element, Entity, MouseState, Subscription, View, ViewContext,
- ViewHandle,
+ ViewHandle, WindowContext,
};
use settings::{settings_file::SettingsFile, Settings};
+use std::sync::Arc;
+use util::ResultExt;
use workspace::{
- item::ItemHandle, notifications::simple_message_notification::OsOpen, DismissToast,
- StatusItemView,
+ item::ItemHandle, notifications::simple_message_notification::OsOpen, StatusItemView, Toast,
+ Workspace,
};
-use copilot::{Copilot, Reinstall, SignIn, SignOut, Status};
-
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
const COPILOT_STARTING_TOAST_ID: usize = 1337;
const COPILOT_ERROR_TOAST_ID: usize = 1338;
-#[derive(Clone, PartialEq)]
-pub struct DeployCopilotMenu;
-
-#[derive(Clone, PartialEq)]
-pub struct DeployCopilotStartMenu;
-
-#[derive(Clone, PartialEq)]
-pub struct HideCopilot;
-
-#[derive(Clone, PartialEq)]
-pub struct InitiateSignIn;
-
-#[derive(Clone, PartialEq)]
-pub struct ToggleCopilotForLanguage {
- language: Arc<str>,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct ToggleCopilotGlobally;
-
-// TODO: Make the other code path use `get_or_insert` logic for this modal
-#[derive(Clone, PartialEq)]
-pub struct DeployCopilotModal;
-
-impl_internal_actions!(
- copilot,
- [
- DeployCopilotMenu,
- DeployCopilotStartMenu,
- HideCopilot,
- InitiateSignIn,
- DeployCopilotModal,
- ToggleCopilotForLanguage,
- ToggleCopilotGlobally,
- ]
-);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(CopilotButton::deploy_copilot_menu);
- cx.add_action(CopilotButton::deploy_copilot_start_menu);
- cx.add_action(
- |_: &mut CopilotButton, action: &ToggleCopilotForLanguage, cx| {
- let language = action.language.clone();
- let show_copilot_suggestions = cx
- .global::<Settings>()
- .show_copilot_suggestions(Some(&language));
-
- SettingsFile::update(cx, move |file_contents| {
- file_contents.languages.insert(
- language,
- settings::EditorSettings {
- show_copilot_suggestions: Some((!show_copilot_suggestions).into()),
- ..Default::default()
- },
- );
- })
- },
- );
-
- cx.add_action(|_: &mut CopilotButton, _: &ToggleCopilotGlobally, cx| {
- let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None);
- SettingsFile::update(cx, move |file_contents| {
- file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
- })
- });
-
- cx.add_action(|_: &mut CopilotButton, _: &HideCopilot, cx| {
- SettingsFile::update(cx, move |file_contents| {
- file_contents.features.copilot = Some(false)
- })
- });
-
- cx.add_action(|_: &mut CopilotButton, _: &InitiateSignIn, cx| {
- let Some(copilot) = Copilot::global(cx) else {
- return;
- };
- let status = copilot.read(cx).status();
-
- match status {
- Status::Starting { task } => {
- cx.dispatch_action(workspace::Toast::new(
- COPILOT_STARTING_TOAST_ID,
- "Copilot is starting...",
- ));
- let window_id = cx.window_id();
- let task = task.to_owned();
- cx.spawn(|handle, mut cx| async move {
- task.await;
- cx.update(|cx| {
- if let Some(copilot) = Copilot::global(cx) {
- let status = copilot.read(cx).status();
- match status {
- Status::Authorized => cx.dispatch_action_at(
- window_id,
- handle.id(),
- workspace::Toast::new(
- COPILOT_STARTING_TOAST_ID,
- "Copilot has started!",
- ),
- ),
- _ => {
- cx.dispatch_action_at(
- window_id,
- handle.id(),
- DismissToast::new(COPILOT_STARTING_TOAST_ID),
- );
- cx.dispatch_action_at(window_id, handle.id(), SignIn)
- }
- }
- }
- })
- })
- .detach();
- }
- _ => cx.dispatch_action(SignIn),
- }
- })
-}
-
pub struct CopilotButton {
popup_menu: ViewHandle<ContextMenu>,
editor_subscription: Option<(Subscription, usize)>,
@@ -217,15 +96,25 @@ impl View for CopilotButton {
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, {
let status = status.clone();
- move |_, _, cx| match status {
- Status::Authorized => cx.dispatch_action(DeployCopilotMenu),
- Status::Error(ref e) => cx.dispatch_action(workspace::Toast::new_action(
- COPILOT_ERROR_TOAST_ID,
- format!("Copilot can't be started: {}", e),
- "Reinstall Copilot",
- Reinstall,
- )),
- _ => cx.dispatch_action(DeployCopilotStartMenu),
+ move |_, this, cx| match status {
+ Status::Authorized => this.deploy_copilot_menu(cx),
+ Status::Error(ref e) => {
+ if let Some(workspace) = cx.root_view().clone().downcast::<Workspace>()
+ {
+ workspace.update(cx, |workspace, cx| {
+ workspace.show_toast(
+ Toast::new_action(
+ COPILOT_ERROR_TOAST_ID,
+ format!("Copilot can't be started: {}", e),
+ "Reinstall Copilot",
+ Reinstall,
+ ),
+ cx,
+ );
+ });
+ }
+ }
+ _ => this.deploy_copilot_start_menu(cx),
}
})
.with_tooltip::<Self>(
@@ -264,15 +153,15 @@ impl CopilotButton {
}
}
- pub fn deploy_copilot_start_menu(
- &mut self,
- _: &DeployCopilotStartMenu,
- cx: &mut ViewContext<Self>,
- ) {
+ pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext<Self>) {
let mut menu_options = Vec::with_capacity(2);
- menu_options.push(ContextMenuItem::item("Sign In", InitiateSignIn));
- menu_options.push(ContextMenuItem::item("Disable Copilot", HideCopilot));
+ menu_options.push(ContextMenuItem::handler("Sign In", |cx| {
+ initiate_sign_in(cx)
+ }));
+ menu_options.push(ContextMenuItem::handler("Disable Copilot", |cx| {
+ hide_copilot(cx)
+ }));
self.popup_menu.update(cx, |menu, cx| {
menu.show(
@@ -284,53 +173,48 @@ impl CopilotButton {
});
}
- pub fn deploy_copilot_menu(&mut self, _: &DeployCopilotMenu, cx: &mut ViewContext<Self>) {
+ pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
let settings = cx.global::<Settings>();
let mut menu_options = Vec::with_capacity(6);
- if let Some(language) = &self.language {
+ if let Some(language) = self.language.clone() {
let language_enabled = settings.show_copilot_suggestions(Some(language.as_ref()));
-
- menu_options.push(ContextMenuItem::item(
+ menu_options.push(ContextMenuItem::handler(
format!(
"{} Suggestions for {}",
if language_enabled { "Hide" } else { "Show" },
language
),
- ToggleCopilotForLanguage {
- language: language.to_owned(),
- },
+ move |cx| toggle_copilot_for_language(language.clone(), cx),
));
}
let globally_enabled = cx.global::<Settings>().show_copilot_suggestions(None);
- menu_options.push(ContextMenuItem::item(
+ menu_options.push(ContextMenuItem::handler(
if globally_enabled {
"Hide Suggestions for All Files"
} else {
"Show Suggestions for All Files"
},
- ToggleCopilotGlobally,
+ |cx| toggle_copilot_globally(cx),
));
menu_options.push(ContextMenuItem::Separator);
let icon_style = settings.theme.copilot.out_link_icon.clone();
- menu_options.push(ContextMenuItem::element_item(
- Box::new(
- move |state: &mut MouseState, style: &theme::ContextMenuItem| {
- Flex::row()
- .with_child(Label::new("Copilot Settings", style.label.clone()))
- .with_child(theme::ui::icon(icon_style.style_for(state, false)))
- .align_children_center()
- .into_any()
- },
- ),
+ menu_options.push(ContextMenuItem::action(
+ move |state: &mut MouseState, style: &theme::ContextMenuItem| {
+ Flex::row()
+ .with_child(Label::new("Copilot Settings", style.label.clone()))
+ .with_child(theme::ui::icon(icon_style.style_for(state, false)))
+ .align_children_center()
+ .into_any()
+ },
OsOpen::new(COPILOT_SETTINGS_URL),
));
- menu_options.push(ContextMenuItem::item("Sign Out", SignOut));
+ menu_options.push(ContextMenuItem::action("Sign Out", SignOut));
self.popup_menu.update(cx, |menu, cx| {
menu.show(
@@ -375,3 +259,80 @@ impl StatusItemView for CopilotButton {
cx.notify();
}
}
+
+fn toggle_copilot_globally(cx: &mut AppContext) {
+ let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None);
+ SettingsFile::update(cx, move |file_contents| {
+ file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
+ });
+}
+
+fn toggle_copilot_for_language(language: Arc<str>, cx: &mut AppContext) {
+ let show_copilot_suggestions = cx
+ .global::<Settings>()
+ .show_copilot_suggestions(Some(&language));
+
+ SettingsFile::update(cx, move |file_contents| {
+ file_contents.languages.insert(
+ language,
+ settings::EditorSettings {
+ show_copilot_suggestions: Some((!show_copilot_suggestions).into()),
+ ..Default::default()
+ },
+ );
+ })
+}
+
+fn hide_copilot(cx: &mut AppContext) {
+ SettingsFile::update(cx, move |file_contents| {
+ file_contents.features.copilot = Some(false)
+ })
+}
+
+fn initiate_sign_in(cx: &mut WindowContext) {
+ let Some(copilot) = Copilot::global(cx) else {
+ return;
+ };
+ let status = copilot.read(cx).status();
+
+ match status {
+ Status::Starting { task } => {
+ let Some(workspace) = cx.root_view().clone().downcast::<Workspace>() else {
+ return;
+ };
+
+ workspace.update(cx, |workspace, cx| {
+ workspace.show_toast(
+ Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot is starting..."),
+ cx,
+ )
+ });
+ let workspace = workspace.downgrade();
+ cx.spawn(|mut cx| async move {
+ task.await;
+ if let Some(copilot) = cx.read(Copilot::global) {
+ workspace
+ .update(&mut cx, |workspace, cx| match copilot.read(cx).status() {
+ Status::Authorized => workspace.show_toast(
+ Toast::new(COPILOT_STARTING_TOAST_ID, "Copilot has started!"),
+ cx,
+ ),
+ _ => {
+ workspace.dismiss_toast(COPILOT_STARTING_TOAST_ID, cx);
+ copilot
+ .update(cx, |copilot, cx| copilot.sign_in(cx))
+ .detach_and_log_err(cx);
+ }
+ })
+ .log_err();
+ }
+ })
+ .detach();
+ }
+ _ => {
+ copilot
+ .update(cx, |copilot, cx| copilot.sign_in(cx))
+ .detach_and_log_err(cx);
+ }
+ }
+}
@@ -10,8 +10,8 @@ use editor::{
Editor, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
};
use gpui::{
- actions, elements::*, fonts::TextStyle, impl_internal_actions, serde_json, AnyViewHandle,
- AppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ actions, elements::*, fonts::TextStyle, serde_json, AnyViewHandle, AppContext, Entity,
+ ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle,
};
use language::{
Anchor, Bias, Buffer, Diagnostic, DiagnosticEntry, DiagnosticSeverity, Point, Selection,
@@ -38,8 +38,6 @@ use workspace::{
actions!(diagnostics, [Deploy]);
-impl_internal_actions!(diagnostics, [Jump]);
-
const CONTEXT_LINE_COUNT: u32 = 1;
pub fn init(cx: &mut AppContext) {
@@ -551,6 +549,11 @@ impl Item for ProjectDiagnosticsEditor {
false
}
+ fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
+ self.editor
+ .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+ }
+
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
self.editor
.update(cx, |editor, cx| editor.navigate(data, cx))
@@ -37,7 +37,7 @@ use gpui::{
executor,
fonts::{self, HighlightStyle, TextStyle},
geometry::vector::Vector2F,
- impl_actions, impl_internal_actions,
+ impl_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton},
serde_json::{self, json},
@@ -45,7 +45,7 @@ use gpui::{
ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
-use hover_popover::{hide_hover, HideHover, HoverState};
+use hover_popover::{hide_hover, HoverState};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
pub use language::{char_kind, CharKind};
@@ -86,7 +86,7 @@ use std::{
pub use sum_tree::Bias;
use theme::{DiagnosticStyle, Theme};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
-use workspace::{ItemNavHistory, ViewId, Workspace, WorkspaceId};
+use workspace::{ItemNavHistory, ViewId, Workspace};
use crate::git::diff_hunk_to_display;
@@ -104,16 +104,6 @@ pub struct SelectNext {
pub replace_newest: bool,
}
-#[derive(Clone, PartialEq)]
-pub struct Select(pub SelectPhase);
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct Jump {
- path: ProjectPath,
- position: Point,
- anchor: language::Anchor,
-}
-
#[derive(Clone, Deserialize, PartialEq)]
pub struct SelectToBeginningOfLine {
#[serde(default)]
@@ -285,8 +275,6 @@ impl_actions!(
]
);
-impl_internal_actions!(editor, [Select, Jump]);
-
enum DocumentHighlightRead {}
enum DocumentHighlightWrite {}
enum InputComposition {}
@@ -299,7 +287,6 @@ pub enum Direction {
pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::new_file);
- cx.add_action(Editor::select);
cx.add_action(Editor::cancel);
cx.add_action(Editor::newline);
cx.add_action(Editor::newline_above);
@@ -381,7 +368,6 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::show_completions);
cx.add_action(Editor::toggle_code_actions);
cx.add_action(Editor::open_excerpts);
- cx.add_action(Editor::jump);
cx.add_action(Editor::toggle_soft_wrap);
cx.add_action(Editor::reveal_in_finder);
cx.add_action(Editor::copy_path);
@@ -400,8 +386,6 @@ pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::copilot_suggest);
hover_popover::init(cx);
- link_go_to_definition::init(cx);
- mouse_context_menu::init(cx);
scroll::actions::init(cx);
workspace::register_project_item::<Editor>(cx);
@@ -509,7 +493,7 @@ pub struct Editor {
pending_rename: Option<RenameState>,
searchable: bool,
cursor_shape: CursorShape,
- workspace_id: Option<WorkspaceId>,
+ workspace: Option<(WeakViewHandle<Workspace>, i64)>,
keymap_context_layers: BTreeMap<TypeId, KeymapContext>,
input_enabled: bool,
read_only: bool,
@@ -1282,7 +1266,7 @@ impl Editor {
searchable: true,
override_text_style: None,
cursor_shape: Default::default(),
- workspace_id: None,
+ workspace: None,
keymap_context_layers: Default::default(),
input_enabled: true,
read_only: false,
@@ -1495,7 +1479,7 @@ impl Editor {
}
}
- hide_hover(self, &HideHover, cx);
+ hide_hover(self, cx);
if old_cursor_position.to_display_point(&display_map).row()
!= new_cursor_position.to_display_point(&display_map).row()
@@ -1563,7 +1547,7 @@ impl Editor {
});
}
- fn select(&mut self, Select(phase): &Select, cx: &mut ViewContext<Self>) {
+ fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext<Self>) {
self.hide_context_menu(cx);
match phase {
@@ -1571,20 +1555,20 @@ impl Editor {
position,
add,
click_count,
- } => self.begin_selection(*position, *add, *click_count, cx),
+ } => self.begin_selection(position, add, click_count, cx),
SelectPhase::BeginColumnar {
position,
goal_column,
- } => self.begin_columnar_selection(*position, *goal_column, cx),
+ } => self.begin_columnar_selection(position, goal_column, cx),
SelectPhase::Extend {
position,
click_count,
- } => self.extend_selection(*position, *click_count, cx),
+ } => self.extend_selection(position, click_count, cx),
SelectPhase::Update {
position,
goal_column,
scroll_position,
- } => self.update_selection(*position, *goal_column, *scroll_position, cx),
+ } => self.update_selection(position, goal_column, scroll_position, cx),
SelectPhase::End => self.end_selection(cx),
}
}
@@ -1879,7 +1863,7 @@ impl Editor {
return;
}
- if hide_hover(self, &HideHover, cx) {
+ if hide_hover(self, cx) {
return;
}
@@ -6756,10 +6740,14 @@ impl Editor {
});
}
- fn jump(workspace: &mut Workspace, action: &Jump, cx: &mut ViewContext<Workspace>) {
- let editor = workspace.open_path(action.path.clone(), None, true, cx);
- let position = action.position;
- let anchor = action.anchor;
+ fn jump(
+ workspace: &mut Workspace,
+ path: ProjectPath,
+ position: Point,
+ anchor: language::Anchor,
+ cx: &mut ViewContext<Workspace>,
+ ) {
+ let editor = workspace.open_path(path, None, true, cx);
cx.spawn(|_, mut cx| async move {
let editor = editor
.await?
@@ -7025,7 +7013,7 @@ impl View for Editor {
if font_changed {
cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
- hide_hover(editor, &HideHover, cx);
+ hide_hover(editor, cx);
hide_link_definition(editor, cx);
});
}
@@ -7074,7 +7062,7 @@ impl View for Editor {
self.buffer
.update(cx, |buffer, cx| buffer.remove_active_selections(cx));
self.hide_context_menu(cx);
- hide_hover(self, &HideHover, cx);
+ hide_hover(self, cx);
cx.emit(Event::Blurred);
cx.notify();
}
@@ -24,7 +24,7 @@ use util::{
};
use workspace::{
item::{FollowableItem, Item, ItemHandle},
- NavigationEntry, Pane, ViewId,
+ NavigationEntry, ViewId,
};
#[gpui::test]
@@ -486,12 +486,15 @@ fn test_clone(cx: &mut TestAppContext) {
}
#[gpui::test]
-fn test_navigation_history(cx: &mut TestAppContext) {
+async fn test_navigation_history(cx: &mut TestAppContext) {
cx.update(|cx| cx.set_global(Settings::test(cx)));
cx.set_global(DragAndDrop::<Workspace>::default());
use workspace::item::Item;
- let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx));
+ let fs = FakeFs::new(cx.background());
+ let project = Project::test(fs, [], cx).await;
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
cx.add_view(&pane, |cx| {
let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
let mut editor = build_editor(buffer.clone(), cx);
@@ -5576,7 +5579,8 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
Settings::test_async(cx);
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
- let (_, pane) = cx.add_window(|cx| Pane::new(0, None, || &[], cx));
+ let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+ let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
let leader = pane.update(cx, |_, cx| {
let multibuffer = cx.add_model(|_| MultiBuffer::new(0));
@@ -1,20 +1,19 @@
use super::{
display_map::{BlockContext, ToDisplayPoint},
- Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Select, SelectPhase, SoftWrap,
- ToPoint, MAX_LINE_LEN,
+ Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, SelectPhase, SoftWrap, ToPoint,
+ MAX_LINE_LEN,
};
use crate::{
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
git::{diff_hunk_to_display, DisplayDiffHunk},
hover_popover::{
- HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
+ hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
+ MIN_POPOVER_LINE_HEIGHT,
},
link_go_to_definition::{
- GoToFetchedDefinition, GoToFetchedTypeDefinition, UpdateGoToDefinitionLink,
+ go_to_fetched_definition, go_to_fetched_type_definition, update_go_to_definition_link,
},
- mouse_context_menu::DeployMouseContextMenu,
- scroll::actions::Scroll,
- EditorStyle, GutterHover, UnfoldAt,
+ mouse_context_menu, EditorStyle, GutterHover, UnfoldAt,
};
use clock::ReplicaId;
use collections::{BTreeMap, HashMap};
@@ -115,9 +114,10 @@ impl EditorElement {
)
.on_down(MouseButton::Left, {
let position_map = position_map.clone();
- move |e, _, cx| {
+ move |event, editor, cx| {
if !Self::mouse_down(
- e.platform_event,
+ editor,
+ event.platform_event,
position_map.as_ref(),
text_bounds,
gutter_bounds,
@@ -129,8 +129,9 @@ impl EditorElement {
})
.on_down(MouseButton::Right, {
let position_map = position_map.clone();
- move |event, _, cx| {
+ move |event, editor, cx| {
if !Self::mouse_right_down(
+ editor,
event.position,
position_map.as_ref(),
text_bounds,
@@ -144,12 +145,12 @@ impl EditorElement {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_up(
+ editor,
event.position,
event.cmd,
event.shift,
position_map.as_ref(),
text_bounds,
- editor,
cx,
) {
cx.propagate_event()
@@ -160,10 +161,10 @@ impl EditorElement {
let position_map = position_map.clone();
move |event, editor, cx| {
if !Self::mouse_dragged(
+ editor,
event.platform_event,
position_map.as_ref(),
text_bounds,
- editor,
cx,
) {
cx.propagate_event()
@@ -172,24 +173,31 @@ impl EditorElement {
})
.on_move({
let position_map = position_map.clone();
- move |e, _, cx| {
- if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) {
+ move |event, editor, cx| {
+ if !Self::mouse_moved(
+ editor,
+ event.platform_event,
+ &position_map,
+ text_bounds,
+ cx,
+ ) {
cx.propagate_event()
}
}
})
- .on_move_out(move |_, _: &mut Editor, cx| {
+ .on_move_out(move |_, editor: &mut Editor, cx| {
if has_popovers {
- cx.dispatch_action(HideHover);
+ hide_hover(editor, cx);
}
})
.on_scroll({
let position_map = position_map.clone();
- move |e, _, cx| {
+ move |event, editor, cx| {
if !Self::scroll(
- e.position,
- *e.delta.raw(),
- e.delta.precise(),
+ editor,
+ event.position,
+ *event.delta.raw(),
+ event.delta.precise(),
&position_map,
bounds,
cx,
@@ -212,6 +220,7 @@ impl EditorElement {
}
fn mouse_down(
+ editor: &mut Editor,
MouseButtonEvent {
position,
modifiers:
@@ -239,27 +248,37 @@ impl EditorElement {
let (position, target_position) = position_map.point_for_position(text_bounds, position);
if shift && alt {
- cx.dispatch_action(Select(SelectPhase::BeginColumnar {
- position,
- goal_column: target_position.column(),
- }));
+ editor.select(
+ SelectPhase::BeginColumnar {
+ position,
+ goal_column: target_position.column(),
+ },
+ cx,
+ );
} else if shift && !ctrl && !alt && !cmd {
- cx.dispatch_action(Select(SelectPhase::Extend {
- position,
- click_count,
- }));
+ editor.select(
+ SelectPhase::Extend {
+ position,
+ click_count,
+ },
+ cx,
+ );
} else {
- cx.dispatch_action(Select(SelectPhase::Begin {
- position,
- add: alt,
- click_count,
- }));
+ editor.select(
+ SelectPhase::Begin {
+ position,
+ add: alt,
+ click_count,
+ },
+ cx,
+ );
}
true
}
fn mouse_right_down(
+ editor: &mut Editor,
position: Vector2F,
position_map: &PositionMap,
text_bounds: RectF,
@@ -270,38 +289,45 @@ impl EditorElement {
}
let (point, _) = position_map.point_for_position(text_bounds, position);
-
- cx.dispatch_action(DeployMouseContextMenu { position, point });
+ mouse_context_menu::deploy_context_menu(editor, position, point, cx);
true
}
fn mouse_up(
+ editor: &mut Editor,
position: Vector2F,
cmd: bool,
shift: bool,
position_map: &PositionMap,
text_bounds: RectF,
- editor: &mut Editor,
cx: &mut EventContext<Editor>,
) -> bool {
let end_selection = editor.has_pending_selection();
let pending_nonempty_selections = editor.has_pending_nonempty_selection();
if end_selection {
- cx.dispatch_action(Select(SelectPhase::End));
+ editor.select(SelectPhase::End, cx);
}
- if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
- let (point, target_point) = position_map.point_for_position(text_bounds, position);
+ if let Some(workspace) = editor
+ .workspace
+ .as_ref()
+ .and_then(|(workspace, _)| workspace.upgrade(cx))
+ {
+ if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) {
+ let (point, target_point) = position_map.point_for_position(text_bounds, position);
- if point == target_point {
- if shift {
- cx.dispatch_action(GoToFetchedTypeDefinition { point });
- } else {
- cx.dispatch_action(GoToFetchedDefinition { point });
- }
+ if point == target_point {
+ workspace.update(cx, |workspace, cx| {
+ if shift {
+ go_to_fetched_type_definition(workspace, point, cx);
+ } else {
+ go_to_fetched_definition(workspace, point, cx);
+ }
+ });
- return true;
+ return true;
+ }
}
}
@@ -309,6 +335,7 @@ impl EditorElement {
}
fn mouse_dragged(
+ editor: &mut Editor,
MouseMovedEvent {
modifiers: Modifiers { cmd, shift, .. },
position,
@@ -316,7 +343,6 @@ impl EditorElement {
}: MouseMovedEvent,
position_map: &PositionMap,
text_bounds: RectF,
- editor: &mut Editor,
cx: &mut EventContext<Editor>,
) -> bool {
// This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed
@@ -332,11 +358,7 @@ impl EditorElement {
None
};
- cx.dispatch_action(UpdateGoToDefinitionLink {
- point,
- cmd_held: cmd,
- shift_held: shift,
- });
+ update_go_to_definition_link(editor, point, cmd, shift, cx);
if editor.has_pending_selection() {
let mut scroll_delta = Vector2F::zero();
@@ -368,22 +390,25 @@ impl EditorElement {
let (position, target_position) =
position_map.point_for_position(text_bounds, position);
- cx.dispatch_action(Select(SelectPhase::Update {
- position,
- goal_column: target_position.column(),
- scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
- .clamp(Vector2F::zero(), position_map.scroll_max),
- }));
-
- cx.dispatch_action(HoverAt { point });
+ editor.select(
+ SelectPhase::Update {
+ position,
+ goal_column: target_position.column(),
+ scroll_position: (position_map.snapshot.scroll_position() + scroll_delta)
+ .clamp(Vector2F::zero(), position_map.scroll_max),
+ },
+ cx,
+ );
+ hover_at(editor, point, cx);
true
} else {
- cx.dispatch_action(HoverAt { point });
+ hover_at(editor, point, cx);
false
}
}
fn mouse_moved(
+ editor: &mut Editor,
MouseMovedEvent {
modifiers: Modifiers { shift, cmd, .. },
position,
@@ -397,18 +422,14 @@ impl EditorElement {
// Don't trigger hover popover if mouse is hovering over context menu
let point = position_to_display_point(position, text_bounds, position_map);
- cx.dispatch_action(UpdateGoToDefinitionLink {
- point,
- cmd_held: cmd,
- shift_held: shift,
- });
-
- cx.dispatch_action(HoverAt { point });
+ update_go_to_definition_link(editor, point, cmd, shift, cx);
+ hover_at(editor, point, cx);
true
}
fn scroll(
+ editor: &mut Editor,
position: Vector2F,
mut delta: Vector2F,
precise: bool,
@@ -436,11 +457,7 @@ impl EditorElement {
let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width;
let y = (scroll_position.y() * line_height - delta.y()) / line_height;
let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), position_map.scroll_max);
-
- cx.dispatch_action(Scroll {
- scroll_position,
- axis,
- });
+ editor.scroll(scroll_position, axis, cx);
true
}
@@ -1421,18 +1438,15 @@ impl EditorElement {
} => {
let id = *id;
let jump_icon = project::File::from_dyn(buffer.file()).map(|file| {
- let jump_position = range
+ let jump_path = ProjectPath {
+ worktree_id: file.worktree_id(cx),
+ path: file.path.clone(),
+ };
+ let jump_anchor = range
.primary
.as_ref()
.map_or(range.context.start, |primary| primary.start);
- let jump_action = crate::Jump {
- path: ProjectPath {
- worktree_id: file.worktree_id(cx),
- path: file.path.clone(),
- },
- position: language::ToPoint::to_point(&jump_position, buffer),
- anchor: jump_position,
- };
+ let jump_position = language::ToPoint::to_point(&jump_anchor, buffer);
enum JumpIcon {}
MouseEventHandler::<JumpIcon, _>::new(id.into(), cx, |state, _| {
@@ -1449,8 +1463,22 @@ impl EditorElement {
.with_height(style.button_width)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(jump_action.clone())
+ .on_click(MouseButton::Left, move |_, editor, cx| {
+ if let Some(workspace) = editor
+ .workspace
+ .as_ref()
+ .and_then(|(workspace, _)| workspace.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ Editor::jump(
+ workspace,
+ jump_path.clone(),
+ jump_position,
+ jump_anchor,
+ cx,
+ );
+ });
+ }
})
.with_tooltip::<JumpIcon>(
id.into(),
@@ -3,7 +3,6 @@ use gpui::{
actions,
elements::{Flex, MouseEventHandler, Padding, ParentElement, Text},
fonts::{HighlightStyle, Underline, Weight},
- impl_internal_actions,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, CursorRegion, Element, ModelHandle, MouseRegion, Task, ViewContext,
};
@@ -25,21 +24,10 @@ pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.;
pub const MIN_POPOVER_LINE_HEIGHT: f32 = 4.;
pub const HOVER_POPOVER_GAP: f32 = 10.;
-#[derive(Clone, PartialEq)]
-pub struct HoverAt {
- pub point: Option<DisplayPoint>,
-}
-
-#[derive(Copy, Clone, PartialEq)]
-pub struct HideHover;
-
actions!(editor, [Hover]);
-impl_internal_actions!(editor, [HoverAt, HideHover]);
pub fn init(cx: &mut AppContext) {
cx.add_action(hover);
- cx.add_action(hover_at);
- cx.add_action(hide_hover);
}
/// Bindable action which uses the most recent selection head to trigger a hover
@@ -50,12 +38,12 @@ pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) {
/// The internal hover action dispatches between `show_hover` or `hide_hover`
/// depending on whether a point to hover over is provided.
-pub fn hover_at(editor: &mut Editor, action: &HoverAt, cx: &mut ViewContext<Editor>) {
+pub fn hover_at(editor: &mut Editor, point: Option<DisplayPoint>, cx: &mut ViewContext<Editor>) {
if cx.global::<Settings>().hover_popover_enabled {
- if let Some(point) = action.point {
+ if let Some(point) = point {
show_hover(editor, point, false, cx);
} else {
- hide_hover(editor, &HideHover, cx);
+ hide_hover(editor, cx);
}
}
}
@@ -63,7 +51,7 @@ pub fn hover_at(editor: &mut Editor, action: &HoverAt, cx: &mut ViewContext<Edit
/// Hides the type information popup.
/// Triggered by the `Hover` action when the cursor is not over a symbol or when the
/// selections changed.
-pub fn hide_hover(editor: &mut Editor, _: &HideHover, cx: &mut ViewContext<Editor>) -> bool {
+pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext<Editor>) -> bool {
let did_hide = editor.hover_state.info_popover.take().is_some()
| editor.hover_state.diagnostic_popover.take().is_some();
@@ -130,7 +118,7 @@ fn show_hover(
// Hover triggered from same location as last time. Don't show again.
return;
} else {
- hide_hover(editor, &HideHover, cx);
+ hide_hover(editor, cx);
}
}
}
@@ -736,15 +724,7 @@ mod tests {
fn test() { printˇln!(); }
"});
- cx.update_editor(|editor, cx| {
- hover_at(
- editor,
- &HoverAt {
- point: Some(hover_point),
- },
- cx,
- )
- });
+ cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
assert!(!cx.editor(|editor, _| editor.hover_state.visible()));
// After delay, hover should be visible.
@@ -783,15 +763,7 @@ mod tests {
let mut request = cx
.lsp
.handle_request::<lsp::request::HoverRequest, _, _>(|_, _| async move { Ok(None) });
- cx.update_editor(|editor, cx| {
- hover_at(
- editor,
- &HoverAt {
- point: Some(hover_point),
- },
- cx,
- )
- });
+ cx.update_editor(|editor, cx| hover_at(editor, Some(hover_point), cx));
cx.foreground()
.advance_clock(Duration::from_millis(HOVER_DELAY_MILLIS + 100));
request.next().await;
@@ -794,7 +794,7 @@ impl Item for Editor {
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
let workspace_id = workspace.database_id();
let item_id = cx.view_id();
- self.workspace_id = Some(workspace_id);
+ self.workspace = Some((workspace.weak_handle(), workspace.database_id()));
fn serialize(
buffer: ModelHandle<Buffer>,
@@ -819,9 +819,9 @@ impl Item for Editor {
serialize(buffer.clone(), workspace_id, item_id, cx);
cx.subscribe(&buffer, |this, buffer, event, cx| {
- if let Some(workspace_id) = this.workspace_id {
+ if let Some((_, workspace_id)) = this.workspace.as_ref() {
if let language::Event::FileHandleChanged = event {
- serialize(buffer, workspace_id, cx.view_id(), cx);
+ serialize(buffer, *workspace_id, cx.view_id(), cx);
}
}
})
@@ -1,6 +1,6 @@
use std::ops::Range;
-use gpui::{impl_internal_actions, AppContext, Task, ViewContext};
+use gpui::{Task, ViewContext};
use language::{Bias, ToOffset};
use project::LocationLink;
use settings::Settings;
@@ -8,42 +8,9 @@ use util::TryFutureExt;
use workspace::Workspace;
use crate::{
- Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, Select,
- SelectPhase,
+ Anchor, DisplayPoint, Editor, EditorSnapshot, GoToDefinition, GoToTypeDefinition, SelectPhase,
};
-#[derive(Clone, PartialEq)]
-pub struct UpdateGoToDefinitionLink {
- pub point: Option<DisplayPoint>,
- pub cmd_held: bool,
- pub shift_held: bool,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct GoToFetchedDefinition {
- pub point: DisplayPoint,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct GoToFetchedTypeDefinition {
- pub point: DisplayPoint,
-}
-
-impl_internal_actions!(
- editor,
- [
- UpdateGoToDefinitionLink,
- GoToFetchedDefinition,
- GoToFetchedTypeDefinition
- ]
-);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(update_go_to_definition_link);
- cx.add_action(go_to_fetched_definition);
- cx.add_action(go_to_fetched_type_definition);
-}
-
#[derive(Debug, Default)]
pub struct LinkGoToDefinitionState {
pub last_mouse_location: Option<Anchor>,
@@ -55,11 +22,9 @@ pub struct LinkGoToDefinitionState {
pub fn update_go_to_definition_link(
editor: &mut Editor,
- &UpdateGoToDefinitionLink {
- point,
- cmd_held,
- shift_held,
- }: &UpdateGoToDefinitionLink,
+ point: Option<DisplayPoint>,
+ cmd_held: bool,
+ shift_held: bool,
cx: &mut ViewContext<Editor>,
) {
let pending_nonempty_selection = editor.has_pending_nonempty_selection();
@@ -286,7 +251,7 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
pub fn go_to_fetched_definition(
workspace: &mut Workspace,
- &GoToFetchedDefinition { point }: &GoToFetchedDefinition,
+ point: DisplayPoint,
cx: &mut ViewContext<Workspace>,
) {
go_to_fetched_definition_of_kind(LinkDefinitionKind::Symbol, workspace, point, cx);
@@ -294,7 +259,7 @@ pub fn go_to_fetched_definition(
pub fn go_to_fetched_type_definition(
workspace: &mut Workspace,
- &GoToFetchedTypeDefinition { point }: &GoToFetchedTypeDefinition,
+ point: DisplayPoint,
cx: &mut ViewContext<Workspace>,
) {
go_to_fetched_definition_of_kind(LinkDefinitionKind::Type, workspace, point, cx);
@@ -334,11 +299,11 @@ fn go_to_fetched_definition_of_kind(
} else {
editor_handle.update(cx, |editor, cx| {
editor.select(
- &Select(SelectPhase::Begin {
+ SelectPhase::Begin {
position: point,
add: false,
click_count: 1,
- }),
+ },
cx,
);
});
@@ -411,15 +376,7 @@ mod tests {
// Press cmd+shift to trigger highlight
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: true,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, true, cx);
});
requests.next().await;
cx.foreground().run_until_parked();
@@ -470,11 +427,7 @@ mod tests {
});
cx.update_workspace(|workspace, cx| {
- go_to_fetched_type_definition(
- workspace,
- &GoToFetchedTypeDefinition { point: hover_point },
- cx,
- );
+ go_to_fetched_type_definition(workspace, hover_point, cx);
});
requests.next().await;
cx.foreground().run_until_parked();
@@ -527,15 +480,7 @@ mod tests {
});
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, false, cx);
});
requests.next().await;
cx.foreground().run_until_parked();
@@ -569,15 +514,7 @@ mod tests {
])))
});
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, false, cx);
});
requests.next().await;
cx.foreground().run_until_parked();
@@ -599,15 +536,7 @@ mod tests {
Ok(Some(lsp::GotoDefinitionResponse::Link(vec![])))
});
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, false, cx);
});
requests.next().await;
cx.foreground().run_until_parked();
@@ -624,15 +553,7 @@ mod tests {
fn do_work() { teˇst(); }
"});
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: false,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), false, false, cx);
});
cx.foreground().run_until_parked();
@@ -691,15 +612,7 @@ mod tests {
// Moving the mouse restores the highlights.
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, false, cx);
});
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
@@ -713,15 +626,7 @@ mod tests {
fn do_work() { tesˇt(); }
"});
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, false, cx);
});
cx.foreground().run_until_parked();
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
@@ -731,7 +636,7 @@ mod tests {
// Cmd click with existing definition doesn't re-request and dismisses highlight
cx.update_workspace(|workspace, cx| {
- go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
+ go_to_fetched_definition(workspace, hover_point, cx);
});
// Assert selection moved to to definition
cx.lsp
@@ -772,7 +677,7 @@ mod tests {
])))
});
cx.update_workspace(|workspace, cx| {
- go_to_fetched_definition(workspace, &GoToFetchedDefinition { point: hover_point }, cx);
+ go_to_fetched_definition(workspace, hover_point, cx);
});
requests.next().await;
cx.foreground().run_until_parked();
@@ -817,15 +722,7 @@ mod tests {
});
});
cx.update_editor(|editor, cx| {
- update_go_to_definition_link(
- editor,
- &UpdateGoToDefinitionLink {
- point: Some(hover_point),
- cmd_held: true,
- shift_held: false,
- },
- cx,
- );
+ update_go_to_definition_link(editor, Some(hover_point), true, false, cx);
});
cx.foreground().run_until_parked();
assert!(requests.try_next().is_err());
@@ -1,29 +1,14 @@
-use context_menu::ContextMenuItem;
-use gpui::{
- elements::AnchorCorner, geometry::vector::Vector2F, impl_internal_actions, AppContext,
- ViewContext,
-};
-
use crate::{
DisplayPoint, Editor, EditorMode, FindAllReferences, GoToDefinition, GoToTypeDefinition,
Rename, RevealInFinder, SelectMode, ToggleCodeActions,
};
-
-#[derive(Clone, PartialEq)]
-pub struct DeployMouseContextMenu {
- pub position: Vector2F,
- pub point: DisplayPoint,
-}
-
-impl_internal_actions!(editor, [DeployMouseContextMenu]);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(deploy_context_menu);
-}
+use context_menu::ContextMenuItem;
+use gpui::{elements::AnchorCorner, geometry::vector::Vector2F, ViewContext};
pub fn deploy_context_menu(
editor: &mut Editor,
- &DeployMouseContextMenu { position, point }: &DeployMouseContextMenu,
+ position: Vector2F,
+ point: DisplayPoint,
cx: &mut ViewContext<Editor>,
) {
if !editor.focused {
@@ -51,18 +36,18 @@ pub fn deploy_context_menu(
position,
AnchorCorner::TopLeft,
vec![
- ContextMenuItem::item("Rename Symbol", Rename),
- ContextMenuItem::item("Go to Definition", GoToDefinition),
- ContextMenuItem::item("Go to Type Definition", GoToTypeDefinition),
- ContextMenuItem::item("Find All References", FindAllReferences),
- ContextMenuItem::item(
+ ContextMenuItem::action("Rename Symbol", Rename),
+ ContextMenuItem::action("Go to Definition", GoToDefinition),
+ ContextMenuItem::action("Go to Type Definition", GoToTypeDefinition),
+ ContextMenuItem::action("Find All References", FindAllReferences),
+ ContextMenuItem::action(
"Code Actions",
ToggleCodeActions {
deployed_from_indicator: false,
},
),
ContextMenuItem::Separator,
- ContextMenuItem::item("Reveal in Finder", RevealInFinder),
+ ContextMenuItem::action("Reveal in Finder", RevealInFinder),
],
cx,
);
@@ -98,16 +83,7 @@ mod tests {
do_wˇork();
}
"});
- cx.update_editor(|editor, cx| {
- deploy_context_menu(
- editor,
- &DeployMouseContextMenu {
- position: Default::default(),
- point,
- },
- cx,
- )
- });
+ cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
cx.assert_editor_state(indoc! {"
fn test() {
@@ -17,7 +17,7 @@ use workspace::WorkspaceId;
use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint},
- hover_popover::{hide_hover, HideHover},
+ hover_popover::hide_hover,
persistence::DB,
Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint,
};
@@ -307,14 +307,10 @@ impl Editor {
) {
let map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
- hide_hover(self, &HideHover, cx);
- self.scroll_manager.set_scroll_position(
- scroll_position,
- &map,
- local,
- self.workspace_id,
- cx,
- );
+ hide_hover(self, cx);
+ let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
+ self.scroll_manager
+ .set_scroll_position(scroll_position, &map, local, workspace_id, cx);
}
pub fn scroll_position(&self, cx: &mut ViewContext<Self>) -> Vector2F {
@@ -323,13 +319,14 @@ impl Editor {
}
pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext<Self>) {
- hide_hover(self, &HideHover, cx);
+ hide_hover(self, cx);
+ let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
let top_row = scroll_anchor
.top_anchor
.to_point(&self.buffer().read(cx).snapshot(cx))
.row;
self.scroll_manager
- .set_anchor(scroll_anchor, top_row, true, self.workspace_id, cx);
+ .set_anchor(scroll_anchor, top_row, true, workspace_id, cx);
}
pub(crate) fn set_scroll_anchor_remote(
@@ -337,13 +334,14 @@ impl Editor {
scroll_anchor: ScrollAnchor,
cx: &mut ViewContext<Self>,
) {
- hide_hover(self, &HideHover, cx);
+ hide_hover(self, cx);
+ let workspace_id = self.workspace.as_ref().map(|workspace| workspace.1);
let top_row = scroll_anchor
.top_anchor
.to_point(&self.buffer().read(cx).snapshot(cx))
.row;
self.scroll_manager
- .set_anchor(scroll_anchor, top_row, false, self.workspace_id, cx);
+ .set_anchor(scroll_anchor, top_row, false, workspace_id, cx);
}
pub fn scroll_screen(&mut self, amount: &ScrollAmount, cx: &mut ViewContext<Self>) {
@@ -1,6 +1,4 @@
-use gpui::{
- actions, geometry::vector::Vector2F, impl_internal_actions, AppContext, Axis, ViewContext,
-};
+use gpui::{actions, geometry::vector::Vector2F, AppContext, Axis, ViewContext};
use language::Bias;
use crate::{Editor, EditorMode};
@@ -23,17 +21,8 @@ actions!(
]
);
-#[derive(Clone, PartialEq)]
-pub struct Scroll {
- pub scroll_position: Vector2F,
- pub axis: Option<Axis>,
-}
-
-impl_internal_actions!(editor, [Scroll]);
-
pub fn init(cx: &mut AppContext) {
cx.add_action(Editor::next_screen);
- cx.add_action(Editor::scroll);
cx.add_action(Editor::scroll_cursor_top);
cx.add_action(Editor::scroll_cursor_center);
cx.add_action(Editor::scroll_cursor_bottom);
@@ -75,9 +64,14 @@ impl Editor {
Some(())
}
- fn scroll(&mut self, action: &Scroll, cx: &mut ViewContext<Self>) {
- self.scroll_manager.update_ongoing_scroll(action.axis);
- self.set_scroll_position(action.scroll_position, cx);
+ pub fn scroll(
+ &mut self,
+ scroll_position: Vector2F,
+ axis: Option<Axis>,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.scroll_manager.update_ongoing_scroll(axis);
+ self.set_scroll_position(scroll_position, cx);
}
fn scroll_cursor_top(editor: &mut Editor, _: &ScrollCursorTop, cx: &mut ViewContext<Editor>) {
@@ -66,24 +66,6 @@ macro_rules! impl_actions {
};
}
-/// Implement the `Action` trait for a set of existing types that are
-/// not intended to be constructed via a keymap file, but only dispatched
-/// internally.
-#[macro_export]
-macro_rules! impl_internal_actions {
- ($namespace:path, [ $($name:ident),* $(,)? ]) => {
- $(
- $crate::__impl_action! {
- $namespace,
- $name,
- fn from_json_str(_: &str) -> $crate::anyhow::Result<Box<dyn $crate::Action>> {
- Err($crate::anyhow::anyhow!("internal action"))
- }
- }
- )*
- };
-}
-
#[doc(hidden)]
#[macro_export]
macro_rules! __impl_action {
@@ -1,100 +0,0 @@
-use std::ops::{Deref, DerefMut};
-
-use collections::{HashMap, HashSet};
-
-use crate::{Action, ElementBox, Event, FontCache, MutableAppContext, TextLayoutCache};
-
-pub struct EventContext<'a> {
- rendered_views: &'a mut HashMap<usize, ElementBox>,
- pub font_cache: &'a FontCache,
- pub text_layout_cache: &'a TextLayoutCache,
- pub app: &'a mut MutableAppContext,
- pub window_id: usize,
- pub notify_count: usize,
- view_stack: Vec<usize>,
- pub(crate) handled: bool,
- pub(crate) invalidated_views: HashSet<usize>,
-}
-
-impl<'a> EventContext<'a> {
- pub(crate) fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool {
- if let Some(mut element) = self.rendered_views.remove(&view_id) {
- let result =
- self.with_current_view(view_id, |this| element.dispatch_event(event, this));
- self.rendered_views.insert(view_id, element);
- result
- } else {
- false
- }
- }
-
- pub(crate) fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
- where
- F: FnOnce(&mut Self) -> T,
- {
- self.view_stack.push(view_id);
- let result = f(self);
- self.view_stack.pop();
- result
- }
-
- pub fn window_id(&self) -> usize {
- self.window_id
- }
-
- pub fn view_id(&self) -> Option<usize> {
- self.view_stack.last().copied()
- }
-
- pub fn is_parent_view_focused(&self) -> bool {
- if let Some(parent_view_id) = self.view_stack.last() {
- self.app.focused_view_id(self.window_id) == Some(*parent_view_id)
- } else {
- false
- }
- }
-
- pub fn focus_parent_view(&mut self) {
- if let Some(parent_view_id) = self.view_stack.last() {
- self.app.focus(self.window_id, Some(*parent_view_id))
- }
- }
-
- pub fn dispatch_any_action(&mut self, action: Box<dyn Action>) {
- self.app
- .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action)
- }
-
- pub fn dispatch_action<A: Action>(&mut self, action: A) {
- self.dispatch_any_action(Box::new(action));
- }
-
- pub fn notify(&mut self) {
- self.notify_count += 1;
- if let Some(view_id) = self.view_stack.last() {
- self.invalidated_views.insert(*view_id);
- }
- }
-
- pub fn notify_count(&self) -> usize {
- self.notify_count
- }
-
- pub fn propogate_event(&mut self) {
- self.handled = false;
- }
-}
-
-impl<'a> Deref for EventContext<'a> {
- type Target = MutableAppContext;
-
- fn deref(&self) -> &Self::Target {
- self.app
- }
-}
-
-impl<'a> DerefMut for EventContext<'a> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- self.app
- }
-}
@@ -1,309 +0,0 @@
-use std::sync::Arc;
-
-use collections::{HashMap, HashSet};
-use pathfinder_geometry::vector::Vector2F;
-
-use crate::{
- scene::{
- ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent,
- MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
- },
- CursorRegion, CursorStyle, ElementBox, Event, EventContext, FontCache, MouseButton,
- MouseMovedEvent, MouseRegion, MouseRegionId, MutableAppContext, Scene, TextLayoutCache,
-};
-
-pub struct EventDispatcher {
- window_id: usize,
- font_cache: Arc<FontCache>,
-
- last_mouse_moved_event: Option<Event>,
- cursor_regions: Vec<CursorRegion>,
- mouse_regions: Vec<(MouseRegion, usize)>,
- clicked_regions: Vec<MouseRegion>,
- clicked_button: Option<MouseButton>,
- mouse_position: Vector2F,
- hovered_region_ids: HashSet<MouseRegionId>,
-}
-
-impl EventDispatcher {
- pub fn new(window_id: usize, font_cache: Arc<FontCache>) -> Self {
- Self {
- window_id,
- font_cache,
-
- last_mouse_moved_event: Default::default(),
- cursor_regions: Default::default(),
- mouse_regions: Default::default(),
- clicked_regions: Default::default(),
- clicked_button: Default::default(),
- mouse_position: Default::default(),
- hovered_region_ids: Default::default(),
- }
- }
-
- pub fn clicked_region_ids(&self) -> Option<(Vec<MouseRegionId>, MouseButton)> {
- self.clicked_button.map(|button| {
- (
- self.clicked_regions
- .iter()
- .filter_map(MouseRegion::id)
- .collect(),
- button,
- )
- })
- }
-
- pub fn hovered_region_ids(&self) -> HashSet<MouseRegionId> {
- self.hovered_region_ids.clone()
- }
-
- pub fn update_mouse_regions(&mut self, scene: &Scene) {
- self.cursor_regions = scene.cursor_regions();
- self.mouse_regions = scene.mouse_regions();
- }
-
- pub fn redispatch_mouse_moved_event<'a>(&'a mut self, cx: &mut EventContext<'a>) {
- if let Some(event) = self.last_mouse_moved_event.clone() {
- self.dispatch_event(event, true, cx);
- }
- }
-
- pub fn dispatch_event<'a>(
- &'a mut self,
- event: Event,
- event_reused: bool,
- cx: &mut EventContext<'a>,
- ) -> bool {
- let root_view_id = cx.root_view_id(self.window_id);
- if root_view_id.is_none() {
- return false;
- }
-
- let root_view_id = root_view_id.unwrap();
- //1. Allocate the correct set of GPUI events generated from the platform events
- // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
- // -> Also moves around mouse related state
- let events_to_send = self.select_region_events(&event, cx, event_reused);
-
- // For a given platform event, potentially multiple mouse region events can be created. For a given
- // region event, dispatch continues until a mouse region callback fails to propogate (handled is set to true)
- // If no region handles any of the produced platform events, we fallback to the old dispatch event style.
- let mut invalidated_views: HashSet<usize> = Default::default();
- let mut any_event_handled = false;
- for mut region_event in events_to_send {
- //2. Find mouse regions relevant to each region_event. For example, if the event is click, select
- // the clicked_regions that overlap with the mouse position
- let valid_regions = self.select_relevant_mouse_regions(®ion_event);
- let hovered_region_ids = self.hovered_region_ids.clone();
-
- //3. Dispatch region event ot each valid mouse region
- for valid_region in valid_regions.into_iter() {
- region_event.set_region(valid_region.bounds);
- if let MouseRegionEvent::Hover(e) = &mut region_event {
- e.started = valid_region
- .id()
- .map(|region_id| hovered_region_ids.contains(®ion_id))
- .unwrap_or(false)
- }
-
- if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) {
- if !event_reused {
- invalidated_views.insert(valid_region.view_id);
- }
-
- cx.handled = true;
- cx.with_current_view(valid_region.view_id, {
- let region_event = region_event.clone();
- |cx| {
- callback(region_event, cx);
- }
- });
-
- // For bubbling events, if the event was handled, don't continue dispatching
- // This only makes sense for local events.
- if cx.handled && region_event.is_local() {
- break;
- }
- }
- }
-
- // Keep track if any platform event was handled
- any_event_handled = any_event_handled && cx.handled;
- }
-
- if !any_event_handled {
- // No platform event was handled, so fall back to old mouse event dispatch style
- any_event_handled = cx.dispatch_event(root_view_id, &event);
- }
-
- // Notify any views which have been validated from event callbacks
- for view_id in invalidated_views {
- cx.notify_view(self.window_id, view_id);
- }
-
- any_event_handled
- }
-
- fn select_region_events(
- &mut self,
- event: &Event,
- cx: &mut MutableAppContext,
- event_reused: bool,
- ) -> Vec<MouseRegionEvent> {
- let mut events_to_send = Vec::new();
- match event {
- Event::MouseDown(e) => {
- //Click events are weird because they can be fired after a drag event.
- //MDN says that browsers handle this by starting from 'the most
- //specific ancestor element that contained both [positions]'
- //So we need to store the overlapping regions on mouse down.
- self.clicked_regions = self
- .mouse_regions
- .iter()
- .filter_map(|(region, _)| {
- region
- .bounds
- .contains_point(e.position)
- .then(|| region.clone())
- })
- .collect();
- self.clicked_button = Some(e.button);
-
- events_to_send.push(MouseRegionEvent::Down(DownRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- events_to_send.push(MouseRegionEvent::DownOut(DownOutRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
- Event::MouseUp(e) => {
- //NOTE: The order of event pushes is important! MouseUp events MUST be fired
- //before click events, and so the UpRegionEvent events need to be pushed before
- //ClickRegionEvents
- events_to_send.push(MouseRegionEvent::Up(UpRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
- Event::MouseMoved(
- e @ MouseMovedEvent {
- position,
- pressed_button,
- ..
- },
- ) => {
- let mut style_to_assign = CursorStyle::Arrow;
- for region in self.cursor_regions.iter().rev() {
- if region.bounds.contains_point(*position) {
- style_to_assign = region.style;
- break;
- }
- }
-
- cx.platform().set_cursor_style(style_to_assign);
-
- if !event_reused {
- if pressed_button.is_some() {
- events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent {
- region: Default::default(),
- prev_mouse_position: self.mouse_position,
- platform_event: e.clone(),
- }));
- }
- events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- }));
- }
-
- events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent {
- region: Default::default(),
- platform_event: e.clone(),
- started: false,
- }));
-
- self.last_mouse_moved_event = Some(event.clone());
- }
- _ => {}
- }
- if let Some(position) = event.position() {
- self.mouse_position = position;
- }
- events_to_send
- }
-
- fn select_relevant_mouse_regions(
- &mut self,
- region_event: &MouseRegionEvent,
- ) -> Vec<MouseRegion> {
- let mut valid_regions = Vec::new();
- //GPUI elements are arranged by depth but sibling elements can register overlapping
- //mouse regions. As such, hover events are only fired on overlapping elements which
- //are at the same depth as the deepest element which overlaps with the mouse.
- if let MouseRegionEvent::Hover(_) = *region_event {
- let mut top_most_depth = None;
- let mouse_position = self.mouse_position.clone();
- for (region, depth) in self.mouse_regions.iter().rev() {
- let contains_mouse = region.bounds.contains_point(mouse_position);
-
- if contains_mouse && top_most_depth.is_none() {
- top_most_depth = Some(depth);
- }
-
- if let Some(region_id) = region.id() {
- //This unwrap relies on short circuiting boolean expressions
- //The right side of the && is only executed when contains_mouse
- //is true, and we know above that when contains_mouse is true
- //top_most_depth is set
- if contains_mouse && depth == top_most_depth.unwrap() {
- //Ensure that hover entrance events aren't sent twice
- if self.hovered_region_ids.insert(region_id) {
- valid_regions.push(region.clone());
- }
- } else {
- //Ensure that hover exit events aren't sent twice
- if self.hovered_region_ids.remove(®ion_id) {
- valid_regions.push(region.clone());
- }
- }
- }
- }
- } else if let MouseRegionEvent::Click(e) = region_event {
- //Clear stored clicked_regions
- let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new());
- self.clicked_button = None;
-
- //Find regions which still overlap with the mouse since the last MouseDown happened
- for clicked_region in clicked_regions.into_iter().rev() {
- if clicked_region.bounds.contains_point(e.position) {
- valid_regions.push(clicked_region);
- }
- }
- } else if region_event.is_local() {
- for (mouse_region, _) in self.mouse_regions.iter().rev() {
- //Contains
- if mouse_region.bounds.contains_point(self.mouse_position) {
- valid_regions.push(mouse_region.clone());
- }
- }
- } else {
- for (mouse_region, _) in self.mouse_regions.iter().rev() {
- //NOT contains
- if !mouse_region.bounds.contains_point(self.mouse_position) {
- valid_regions.push(mouse_region.clone());
- }
- }
- }
- valid_regions
- }
-}
@@ -1,6 +1,3 @@
-#[derive(Clone, PartialEq)]
-pub struct SelectIndex(pub usize);
-
gpui::actions!(
menu,
[
@@ -12,5 +9,3 @@ gpui::actions!(
SelectLast
]
);
-
-gpui::impl_internal_actions!(menu, [SelectIndex]);
@@ -7,7 +7,7 @@ use gpui::{
AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext,
ViewHandle,
};
-use menu::{Cancel, Confirm, SelectFirst, SelectIndex, SelectLast, SelectNext, SelectPrev};
+use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
use parking_lot::Mutex;
use std::{cmp, sync::Arc};
use util::ResultExt;
@@ -104,8 +104,8 @@ impl<D: PickerDelegate> View for Picker<D> {
// Capture mouse events
.on_down(MouseButton::Left, |_, _, _| {})
.on_up(MouseButton::Left, |_, _, _| {})
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(SelectIndex(ix))
+ .on_click(MouseButton::Left, move |_, picker, cx| {
+ picker.select_index(ix, cx);
})
.with_cursor_style(CursorStyle::PointingHand)
.into_any()
@@ -151,7 +151,6 @@ impl<D: PickerDelegate> Picker<D> {
cx.add_action(Self::select_last);
cx.add_action(Self::select_next);
cx.add_action(Self::select_prev);
- cx.add_action(Self::select_index);
cx.add_action(Self::confirm);
cx.add_action(Self::cancel);
}
@@ -265,8 +264,7 @@ impl<D: PickerDelegate> Picker<D> {
cx.notify();
}
- pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext<Self>) {
- let index = action.0;
+ pub fn select_index(&mut self, index: usize, cx: &mut ViewContext<Self>) {
if self.delegate.match_count() > 0 {
self.confirmed = true;
self.delegate.set_selected_index(index, cx);
@@ -10,7 +10,6 @@ use gpui::{
ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
},
geometry::vector::Vector2F,
- impl_internal_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, PromptLevel},
AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext,
@@ -88,28 +87,6 @@ pub struct EntryDetails {
is_cut: bool,
}
-#[derive(Clone, PartialEq)]
-pub struct ToggleExpanded(pub ProjectEntryId);
-
-#[derive(Clone, PartialEq)]
-pub struct Open {
- pub entry_id: ProjectEntryId,
- pub change_focus: bool,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct MoveProjectEntry {
- pub entry_to_move: ProjectEntryId,
- pub destination: ProjectEntryId,
- pub destination_is_file: bool,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct DeployContextMenu {
- pub position: Vector2F,
- pub entry_id: ProjectEntryId,
-}
-
actions!(
project_panel,
[
@@ -128,19 +105,12 @@ actions!(
ToggleFocus
]
);
-impl_internal_actions!(
- project_panel,
- [Open, ToggleExpanded, DeployContextMenu, MoveProjectEntry]
-);
pub fn init(cx: &mut AppContext) {
- cx.add_action(ProjectPanel::deploy_context_menu);
cx.add_action(ProjectPanel::expand_selected_entry);
cx.add_action(ProjectPanel::collapse_selected_entry);
- cx.add_action(ProjectPanel::toggle_expanded);
cx.add_action(ProjectPanel::select_prev);
cx.add_action(ProjectPanel::select_next);
- cx.add_action(ProjectPanel::open_entry);
cx.add_action(ProjectPanel::new_file);
cx.add_action(ProjectPanel::new_directory);
cx.add_action(ProjectPanel::rename);
@@ -157,7 +127,6 @@ pub fn init(cx: &mut AppContext) {
this.paste(action, cx);
},
);
- cx.add_action(ProjectPanel::move_entry);
}
pub enum Event {
@@ -277,10 +246,14 @@ impl ProjectPanel {
project_panel
}
- fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
+ fn deploy_context_menu(
+ &mut self,
+ position: Vector2F,
+ entry_id: ProjectEntryId,
+ cx: &mut ViewContext<Self>,
+ ) {
let project = self.project.read(cx);
- let entry_id = action.entry_id;
let worktree_id = if let Some(id) = project.worktree_id_for_entry(entry_id, cx) {
id
} else {
@@ -296,43 +269,43 @@ impl ProjectPanel {
if let Some((worktree, entry)) = self.selected_entry(cx) {
let is_root = Some(entry) == worktree.root_entry();
if !project.is_remote() {
- menu_entries.push(ContextMenuItem::item(
+ menu_entries.push(ContextMenuItem::action(
"Add Folder to Project",
workspace::AddFolderToProject,
));
if is_root {
- menu_entries.push(ContextMenuItem::item(
- "Remove from Project",
- workspace::RemoveWorktreeFromProject(worktree_id),
- ));
+ let project = self.project.clone();
+ menu_entries.push(ContextMenuItem::handler("Remove from Project", move |cx| {
+ project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+ }));
}
}
- menu_entries.push(ContextMenuItem::item("New File", NewFile));
- menu_entries.push(ContextMenuItem::item("New Folder", NewDirectory));
+ menu_entries.push(ContextMenuItem::action("New File", NewFile));
+ menu_entries.push(ContextMenuItem::action("New Folder", NewDirectory));
menu_entries.push(ContextMenuItem::Separator);
- menu_entries.push(ContextMenuItem::item("Cut", Cut));
- menu_entries.push(ContextMenuItem::item("Copy", Copy));
+ menu_entries.push(ContextMenuItem::action("Cut", Cut));
+ menu_entries.push(ContextMenuItem::action("Copy", Copy));
menu_entries.push(ContextMenuItem::Separator);
- menu_entries.push(ContextMenuItem::item("Copy Path", CopyPath));
- menu_entries.push(ContextMenuItem::item(
+ menu_entries.push(ContextMenuItem::action("Copy Path", CopyPath));
+ menu_entries.push(ContextMenuItem::action(
"Copy Relative Path",
CopyRelativePath,
));
- menu_entries.push(ContextMenuItem::item("Reveal in Finder", RevealInFinder));
+ menu_entries.push(ContextMenuItem::action("Reveal in Finder", RevealInFinder));
if let Some(clipboard_entry) = self.clipboard_entry {
if clipboard_entry.worktree_id() == worktree.id() {
- menu_entries.push(ContextMenuItem::item("Paste", Paste));
+ menu_entries.push(ContextMenuItem::action("Paste", Paste));
}
}
menu_entries.push(ContextMenuItem::Separator);
- menu_entries.push(ContextMenuItem::item("Rename", Rename));
+ menu_entries.push(ContextMenuItem::action("Rename", Rename));
if !is_root {
- menu_entries.push(ContextMenuItem::item("Delete", Delete));
+ menu_entries.push(ContextMenuItem::action("Delete", Delete));
}
}
self.context_menu.update(cx, |menu, cx| {
- menu.show(action.position, AnchorCorner::TopLeft, menu_entries, cx);
+ menu.show(position, AnchorCorner::TopLeft, menu_entries, cx);
});
cx.notify();
@@ -391,8 +364,7 @@ impl ProjectPanel {
}
}
- fn toggle_expanded(&mut self, action: &ToggleExpanded, cx: &mut ViewContext<Self>) {
- let entry_id = action.0;
+ fn toggle_expanded(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext<Self>) {
if let Some(worktree_id) = self.project.read(cx).worktree_id_for_entry(entry_id, cx) {
if let Some(expanded_dir_ids) = self.expanded_dir_ids.get_mut(&worktree_id) {
match expanded_dir_ids.binary_search(&entry_id) {
@@ -440,13 +412,7 @@ impl ProjectPanel {
Some(task)
} else if let Some((_, entry)) = self.selected_entry(cx) {
if entry.is_file() {
- self.open_entry(
- &Open {
- entry_id: entry.id,
- change_focus: true,
- },
- cx,
- );
+ self.open_entry(entry.id, true, cx);
}
None
} else {
@@ -510,13 +476,7 @@ impl ProjectPanel {
}
this.update_visible_entries(None, cx);
if is_new_entry && !is_dir {
- this.open_entry(
- &Open {
- entry_id: new_entry.id,
- change_focus: true,
- },
- cx,
- );
+ this.open_entry(new_entry.id, true, cx);
}
cx.notify();
})?;
@@ -531,10 +491,15 @@ impl ProjectPanel {
cx.notify();
}
- fn open_entry(&mut self, action: &Open, cx: &mut ViewContext<Self>) {
+ fn open_entry(
+ &mut self,
+ entry_id: ProjectEntryId,
+ focus_opened_item: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
cx.emit(Event::OpenedEntry {
- entry_id: action.entry_id,
- focus_opened_item: action.change_focus,
+ entry_id,
+ focus_opened_item,
});
}
@@ -816,11 +781,9 @@ impl ProjectPanel {
fn move_entry(
&mut self,
- &MoveProjectEntry {
- entry_to_move,
- destination,
- destination_is_file,
- }: &MoveProjectEntry,
+ entry_to_move: ProjectEntryId,
+ destination: ProjectEntryId,
+ destination_is_file: bool,
cx: &mut ViewContext<Self>,
) {
let destination_worktree = self.project.update(cx, |project, cx| {
@@ -1196,34 +1159,29 @@ impl ProjectPanel {
cx,
)
})
- .on_click(MouseButton::Left, move |e, _, cx| {
+ .on_click(MouseButton::Left, move |event, this, cx| {
if !show_editor {
if kind == EntryKind::Dir {
- cx.dispatch_action(ToggleExpanded(entry_id))
+ this.toggle_expanded(entry_id, cx);
} else {
- cx.dispatch_action(Open {
- entry_id,
- change_focus: e.click_count > 1,
- })
+ this.open_entry(entry_id, event.click_count > 1, cx);
}
}
})
- .on_down(MouseButton::Right, move |e, _, cx| {
- cx.dispatch_action(DeployContextMenu {
- entry_id,
- position: e.position,
- })
+ .on_down(MouseButton::Right, move |event, this, cx| {
+ this.deploy_context_menu(event.position, entry_id, cx);
})
- .on_up(MouseButton::Left, move |_, _, cx| {
+ .on_up(MouseButton::Left, move |_, this, cx| {
if let Some((_, dragged_entry)) = cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<ProjectEntryId>(cx.window_id())
{
- cx.dispatch_action(MoveProjectEntry {
- entry_to_move: *dragged_entry,
- destination: entry_id,
- destination_is_file: matches!(details.kind, EntryKind::File(_)),
- });
+ this.move_entry(
+ *dragged_entry,
+ entry_id,
+ matches!(details.kind, EntryKind::File(_)),
+ cx,
+ );
}
})
.on_move(move |_, this, cx| {
@@ -1307,14 +1265,11 @@ impl View for ProjectPanel {
.with_style(container_style)
.expanded()
})
- .on_down(MouseButton::Right, move |e, _, cx| {
+ .on_down(MouseButton::Right, move |event, this, cx| {
// When deploying the context menu anywhere below the last project entry,
// act as if the user clicked the root of the last worktree.
if let Some(entry_id) = last_worktree_root_id {
- cx.dispatch_action(DeployContextMenu {
- entry_id,
- position: e.position,
- })
+ this.deploy_context_menu(event.position, entry_id, cx);
}
}),
)
@@ -1895,7 +1850,7 @@ mod tests {
let worktree = worktree.read(cx);
if let Ok(relative_path) = path.strip_prefix(worktree.root_name()) {
let entry_id = worktree.entry_for_path(relative_path).unwrap().id;
- panel.toggle_expanded(&ToggleExpanded(entry_id), cx);
+ panel.toggle_expanded(entry_id, cx);
return;
}
}
@@ -5,30 +5,30 @@ use gpui::{
actions,
anyhow::Result,
elements::{Flex, ParentElement},
- AnyElement, AppContext, Element, Task, ViewContext,
+ AnyElement, AppContext, Element, Task, ViewContext, WeakViewHandle,
};
use highlighted_workspace_location::HighlightedWorkspaceLocation;
use ordered_float::OrderedFloat;
use picker::{Picker, PickerDelegate, PickerEvent};
use settings::Settings;
-use std::sync::Arc;
+use std::sync::{Arc, Weak};
use workspace::{
- notifications::simple_message_notification::MessageNotification, OpenPaths, Workspace,
+ notifications::simple_message_notification::MessageNotification, AppState, Workspace,
WorkspaceLocation, WORKSPACE_DB,
};
actions!(projects, [OpenRecent]);
-pub fn init(cx: &mut AppContext) {
- cx.add_async_action(toggle);
+pub fn init(cx: &mut AppContext, app_state: Weak<AppState>) {
+ cx.add_async_action(
+ move |_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext<Workspace>| {
+ toggle(app_state.clone(), cx)
+ },
+ );
RecentProjects::init(cx);
}
-fn toggle(
- _: &mut Workspace,
- _: &OpenRecent,
- cx: &mut ViewContext<Workspace>,
-) -> Option<Task<Result<()>>> {
+fn toggle(app_state: Weak<AppState>, cx: &mut ViewContext<Workspace>) -> Option<Task<Result<()>>> {
Some(cx.spawn(|workspace, mut cx| async move {
let workspace_locations: Vec<_> = cx
.background()
@@ -46,9 +46,17 @@ fn toggle(
workspace.update(&mut cx, |workspace, cx| {
if !workspace_locations.is_empty() {
workspace.toggle_modal(cx, |_, cx| {
+ let workspace = cx.weak_handle();
cx.add_view(|cx| {
- RecentProjects::new(RecentProjectsDelegate::new(workspace_locations), cx)
- .with_max_size(800., 1200.)
+ RecentProjects::new(
+ RecentProjectsDelegate::new(
+ workspace,
+ workspace_locations,
+ app_state.clone(),
+ ),
+ cx,
+ )
+ .with_max_size(800., 1200.)
})
});
} else {
@@ -64,15 +72,23 @@ fn toggle(
type RecentProjects = Picker<RecentProjectsDelegate>;
struct RecentProjectsDelegate {
+ workspace: WeakViewHandle<Workspace>,
workspace_locations: Vec<WorkspaceLocation>,
+ app_state: Weak<AppState>,
selected_match_index: usize,
matches: Vec<StringMatch>,
}
impl RecentProjectsDelegate {
- fn new(workspace_locations: Vec<WorkspaceLocation>) -> Self {
+ fn new(
+ workspace: WeakViewHandle<Workspace>,
+ workspace_locations: Vec<WorkspaceLocation>,
+ app_state: Weak<AppState>,
+ ) -> Self {
Self {
+ workspace,
workspace_locations,
+ app_state,
selected_match_index: 0,
matches: Default::default(),
}
@@ -139,11 +155,22 @@ impl PickerDelegate for RecentProjectsDelegate {
}
fn confirm(&mut self, cx: &mut ViewContext<RecentProjects>) {
- if let Some(selected_match) = &self.matches.get(self.selected_index()) {
+ if let Some(((selected_match, workspace), app_state)) = self
+ .matches
+ .get(self.selected_index())
+ .zip(self.workspace.upgrade(cx))
+ .zip(self.app_state.upgrade())
+ {
let workspace_location = &self.workspace_locations[selected_match.candidate_id];
- cx.dispatch_action(OpenPaths {
- paths: workspace_location.paths().as_ref().clone(),
- });
+ workspace
+ .update(cx, |workspace, cx| {
+ workspace.open_workspace_for_paths(
+ workspace_location.paths().as_ref().clone(),
+ app_state,
+ cx,
+ )
+ })
+ .detach_and_log_err(cx);
cx.emit(PickerEvent::Dismiss);
}
}
@@ -332,6 +332,11 @@ impl Item for ProjectSearchView {
Some(Self::new(model, cx))
}
+ fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
+ self.results_editor
+ .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx));
+ }
+
fn set_nav_history(&mut self, nav_history: ItemNavHistory, cx: &mut ViewContext<Self>) {
self.results_editor.update(cx, |editor, _| {
editor.set_nav_history(Some(nav_history));
@@ -1,33 +1,14 @@
+use crate::TerminalView;
use context_menu::{ContextMenu, ContextMenuItem};
use gpui::{
elements::*,
- impl_internal_actions,
platform::{CursorStyle, MouseButton},
- AnyElement, AppContext, Element, Entity, View, ViewContext, ViewHandle, WeakModelHandle,
- WeakViewHandle,
+ AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
};
use settings::Settings;
use std::any::TypeId;
-use terminal::Terminal;
use workspace::{dock::FocusDock, item::ItemHandle, NewTerminal, StatusItemView, Workspace};
-use crate::TerminalView;
-
-#[derive(Clone, PartialEq)]
-pub struct DeployTerminalMenu;
-
-#[derive(Clone, PartialEq)]
-pub struct FocusTerminal {
- terminal_handle: WeakModelHandle<Terminal>,
-}
-
-impl_internal_actions!(terminal, [FocusTerminal, DeployTerminalMenu]);
-
-pub fn init(cx: &mut AppContext) {
- cx.add_action(TerminalButton::deploy_terminal_menu);
- cx.add_action(TerminalButton::focus_terminal);
-}
-
pub struct TerminalButton {
workspace: WeakViewHandle<Workspace>,
popup_menu: ViewHandle<ContextMenu>,
@@ -94,9 +75,9 @@ impl View for TerminalButton {
}
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
+ .on_click(MouseButton::Left, move |_, this, cx| {
if has_terminals {
- cx.dispatch_action(DeployTerminalMenu);
+ this.deploy_terminal_menu(cx);
} else {
if !active {
cx.dispatch_action(FocusDock);
@@ -129,12 +110,8 @@ impl TerminalButton {
}
}
- pub fn deploy_terminal_menu(
- &mut self,
- _action: &DeployTerminalMenu,
- cx: &mut ViewContext<Self>,
- ) {
- let mut menu_options = vec![ContextMenuItem::item("New Terminal", NewTerminal)];
+ pub fn deploy_terminal_menu(&mut self, cx: &mut ViewContext<Self>) {
+ let mut menu_options = vec![ContextMenuItem::action("New Terminal", NewTerminal)];
if let Some(workspace) = self.workspace.upgrade(cx) {
let project = workspace.read(cx).project().read(cx);
@@ -146,10 +123,24 @@ impl TerminalButton {
for local_terminal_handle in local_terminal_handles {
if let Some(terminal) = local_terminal_handle.upgrade(cx) {
- menu_options.push(ContextMenuItem::item(
+ let workspace = self.workspace.clone();
+ let local_terminal_handle = local_terminal_handle.clone();
+ menu_options.push(ContextMenuItem::handler(
terminal.read(cx).title(),
- FocusTerminal {
- terminal_handle: local_terminal_handle.clone(),
+ move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ let terminal = workspace
+ .items_of_type::<TerminalView>(cx)
+ .find(|terminal| {
+ terminal.read(cx).model().downgrade()
+ == local_terminal_handle
+ });
+ if let Some(terminal) = terminal {
+ workspace.activate_item(&terminal, cx);
+ }
+ });
+ }
},
))
}
@@ -165,21 +156,6 @@ impl TerminalButton {
);
});
}
-
- pub fn focus_terminal(&mut self, action: &FocusTerminal, cx: &mut ViewContext<Self>) {
- if let Some(workspace) = self.workspace.upgrade(cx) {
- workspace.update(cx, |workspace, cx| {
- let terminal = workspace
- .items_of_type::<TerminalView>(cx)
- .find(|terminal| {
- terminal.read(cx).model().downgrade() == action.terminal_handle
- });
- if let Some(terminal) = terminal {
- workspace.activate_item(&terminal, cx);
- }
- });
- }
- }
}
impl StatusItemView for TerminalButton {
@@ -33,7 +33,7 @@ use util::ResultExt;
use std::{fmt::Debug, ops::RangeInclusive};
use std::{mem, ops::Range};
-use crate::{DeployContextMenu, TerminalView};
+use crate::TerminalView;
///The information generated during layout that is nescessary for painting
pub struct LayoutState {
@@ -429,19 +429,20 @@ impl TerminalElement {
),
)
// Context menu
- .on_click(MouseButton::Right, move |e, _: &mut TerminalView, cx| {
- let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
- conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(e.shift))
- } else {
- // If we can't get the model handle, probably can't deploy the context menu
- true
- };
- if !mouse_mode {
- cx.dispatch_action(DeployContextMenu {
- position: e.position,
- });
- }
- })
+ .on_click(
+ MouseButton::Right,
+ move |event, view: &mut TerminalView, cx| {
+ let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx) {
+ conn_handle.update(cx, |terminal, _cx| terminal.mouse_mode(event.shift))
+ } else {
+ // If we can't get the model handle, probably can't deploy the context menu
+ true
+ };
+ if !mouse_mode {
+ view.deploy_context_menu(event.position, cx);
+ }
+ },
+ )
.on_move(move |event, _: &mut TerminalView, cx| {
if cx.is_parent_view_focused() {
if let Some(conn_handle) = connection.upgrade(cx) {
@@ -9,7 +9,7 @@ use gpui::{
actions,
elements::{AnchorCorner, ChildView, Flex, Label, ParentElement, Stack},
geometry::vector::Vector2F,
- impl_actions, impl_internal_actions,
+ impl_actions,
keymap_matcher::{KeymapContext, Keystroke},
platform::KeyDownEvent,
AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Task, View, ViewContext,
@@ -50,11 +50,6 @@ const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
#[derive(Clone, Debug, PartialEq)]
pub struct ScrollTerminal(pub i32);
-#[derive(Clone, PartialEq)]
-pub struct DeployContextMenu {
- pub position: Vector2F,
-}
-
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct SendText(String);
@@ -68,8 +63,6 @@ actions!(
impl_actions!(terminal, [SendText, SendKeystroke]);
-impl_internal_actions!(project_panel, [DeployContextMenu]);
-
pub fn init(cx: &mut AppContext) {
cx.add_action(TerminalView::deploy);
@@ -78,7 +71,6 @@ pub fn init(cx: &mut AppContext) {
//Useful terminal views
cx.add_action(TerminalView::send_text);
cx.add_action(TerminalView::send_keystroke);
- cx.add_action(TerminalView::deploy_context_menu);
cx.add_action(TerminalView::copy);
cx.add_action(TerminalView::paste);
cx.add_action(TerminalView::clear);
@@ -197,14 +189,14 @@ impl TerminalView {
cx.emit(Event::Wakeup);
}
- pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
+ pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext<Self>) {
let menu_entries = vec![
- ContextMenuItem::item("Clear", Clear),
- ContextMenuItem::item("Close", pane::CloseActiveItem),
+ ContextMenuItem::action("Clear", Clear),
+ ContextMenuItem::action("Close", pane::CloseActiveItem),
];
self.context_menu.update(cx, |menu, cx| {
- menu.show(action.position, AnchorCorner::TopLeft, menu_entries, cx)
+ menu.show(position, AnchorCorner::TopLeft, menu_entries, cx)
});
cx.notify();
@@ -139,27 +139,11 @@ pub fn keystroke_label<V: View>(
) -> Container<V> {
// FIXME: Put the theme in it's own global so we can
// query the keystroke style on our own
- keystroke_label_for(
- cx.handle().id(),
- label_text,
- label_style,
- keystroke_style,
- action,
- )
-}
-
-pub fn keystroke_label_for<V: View>(
- view_id: usize,
- label_text: &'static str,
- label_style: &ContainedText,
- keystroke_style: &ContainedText,
- action: Box<dyn Action>,
-) -> Container<V> {
Flex::row()
.with_child(Label::new(label_text, label_style.text.clone()).contained())
.with_child(
KeystrokeLabel::new(
- view_id,
+ cx.view_id(),
action,
keystroke_style.container,
keystroke_style.text.clone(),
@@ -1,13 +1,10 @@
mod toggle_dock_button;
-use serde::Deserialize;
-
use collections::HashMap;
use gpui::{
actions,
elements::{ChildView, Empty, MouseEventHandler, ParentElement, Side, Stack},
geometry::vector::Vector2F,
- impl_internal_actions,
platform::{CursorStyle, MouseButton},
AnyElement, AppContext, Border, Element, SizeConstraint, ViewContext, ViewHandle,
};
@@ -17,12 +14,6 @@ use theme::Theme;
use crate::{sidebar::SidebarSide, BackgroundActions, ItemHandle, Pane, Workspace};
pub use toggle_dock_button::ToggleDockButton;
-#[derive(PartialEq, Clone, Deserialize)]
-pub struct MoveDock(pub DockAnchor);
-
-#[derive(PartialEq, Clone)]
-pub struct AddDefaultItemToDock;
-
actions!(
dock,
[
@@ -35,16 +26,10 @@ actions!(
RemoveTabFromDock,
]
);
-impl_internal_actions!(dock, [MoveDock, AddDefaultItemToDock]);
pub fn init(cx: &mut AppContext) {
cx.add_action(Dock::focus_dock);
cx.add_action(Dock::hide_dock);
- cx.add_action(
- |workspace: &mut Workspace, &MoveDock(dock_anchor), cx: &mut ViewContext<Workspace>| {
- Dock::move_dock(workspace, dock_anchor, true, cx);
- },
- );
cx.add_action(
|workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext<Workspace>| {
Dock::move_dock(workspace, DockAnchor::Right, true, cx);
@@ -182,21 +167,14 @@ pub struct Dock {
impl Dock {
pub fn new(
- workspace_id: usize,
default_item_factory: DockDefaultItemFactory,
background_actions: BackgroundActions,
cx: &mut ViewContext<Workspace>,
) -> Self {
let position = DockPosition::Hidden(cx.global::<Settings>().default_dock_anchor);
-
- let pane = cx.add_view(|cx| {
- Pane::new(
- workspace_id,
- Some(position.anchor()),
- background_actions,
- cx,
- )
- });
+ let workspace = cx.weak_handle();
+ let pane =
+ cx.add_view(|cx| Pane::new(workspace, Some(position.anchor()), background_actions, cx));
pane.update(cx, |pane, cx| {
pane.set_active(false, cx);
});
@@ -426,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::{
@@ -441,7 +421,7 @@ mod tests {
},
register_deserializable_item,
sidebar::Sidebar,
- ItemHandle, Workspace,
+ AppState, ItemHandle, Workspace,
};
pub fn default_item_factory(
@@ -489,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,
)
});
@@ -582,7 +571,7 @@ mod tests {
#[gpui::test]
async fn test_toggle_dock_focus(cx: &mut TestAppContext) {
- let cx = DockTestContext::new(cx).await;
+ let mut cx = DockTestContext::new(cx).await;
cx.move_dock(DockAnchor::Right);
cx.assert_dock_pane_active();
@@ -620,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,
)
});
@@ -728,8 +726,8 @@ mod tests {
})
}
- pub fn move_dock(&self, anchor: DockAnchor) {
- self.cx.dispatch_action(self.window_id, MoveDock(anchor));
+ pub fn move_dock(&mut self, anchor: DockAnchor) {
+ self.update_workspace(|workspace, cx| Dock::move_dock(workspace, anchor, true, cx));
}
pub fn hide_dock(&self) {
@@ -67,9 +67,17 @@ impl View for ToggleDockButton {
}
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_up(MouseButton::Left, move |event, _, cx| {
+ .on_up(MouseButton::Left, move |event, this, cx| {
let drop_index = dock_pane.read(cx).items_len() + 1;
- handle_dropped_item(event, &dock_pane.downgrade(), drop_index, false, None, cx);
+ handle_dropped_item(
+ event,
+ this.workspace.clone(),
+ &dock_pane.downgrade(),
+ drop_index,
+ false,
+ None,
+ cx,
+ );
});
if dock_position.is_visible() {
@@ -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,9 +1,7 @@
-use std::{any::TypeId, ops::DerefMut};
-
+use crate::{Toast, Workspace};
use collections::HashSet;
use gpui::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle};
-
-use crate::Workspace;
+use std::{any::TypeId, ops::DerefMut};
pub fn init(cx: &mut AppContext) {
cx.set_global(NotificationTracker::new());
@@ -113,6 +111,28 @@ impl Workspace {
self.dismiss_notification_internal(type_id, id, cx)
}
+ pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
+ self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
+ self.show_notification(toast.id, cx, |cx| {
+ cx.add_view(|_cx| match &toast.click {
+ Some((click_msg, action)) => {
+ simple_message_notification::MessageNotification::new_boxed_action(
+ toast.msg.clone(),
+ action.boxed_clone(),
+ click_msg.clone(),
+ )
+ }
+ None => {
+ simple_message_notification::MessageNotification::new_message(toast.msg.clone())
+ }
+ })
+ })
+ }
+
+ pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
+ self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
+ }
+
fn dismiss_notification_internal(
&mut self,
type_id: TypeId,
@@ -20,11 +20,12 @@ use gpui::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- impl_actions, impl_internal_actions,
+ impl_actions,
keymap_matcher::KeymapContext,
platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
- Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, ModelHandle,
- MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+ Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
+ ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+ WindowContext,
};
use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
@@ -74,14 +75,6 @@ actions!(
]
);
-#[derive(Clone, PartialEq)]
-pub struct MoveItem {
- pub item_id: usize,
- pub from: WeakViewHandle<Pane>,
- pub to: WeakViewHandle<Pane>,
- pub destination_index: usize,
-}
-
#[derive(Clone, Deserialize, PartialEq)]
pub struct GoBack {
#[serde(skip_deserializing)]
@@ -94,36 +87,7 @@ pub struct GoForward {
pub pane: Option<WeakViewHandle<Pane>>,
}
-#[derive(Clone, PartialEq)]
-pub struct DeploySplitMenu;
-
-#[derive(Clone, PartialEq)]
-pub struct DeployDockMenu;
-
-#[derive(Clone, PartialEq)]
-pub struct DeployNewMenu;
-
-#[derive(Clone, PartialEq)]
-pub struct DeployTabContextMenu {
- pub position: Vector2F,
- pub item_id: usize,
- pub pane: WeakViewHandle<Pane>,
-}
-
impl_actions!(pane, [GoBack, GoForward, ActivateItem]);
-impl_internal_actions!(
- pane,
- [
- CloseItemById,
- CloseItemsToTheLeftById,
- CloseItemsToTheRightById,
- DeployTabContextMenu,
- DeploySplitMenu,
- DeployNewMenu,
- DeployDockMenu,
- MoveItem
- ]
-);
const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
@@ -148,68 +112,10 @@ pub fn init(cx: &mut AppContext) {
cx.add_async_action(Pane::close_items_to_the_left);
cx.add_async_action(Pane::close_items_to_the_right);
cx.add_async_action(Pane::close_all_items);
- cx.add_async_action(|workspace: &mut Workspace, action: &CloseItemById, cx| {
- let pane = action.pane.upgrade(cx)?;
- let task = Pane::close_item_by_id(workspace, pane, action.item_id, cx);
- Some(cx.foreground().spawn(async move {
- task.await?;
- Ok(())
- }))
- });
- cx.add_async_action(
- |workspace: &mut Workspace, action: &CloseItemsToTheLeftById, cx| {
- let pane = action.pane.upgrade(cx)?;
- let task = Pane::close_items_to_the_left_by_id(workspace, pane, action.item_id, cx);
- Some(cx.foreground().spawn(async move {
- task.await?;
- Ok(())
- }))
- },
- );
- cx.add_async_action(
- |workspace: &mut Workspace, action: &CloseItemsToTheRightById, cx| {
- let pane = action.pane.upgrade(cx)?;
- let task = Pane::close_items_to_the_right_by_id(workspace, pane, action.item_id, cx);
- Some(cx.foreground().spawn(async move {
- task.await?;
- Ok(())
- }))
- },
- );
- cx.add_action(
- |workspace,
- MoveItem {
- from,
- to,
- item_id,
- destination_index,
- },
- cx| {
- // Get item handle to move
- let from = if let Some(from) = from.upgrade(cx) {
- from
- } else {
- return;
- };
-
- // Add item to new pane at given index
- let to = if let Some(to) = to.upgrade(cx) {
- to
- } else {
- return;
- };
-
- Pane::move_item(workspace, from, to, *item_id, *destination_index, cx)
- },
- );
cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
- cx.add_action(Pane::deploy_split_menu);
- cx.add_action(Pane::deploy_dock_menu);
- cx.add_action(Pane::deploy_new_menu);
- cx.add_action(Pane::deploy_tab_context_menu);
cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
Pane::reopen_closed_item(workspace, cx).detach();
});
@@ -243,7 +149,7 @@ pub struct Pane {
tab_context_menu: ViewHandle<ContextMenu>,
docked: Option<DockAnchor>,
_background_actions: BackgroundActions,
- _workspace_id: usize,
+ workspace: WeakViewHandle<Workspace>,
}
pub struct ItemNavHistory {
@@ -315,7 +221,7 @@ impl TabBarContextMenu {
impl Pane {
pub fn new(
- workspace_id: usize,
+ workspace: WeakViewHandle<Workspace>,
docked: Option<DockAnchor>,
background_actions: BackgroundActions,
cx: &mut ViewContext<Self>,
@@ -349,7 +255,7 @@ impl Pane {
tab_context_menu: cx.add_view(ContextMenu::new),
docked,
_background_actions: background_actions,
- _workspace_id: workspace_id,
+ workspace,
}
}
@@ -1223,16 +1129,16 @@ impl Pane {
cx.emit(Event::Split(direction));
}
- fn deploy_split_menu(&mut self, _: &DeploySplitMenu, cx: &mut ViewContext<Self>) {
+ fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
menu.show(
Default::default(),
AnchorCorner::TopRight,
vec![
- ContextMenuItem::item("Split Right", SplitRight),
- ContextMenuItem::item("Split Left", SplitLeft),
- ContextMenuItem::item("Split Up", SplitUp),
- ContextMenuItem::item("Split Down", SplitDown),
+ ContextMenuItem::action("Split Right", SplitRight),
+ ContextMenuItem::action("Split Left", SplitLeft),
+ ContextMenuItem::action("Split Up", SplitUp),
+ ContextMenuItem::action("Split Down", SplitDown),
],
cx,
);
@@ -1241,15 +1147,15 @@ impl Pane {
self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
}
- fn deploy_dock_menu(&mut self, _: &DeployDockMenu, cx: &mut ViewContext<Self>) {
+ fn deploy_dock_menu(&mut self, cx: &mut ViewContext<Self>) {
self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
menu.show(
Default::default(),
AnchorCorner::TopRight,
vec![
- ContextMenuItem::item("Anchor Dock Right", AnchorDockRight),
- ContextMenuItem::item("Anchor Dock Bottom", AnchorDockBottom),
- ContextMenuItem::item("Expand Dock", ExpandDock),
+ ContextMenuItem::action("Anchor Dock Right", AnchorDockRight),
+ ContextMenuItem::action("Anchor Dock Bottom", AnchorDockBottom),
+ ContextMenuItem::action("Expand Dock", ExpandDock),
],
cx,
);
@@ -1258,15 +1164,15 @@ impl Pane {
self.tab_bar_context_menu.kind = TabBarContextMenuKind::Dock;
}
- fn deploy_new_menu(&mut self, _: &DeployNewMenu, cx: &mut ViewContext<Self>) {
+ fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
menu.show(
Default::default(),
AnchorCorner::TopRight,
vec![
- ContextMenuItem::item("New File", NewFile),
- ContextMenuItem::item("New Terminal", NewTerminal),
- ContextMenuItem::item("New Search", NewSearch),
+ ContextMenuItem::action("New File", NewFile),
+ ContextMenuItem::action("New Terminal", NewTerminal),
+ ContextMenuItem::action("New Search", NewSearch),
],
cx,
);
@@ -1277,56 +1183,87 @@ impl Pane {
fn deploy_tab_context_menu(
&mut self,
- action: &DeployTabContextMenu,
+ position: Vector2F,
+ target_item_id: usize,
cx: &mut ViewContext<Self>,
) {
- let target_item_id = action.item_id;
- let target_pane = action.pane.clone();
let active_item_id = self.items[self.active_item_index].id();
let is_active_item = target_item_id == active_item_id;
+ let target_pane = cx.weak_handle();
// The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on. Currenlty, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
self.tab_context_menu.update(cx, |menu, cx| {
menu.show(
- action.position,
+ position,
AnchorCorner::TopLeft,
if is_active_item {
vec![
- ContextMenuItem::item("Close Active Item", CloseActiveItem),
- ContextMenuItem::item("Close Inactive Items", CloseInactiveItems),
- ContextMenuItem::item("Close Clean Items", CloseCleanItems),
- ContextMenuItem::item("Close Items To The Left", CloseItemsToTheLeft),
- ContextMenuItem::item("Close Items To The Right", CloseItemsToTheRight),
- ContextMenuItem::item("Close All Items", CloseAllItems),
+ ContextMenuItem::action("Close Active Item", CloseActiveItem),
+ ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+ ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+ ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
+ ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
+ ContextMenuItem::action("Close All Items", CloseAllItems),
]
} else {
// In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
vec![
- ContextMenuItem::item(
- "Close Inactive Item",
- CloseItemById {
- item_id: target_item_id,
- pane: target_pane.clone(),
- },
- ),
- ContextMenuItem::item("Close Inactive Items", CloseInactiveItems),
- ContextMenuItem::item("Close Clean Items", CloseCleanItems),
- ContextMenuItem::item(
- "Close Items To The Left",
- CloseItemsToTheLeftById {
- item_id: target_item_id,
- pane: target_pane.clone(),
- },
- ),
- ContextMenuItem::item(
- "Close Items To The Right",
- CloseItemsToTheRightById {
- item_id: target_item_id,
- pane: target_pane.clone(),
- },
- ),
- ContextMenuItem::item("Close All Items", CloseAllItems),
+ ContextMenuItem::handler("Close Inactive Item", {
+ let workspace = self.workspace.clone();
+ let pane = target_pane.clone();
+ move |cx| {
+ if let Some((workspace, pane)) =
+ workspace.upgrade(cx).zip(pane.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ Self::close_item_by_id(workspace, pane, target_item_id, cx)
+ .detach_and_log_err(cx);
+ })
+ }
+ }
+ }),
+ ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
+ ContextMenuItem::action("Close Clean Items", CloseCleanItems),
+ ContextMenuItem::handler("Close Items To The Left", {
+ let workspace = self.workspace.clone();
+ let pane = target_pane.clone();
+ move |cx| {
+ if let Some((workspace, pane)) =
+ workspace.upgrade(cx).zip(pane.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ Self::close_items_to_the_left_by_id(
+ workspace,
+ pane,
+ target_item_id,
+ cx,
+ )
+ .detach_and_log_err(cx);
+ })
+ }
+ }
+ }),
+ ContextMenuItem::handler("Close Items To The Right", {
+ let workspace = self.workspace.clone();
+ let pane = target_pane.clone();
+ move |cx| {
+ if let Some((workspace, pane)) =
+ workspace.upgrade(cx).zip(pane.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ Self::close_items_to_the_right_by_id(
+ workspace,
+ pane,
+ target_item_id,
+ cx,
+ )
+ .detach_and_log_err(cx);
+ })
+ }
+ }
+ }),
+ ContextMenuItem::action("Close All Items", CloseAllItems),
]
},
cx,
@@ -1407,24 +1344,28 @@ impl Pane {
cx.dispatch_action(ActivateItem(ix));
})
.on_click(MouseButton::Middle, {
- let item = item.clone();
- let pane = pane.clone();
- move |_, _, cx| {
- cx.dispatch_action(CloseItemById {
- item_id: item.id(),
- pane: pane.clone(),
- })
+ let item_id = item.id();
+ move |_, pane, cx| {
+ let workspace = pane.workspace.clone();
+ let pane = cx.weak_handle();
+ cx.window_context().defer(move |cx| {
+ if let Some((workspace, pane)) =
+ workspace.upgrade(cx).zip(pane.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ Self::close_item_by_id(
+ workspace, pane, item_id, cx,
+ )
+ .detach_and_log_err(cx);
+ });
+ }
+ });
}
})
.on_down(
MouseButton::Right,
- move |e, _, cx| {
- let item = item.clone();
- cx.dispatch_action(DeployTabContextMenu {
- position: e.position,
- item_id: item.id(),
- pane: pane.clone(),
- });
+ move |event, pane, cx| {
+ pane.deploy_tab_context_menu(event.position, item.id(), cx);
},
);
@@ -1622,10 +1563,17 @@ impl Pane {
.on_click(MouseButton::Left, {
let pane = pane.clone();
move |_, _, cx| {
- cx.dispatch_action(CloseItemById {
- item_id,
- pane: pane.clone(),
- })
+ let pane = pane.clone();
+ cx.window_context().defer(move |cx| {
+ if let Some(pane) = pane.upgrade(cx) {
+ if let Some(workspace) = pane.read(cx).workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ Self::close_item_by_id(workspace, pane, item_id, cx)
+ .detach_and_log_err(cx);
+ });
+ }
+ }
+ });
}
})
.into_any_named("close-tab-icon")
@@ -1654,7 +1602,7 @@ impl Pane {
0,
"icons/plus_12.svg",
cx,
- DeployNewMenu,
+ |pane, cx| pane.deploy_new_menu(cx),
self.tab_bar_context_menu
.handle_if_kind(TabBarContextMenuKind::New),
))
@@ -1668,7 +1616,7 @@ impl Pane {
1,
dock_icon,
cx,
- DeployDockMenu,
+ |pane, cx| pane.deploy_dock_menu(cx),
self.tab_bar_context_menu
.handle_if_kind(TabBarContextMenuKind::Dock),
)
@@ -1679,17 +1627,22 @@ impl Pane {
2,
"icons/split_12.svg",
cx,
- DeploySplitMenu,
+ |pane, cx| pane.deploy_split_menu(cx),
self.tab_bar_context_menu
.handle_if_kind(TabBarContextMenuKind::Split),
)
}),
)
// Add the close dock button if this pane is a dock
- .with_children(
- self.docked
- .map(|_| render_tab_bar_button(3, "icons/x_mark_8.svg", cx, HideDock, None)),
- )
+ .with_children(self.docked.map(|_| {
+ render_tab_bar_button(
+ 3,
+ "icons/x_mark_8.svg",
+ cx,
+ |_, cx| cx.dispatch_action(HideDock),
+ None,
+ )
+ }))
.contained()
.with_style(theme.workspace.tab_bar.pane_button_container)
.flex(1., false)
@@ -1863,11 +1816,11 @@ impl View for Pane {
}
}
-fn render_tab_bar_button<A: Action + Clone>(
+fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
index: usize,
icon: &'static str,
cx: &mut ViewContext<Pane>,
- action: A,
+ on_click: F,
context_menu: Option<ViewHandle<ContextMenu>>,
) -> AnyElement<Pane> {
enum TabBarButton {}
@@ -1887,9 +1840,7 @@ fn render_tab_bar_button<A: Action + Clone>(
.with_height(style.button_width)
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, _, cx| {
- cx.dispatch_action(action.clone());
- }),
+ .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)),
)
.with_children(
context_menu.map(|menu| ChildView::new(&menu, cx).aligned().bottom().right()),
@@ -10,10 +10,7 @@ use gpui::{
use project::ProjectEntryId;
use settings::Settings;
-use crate::{
- MoveItem, OpenProjectEntryInPane, Pane, SplitDirection, SplitWithItem, SplitWithProjectEntry,
- Workspace,
-};
+use crate::{Pane, SplitDirection, Workspace};
use super::DraggedItem;
@@ -72,9 +69,18 @@ where
}))
})
.on_up(MouseButton::Left, {
- move |event, _, cx| {
+ move |event, pane, cx| {
+ let workspace = pane.workspace.clone();
let pane = cx.weak_handle();
- handle_dropped_item(event, &pane, drop_index, allow_same_pane, split_margin, cx);
+ handle_dropped_item(
+ event,
+ workspace,
+ &pane,
+ drop_index,
+ allow_same_pane,
+ split_margin,
+ cx,
+ );
cx.notify();
}
})
@@ -97,6 +103,7 @@ where
pub fn handle_dropped_item<V: View>(
event: MouseUp,
+ workspace: WeakViewHandle<Workspace>,
pane: &WeakViewHandle<Pane>,
index: usize,
allow_same_pane: bool,
@@ -126,36 +133,74 @@ pub fn handle_dropped_item<V: View>(
{
let pane_to_split = pane.clone();
match action {
- Action::Move(from, item_id_to_move) => cx.dispatch_action(SplitWithItem {
- from,
- item_id_to_move,
- pane_to_split,
- split_direction,
- }),
- Action::Open(project_entry) => cx.dispatch_action(SplitWithProjectEntry {
- pane_to_split,
- split_direction,
- project_entry,
- }),
+ Action::Move(from, item_id_to_move) => {
+ cx.window_context().defer(move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ workspace.split_pane_with_item(
+ pane_to_split,
+ split_direction,
+ from,
+ item_id_to_move,
+ cx,
+ );
+ })
+ }
+ });
+ }
+ Action::Open(project_entry) => {
+ cx.window_context().defer(move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ if let Some(task) = workspace.split_pane_with_project_entry(
+ pane_to_split,
+ split_direction,
+ project_entry,
+ cx,
+ ) {
+ task.detach_and_log_err(cx);
+ }
+ })
+ }
+ });
+ }
};
} else {
match action {
Action::Move(from, item_id) => {
if pane != &from || allow_same_pane {
- cx.dispatch_action(MoveItem {
- item_id,
- from,
- to: pane.clone(),
- destination_index: index,
- })
+ let pane = pane.clone();
+ cx.window_context().defer(move |cx| {
+ if let Some(((workspace, from), to)) = workspace
+ .upgrade(cx)
+ .zip(from.upgrade(cx))
+ .zip(pane.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ Pane::move_item(workspace, from, to, item_id, index, cx);
+ })
+ }
+ });
} else {
cx.propagate_event();
}
}
- Action::Open(project_entry) => cx.dispatch_action(OpenProjectEntryInPane {
- pane: pane.clone(),
- project_entry,
- }),
+ Action::Open(project_entry) => {
+ let pane = pane.clone();
+ cx.window_context().defer(move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ if let Some(path) =
+ workspace.project.read(cx).path_for_entry(project_entry, cx)
+ {
+ workspace
+ .open_path(path, Some(pane), true, cx)
+ .detach_and_log_err(cx);
+ }
+ });
+ }
+ });
+ }
}
}
}
@@ -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,
@@ -38,7 +37,7 @@ use gpui::{
rect::RectF,
vector::{vec2f, Vector2F},
},
- impl_actions, impl_internal_actions,
+ impl_actions,
keymap_matcher::KeymapContext,
platform::{
CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
@@ -135,41 +134,6 @@ pub struct OpenPaths {
#[derive(Clone, Deserialize, PartialEq)]
pub struct ActivatePane(pub usize);
-#[derive(Clone, PartialEq)]
-pub struct ToggleFollow(pub PeerId);
-
-#[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,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct SplitWithItem {
- pane_to_split: WeakViewHandle<Pane>,
- split_direction: SplitDirection,
- from: WeakViewHandle<Pane>,
- item_id_to_move: usize,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct SplitWithProjectEntry {
- pane_to_split: WeakViewHandle<Pane>,
- split_direction: SplitDirection,
- project_entry: ProjectEntryId,
-}
-
-#[derive(Clone, PartialEq)]
-pub struct OpenProjectEntryInPane {
- pane: WeakViewHandle<Pane>,
- project_entry: ProjectEntryId,
-}
-
pub struct Toast {
id: usize,
msg: Cow<'static, str>,
@@ -220,34 +184,8 @@ impl Clone for Toast {
}
}
-#[derive(Clone, PartialEq)]
-pub struct DismissToast {
- id: usize,
-}
-
-impl DismissToast {
- pub fn new(id: usize) -> Self {
- DismissToast { id }
- }
-}
-
pub type WorkspaceId = i64;
-impl_internal_actions!(
- workspace,
- [
- OpenPaths,
- ToggleFollow,
- JoinProject,
- OpenSharedScreen,
- RemoveWorktreeFromProject,
- SplitWithItem,
- SplitWithProjectEntry,
- OpenProjectEntryInPane,
- Toast,
- DismissToast
- ]
-);
impl_actions!(workspace, [ActivatePane]);
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
@@ -255,81 +193,53 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
dock::init(cx);
notifications::init(cx);
- cx.add_global_action(|_: &Open, cx: &mut AppContext| {
- let mut paths = cx.prompt_for_paths(PathPromptOptions {
- files: true,
- directories: true,
- multiple: true,
- });
-
- cx.spawn(|mut cx| async move {
- if let Some(paths) = paths.recv().await.flatten() {
- cx.update(|cx| cx.dispatch_global_action(OpenPaths { paths }));
- }
- })
- .detach();
- });
- cx.add_action(|_, _: &Open, cx: &mut ViewContext<Workspace>| {
- let mut paths = cx.prompt_for_paths(PathPromptOptions {
- files: true,
- directories: true,
- multiple: true,
- });
-
- let handle = cx.handle().downgrade();
- cx.spawn(|_, mut cx| async move {
- if let Some(paths) = paths.recv().await.flatten() {
- cx.update(|cx| {
- cx.dispatch_action_at(handle.window_id(), handle.id(), OpenPaths { paths })
- })
- }
- })
- .detach();
- });
cx.add_global_action({
let app_state = Arc::downgrade(&app_state);
- move |action: &OpenPaths, cx: &mut AppContext| {
+ move |_: &Open, cx: &mut AppContext| {
+ let mut paths = cx.prompt_for_paths(PathPromptOptions {
+ files: true,
+ directories: true,
+ multiple: true,
+ });
+
if let Some(app_state) = app_state.upgrade() {
- open_paths(&action.paths, &app_state, None, cx).detach();
+ cx.spawn(move |mut cx| async move {
+ if let Some(paths) = paths.recv().await.flatten() {
+ cx.update(|cx| {
+ open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
+ });
+ }
+ })
+ .detach();
}
}
});
- cx.add_async_action({
+ cx.add_action({
let app_state = Arc::downgrade(&app_state);
- move |workspace, action: &OpenPaths, cx: &mut ViewContext<Workspace>| {
- if !workspace.project().read(cx).is_local() {
- cx.propagate_action();
- return None;
- }
-
- let app_state = app_state.upgrade()?;
- let window_id = cx.window_id();
- let action = action.clone();
- let is_remote = workspace.project.read(cx).is_remote();
- let has_worktree = workspace.project.read(cx).worktrees(cx).next().is_some();
- let has_dirty_items = workspace.items(cx).any(|item| item.is_dirty(cx));
- let close_task = if is_remote || has_worktree || has_dirty_items {
- None
- } else {
- Some(workspace.prepare_to_close(false, cx))
- };
+ move |_, _: &Open, cx: &mut ViewContext<Workspace>| {
+ let mut paths = cx.prompt_for_paths(PathPromptOptions {
+ files: true,
+ directories: true,
+ multiple: true,
+ });
- Some(cx.spawn(|_, mut cx| async move {
- let window_id_to_replace = if let Some(close_task) = close_task {
- if !close_task.await? {
- return Ok(());
+ if let Some(app_state) = app_state.upgrade() {
+ cx.spawn(|this, mut cx| async move {
+ if let Some(paths) = paths.recv().await.flatten() {
+ if let Some(task) = this
+ .update(&mut cx, |this, cx| {
+ this.open_workspace_for_paths(paths, app_state, cx)
+ })
+ .log_err()
+ {
+ task.await.log_err();
+ }
}
- Some(window_id)
- } else {
- None
- };
- cx.update(|cx| open_paths(&action.paths, &app_state, window_id_to_replace, cx))
- .await?;
- Ok(())
- }))
+ })
+ .detach();
+ }
}
});
-
cx.add_global_action({
let app_state = Arc::downgrade(&app_state);
move |_: &NewWindow, cx: &mut AppContext| {
@@ -347,14 +257,11 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
}
});
- cx.add_async_action(Workspace::toggle_follow);
cx.add_async_action(Workspace::follow_next_collaborator);
cx.add_async_action(Workspace::close);
cx.add_global_action(Workspace::close_global);
cx.add_async_action(Workspace::save_all);
- cx.add_action(Workspace::open_shared_screen);
cx.add_action(Workspace::add_folder_to_project);
- cx.add_action(Workspace::remove_folder_from_project);
cx.add_action(
|workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
let pane = workspace.active_pane().clone();
@@ -384,30 +291,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
});
cx.add_action(Workspace::activate_pane_at_index);
- cx.add_action(Workspace::split_pane_with_item);
- cx.add_async_action(Workspace::split_pane_with_project_entry);
-
- cx.add_async_action(
- |workspace: &mut Workspace,
- OpenProjectEntryInPane {
- pane,
- project_entry,
- }: &_,
- cx| {
- workspace
- .project
- .read(cx)
- .path_for_entry(*project_entry, cx)
- .map(|path| {
- let task = workspace.open_path(path, Some(pane.clone()), true, cx);
- cx.foreground().spawn(async move {
- task.await?;
- Ok(())
- })
- })
- },
- );
-
cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
cx.spawn(|workspace, mut cx| async move {
let err = install_cli::install_cli(&cx)
@@ -431,24 +314,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
.detach();
});
- cx.add_action(|workspace: &mut Workspace, alert: &Toast, cx| {
- workspace.dismiss_notification::<MessageNotification>(alert.id, cx);
- workspace.show_notification(alert.id, cx, |cx| {
- cx.add_view(|_cx| match &alert.click {
- Some((click_msg, action)) => MessageNotification::new_boxed_action(
- alert.msg.clone(),
- action.boxed_clone(),
- click_msg.clone(),
- ),
- None => MessageNotification::new_message(alert.msg.clone()),
- })
- })
- });
-
- cx.add_action(|workspace: &mut Workspace, alert: &DismissToast, cx| {
- workspace.dismiss_notification::<MessageNotification>(alert.id, cx);
- });
-
let client = &app_state.client;
client.add_view_request_handler(Workspace::handle_follow);
client.add_view_message_handler(Workspace::handle_unfollow);
@@ -617,10 +482,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>,
@@ -641,7 +503,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<()>>,
@@ -671,8 +533,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();
@@ -709,8 +570,8 @@ impl Workspace {
let weak_handle = cx.weak_handle();
- let center_pane =
- cx.add_view(|cx| Pane::new(weak_handle.id(), 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)
@@ -719,18 +580,14 @@ impl Workspace {
cx.focus(¢er_pane);
cx.emit(Event::PaneAdded(center_pane.clone()));
let dock = Dock::new(
- weak_handle.id(),
- dock_default_factory,
- background_actions,
+ 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;
@@ -823,10 +680,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(),
@@ -836,7 +690,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,
@@ -925,8 +779,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);
@@ -1036,8 +889,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> {
@@ -1045,7 +902,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>) {
@@ -1243,6 +1100,37 @@ impl Workspace {
})
}
+ pub fn open_workspace_for_paths(
+ &mut self,
+ paths: Vec<PathBuf>,
+ app_state: Arc<AppState>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ let window_id = cx.window_id();
+ let is_remote = self.project.read(cx).is_remote();
+ let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
+ let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
+ let close_task = if is_remote || has_worktree || has_dirty_items {
+ None
+ } else {
+ Some(self.prepare_to_close(false, cx))
+ };
+
+ cx.spawn(|_, mut cx| async move {
+ let window_id_to_replace = if let Some(close_task) = close_task {
+ if !close_task.await? {
+ return Ok(());
+ }
+ Some(window_id)
+ } else {
+ None
+ };
+ cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
+ .await?;
+ Ok(())
+ })
+ }
+
#[allow(clippy::type_complexity)]
pub fn open_paths(
&mut self,
@@ -1250,7 +1138,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();
@@ -1319,15 +1207,6 @@ impl Workspace {
.detach_and_log_err(cx);
}
- fn remove_folder_from_project(
- &mut self,
- RemoveWorktreeFromProject(worktree_id): &RemoveWorktreeFromProject,
- cx: &mut ViewContext<Self>,
- ) {
- self.project
- .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx));
- }
-
fn project_path_for_path(
project: ModelHandle<Project>,
abs_path: &Path,
@@ -1561,8 +1440,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().id(), 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)
@@ -1678,10 +1563,8 @@ impl Workspace {
item
}
- pub fn open_shared_screen(&mut self, action: &OpenSharedScreen, cx: &mut ViewContext<Self>) {
- if let Some(shared_screen) =
- self.shared_screen_for_peer(action.peer_id, &self.active_pane, cx)
- {
+ pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
+ if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
let pane = self.active_pane.clone();
Pane::add_item(self, &pane, Box::new(shared_screen), false, true, None, cx);
}
@@ -1755,7 +1638,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),
@@ -1833,35 +1716,37 @@ impl Workspace {
maybe_pane_handle
}
- pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext<Self>) {
- let Some(pane_to_split) = action.pane_to_split.upgrade(cx) else { return; };
- let Some(from) = action.from.upgrade(cx) else { return; };
+ pub fn split_pane_with_item(
+ &mut self,
+ pane_to_split: WeakViewHandle<Pane>,
+ split_direction: SplitDirection,
+ from: WeakViewHandle<Pane>,
+ item_id_to_move: usize,
+ cx: &mut ViewContext<Self>,
+ ) {
+ let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
+ let Some(from) = from.upgrade(cx) else { return; };
if &pane_to_split == self.dock_pane() {
warn!("Can't split dock pane.");
return;
}
let new_pane = self.add_pane(cx);
- Pane::move_item(
- self,
- from.clone(),
- new_pane.clone(),
- action.item_id_to_move,
- 0,
- cx,
- );
+ Pane::move_item(self, from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
self.center
- .split(&pane_to_split, &new_pane, action.split_direction)
+ .split(&pane_to_split, &new_pane, split_direction)
.unwrap();
cx.notify();
}
pub fn split_pane_with_project_entry(
&mut self,
- action: &SplitWithProjectEntry,
+ pane_to_split: WeakViewHandle<Pane>,
+ split_direction: SplitDirection,
+ project_entry: ProjectEntryId,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
- let pane_to_split = action.pane_to_split.upgrade(cx)?;
+ let pane_to_split = pane_to_split.upgrade(cx)?;
if &pane_to_split == self.dock_pane() {
warn!("Can't split dock pane.");
return None;
@@ -1869,13 +1754,10 @@ impl Workspace {
let new_pane = self.add_pane(cx);
self.center
- .split(&pane_to_split, &new_pane, action.split_direction)
+ .split(&pane_to_split, &new_pane, split_direction)
.unwrap();
- let path = self
- .project
- .read(cx)
- .path_for_entry(action.project_entry, cx)?;
+ let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
Some(cx.foreground().spawn(async move {
task.await?;
@@ -1924,8 +1806,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();
}
@@ -1945,10 +1830,9 @@ impl Workspace {
pub fn toggle_follow(
&mut self,
- ToggleFollow(leader_id): &ToggleFollow,
+ leader_id: PeerId,
cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
- let leader_id = *leader_id;
let pane = self.active_pane().clone();
if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
@@ -1966,7 +1850,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),
});
@@ -2027,7 +1911,7 @@ impl Workspace {
next_leader_id
.or_else(|| collaborators.keys().copied().next())
- .and_then(|leader_id| self.toggle_follow(&ToggleFollow(leader_id), cx))
+ .and_then(|leader_id| self.toggle_follow(leader_id, cx))
}
pub fn unfollow(
@@ -2045,7 +1929,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),
@@ -2226,7 +2111,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()?);
@@ -2434,7 +2319,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(),
@@ -2766,7 +2652,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)
}
}
@@ -2852,6 +2749,7 @@ impl View for Workspace {
&self.follower_states_by_leader,
self.active_call(),
self.active_pane(),
+ &self.app_state,
cx,
))
.flex(1., true),
@@ -3083,6 +2981,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()?;
@@ -3109,16 +3088,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, |_| {
@@ -3182,16 +3152,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()
});
@@ -3281,16 +3242,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());
@@ -3325,9 +3277,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()
@@ -3434,9 +3384,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.
@@ -3543,9 +3491,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)])
@@ -3662,9 +3608,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)])
@@ -44,7 +44,7 @@ use theme::ThemeRegistry;
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
use workspace::{
self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, NewFile,
- OpenPaths, Workspace,
+ Workspace,
};
use zed::{self, build_window_options, initialize_workspace, languages, menus, OpenSettings};
@@ -160,7 +160,6 @@ fn main() {
vim::init(cx);
terminal_view::init(cx);
theme_testbench::init(cx);
- recent_projects::init(cx);
copilot::init(http.clone(), node_runtime, cx);
cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
@@ -194,12 +193,13 @@ fn main() {
auto_update::init(http, client::ZED_SERVER_URL.clone(), cx);
workspace::init(app_state.clone(), cx);
+ recent_projects::init(cx, Arc::downgrade(&app_state));
journal::init(app_state.clone(), cx);
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);
@@ -212,7 +212,7 @@ fn main() {
cx.spawn(|cx| async move { restore_or_create_workspace(&app_state, cx).await })
.detach()
} else {
- cx.dispatch_global_action(OpenPaths { paths });
+ workspace::open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx);
}
} else {
if let Ok(Some(connection)) = cli_connections_rx.try_next() {
@@ -267,11 +267,9 @@ fn main() {
async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
if let Some(location) = workspace::last_opened_workspace_paths().await {
- cx.update(|cx| {
- cx.dispatch_global_action(OpenPaths {
- paths: location.paths().as_ref().clone(),
- })
- });
+ cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))
+ .await
+ .log_err();
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
cx.update(|cx| show_welcome_experience(app_state, cx));
} else {
@@ -31,7 +31,7 @@ use serde::Deserialize;
use serde_json::to_string_pretty;
use settings::Settings;
use std::{borrow::Cow, env, path::Path, str, sync::Arc};
-use terminal_view::terminal_button::{self, TerminalButton};
+use terminal_view::terminal_button::TerminalButton;
use util::{channel::ReleaseChannel, paths, ResultExt};
use uuid::Uuid;
pub use workspace;
@@ -73,7 +73,6 @@ actions!(
const MIN_FONT_SIZE: f32 = 6.0;
pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
- terminal_button::init(cx);
cx.add_action(about);
cx.add_global_action(|_: &Hide, cx: &mut gpui::AppContext| {
cx.platform().hide();
@@ -261,7 +260,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
},
);
activity_indicator::init(cx);
- copilot_button::init(cx);
lsp_log::init(cx);
call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
settings::KeymapFileContent::load_defaults(cx);
@@ -302,8 +300,9 @@ 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, app_state.user_store.clone(), cx)
+ });
workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
let project_panel = ProjectPanel::new(workspace.project().clone(), cx);